[
  {
    "path": ".gitignore",
    "content": "# Generated by MacOSX\n.DS_Store\n\n# Generated by VIM\n.*.swp\n\n# Generated by IntelliJ\n.idea/\n*.iml\n/*/*.iml\n/*/*/*.iml\n\n# PluginDemo\nPluginDemo/.idea/\nPluginDemo/.gradle/\nPluginDemo/build/\nPluginDemo/*.iml\nPluginDemo/local.properties\nPluginDemo/host/\n\n# Generated by Gradle\n.gradle\nbuild/\n\n# Generated by Eclipse\n.metadata\n.settings\n.project\n.classpath\n\n# Files generated by Maven\ntarget/\npom.xml.tag\npom.xml.releaseBackup\npom.xml.versionsBackup\npom.xml.next\nrelease.properties\ndependency-reduced-pom.xml\nbuildNumber.properties\n\nlocal.properties\nbuildSrc\n"
  },
  {
    "path": "AndroidStub/README.md",
    "content": "# Android Framework Stub\n\n该库主要是针对在 app 中无法直接调用 Android Framework 中很多隐藏的 API 而创造的一系列 stub 类用来欺骗编译器，从而避免了使用反射去调用造成性能损失\n"
  },
  {
    "path": "AndroidStub/build.gradle",
    "content": "group = 'com.didichuxing.foundation'\nversion = '0.0.5'\n\napply plugin: 'com.android.library'\n\nandroid {\n    compileSdkVersion VERSION_COMPILE_SDK\n    buildToolsVersion VERSION_BUILD_TOOLS\n\n    defaultConfig {\n        minSdkVersion VERSION_MIN_SDK\n        targetSdkVersion VERSION_TARGET_SDK\n        versionCode 1\n        versionName \"1.0\"\n    }\n    compileOptions {\n        sourceCompatibility SOURCE_COMPATIBILITY\n    }\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n\n    lintOptions {\n        abortOnError false\n    }\n}\n\nandroid.libraryVariants.all  { variant ->\n    def jarTask = project.tasks.create(\"jar${variant.name.capitalize()}\", Jar)\n    jarTask.excludes = [\n        'android/BuildConfig.class',\n        'android/R.class'\n    ]\n    jarTask.dependsOn variant.javaCompile\n    jarTask.from variant.javaCompile.destinationDir\n    artifacts.add('archives', jarTask)\n}\n\napply plugin: 'maven'\n\nuploadArchives {\n    repositories {\n        mavenDeployer {\n            pom.project {\n                groupId project.group\n                artifactId 'android-framework'\n                version project.version\n                description project.description ?: ''\n                packaging 'jar'\n            }\n            repository(url: MAVEN_RELEASES_REPOSITORY_URL) {\n                authentication(userName: MAVEN_USERNAME, password: MAVEN_PASSWORD)\n            }\n            snapshotRepository(url: MAVEN_SNAPSHOTS_REPOSITORY_URL) {\n                authentication(userName: MAVEN_USERNAME, password: MAVEN_PASSWORD)\n            }\n        }\n    }\n}\n\ndependencies {\n    api 'com.android.support:support-annotations:22.2.0'\n    api 'com.android.databinding:library:1.3.1'\n    api 'com.android.databinding:baseLibrary:3.0.0'\n}"
  },
  {
    "path": "AndroidStub/gradle.properties",
    "content": "MAVEN_RELEASES_REPOSITORY_URL=\nMAVEN_SNAPSHOTS_REPOSITORY_URL=\nMAVEN_USERNAME=test\nMAVEN_PASSWORD=test\n"
  },
  {
    "path": "AndroidStub/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /Users/johnson/Library/Android/sdk/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n"
  },
  {
    "path": "AndroidStub/src/main/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"android\">\n\n    <application />\n\n</manifest>\n"
  },
  {
    "path": "AndroidStub/src/main/aidl/android/app/IActivityManager.aidl",
    "content": "package android.app;\n\nimport android.app.IApplicationThread;\nimport android.app.IServiceConnection;\nimport android.content.ComponentName;\nimport android.content.Intent;\nimport android.content.IIntentSender;\nimport android.os.IBinder;\nimport android.os.IInterface;\n\n/**\n * @author johnsonlee\n */\ninterface IActivityManager {\n\n    ComponentName startService(in IApplicationThread caller, in Intent service, in String resolvedType, in String callingPackage, in int userId);\n\n    int stopService(in IApplicationThread caller, in Intent service, in String resolvedType, in int userId);\n\n    boolean stopServiceToken(in ComponentName className, in IBinder token, in int startId);\n\n    void setServiceForeground(in ComponentName className, in IBinder token, in int id, in Notification notification, in boolean keepNotification);\n\n    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);\n\n    boolean unbindService(in IServiceConnection connection);\n\n    void publishService(in IBinder token, in Intent intent, in IBinder service);\n\n    void unbindFinished(in IBinder token, in Intent service, in boolean doRebind);\n\n    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);\n\n    void cancelIntentSender(in IIntentSender sender);\n\n    String getPackageForIntentSender(in IIntentSender sender);\n\n    int getUidForIntentSender(in IIntentSender sender);\n\n}\n"
  },
  {
    "path": "AndroidStub/src/main/aidl/android/app/IApplicationThread.aidl",
    "content": "package android.app;\n\ninterface IApplicationThread {\n}\n"
  },
  {
    "path": "AndroidStub/src/main/aidl/android/app/INotificationManager.aidl",
    "content": "/*\n * Copyright 2007, The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage android.app;\nimport android.app.Notification;\nimport android.content.ComponentName;\nimport android.content.Intent;\nimport android.net.Uri;\nimport android.os.Bundle;\n\ninterface INotificationManager\n{\n    void cancelAllNotifications(String pkg, int userId);\n    void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id, in Notification notification, inout int[] idReceived, int userId);\n    void cancelNotificationWithTag(String pkg, String tag, int id, int userId);\n    void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled);\n    boolean areNotificationsEnabledForPackage(String pkg, int uid);\n    boolean areNotificationsEnabled(String pkg);\n}\n"
  },
  {
    "path": "AndroidStub/src/main/aidl/android/app/IServiceConnection.aidl",
    "content": "/* //device/java/android/android/app/IServiceConnection.aidl\n**\n** Copyright 2007, The Android Open Source Project\n**\n** Licensed under the Apache License, Version 2.0 (the \"License\");\n** you may not use this file except in compliance with the License.\n** You may obtain a copy of the License at\n**\n**     http://www.apache.org/licenses/LICENSE-2.0\n**\n** Unless required by applicable law or agreed to in writing, software\n** distributed under the License is distributed on an \"AS IS\" BASIS,\n** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n** See the License for the specific language governing permissions and\n** limitations under the License.\n*/\n\npackage android.app;\n\nimport android.content.ComponentName;\n\n/** @hide */\noneway interface IServiceConnection {\n//    void connected(in ComponentName name, IBinder service);\n\n    /** Added in Android O */\n    void connected(in ComponentName name, IBinder service, boolean dead);\n}\n"
  },
  {
    "path": "AndroidStub/src/main/aidl/android/content/IIntentReceiver.aidl",
    "content": "/*\n * Copyright (C) 2006 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage android.content;\n\nimport android.content.Intent;\nimport android.os.Bundle;\n\n/**\n * System private API for dispatching intent broadcasts.  This is given to the\n * activity manager as part of registering for an intent broadcasts, and is\n * called when it receives intents.\n */\noneway interface IIntentReceiver {\n    void performReceive(in Intent intent, int resultCode, String data, in Bundle extras, boolean ordered, boolean sticky, int sendingUser);\n}\n"
  },
  {
    "path": "AndroidStub/src/main/aidl/android/content/IIntentSender.aidl",
    "content": "/*\n * Copyright (C) 2006 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage android.content;\n\nimport android.content.IIntentReceiver;\nimport android.content.Intent;\nimport android.os.Bundle;\n\noneway interface IIntentSender {\n\n    void send(int code, in Intent intent, String resolvedType, IIntentReceiver finishedReceiver, String requiredPermission, in Bundle options);\n\n}"
  },
  {
    "path": "AndroidStub/src/main/java/android/app/ActivityManagerNative.java",
    "content": "package android.app;\n\nimport android.content.Intent;\nimport android.os.Binder;\nimport android.os.IBinder;\n\n/**\n * @author johnsonlee\n */\npublic abstract class ActivityManagerNative extends Binder implements IActivityManager {\n\n    public static IActivityManager getDefault() {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public static boolean isSystemReady() {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public static void broadcastStickyIntent(final Intent intent, final String permission, final int userId) {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    static public IActivityManager asInterface(IBinder obj) {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public ActivityManagerNative() {\n        throw new RuntimeException(\"Stub!\");\n    }\n}\n"
  },
  {
    "path": "AndroidStub/src/main/java/android/app/ActivityThread.java",
    "content": "package android.app;\n\nimport android.content.Intent;\nimport android.content.pm.ActivityInfo;\nimport android.os.Handler;\nimport android.os.IBinder;\nimport android.os.Looper;\n\n/**\n * @author johnsonlee\n */\npublic final class ActivityThread {\n\n    public static ActivityThread currentActivityThread() {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public static boolean isSystem() {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public static String currentOpPackageName() {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public static String currentPackageName() {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public static String currentProcessName() {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public static Application currentApplication() {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public ApplicationThread getApplicationThread() {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public Instrumentation getInstrumentation() {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public Looper getLooper() {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public Application getApplication() {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public String getProcessName() {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public final ActivityInfo resolveActivityInfo(final Intent intent) {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public final Activity getActivity(final IBinder token) {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    final Handler getHandler() {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    private class ApplicationThread extends ApplicationThreadNative {\n\n\n    }\n}\n"
  },
  {
    "path": "AndroidStub/src/main/java/android/app/ApplicationThreadNative.java",
    "content": "package android.app;\n\nimport android.os.Binder;\nimport android.os.IBinder;\n\n/**\n * @author johnsonlee\n */\npublic abstract class ApplicationThreadNative extends Binder implements IApplicationThread {\n\n    @Override\n    public IBinder asBinder() {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n}\n"
  },
  {
    "path": "AndroidStub/src/main/java/android/app/Instrumentation.java",
    "content": "package android.app;\n\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.os.IBinder;\nimport android.os.PersistableBundle;\n\n/**\n * Created by qiaopu on 2018/5/7.\n */\npublic class Instrumentation {\n    \n    public Application newApplication(ClassLoader cl, String className, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException {\n        throw new RuntimeException(\"Stub!\");\n    }\n    \n    public void callApplicationOnCreate(Application app) {\n        throw new RuntimeException(\"Stub!\");\n    }\n    \n    public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {\n        throw new RuntimeException(\"Stub!\");\n    }\n    \n    public void callActivityOnCreate(Activity activity, Bundle icicle) {\n        throw new RuntimeException(\"Stub!\");\n    }\n    \n    public void callActivityOnCreate(Activity activity, Bundle icicle, PersistableBundle persistentState) {\n        throw new RuntimeException(\"Stub!\");\n    }\n    \n    public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode) {\n        throw new RuntimeException(\"Stub!\");\n    }\n    \n    public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) {\n        throw new RuntimeException(\"Stub!\");\n    }\n    \n    public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Fragment target, Intent intent, int requestCode, Bundle options) {\n        throw new RuntimeException(\"Stub!\");\n    }\n    \n    public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, String target, Intent intent, int requestCode, Bundle options) {\n        throw new RuntimeException(\"Stub!\");\n    }\n    \n    public Context getContext() {\n        throw new RuntimeException(\"Stub!\");\n    }\n    \n    public Context getTargetContext() {\n        throw new RuntimeException(\"Stub!\");\n    }\n    \n    public ComponentName getComponentName() {\n        throw new RuntimeException(\"Stub!\");\n    }\n    \n    public static final class ActivityResult {\n        public ActivityResult(int resultCode, Intent resultData) {\n            throw new RuntimeException(\"Stub!\");\n        }\n    }\n}\n"
  },
  {
    "path": "AndroidStub/src/main/java/android/app/LoadedApk.java",
    "content": "package android.app;\n\nimport android.content.BroadcastReceiver;\nimport android.content.Context;\nimport android.content.IIntentReceiver;\nimport android.content.pm.ApplicationInfo;\nimport android.content.res.AssetManager;\nimport android.content.res.Resources;\nimport android.os.Handler;\n\nimport java.io.File;\n\n/**\n * Created by johnson on 24/8/16.\n */\npublic final class LoadedApk {\n\n    public String getPackageName() {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public ApplicationInfo getApplicationInfo() {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public ClassLoader getClassLoader() {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public String getAppDir() {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public String getLibDir() {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public String getResDir() {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public String[] getSplitAppDirs() {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public String[] getSplitResDirs() {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public String[] getOverlayDirs() {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public String getDataDir() {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public File getDataDirFile() {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public AssetManager getAssets(final ActivityThread mainThread) {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public Resources getResources(final ActivityThread mainThread) {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public IIntentReceiver getReceiverDispatcher(final BroadcastReceiver r, final Context context, final Handler handler, final Instrumentation instrumentation, final boolean registered) {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public IIntentReceiver forgetReceiverDispatcher(final Context context, final BroadcastReceiver r) {\n        throw new RuntimeException(\"Stub!\");\n    }\n}\n"
  },
  {
    "path": "AndroidStub/src/main/java/android/app/ResourcesManager.java",
    "content": "package android.app;\n\n/**\n * Created by qiaopu on 2018/4/25.\n */\npublic class ResourcesManager {\n    \n    public static ResourcesManager getInstance() {\n        throw new RuntimeException(\"Stub!\");\n    }\n    \n    public void appendLibAssetForMainAssetPath(String assetPath, String libAsset) {\n        throw new RuntimeException(\"Stub!\");\n    }\n    \n}"
  },
  {
    "path": "AndroidStub/src/main/java/android/content/ContentResolver.java",
    "content": "package android.content;\n\nimport android.net.Uri;\nimport android.os.Bundle;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\n\n/**\n * Created by qiaopu on 2018/5/7.\n */\npublic abstract class ContentResolver {\n    \n    public ContentResolver(Context context) {\n        throw new RuntimeException(\"Stub!\");\n    }\n    \n    public final @Nullable\n    Bundle call(@NonNull Uri uri, @NonNull String method,\n                @Nullable String arg, @Nullable Bundle extras) {\n        throw new RuntimeException(\"Stub!\");\n    }\n    \n    protected abstract IContentProvider acquireProvider(Context c, String name);\n    \n    protected IContentProvider acquireExistingProvider(Context c, String name) {\n        throw new RuntimeException(\"Stub!\");\n    }\n    \n    public abstract boolean releaseProvider(IContentProvider icp);\n    \n    protected abstract IContentProvider acquireUnstableProvider(Context c, String name);\n    \n    public abstract boolean releaseUnstableProvider(IContentProvider icp);\n    \n    public abstract void unstableProviderDied(IContentProvider icp);\n    \n    public void appNotRespondingViaProvider(IContentProvider icp) {\n        throw new RuntimeException(\"Stub!\");\n    }\n}\n"
  },
  {
    "path": "AndroidStub/src/main/java/android/content/IContentProvider.java",
    "content": "/*\n * Copyright (C) 2006 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage android.content;\n\nimport android.os.IInterface;\n\n/**\n * The ipc interface to talk to a content provider.\n * @hide\n */\npublic interface IContentProvider extends IInterface {\n}\n"
  },
  {
    "path": "AndroidStub/src/main/java/android/content/pm/ManifestDigest.java",
    "content": "package android.content.pm;\n\nimport android.os.Parcel;\nimport android.os.Parcelable;\n\nimport java.util.jar.Attributes;\n\n/**\n * @author johnsonlee\n */\npublic class ManifestDigest implements Parcelable {\n\n    ManifestDigest(final byte[] digest) {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    private ManifestDigest(final Parcel source) {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    static ManifestDigest fromAttributes(final Attributes attributes) {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    @Override\n    public int describeContents() {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    @Override\n    public int hashCode() {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    @Override\n    public String toString() {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    @Override\n    public void writeToParcel(final Parcel dest, final int flags) {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public static final Parcelable.Creator<ManifestDigest> CREATOR = new Parcelable.Creator<ManifestDigest>() {\n        public ManifestDigest createFromParcel(Parcel source) {\n            return new ManifestDigest(source);\n        }\n\n        public ManifestDigest[] newArray(int size) {\n            return new ManifestDigest[size];\n        }\n    };\n\n}\n"
  },
  {
    "path": "AndroidStub/src/main/java/android/content/pm/PackageParser.java",
    "content": "package android.content.pm;\n\nimport android.annotation.SuppressLint;\nimport android.content.ComponentName;\nimport android.content.IntentFilter;\nimport android.content.res.TypedArray;\nimport android.os.Bundle;\nimport android.util.ArrayMap;\nimport android.util.ArraySet;\nimport android.util.DisplayMetrics;\n\nimport java.io.File;\nimport java.io.PrintWriter;\nimport java.security.PublicKey;\nimport java.security.cert.Certificate;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Set;\n\n/**\n * @author johnsonlee\n */\npublic class PackageParser {\n\n    public final static int PARSE_IS_SYSTEM = 1 << 0;\n    public final static int PARSE_CHATTY = 1 << 1;\n    public final static int PARSE_MUST_BE_APK = 1 << 2;\n    public final static int PARSE_IGNORE_PROCESSES = 1 << 3;\n    public final static int PARSE_FORWARD_LOCK = 1 << 4;\n    public final static int PARSE_EXTERNAL_STORAGE = 1 << 5;\n    public final static int PARSE_IS_SYSTEM_DIR = 1 << 6;\n    public final static int PARSE_IS_PRIVILEGED = 1 << 7;\n    public final static int PARSE_COLLECT_CERTIFICATES = 1 << 8;\n    public final static int PARSE_TRUSTED_OVERLAY = 1 << 9;\n\n    public static class NewPermissionInfo {\n        public final String name;\n        public final int sdkVersion;\n        public final int fileVersion;\n\n        public NewPermissionInfo(String name, int sdkVersion, int fileVersion) {\n            throw new RuntimeException(\"Stub!\");\n        }\n    }\n\n    public static class SplitPermissionInfo {\n        public final String rootPerm;\n        public final String[] newPerms;\n        public final int targetSdk;\n\n        public SplitPermissionInfo(String rootPerm, String[] newPerms, int targetSdk) {\n            throw new RuntimeException(\"Stub!\");\n        }\n    }\n\n    public static final PackageParser.NewPermissionInfo NEW_PERMISSIONS[] = new PackageParser.NewPermissionInfo[]{\n            new PackageParser.NewPermissionInfo(android.Manifest.permission.WRITE_EXTERNAL_STORAGE, android.os.Build.VERSION_CODES.DONUT, 0),\n            new PackageParser.NewPermissionInfo(android.Manifest.permission.READ_PHONE_STATE, android.os.Build.VERSION_CODES.DONUT, 0)\n    };\n\n    static class ParsePackageItemArgs {\n        final Package owner;\n        final String[] outError;\n        final int nameRes;\n        final int labelRes;\n        final int iconRes;\n        final int logoRes;\n        final int bannerRes;\n\n        String tag;\n        TypedArray sa;\n\n        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!\");\n        }\n    }\n\n    static class ParseComponentArgs extends ParsePackageItemArgs {\n        final String[] sepProcesses;\n        final int processRes;\n        final int descriptionRes;\n        final int enabledRes;\n        int flags;\n\n        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) {\n            super(owner, outError, nameRes, labelRes, iconRes, logoRes, bannerRes);\n            throw new RuntimeException(\"Stub!\");\n        }\n    }\n\n    public static class PackageLite {\n        public final String packageName;\n        public final int versionCode;\n        public final int installLocation;\n        public final VerifierInfo[] verifiers;\n\n        /** Names of any split APKs, ordered by parsed splitName */\n        public final String[] splitNames;\n\n        /**\n         * Path where this package was found on disk. For monolithic packages\n         * this is path to single base APK file; for cluster packages this is\n         * path to the cluster directory.\n         */\n        public final String codePath;\n\n        /** Path of base APK */\n        public final String baseCodePath;\n        /** Paths of any split APKs, ordered by parsed splitName */\n        public final String[] splitCodePaths;\n\n        /** Revision code of base APK */\n        public final int baseRevisionCode;\n        /** Revision codes of any split APKs, ordered by parsed splitName */\n        public final int[] splitRevisionCodes;\n\n        public final boolean coreApp;\n        public final boolean multiArch;\n        public final boolean extractNativeLibs;\n\n        public PackageLite(final String codePath, final ApkLite baseApk, final String[] splitNames, final String[] splitCodePaths, final int[] splitRevisionCodes) {\n            throw new RuntimeException(\"Stub!\");\n        }\n\n        public List<String> getAllCodePaths() {\n            throw new RuntimeException(\"Stub!\");\n        }\n    }\n\n    public static class ApkLite {\n        public final String codePath;\n        public final String packageName;\n        public final String splitName;\n        public final int versionCode;\n        public final int revisionCode;\n        public final int installLocation;\n        public final VerifierInfo[] verifiers;\n        public final Signature[] signatures;\n        public final boolean coreApp;\n        public final boolean multiArch;\n        public final boolean extractNativeLibs;\n\n        public ApkLite(final String codePath, final String packageName, final String splitName, final int versionCode, final int revisionCode, final int installLocation, final List<VerifierInfo> verifiers, final Signature[] signatures, final boolean coreApp, final boolean multiArch, final boolean extractNativeLibs) {\n            throw new RuntimeException(\"Stub!\");\n        }\n    }\n\n    /**\n     * For Android 5.0+\n     */\n    public PackageParser() {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public PackageParser(final String archiveSourcePath) {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public void setSeparateProcesses(final String[] procs) {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public void setOnlyCoreApps(final boolean onlyCoreApps) {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public void setDisplayMetrics(final DisplayMetrics metrics) {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public static final boolean isApkFile(final File file) {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public static PackageInfo generatePackageInfo(final PackageParser.Package p, final int gids[], final int flags, final long firstInstallTime, final long lastUpdateTime, final Set<String> grantedPermissions, final PackageUserState state) {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public static boolean isAvailable(final PackageUserState state) {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public static PackageInfo generatePackageInfo(final PackageParser.Package p, final int gids[], final int flags, final long firstInstallTime, final long lastUpdateTime, final Set<String> grantedPermissions, final PackageUserState state, final int userId) {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    /**\n     * Parse only lightweight details about the package at the given location.\n     * Automatically detects if the package is a monolithic style (single APK\n     * file) or cluster style (directory of APKs).\n     * <p>\n     * This performs sanity checking on cluster style packages, such as\n     * requiring identical package name and version codes, a single base APK,\n     * and unique split names.\n     *\n     * @see PackageParser#parsePackage(File, int)\n     */\n    public static PackageLite parsePackageLite(final File packageFile, final int flags) throws PackageParserException {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    /**\n     * Parse the package at the given location. Automatically detects if the\n     * package is a monolithic style (single APK file) or cluster style\n     * (directory of APKs).\n     * <p>\n     * This performs sanity checking on cluster style packages, such as\n     * requiring identical package name and version codes, a single base APK,\n     * and unique split names.\n     * <p>\n     * Note that this <em>does not</em> perform signature verification; that\n     * must be done separately in {@link #collectCertificates(Package, int)}.\n     *\n     * @see #parsePackageLite(File, int)\n     * @since Android 5.0+\n     */\n    public Package parsePackage(final File packageFile, final int flags) throws PackageParserException {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    /**\n     *\n     * @param sourceFile\n     * @param destCodePath\n     * @param metrics\n     * @param flags\n     * @return\n     * @since Android 2.3+\n     */\n    public Package parsePackage(final File sourceFile, final String destCodePath, final DisplayMetrics metrics, final int flags) {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public void collectManifestDigest(final Package pkg) throws PackageParserException {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public void collectCertificates(final Package pkg, final int flags) throws PackageParserException {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    /**\n     * Utility method that retrieves lightweight details about a single APK\n     * file, including package name, split name, and install location.\n     *\n     * @param apkFile path to a single APK\n     * @param flags optional parse flags, such as\n     *            {@link #PARSE_COLLECT_CERTIFICATES}\n     */\n    public static ApkLite parseApkLite(final File apkFile, final int flags) throws PackageParserException {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public static ApplicationInfo generateApplicationInfo(final Package p, final int flags, final PackageUserState state) {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public static ApplicationInfo generateApplicationInfo(final Package p, final int flags, final PackageUserState state, final int userId) {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public static ApplicationInfo generateApplicationInfo(final ApplicationInfo ai, final int flags, final PackageUserState state, final int userId) {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public static final PermissionInfo generatePermissionInfo(final Permission p, final int flags) {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public static final PermissionGroupInfo generatePermissionGroupInfo(final PermissionGroup pg, final int flags) {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public static final InstrumentationInfo generateInstrumentationInfo(final Instrumentation i, final int flags) {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public static final ServiceInfo generateServiceInfo(final Service s, final int flags, final PackageUserState state, final int userId) {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public static final ProviderInfo generateProviderInfo(final Provider p, final int flags, final PackageUserState state, final int userId) {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public static final ActivityInfo generateActivityInfo(final Activity a, final int flags, final PackageUserState state, final int userId) {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public static final ActivityInfo generateActivityInfo(final ActivityInfo ai, final int flags, final PackageUserState state, final int userId) {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    /**\n     * Representation of a full package parsed from APK files on disk. A package\n     * consists of a single base APK, and zero or more split APKs.\n     */\n    public final static class Package {\n\n        public String packageName;\n\n        /** Names of any split APKs, ordered by parsed splitName */\n        public String[] splitNames;\n\n        // TODO: work towards making these paths invariant\n\n        public String volumeUuid;\n\n        /**\n         * Path where this package was found on disk. For monolithic packages\n         * this is path to single base APK file; for cluster packages this is\n         * path to the cluster directory.\n         */\n        public String codePath;\n\n        /** Path of base APK */\n        public String baseCodePath;\n        /** Paths of any split APKs, ordered by parsed splitName */\n        public String[] splitCodePaths;\n\n        /** Revision code of base APK */\n        public int baseRevisionCode;\n        /** Revision codes of any split APKs, ordered by parsed splitName */\n        public int[] splitRevisionCodes;\n\n        /** Flags of any split APKs; ordered by parsed splitName */\n        public int[] splitFlags;\n\n        /**\n         * Private flags of any split APKs; ordered by parsed splitName.\n         *\n         * {@hide}\n         */\n        public int[] splitPrivateFlags;\n\n        public boolean baseHardwareAccelerated;\n\n        // For now we only support one application per package.\n        public final ApplicationInfo applicationInfo = new ApplicationInfo();\n\n        public final ArrayList<Permission> permissions = new ArrayList<Permission>(0);\n        public final ArrayList<PermissionGroup> permissionGroups = new ArrayList<PermissionGroup>(0);\n        public final ArrayList<Activity> activities = new ArrayList<Activity>(0);\n        public final ArrayList<Activity> receivers = new ArrayList<Activity>(0);\n        public final ArrayList<Provider> providers = new ArrayList<Provider>(0);\n        public final ArrayList<Service> services = new ArrayList<Service>(0);\n        public final ArrayList<Instrumentation> instrumentation = new ArrayList<Instrumentation>(0);\n\n        public final ArrayList<String> requestedPermissions = new ArrayList<String>();\n\n        public ArrayList<String> protectedBroadcasts;\n\n        public ArrayList<String> libraryNames = null;\n        public ArrayList<String> usesLibraries = null;\n        public ArrayList<String> usesOptionalLibraries = null;\n        public String[] usesLibraryFiles = null;\n\n        public ArrayList<ActivityIntentInfo> preferredActivityFilters = null;\n\n        public ArrayList<String> mOriginalPackages = null;\n        public String mRealPackage = null;\n        public ArrayList<String> mAdoptPermissions = null;\n\n        // We store the application meta-data independently to avoid multiple unwanted references\n        public Bundle mAppMetaData = null;\n\n        // The version code declared for this package.\n        public int mVersionCode;\n\n        // The version name declared for this package.\n        public String mVersionName;\n\n        // The shared user id that this package wants to use.\n        public String mSharedUserId;\n\n        // The shared user label that this package wants to use.\n        public int mSharedUserLabel;\n\n        // Signatures that were read from the package.\n        public Signature[] mSignatures;\n        public SigningDetails mSigningDetails;\n        public Certificate[][] mCertificates;\n\n        // For use by package manager service for quick lookup of\n        // preferred up order.\n        public int mPreferredOrder = 0;\n\n        // For use by package manager to keep track of where it needs to do dexopt.\n//        public final ArraySet<String> mDexOptPerformed = new ArraySet<>(4);\n\n        // For use by package manager to keep track of when a package was last used.\n        public long mLastPackageUsageTimeInMills;\n\n        // // User set enabled state.\n        // public int mSetEnabled = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;\n        //\n        // // Whether the package has been stopped.\n        // public boolean mSetStopped = false;\n\n        // Additional data supplied by callers.\n        public Object mExtras;\n\n        // Applications hardware preferences\n        public ArrayList<ConfigurationInfo> configPreferences = null;\n\n        // Applications requested features\n        public ArrayList<FeatureInfo> reqFeatures = null;\n\n        // Applications requested feature groups\n        public ArrayList<FeatureGroupInfo> featureGroups = null;\n\n        public int installLocation;\n\n        public boolean coreApp;\n\n        /* An app that's required for all users and cannot be uninstalled for a user */\n        public boolean mRequiredForAllUsers;\n\n        /* The restricted account authenticator type that is used by this application */\n        public String mRestrictedAccountType;\n\n        /* The required account type without which this application will not function */\n        public String mRequiredAccountType;\n\n        /**\n         * Digest suitable for comparing whether this package's manifest is the\n         * same as another.\n         */\n        public ManifestDigest manifestDigest;\n\n        public String mOverlayTarget;\n        public int mOverlayPriority;\n        public boolean mTrustedOverlay;\n\n        /**\n         * Data used to feed the KeySetManagerService\n         */\n        public ArraySet<PublicKey> mSigningKeys;\n        public ArraySet<String> mUpgradeKeySets;\n        public ArrayMap<String, ArraySet<PublicKey>> mKeySetMapping;\n\n        /**\n         * The install time abi override for this package, if any.\n         *\n         * TODO: This seems like a horrible place to put the abiOverride because\n         * this isn't something the packageParser parsers. However, this fits in with\n         * the rest of the PackageManager where package scanning randomly pushes\n         * and prods fields out of {@code this.applicationInfo}.\n         */\n        public String cpuAbiOverride;\n\n        public Package(String packageName) {\n            throw new RuntimeException(\"Stub!\");\n        }\n\n        public List<String> getAllCodePaths() {\n            throw new RuntimeException(\"Stub!\");\n        }\n\n        /**\n         * Filtered set of {@link #getAllCodePaths()} that excludes\n         * resource-only APKs.\n         */\n        public List<String> getAllCodePathsExcludingResourceOnly() {\n            throw new RuntimeException(\"Stub!\");\n        }\n\n        public void setPackageName(final String newName) {\n            throw new RuntimeException(\"Stub!\");\n        }\n\n        public boolean hasComponentClassName(final String name) {\n            throw new RuntimeException(\"Stub!\");\n        }\n\n        public boolean isForwardLocked() {\n            throw new RuntimeException(\"Stub!\");\n        }\n\n        public boolean isSystemApp() {\n            throw new RuntimeException(\"Stub!\");\n        }\n\n        public boolean isPrivilegedApp() {\n            throw new RuntimeException(\"Stub!\");\n        }\n\n        public boolean isUpdatedSystemApp() {\n            throw new RuntimeException(\"Stub!\");\n        }\n\n        public boolean canHaveOatDir() {\n            throw new RuntimeException(\"Stub!\");\n        }\n\n        @Override\n        public String toString() {\n            throw new RuntimeException(\"Stub!\");\n        }\n    }\n\n    public static class Component<II extends IntentInfo> {\n        public final Package owner;\n        public final ArrayList<II> intents;\n        public final String className;\n        public Bundle metaData;\n\n        ComponentName componentName;\n        String componentShortName;\n\n        public Component(final Package owner) {\n            throw new RuntimeException(\"Stub!\");\n        }\n\n        public Component(final ParsePackageItemArgs args, final PackageItemInfo outInfo) {\n            throw new RuntimeException(\"Stub!\");\n        }\n\n        public Component(final ParseComponentArgs args, final ComponentInfo outInfo) {\n            throw new RuntimeException(\"Stub!\");\n        }\n\n        public Component(final Component<II> clone) {\n            throw new RuntimeException(\"Stub!\");\n        }\n\n        public ComponentName getComponentName() {\n            throw new RuntimeException(\"Stub!\");\n        }\n\n        public void appendComponentShortName(final StringBuilder sb) {\n            throw new RuntimeException(\"Stub!\");\n        }\n\n        public void printComponentShortName(final PrintWriter pw) {\n            throw new RuntimeException(\"Stub!\");\n        }\n\n        public void setPackageName(final String packageName) {\n            throw new RuntimeException(\"Stub!\");\n        }\n    }\n\n    public final static class Permission extends Component<IntentInfo> {\n        public final PermissionInfo info;\n        public boolean tree;\n        public PermissionGroup group;\n\n        public Permission(final Package owner) {\n            super(owner);\n            throw new RuntimeException(\"Stub!\");\n        }\n\n        public Permission(final Package owner, final PermissionInfo info) {\n            super(owner);\n            throw new RuntimeException(\"Stub!\");\n        }\n\n        @Override\n        public void setPackageName(final String packageName) {\n            throw new RuntimeException(\"Stub!\");\n        }\n\n        @Override\n        public String toString() {\n            throw new RuntimeException(\"Stub!\");\n        }\n    }\n\n    public final static class PermissionGroup extends Component<IntentInfo> {\n        public final PermissionGroupInfo info;\n\n        public PermissionGroup(final Package owner) {\n            super(owner);\n            throw new RuntimeException(\"Stub!\");\n        }\n\n        public PermissionGroup(final Package owner, final PermissionGroupInfo info) {\n            super(owner);\n            throw new RuntimeException(\"Stub!\");\n        }\n\n        @Override\n        public void setPackageName(final String packageName) {\n            throw new RuntimeException(\"Stub!\");\n        }\n\n        @Override\n        public String toString() {\n            throw new RuntimeException(\"Stub!\");\n        }\n    }\n\n    public final static class Activity extends Component<ActivityIntentInfo> {\n        public final ActivityInfo info;\n\n        public Activity(final ParseComponentArgs args, final ActivityInfo info) {\n            super(args, info);\n            throw new RuntimeException(\"Stub!\");\n        }\n\n        @Override\n        public void setPackageName(final String packageName) {\n            throw new RuntimeException(\"Stub!\");\n        }\n\n        @Override\n        public String toString() {\n            throw new RuntimeException(\"Stub!\");\n        }\n    }\n\n    public final static class Service extends Component<ServiceIntentInfo> {\n        public final ServiceInfo info;\n\n        public Service(final ParseComponentArgs args, final ServiceInfo info) {\n            super(args, info);\n            throw new RuntimeException(\"Stub!\");\n        }\n\n        @Override\n        public void setPackageName(final String packageName) {\n            throw new RuntimeException(\"Stub!\");\n        }\n\n        @Override\n        public String toString() {\n            throw new RuntimeException(\"Stub!\");\n        }\n    }\n\n    public final static class Provider extends Component<ProviderIntentInfo> {\n        public final ProviderInfo info;\n        public boolean syncable;\n\n        public Provider(final ParseComponentArgs args, final ProviderInfo info) {\n            super(args, info);\n            throw new RuntimeException(\"Stub!\");\n        }\n\n        public Provider(final Provider existingProvider) {\n            super(existingProvider);\n            throw new RuntimeException(\"Stub!\");\n        }\n\n        @Override\n        public void setPackageName(final String packageName) {\n            throw new RuntimeException(\"Stub!\");\n        }\n\n        @Override\n        public String toString() {\n            throw new RuntimeException(\"Stub!\");\n        }\n    }\n\n    public final static class Instrumentation extends Component {\n        public final InstrumentationInfo info;\n\n        public Instrumentation(final ParsePackageItemArgs args, final InstrumentationInfo info) {\n            super(args, info);\n            throw new RuntimeException(\"Stub!\");\n        }\n\n        @Override\n        public void setPackageName(final String packageName) {\n            throw new RuntimeException(\"Stub!\");\n        }\n\n        @Override\n        public String toString() {\n            throw new RuntimeException(\"Stub!\");\n        }\n    }\n\n    @SuppressLint(\"ParcelCreator\")\n    public static class IntentInfo extends IntentFilter {\n        public boolean hasDefault;\n        public int labelRes;\n        public CharSequence nonLocalizedLabel;\n        public int icon;\n        public int logo;\n        public int banner;\n        public int preferred;\n    }\n\n    @SuppressLint(\"ParcelCreator\")\n    public final static class ActivityIntentInfo extends IntentInfo {\n        public final Activity activity;\n\n        public ActivityIntentInfo(final Activity activity) {\n            throw new RuntimeException(\"Stub!\");\n        }\n\n        @Override\n        public String toString() {\n            throw new RuntimeException(\"Stub!\");\n        }\n    }\n\n    @SuppressLint(\"ParcelCreator\")\n    public final static class ServiceIntentInfo extends IntentInfo {\n        public final Service service;\n\n        public ServiceIntentInfo(final Service service) {\n            throw new RuntimeException(\"Stub!\");\n        }\n\n        @Override\n        public String toString() {\n            throw new RuntimeException(\"Stub!\");\n        }\n    }\n\n    @SuppressLint(\"ParcelCreator\")\n    public static final class ProviderIntentInfo extends IntentInfo {\n        public final Provider provider;\n\n        public ProviderIntentInfo(final Provider provider) {\n            throw new RuntimeException(\"Stub!\");\n        }\n\n        @Override\n        public String toString() {\n            throw new RuntimeException(\"Stub!\");\n        }\n    }\n\n    public static class PackageParserException extends Exception {\n\n        public PackageParserException(int error, String detailMessage) {\n            super(detailMessage);\n            throw new RuntimeException(\"Stub!\");\n        }\n\n        public PackageParserException(int error, String detailMessage, Throwable throwable) {\n            super(detailMessage, throwable);\n            throw new RuntimeException(\"Stub!\");\n        }\n\n    }\n    \n    public static class SigningDetails {\n        public Signature[] signatures;\n    }\n\n}\n"
  },
  {
    "path": "AndroidStub/src/main/java/android/content/pm/PackageUserState.java",
    "content": "package android.content.pm;\n\nimport android.util.ArraySet;\n\n/**\n * @author johnsonlee\n */\npublic class PackageUserState {\n\n    public boolean stopped;\n    public boolean notLaunched;\n    public boolean installed;\n    public boolean hidden; // Is the app restricted by owner / admin\n    public int enabled;\n    public boolean blockUninstall;\n\n    public String lastDisableAppCaller;\n\n    public ArraySet<String> disabledComponents;\n    public ArraySet<String> enabledComponents;\n\n    public int domainVerificationStatus;\n    public int appLinkGeneration;\n\n    public PackageUserState() {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public PackageUserState(final PackageUserState o) {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n}\n"
  },
  {
    "path": "AndroidStub/src/main/java/android/content/pm/VerifierInfo.java",
    "content": "package android.content.pm;\n\nimport android.os.Parcel;\nimport android.os.Parcelable;\n\nimport java.security.PublicKey;\n\n/**\n * @author johnsonlee\n */\npublic class VerifierInfo implements Parcelable{\n\n    public static final Parcelable.Creator<VerifierInfo> CREATOR = new Parcelable.Creator<VerifierInfo>() {\n        public VerifierInfo createFromParcel(final Parcel source) {\n            return new VerifierInfo(source);\n        }\n\n        public VerifierInfo[] newArray(final int size) {\n            return new VerifierInfo[size];\n        }\n    };\n\n    public VerifierInfo(final String packageName, final PublicKey publicKey) {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    private VerifierInfo(final Parcel source) {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    @Override\n    public int describeContents() {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    @Override\n    public void writeToParcel(final Parcel dest, final int flags) {\n        throw new RuntimeException(\"Stub!\");\n    }\n}\n"
  },
  {
    "path": "AndroidStub/src/main/java/android/content/res/CompatibilityInfo.java",
    "content": "package android.content.res;\n\nimport android.content.pm.ApplicationInfo;\nimport android.os.Parcel;\nimport android.os.Parcelable;\n\n/**\n * Created by qiaopu on 2018/5/3.\n */\npublic class CompatibilityInfo implements Parcelable {\n    \n    public CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw,\n                             boolean forceCompat) {\n        throw new RuntimeException(\"Stub!\");\n    }\n    \n    @Override\n    public int describeContents() {\n        throw new RuntimeException(\"Stub!\");\n    }\n    \n    @Override\n    public void writeToParcel(Parcel dest, int flags) {\n        throw new RuntimeException(\"Stub!\");\n    }\n    \n    public static final Parcelable.Creator<CompatibilityInfo> CREATOR\n        = new Parcelable.Creator<CompatibilityInfo>() {\n        @Override\n        public CompatibilityInfo createFromParcel(Parcel source) {\n            throw new RuntimeException(\"Stub!\");\n        }\n        \n        @Override\n        public CompatibilityInfo[] newArray(int size) {\n            throw new RuntimeException(\"Stub!\");\n        }\n    };\n    \n}\n\n"
  },
  {
    "path": "AndroidStub/src/main/java/android/content/res/Resources.java",
    "content": "package android.content.res;\n\nimport android.graphics.drawable.Drawable;\nimport android.util.DisplayMetrics;\n\n/**\n * Created by qiaopu on 2018/5/18.\n */\npublic class Resources {\n    \n    public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) {\n        throw new RuntimeException(\"Stub!\");\n    }\n    \n    public final AssetManager getAssets() {\n        throw new RuntimeException(\"Stub!\");\n    }\n    \n    public int getColor(int id) throws NotFoundException {\n        throw new RuntimeException(\"Stub!\");\n    }\n    \n    public Configuration getConfiguration() {\n        throw new RuntimeException(\"Stub!\");\n    }\n    \n    public DisplayMetrics getDisplayMetrics() {\n        throw new RuntimeException(\"Stub!\");\n    }\n    \n    public Drawable getDrawable(int id) throws NotFoundException {\n        throw new RuntimeException(\"Stub!\");\n    }\n    \n    public String getString(int id) throws NotFoundException {\n        throw new RuntimeException(\"Stub!\");\n    }\n    \n    public CharSequence getText(int id) throws NotFoundException {\n        throw new RuntimeException(\"Stub!\");\n    }\n    \n    public XmlResourceParser getXml(int id) throws NotFoundException {\n        throw new RuntimeException(\"Stub!\");\n    }\n    \n    public ResourcesImpl getImpl() {\n        throw new RuntimeException(\"Stub!\");\n    }\n    \n    public final Theme newTheme() {\n        throw new RuntimeException(\"Stub!\");\n    }\n    \n    public void updateConfiguration(Configuration config, DisplayMetrics metrics) {\n        throw new RuntimeException(\"Stub!\");\n    }\n    \n    public final class Theme {\n    \n        public void applyStyle(int resId, boolean force) {\n            throw new RuntimeException(\"Stub!\");\n        }\n    \n        public TypedArray obtainStyledAttributes(int[] attrs) {\n            throw new RuntimeException(\"Stub!\");\n        }\n    \n        public void setTo(Theme other) {\n            throw new RuntimeException(\"Stub!\");\n        }\n        \n    }\n    \n    public static class NotFoundException extends RuntimeException {\n    \n    }\n    \n}\n"
  },
  {
    "path": "AndroidStub/src/main/java/android/content/res/ResourcesImpl.java",
    "content": "package android.content.res;\n\n/**\n * Created by qiaopu on 2018/5/18.\n */\npublic class ResourcesImpl {\n\n}\n"
  },
  {
    "path": "AndroidStub/src/main/java/android/content/res/ResourcesKey.java",
    "content": "package android.content.res;\n\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\n\n/**\n * Created by qiaopu on 2018/5/3.\n */\npublic final class ResourcesKey {\n    @Nullable\n    public final String mResDir;\n    \n    @Nullable\n    public final String[] mSplitResDirs;\n    \n    @Nullable\n    public final String[] mOverlayDirs;\n    \n    @Nullable\n    public final String[] mLibDirs;\n    \n    public final int mDisplayId;\n    \n    @NonNull\n    public final Configuration mOverrideConfiguration;\n    \n    @NonNull\n    public final CompatibilityInfo mCompatInfo;\n    \n    public ResourcesKey(@Nullable String resDir,\n                        @Nullable String[] splitResDirs,\n                        @Nullable String[] overlayDirs,\n                        @Nullable String[] libDirs,\n                        int displayId,\n                        @Nullable Configuration overrideConfig,\n                        @Nullable CompatibilityInfo compatInfo) {\n        throw new RuntimeException(\"Stub!\");\n    }\n    \n}\n\n"
  },
  {
    "path": "AndroidStub/src/main/java/android/databinding/DataBinderMapper.java",
    "content": "/*\n * Copyright (C) 2014 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage android.databinding;\nimport android.view.View;\n/**\n * This class will be stripped from the jar and then replaced by the annotation processor\n * as part of the code generation step. This class's existence is just to ensure that\n * compile works and no reflection is needed to access the generated class.\n */\nclass DataBinderMapper {\n    public ViewDataBinding getDataBinder(DataBindingComponent bindingComponent, View view,\n            int layoutId) {\n        return null;\n    }\n    ViewDataBinding getDataBinder(DataBindingComponent bindingComponent, View[] view, int layoutId) {\n        return null;\n    }\n    public int getLayoutId(String tag) { return 0; }\n    public String convertBrIdToString(int id) {\n        return null;\n    }\n    public static int TARGET_MIN_SDK = 0;\n}"
  },
  {
    "path": "AndroidStub/src/main/java/android/databinding/DataBindingComponent.java",
    "content": "/*\n * Copyright (C) 2015 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage android.databinding;\n/**\n * This interface is generated during compilation to contain getters for all used instance\n * BindingAdapters. When a BindingAdapter is an instance method, an instance of the class\n * implementing the method must be instantiated. This interface will be generated with a getter\n * for each class with the name get* where * is simple class name of the declaring BindingAdapter\n * class/interface. Name collisions will be resolved by adding a numeric suffix to the getter.\n * <p>\n * An instance of this class may also be passed into static or instance BindingAdapters as the\n * first parameter.\n * <p>\n * If you are using Dagger 2, you should extend this interface and annotate the extended interface\n * as a Component.\n */\npublic interface DataBindingComponent {\n}"
  },
  {
    "path": "AndroidStub/src/main/java/android/os/ServiceManager.java",
    "content": "package android.os;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * @author johnsonlee\n */\npublic final class ServiceManager {\n\n    private static HashMap<String, IBinder> sCache = new HashMap<String, IBinder>();\n\n    public static IBinder getService(final String name) {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public static void addService(final String name, final IBinder service) {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public static void addService(final String name, final IBinder service, final boolean allowIsolated) {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public static IBinder checkService(final String name) {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public static String[] listServices() throws RemoteException {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public static void initServiceCache(final Map<String, IBinder> cache) {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n}\n"
  },
  {
    "path": "AndroidStub/src/main/java/android/os/SystemProperties.java",
    "content": "package android.os;\n\n/**\n * @author johnsonlee\n */\npublic class SystemProperties {\n\n    public static String get(final String key) {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public static String get(final String key, final String def) {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public static int getInt(final String key, final int def) {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public static long getLong(final String key, final long def) {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public static boolean getBoolean(final String key, final boolean def) {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public static void set(final String key, final String val) {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    public static void addChangeCallback(final Runnable callback) {\n        throw new RuntimeException(\"Stub!\");\n    }\n}\n"
  },
  {
    "path": "AndroidStub/src/main/java/android/util/Singleton.java",
    "content": "package android.util;\n\n/**\n * @author johnsonlee\n */\npublic abstract class Singleton<T> {\n    public Singleton() {\n        throw new RuntimeException(\"Stub!\");\n    }\n\n    protected abstract T create();\n\n    public T get() {\n        throw new RuntimeException(\"Stub!\");\n    }\n}\n"
  },
  {
    "path": "AndroidStub/src/main/java/com/android/internal/R.java",
    "content": "package com.android.internal;\n\n/**\n * @author johnsonlee\n */\npublic final class R {\n\n    public static final class id {\n\n        public static int action0 = 0;\n\n        public static int actions = 0;\n\n        public static int action_divider = 0;\n\n        public static int big_picture = 0;\n\n        public static int big_text = 0;\n\n        public static int big_text2 = 0;\n\n        public static int chronometer = 0;\n\n        public static int icon = 0;\n\n        public static int inbox_text0 = 0;\n\n        public static int inbox_text1 = 0;\n\n        public static int inbox_text2 = 0;\n\n        public static int inbox_text3 = 0;\n\n        public static int inbox_text4 = 0;\n\n        public static int inbox_text5 = 0;\n\n        public static int inbox_text6 = 0;\n\n        public static int inbox_end_pad = 0;\n\n        public static int info = 0;\n\n        public static int line1 = 0;\n\n        public static int line3 = 0;\n\n        public static int overflow_divider = 0;\n\n        public static int progress = 0;\n\n        public static int title = 0;\n\n        public static int text = 0;\n\n        public static int text2 = 0;\n\n        public static int time = 0;\n\n        public static int right_icon = 0;\n\n        public static int fillInIntent = 0;\n\n        public static int status_bar_latest_event_content = 0;\n    }\n\n}\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contribution Guideline\n\nThanks for considering to contribute this project. All issues and pull requests are highly appreciated.\n\n## Pull Requests\n\nBefore sending pull request to this project, please read and follow guidelines below.\n\n1. Branch: We only accept pull request on `dev` branch.\n2. Coding style: Follow the coding style used in VirtualAPK.\n3. Commit message: Use English and be aware of your spell.\n4. Test: Make sure to test your code.\n\nAdd device mode, API version, related log, screenshots and other related information in your pull request if possible.\n\nNOTE: We assume all your contribution can be licensed under the [Apache License 2.0](https://github.com/didi/VirtualAPK/blob/master/LICENSE). \n\n## Issues\n\nWe love clearly described issues. :)\n\nFollowing information can help us to resolve the issue faster.\n\n* Device mode and hardware information.\n* API version.\n* Logs.\n* Screenshots.\n* Steps to reproduce the issue.\n"
  },
  {
    "path": "CoreLibrary/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "CoreLibrary/build.gradle",
    "content": "import com.android.build.gradle.api.ApplicationVariant\nimport com.android.build.gradle.api.LibraryVariant\nimport com.google.common.base.Joiner\n\napply plugin: 'com.android.library'\n\nandroid {\n    compileSdkVersion VERSION_COMPILE_SDK\n    buildToolsVersion VERSION_BUILD_TOOLS\n\n    defaultConfig {\n        minSdkVersion VERSION_MIN_SDK\n        targetSdkVersion VERSION_TARGET_SDK\n        versionCode 1\n        versionName \"1.0.0\"\n    }\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n    compileOptions {\n        sourceCompatibility SOURCE_COMPATIBILITY\n    }\n    lintOptions {\n        abortOnError false\n    }\n}\n\nrepositories {\n    mavenCentral()\n    jcenter()\n}\n\nfinal String projectAndroidStub = ':AndroidStub'\n\ndependencies {\n    compile fileTree(dir: 'libs', include: ['*.jar'])\n\n    provided project(projectAndroidStub)\n\n    testCompile 'junit:junit:4.12'\n}\n\n// Using Stub classes first when compiling.\nafterEvaluate {\n    project.android.libraryVariants.each { LibraryVariant variant ->\n        variant.javaCompile.doFirst { JavaCompile javaCompile ->\n            String projectAndroidStubPath = project.project(projectAndroidStub).projectDir.canonicalPath\n//            println \"projectAndroidStubPath: ${projectAndroidStubPath}\"\n            File stubPath = javaCompile.classpath.find {\n                it.canonicalPath.startsWith(projectAndroidStubPath)\n            }\n            if (stubPath == null) {\n                throw new RuntimeException(\"reset bootclasspath error.\")\n            }\n            javaCompile.options.setBootClasspath(Joiner.on(File.pathSeparator).join(stubPath, javaCompile.options.bootClasspath))\n        }\n    }\n}\n\napply from: 'upload.gradle'\n"
  },
  {
    "path": "CoreLibrary/gradle.properties",
    "content": "\n"
  },
  {
    "path": "CoreLibrary/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /Users/didi/Library/Android/sdk/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n"
  },
  {
    "path": "CoreLibrary/src/androidTest/java/com/didi/virtualapk/core/ApplicationTest.java",
    "content": "package com.didi.virtualapk.core;\n\nimport android.app.Application;\nimport android.test.ApplicationTestCase;\n\n/**\n * <a href=\"http://d.android.com/tools/testing/testing_android.html\">Testing Fundamentals</a>\n */\npublic class ApplicationTest extends ApplicationTestCase<Application> {\n    public ApplicationTest() {\n        super(Application.class);\n    }\n}"
  },
  {
    "path": "CoreLibrary/src/main/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.didi.virtualapk.core\">\n\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n    <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />\n    <uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\" />\n    <uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\" />\n    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />\n    \n    <application>\n        <activity android:exported=\"false\" android:name=\"com.didi.virtualapk.delegate.StubActivity\" android:launchMode=\"standard\"/>\n        <!-- Stub Activities -->\n        <activity android:exported=\"false\" android:name=\".A$1\" android:launchMode=\"standard\"/>\n        <activity android:exported=\"false\" android:name=\".A$2\" android:launchMode=\"standard\"\n            android:theme=\"@android:style/Theme.Translucent\" />\n\n        <!-- Stub Activities -->\n        <activity android:exported=\"false\" android:name=\".B$1\" android:launchMode=\"singleTop\"/>\n        <activity android:exported=\"false\" android:name=\".B$2\" android:launchMode=\"singleTop\"/>\n        <activity android:exported=\"false\" android:name=\".B$3\" android:launchMode=\"singleTop\"/>\n        <activity android:exported=\"false\" android:name=\".B$4\" android:launchMode=\"singleTop\"/>\n        <activity android:exported=\"false\" android:name=\".B$5\" android:launchMode=\"singleTop\"/>\n        <activity android:exported=\"false\" android:name=\".B$6\" android:launchMode=\"singleTop\"/>\n        <activity android:exported=\"false\" android:name=\".B$7\" android:launchMode=\"singleTop\"/>\n        <activity android:exported=\"false\" android:name=\".B$8\" android:launchMode=\"singleTop\"/>\n\n        <!-- Stub Activities -->\n        <activity android:exported=\"false\" android:name=\".C$1\" android:launchMode=\"singleTask\"/>\n        <activity android:exported=\"false\" android:name=\".C$2\" android:launchMode=\"singleTask\"/>\n        <activity android:exported=\"false\" android:name=\".C$3\" android:launchMode=\"singleTask\"/>\n        <activity android:exported=\"false\" android:name=\".C$4\" android:launchMode=\"singleTask\"/>\n        <activity android:exported=\"false\" android:name=\".C$5\" android:launchMode=\"singleTask\"/>\n        <activity android:exported=\"false\" android:name=\".C$6\" android:launchMode=\"singleTask\"/>\n        <activity android:exported=\"false\" android:name=\".C$7\" android:launchMode=\"singleTask\"/>\n        <activity android:exported=\"false\" android:name=\".C$8\" android:launchMode=\"singleTask\"/>\n\n        <!-- Stub Activities -->\n        <activity android:exported=\"false\" android:name=\".D$1\" android:launchMode=\"singleInstance\"/>\n        <activity android:exported=\"false\" android:name=\".D$2\" android:launchMode=\"singleInstance\"/>\n        <activity android:exported=\"false\" android:name=\".D$3\" android:launchMode=\"singleInstance\"/>\n        <activity android:exported=\"false\" android:name=\".D$4\" android:launchMode=\"singleInstance\"/>\n        <activity android:exported=\"false\" android:name=\".D$5\" android:launchMode=\"singleInstance\"/>\n        <activity android:exported=\"false\" android:name=\".D$6\" android:launchMode=\"singleInstance\"/>\n        <activity android:exported=\"false\" android:name=\".D$7\" android:launchMode=\"singleInstance\"/>\n        <activity android:exported=\"false\" android:name=\".D$8\" android:launchMode=\"singleInstance\"/>\n\n        <!-- Local Service running in main process -->\n        <service android:exported=\"false\" android:name=\"com.didi.virtualapk.delegate.LocalService\" />\n\n        <!-- Daemon Service running in child process -->\n        <service android:exported=\"false\" android:name=\"com.didi.virtualapk.delegate.RemoteService\" android:process=\":daemon\">\n            <intent-filter>\n                <action android:name=\"${applicationId}.intent.ACTION_DAEMON_SERVICE\" />\n            </intent-filter>\n        </service>\n\n        <provider\n            android:exported=\"false\"\n            android:name=\"com.didi.virtualapk.delegate.RemoteContentProvider\"\n            android:authorities=\"${applicationId}.VirtualAPK.Provider\"\n            android:process=\":daemon\" />\n\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "CoreLibrary/src/main/java/android/content/ContentResolverWrapper.java",
    "content": "package android.content;\n\nimport android.annotation.TargetApi;\nimport android.os.Build;\n\n/**\n * Wrapper of {@link ContentResolver}\n * Created by qiaopu on 2018/5/7.\n */\npublic abstract class ContentResolverWrapper extends ContentResolver {\n    \n    ContentResolver mBase;\n    \n    public ContentResolverWrapper(Context context) {\n        super(context);\n        mBase = context.getContentResolver();\n    }\n    \n    @Override\n    protected IContentProvider acquireProvider(Context context, String auth) {\n        return mBase.acquireProvider(context, auth);\n    }\n    \n    @Override\n    protected IContentProvider acquireExistingProvider(Context context, String auth) {\n        return mBase.acquireExistingProvider(context, auth);\n    }\n    \n    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)\n    @Override\n    protected IContentProvider acquireUnstableProvider(Context context, String auth) {\n        return mBase.acquireUnstableProvider(context, auth);\n    }\n    \n    @Override\n    public boolean releaseProvider(IContentProvider icp) {\n        return mBase.releaseProvider(icp);\n    }\n    \n    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)\n    @Override\n    public boolean releaseUnstableProvider(IContentProvider icp) {\n        return mBase.releaseUnstableProvider(icp);\n    }\n    \n    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)\n    @Override\n    public void unstableProviderDied(IContentProvider icp) {\n        mBase.unstableProviderDied(icp);\n    }\n    \n    @TargetApi(Build.VERSION_CODES.KITKAT_WATCH)\n    @Override\n    public void appNotRespondingViaProvider(IContentProvider icp) {\n        // dark greylist in Android P\n//        mBase.appNotRespondingViaProvider(icp);\n    }\n\n}\n"
  },
  {
    "path": "CoreLibrary/src/main/java/android/databinding/DataBinderMapperProxy.java",
    "content": "package android.databinding;\n\nimport android.support.annotation.NonNull;\nimport android.util.Log;\nimport android.view.View;\n\nimport com.didi.virtualapk.PluginManager;\nimport com.didi.virtualapk.internal.Constants;\nimport com.didi.virtualapk.internal.LoadedPlugin;\n\nimport java.util.LinkedList;\n\n/**\n * Replace {@link DataBindingUtil#sMapper}.\n * Created by qiaopu on 2018/4/11.\n */\npublic class DataBinderMapperProxy extends DataBinderMapper implements PluginManager.Callback {\n    public static final String TAG = Constants.TAG_PREFIX + \"DataBinderMapperProxy\";\n    \n    private final LinkedList<DataBinderMapper> mMappers;\n    private DataBinderMapper[] mCache;\n    \n    public DataBinderMapperProxy(@NonNull Object source) {\n        mMappers = new LinkedList<>();\n    \n        addMapper((DataBinderMapper) source);\n    }\n    \n    @Override\n    public ViewDataBinding getDataBinder(DataBindingComponent bindingComponent, View view, int layoutId) {\n        ViewDataBinding viewDataBinding;\n    \n        for (DataBinderMapper mapper : getCache()) {\n            viewDataBinding = mapper.getDataBinder(bindingComponent, view, layoutId);\n            if (viewDataBinding != null) {\n//                Log.d(TAG, \"Found by mapper: \" + mapper);\n                return viewDataBinding;\n            }\n        }\n        \n        return null;\n    }\n    \n    @Override\n    ViewDataBinding getDataBinder(DataBindingComponent bindingComponent, View[] view, int layoutId) {\n        ViewDataBinding viewDataBinding;\n    \n        for (DataBinderMapper mapper : getCache()) {\n            viewDataBinding = mapper.getDataBinder(bindingComponent, view, layoutId);\n            if (viewDataBinding != null) {\n//                Log.d(TAG, \"Found by mapper: \" + mapper);\n                return viewDataBinding;\n            }\n        }\n    \n        return null;\n    }\n    \n    @Override\n    public int getLayoutId(String tag) {\n        int layoutId;\n    \n        for (DataBinderMapper mapper : getCache()) {\n            layoutId = mapper.getLayoutId(tag);\n            if (layoutId != 0) {\n//                Log.d(TAG, \"Found by mapper: \" + mapper);\n                return layoutId;\n            }\n        }\n    \n        return 0;\n    }\n    \n    @Override\n    public String convertBrIdToString(int id) {\n        String brId;\n    \n        for (DataBinderMapper mapper : getCache()) {\n            brId = mapper.convertBrIdToString(id);\n            if (brId != null) {\n//                Log.d(TAG, \"Found by mapper: \" + mapper);\n                return brId;\n            }\n        }\n    \n        return null;\n    }\n    \n    @Override\n    public void onAddedLoadedPlugin(LoadedPlugin plugin) {\n        try {\n            String clsName = \"android.databinding.DataBinderMapper_\" + plugin.getPackageName().replace('.', '_');\n            Log.d(TAG, \"Try to find the class: \" + clsName);\n            Class cls = Class.forName(clsName, true, plugin.getClassLoader());\n            Object obj = cls.newInstance();\n    \n            addMapper((DataBinderMapper) obj);\n            \n        } catch (Exception e) {\n            Log.w(TAG, e);\n        }\n    }\n    \n    private void addMapper(DataBinderMapper mapper) {\n        int size = 0;\n        synchronized (mMappers) {\n            mMappers.add(mapper);\n            mCache = null;\n            size = mMappers.size();\n        }\n    \n        Log.d(TAG, \"Added mapper: \" + mapper + \", size: \" + size);\n    }\n    \n    private DataBinderMapper[] getCache() {\n        synchronized (mMappers) {\n            if (mCache == null) {\n                mCache = mMappers.toArray(new DataBinderMapper[mMappers.size()]);\n            }\n            return mCache;\n        }\n    }\n}\n"
  },
  {
    "path": "CoreLibrary/src/main/java/com/didi/virtualapk/PluginManager.java",
    "content": "/*\n * Copyright (C) 2017 Beijing Didi Infinity Technology and Development Co.,Ltd. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.didi.virtualapk;\n\nimport android.app.ActivityManager;\nimport android.app.ActivityManagerNative;\nimport android.app.ActivityThread;\nimport android.app.Application;\nimport android.app.IActivityManager;\nimport android.app.Instrumentation;\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.IContentProvider;\nimport android.content.Intent;\nimport android.content.pm.PackageManager;\nimport android.content.pm.ProviderInfo;\nimport android.content.pm.ResolveInfo;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.util.Log;\nimport android.util.Singleton;\n\nimport com.didi.virtualapk.delegate.ActivityManagerProxy;\nimport com.didi.virtualapk.delegate.IContentProviderProxy;\nimport com.didi.virtualapk.delegate.RemoteContentProvider;\nimport com.didi.virtualapk.internal.ComponentsHandler;\nimport com.didi.virtualapk.internal.Constants;\nimport com.didi.virtualapk.internal.LoadedPlugin;\nimport com.didi.virtualapk.internal.VAInstrumentation;\nimport com.didi.virtualapk.internal.utils.PluginUtil;\nimport com.didi.virtualapk.utils.Reflector;\nimport com.didi.virtualapk.utils.RunUtil;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.InputStream;\nimport java.lang.reflect.Field;\nimport java.lang.reflect.Proxy;\nimport java.util.ArrayList;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * Created by renyugang on 16/8/9.\n */\npublic class PluginManager {\n\n    public static final String TAG = Constants.TAG_PREFIX + \"PluginManager\";\n\n    private static volatile PluginManager sInstance = null;\n\n    // Context of host app\n    protected final Context mContext;\n    protected final Application mApplication;\n    protected ComponentsHandler mComponentsHandler;\n    protected final Map<String, LoadedPlugin> mPlugins = new ConcurrentHashMap<>();\n    protected final List<Callback> mCallbacks = new ArrayList<>();\n\n    protected VAInstrumentation mInstrumentation; // Hooked instrumentation\n    protected IActivityManager mActivityManager; // Hooked IActivityManager binder\n    protected IContentProvider mIContentProvider; // Hooked IContentProvider binder\n\n    public static PluginManager getInstance(Context base) {\n        if (sInstance == null) {\n            synchronized (PluginManager.class) {\n                if (sInstance == null) {\n                    sInstance = createInstance(base);\n                }\n            }\n        }\n\n        return sInstance;\n    }\n    \n    private static PluginManager createInstance(Context context) {\n        try {\n            Bundle metaData = context.getPackageManager()\n                                     .getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA)\n                                     .metaData;\n            \n            if (metaData == null) {\n                return new PluginManager(context);\n            }\n            \n            String factoryClass = metaData.getString(\"VA_FACTORY\");\n            \n            if (factoryClass == null) {\n                return new PluginManager(context);\n            }\n            \n            PluginManager pluginManager = Reflector.on(factoryClass).method(\"create\", Context.class).call(context);\n            \n            if (pluginManager != null) {\n                Log.d(TAG, \"Created a instance of \" + pluginManager.getClass());\n                return pluginManager;\n            }\n    \n        } catch (Exception e) {\n            Log.w(TAG, \"Created the instance error!\", e);\n        }\n    \n        return new PluginManager(context);\n    }\n\n    protected PluginManager(Context context) {\n        if (context instanceof Application) {\n            this.mApplication = (Application) context;\n            this.mContext = mApplication.getBaseContext();\n        } else {\n            final Context app = context.getApplicationContext();\n            if (app == null) {\n                this.mContext = context;\n                this.mApplication = ActivityThread.currentApplication();\n            } else {\n                this.mApplication = (Application) app;\n                this.mContext = mApplication.getBaseContext();\n            }\n        }\n        \n        mComponentsHandler = createComponentsHandler();\n        hookCurrentProcess();\n    }\n\n    protected void hookCurrentProcess() {\n        hookInstrumentationAndHandler();\n        hookSystemServices();\n        hookDataBindingUtil();\n    }\n\n    public void init() {\n        RunUtil.getThreadPool().execute(new Runnable() {\n            @Override\n            public void run() {\n                doInWorkThread();\n            }\n        });\n    }\n\n    protected void doInWorkThread() {\n    }\n    \n    public Application getHostApplication() {\n        return this.mApplication;\n    }\n    \n    protected ComponentsHandler createComponentsHandler() {\n        return new ComponentsHandler(this);\n    }\n    \n    protected VAInstrumentation createInstrumentation(Instrumentation origin) throws Exception {\n        return new VAInstrumentation(this, origin);\n    }\n    \n    protected ActivityManagerProxy createActivityManagerProxy(IActivityManager origin) throws Exception {\n        return new ActivityManagerProxy(this, origin);\n    }\n    \n    protected LoadedPlugin createLoadedPlugin(File apk) throws Exception {\n        return new LoadedPlugin(this, this.mContext, apk);\n    }\n    \n    protected void hookDataBindingUtil() {\n        Reflector.QuietReflector reflector = Reflector.QuietReflector.on(\"android.databinding.DataBindingUtil\").field(\"sMapper\");\n        Object old = reflector.get();\n        if (old != null) {\n            try {\n                Callback callback = Reflector.on(\"android.databinding.DataBinderMapperProxy\").constructor().newInstance();\n                reflector.set(callback);\n                addCallback(callback);\n                Log.d(TAG, \"hookDataBindingUtil succeed : \" + callback);\n            } catch (Reflector.ReflectedException e) {\n                Log.w(TAG, e);\n            }\n        }\n    }\n    \n    public void addCallback(Callback callback) {\n        if (callback == null) {\n            return;\n        }\n        synchronized (mCallbacks) {\n            if (mCallbacks.contains(callback)) {\n                throw new RuntimeException(\"Already added \" + callback + \"!\");\n            }\n            mCallbacks.add(callback);\n        }\n    }\n    \n    public void removeCallback(Callback callback) {\n        synchronized (mCallbacks) {\n            mCallbacks.remove(callback);\n        }\n    }\n\n    /**\n     * hookSystemServices, but need to compatible with Android O in future.\n     */\n    protected void hookSystemServices() {\n        try {\n            Singleton<IActivityManager> defaultSingleton;\n    \n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n                defaultSingleton = Reflector.on(ActivityManager.class).field(\"IActivityManagerSingleton\").get();\n            } else {\n                defaultSingleton = Reflector.on(ActivityManagerNative.class).field(\"gDefault\").get();\n            }\n            IActivityManager origin = defaultSingleton.get();\n            IActivityManager activityManagerProxy = (IActivityManager) Proxy.newProxyInstance(mContext.getClassLoader(), new Class[] { IActivityManager.class },\n                createActivityManagerProxy(origin));\n\n            // Hook IActivityManager from ActivityManagerNative\n            Reflector.with(defaultSingleton).field(\"mInstance\").set(activityManagerProxy);\n\n            if (defaultSingleton.get() == activityManagerProxy) {\n                this.mActivityManager = activityManagerProxy;\n                Log.d(TAG, \"hookSystemServices succeed : \" + mActivityManager);\n            }\n        } catch (Exception e) {\n            Log.w(TAG, e);\n        }\n    }\n    \n    protected void hookInstrumentationAndHandler() {\n        try {\n            ActivityThread activityThread = ActivityThread.currentActivityThread();\n            Instrumentation baseInstrumentation = activityThread.getInstrumentation();\n//            if (baseInstrumentation.getClass().getName().contains(\"lbe\")) {\n//                // reject executing in paralell space, for example, lbe.\n//                System.exit(0);\n//            }\n    \n            final VAInstrumentation instrumentation = createInstrumentation(baseInstrumentation);\n            \n            Reflector.with(activityThread).field(\"mInstrumentation\").set(instrumentation);\n            Handler mainHandler = Reflector.with(activityThread).method(\"getHandler\").call();\n            Reflector.with(mainHandler).field(\"mCallback\").set(instrumentation);\n            this.mInstrumentation = instrumentation;\n            Log.d(TAG, \"hookInstrumentationAndHandler succeed : \" + mInstrumentation);\n        } catch (Exception e) {\n            Log.w(TAG, e);\n        }\n    }\n\n    protected void hookIContentProviderAsNeeded() {\n        Uri uri = Uri.parse(RemoteContentProvider.getUri(mContext));\n        mContext.getContentResolver().call(uri, \"wakeup\", null, null);\n        try {\n            Field authority = null;\n            Field provider = null;\n            ActivityThread activityThread = ActivityThread.currentActivityThread();\n            Map providerMap = Reflector.with(activityThread).field(\"mProviderMap\").get();\n            Iterator iter = providerMap.entrySet().iterator();\n            while (iter.hasNext()) {\n                Map.Entry entry = (Map.Entry) iter.next();\n                Object key = entry.getKey();\n                Object val = entry.getValue();\n                String auth;\n                if (key instanceof String) {\n                    auth = (String) key;\n                } else {\n                    if (authority == null) {\n                        authority = key.getClass().getDeclaredField(\"authority\");\n                        authority.setAccessible(true);\n                    }\n                    auth = (String) authority.get(key);\n                }\n                if (auth.equals(RemoteContentProvider.getAuthority(mContext))) {\n                    if (provider == null) {\n                        provider = val.getClass().getDeclaredField(\"mProvider\");\n                        provider.setAccessible(true);\n                    }\n                    IContentProvider rawProvider = (IContentProvider) provider.get(val);\n                    IContentProvider proxy = IContentProviderProxy.newInstance(mContext, rawProvider);\n                    mIContentProvider = proxy;\n                    Log.d(TAG, \"hookIContentProvider succeed : \" + mIContentProvider);\n                    break;\n                }\n            }\n        } catch (Exception e) {\n            Log.w(TAG, e);\n        }\n    }\n\n    /**\n     * load a plugin into memory, then invoke it's Application.\n     * @param apk the file of plugin, should end with .apk\n     * @throws Exception\n     */\n    public void loadPlugin(File apk) throws Exception {\n        if (null == apk) {\n            throw new IllegalArgumentException(\"error : apk is null.\");\n        }\n\n        if (!apk.exists()) {\n            // throw the FileNotFoundException by opening a stream.\n            InputStream in = new FileInputStream(apk);\n            in.close();\n        }\n\n        LoadedPlugin plugin = createLoadedPlugin(apk);\n        \n        if (null == plugin) {\n            throw new RuntimeException(\"Can't load plugin which is invalid: \" + apk.getAbsolutePath());\n        }\n        \n        this.mPlugins.put(plugin.getPackageName(), plugin);\n        synchronized (mCallbacks) {\n            for (int i = 0; i < mCallbacks.size(); i++) {\n                mCallbacks.get(i).onAddedLoadedPlugin(plugin);\n            }\n        }\n    }\n\n    public LoadedPlugin getLoadedPlugin(Intent intent) {\n        return getLoadedPlugin(PluginUtil.getComponent(intent));\n    }\n\n    public LoadedPlugin getLoadedPlugin(ComponentName component) {\n        if (component == null) {\n            return null;\n        }\n        return this.getLoadedPlugin(component.getPackageName());\n    }\n\n    public LoadedPlugin getLoadedPlugin(String packageName) {\n        return this.mPlugins.get(packageName);\n    }\n\n    public List<LoadedPlugin> getAllLoadedPlugins() {\n        List<LoadedPlugin> list = new ArrayList<>();\n        list.addAll(mPlugins.values());\n        return list;\n    }\n\n    public Context getHostContext() {\n        return this.mContext;\n    }\n\n    public VAInstrumentation getInstrumentation() {\n        return this.mInstrumentation;\n    }\n\n    public IActivityManager getActivityManager() {\n        return this.mActivityManager;\n    }\n\n    public synchronized IContentProvider getIContentProvider() {\n        if (mIContentProvider == null) {\n            hookIContentProviderAsNeeded();\n        }\n\n        return mIContentProvider;\n    }\n\n    public ComponentsHandler getComponentsHandler() {\n        return mComponentsHandler;\n    }\n\n    public ResolveInfo resolveActivity(Intent intent) {\n        return this.resolveActivity(intent, 0);\n    }\n\n    public ResolveInfo resolveActivity(Intent intent, int flags) {\n        for (LoadedPlugin plugin : this.mPlugins.values()) {\n            ResolveInfo resolveInfo = plugin.resolveActivity(intent, flags);\n            if (null != resolveInfo) {\n                return resolveInfo;\n            }\n        }\n\n        return null;\n    }\n\n    public ResolveInfo resolveService(Intent intent, int flags) {\n        for (LoadedPlugin plugin : this.mPlugins.values()) {\n            ResolveInfo resolveInfo = plugin.resolveService(intent, flags);\n            if (null != resolveInfo) {\n                return resolveInfo;\n            }\n        }\n\n        return null;\n    }\n\n    public ProviderInfo resolveContentProvider(String name, int flags) {\n        for (LoadedPlugin plugin : this.mPlugins.values()) {\n            ProviderInfo providerInfo = plugin.resolveContentProvider(name, flags);\n            if (null != providerInfo) {\n                return providerInfo;\n            }\n        }\n\n        return null;\n    }\n\n    /**\n     * used in PluginPackageManager, do not invoke it from outside.\n     */\n    @Deprecated\n    public List<ResolveInfo> queryIntentActivities(Intent intent, int flags) {\n        List<ResolveInfo> resolveInfos = new ArrayList<ResolveInfo>();\n\n        for (LoadedPlugin plugin : this.mPlugins.values()) {\n            List<ResolveInfo> result = plugin.queryIntentActivities(intent, flags);\n            if (null != result && result.size() > 0) {\n                resolveInfos.addAll(result);\n            }\n        }\n\n        return resolveInfos;\n    }\n\n    /**\n     * used in PluginPackageManager, do not invoke it from outside.\n     */\n    @Deprecated\n    public List<ResolveInfo> queryIntentServices(Intent intent, int flags) {\n        List<ResolveInfo> resolveInfos = new ArrayList<ResolveInfo>();\n\n        for (LoadedPlugin plugin : this.mPlugins.values()) {\n            List<ResolveInfo> result = plugin.queryIntentServices(intent, flags);\n            if (null != result && result.size() > 0) {\n                resolveInfos.addAll(result);\n            }\n        }\n\n        return resolveInfos;\n    }\n\n    /**\n     * used in PluginPackageManager, do not invoke it from outside.\n     */\n    @Deprecated\n    public List<ResolveInfo> queryBroadcastReceivers(Intent intent, int flags) {\n        List<ResolveInfo> resolveInfos = new ArrayList<ResolveInfo>();\n\n        for (LoadedPlugin plugin : this.mPlugins.values()) {\n            List<ResolveInfo> result = plugin.queryBroadcastReceivers(intent, flags);\n            if (null != result && result.size() > 0) {\n                resolveInfos.addAll(result);\n            }\n        }\n\n        return resolveInfos;\n    }\n\n    public interface Callback {\n        void onAddedLoadedPlugin(LoadedPlugin plugin);\n    }\n}"
  },
  {
    "path": "CoreLibrary/src/main/java/com/didi/virtualapk/delegate/ActivityManagerProxy.java",
    "content": "/*\n * Copyright (C) 2017 Beijing Didi Infinity Technology and Development Co.,Ltd. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.didi.virtualapk.delegate;\n\nimport android.app.ActivityManagerNative;\nimport android.app.IActivityManager;\nimport android.app.IApplicationThread;\nimport android.app.Service;\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.pm.ResolveInfo;\nimport android.content.pm.ServiceInfo;\nimport android.os.Bundle;\nimport android.os.DeadObjectException;\nimport android.os.IBinder;\nimport android.os.RemoteException;\nimport android.os.ServiceManager;\nimport android.util.Log;\n\nimport com.didi.virtualapk.PluginManager;\nimport com.didi.virtualapk.internal.Constants;\nimport com.didi.virtualapk.internal.utils.PluginUtil;\n\nimport java.lang.reflect.InvocationHandler;\nimport java.lang.reflect.Method;\n\n/**\n * @author johnsonlee\n */\npublic class ActivityManagerProxy implements InvocationHandler {\n\n    private static final String TAG = Constants.TAG_PREFIX + \"IActivityManagerProxy\";\n\n    public static final int INTENT_SENDER_BROADCAST = 1;\n    public static final int INTENT_SENDER_ACTIVITY = 2;\n    public static final int INTENT_SENDER_ACTIVITY_RESULT = 3;\n    public static final int INTENT_SENDER_SERVICE = 4;\n\n    private PluginManager mPluginManager;\n    private IActivityManager mActivityManager;\n\n    public ActivityManagerProxy(PluginManager pluginManager, IActivityManager activityManager) {\n        this.mPluginManager = pluginManager;\n        this.mActivityManager = activityManager;\n    }\n\n    @Override\n    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {\n        if (\"startService\".equals(method.getName())) {\n            try {\n                return startService(proxy, method, args);\n            } catch (Throwable e) {\n                Log.e(TAG, \"Start service error\", e);\n            }\n        } else if (\"stopService\".equals(method.getName())) {\n            try {\n                return stopService(proxy, method, args);\n            } catch (Throwable e) {\n                Log.e(TAG, \"Stop Service error\", e);\n            }\n        } else if (\"stopServiceToken\".equals(method.getName())) {\n            try {\n                return stopServiceToken(proxy, method, args);\n            } catch (Throwable e) {\n                Log.e(TAG, \"Stop service token error\", e);\n            }\n        } else if (\"bindService\".equals(method.getName())) {\n            try {\n                return bindService(proxy, method, args);\n            } catch (Throwable e) {\n                Log.w(TAG, e);\n            }\n        } else if (\"unbindService\".equals(method.getName())) {\n            try {\n                return unbindService(proxy, method, args);\n            } catch (Throwable e) {\n                Log.w(TAG, e);\n            }\n        } else if (\"getIntentSender\".equals(method.getName())) {\n            try {\n                getIntentSender(method, args);\n            } catch (Exception e) {\n                Log.w(TAG, e);\n            }\n        } else if (\"overridePendingTransition\".equals(method.getName())){\n            try {\n                overridePendingTransition(method, args);\n            } catch (Exception e){\n                Log.w(TAG, e);\n            }\n        }\n\n        try {\n            // sometimes system binder has problems.\n            return method.invoke(this.mActivityManager, args);\n        } catch (Throwable th) {\n            Throwable c = th.getCause();\n            if (c != null && c instanceof DeadObjectException) {\n                // retry connect to system binder\n                IBinder ams = ServiceManager.getService(Context.ACTIVITY_SERVICE);\n                if (ams != null) {\n                    IActivityManager am = ActivityManagerNative.asInterface(ams);\n                    mActivityManager = am;\n                }\n            }\n\n            Throwable cause = th;\n            do {\n                if (cause instanceof RemoteException) {\n                    throw cause;\n                }\n            } while ((cause = cause.getCause()) != null);\n\n            throw c != null ? c : th;\n        }\n\n    }\n\n    protected Object startService(Object proxy, Method method, Object[] args) throws Throwable {\n        IApplicationThread appThread = (IApplicationThread) args[0];\n        Intent target = (Intent) args[1];\n        ResolveInfo resolveInfo = this.mPluginManager.resolveService(target, 0);\n        if (null == resolveInfo || null == resolveInfo.serviceInfo) {\n            // is host service\n            return method.invoke(this.mActivityManager, args);\n        }\n\n        return startDelegateServiceForTarget(target, resolveInfo.serviceInfo, null, RemoteService.EXTRA_COMMAND_START_SERVICE);\n    }\n\n    protected Object stopService(Object proxy, Method method, Object[] args) throws Throwable {\n        Intent target = (Intent) args[1];\n        ResolveInfo resolveInfo = this.mPluginManager.resolveService(target, 0);\n        if (null == resolveInfo || null == resolveInfo.serviceInfo) {\n            // is hot service\n            return method.invoke(this.mActivityManager, args);\n        }\n\n        startDelegateServiceForTarget(target, resolveInfo.serviceInfo, null, RemoteService.EXTRA_COMMAND_STOP_SERVICE);\n        return 1;\n    }\n\n    protected Object stopServiceToken(Object proxy, Method method, Object[] args) throws Throwable {\n        ComponentName component = (ComponentName) args[0];\n        Intent target = new Intent().setComponent(component);\n        ResolveInfo resolveInfo = this.mPluginManager.resolveService(target, 0);\n        if (null == resolveInfo || null == resolveInfo.serviceInfo) {\n            // is hot service\n            return method.invoke(this.mActivityManager, args);\n        }\n\n        startDelegateServiceForTarget(target, resolveInfo.serviceInfo, null, RemoteService.EXTRA_COMMAND_STOP_SERVICE);\n        return true;\n    }\n\n    protected Object bindService(Object proxy, Method method, Object[] args) throws Throwable {\n        Intent target = (Intent) args[2];\n        ResolveInfo resolveInfo = this.mPluginManager.resolveService(target, 0);\n        if (null == resolveInfo || null == resolveInfo.serviceInfo) {\n            // is host service\n            return method.invoke(this.mActivityManager, args);\n        }\n\n        Bundle bundle = new Bundle();\n        PluginUtil.putBinder(bundle, \"sc\", (IBinder) args[4]);\n        startDelegateServiceForTarget(target, resolveInfo.serviceInfo, bundle, RemoteService.EXTRA_COMMAND_BIND_SERVICE);\n        mPluginManager.getComponentsHandler().remberIServiceConnection((IBinder) args[4], target);\n        return 1;\n    }\n\n    protected Object unbindService(Object proxy, Method method, Object[] args) throws Throwable {\n        IBinder iServiceConnection = (IBinder)args[0];\n        Intent target = mPluginManager.getComponentsHandler().forgetIServiceConnection(iServiceConnection);\n        if (target == null) {\n            // is host service\n            return method.invoke(this.mActivityManager, args);\n        }\n\n        ResolveInfo resolveInfo = this.mPluginManager.resolveService(target, 0);\n        startDelegateServiceForTarget(target, resolveInfo.serviceInfo, null, RemoteService.EXTRA_COMMAND_UNBIND_SERVICE);\n        return true;\n    }\n\n    protected ComponentName startDelegateServiceForTarget(Intent target, ServiceInfo serviceInfo, Bundle extras, int command) {\n        Intent wrapperIntent = wrapperTargetIntent(target, serviceInfo, extras, command);\n        return mPluginManager.getHostContext().startService(wrapperIntent);\n    }\n\n    protected Intent wrapperTargetIntent(Intent target, ServiceInfo serviceInfo, Bundle extras, int command) {\n        // fill in service with ComponentName\n        target.setComponent(new ComponentName(serviceInfo.packageName, serviceInfo.name));\n        String pluginLocation = mPluginManager.getLoadedPlugin(target.getComponent()).getLocation();\n\n        // start delegate service to run plugin service inside\n        boolean local = PluginUtil.isLocalService(serviceInfo);\n        Class<? extends Service> delegate = local ? LocalService.class : RemoteService.class;\n        Intent intent = new Intent();\n        intent.setClass(mPluginManager.getHostContext(), delegate);\n        intent.putExtra(RemoteService.EXTRA_TARGET, target);\n        intent.putExtra(RemoteService.EXTRA_COMMAND, command);\n        intent.putExtra(RemoteService.EXTRA_PLUGIN_LOCATION, pluginLocation);\n        if (extras != null) {\n            intent.putExtras(extras);\n        }\n\n        return intent;\n    }\n\n    protected void getIntentSender(Method method, Object[] args) {\n        String hostPackageName = mPluginManager.getHostContext().getPackageName();\n        args[1] = hostPackageName;\n\n        Intent target = ((Intent[]) args[5])[0];\n        int intentSenderType = (int)args[0];\n        if (intentSenderType == INTENT_SENDER_ACTIVITY) {\n            mPluginManager.getComponentsHandler().transformIntentToExplicitAsNeeded(target);\n            mPluginManager.getComponentsHandler().markIntentIfNeeded(target);\n        } else if (intentSenderType == INTENT_SENDER_SERVICE) {\n            ResolveInfo resolveInfo = this.mPluginManager.resolveService(target, 0);\n            if (resolveInfo != null && resolveInfo.serviceInfo != null) {\n                // find plugin service\n                Intent wrapperIntent  = wrapperTargetIntent(target, resolveInfo.serviceInfo, null, RemoteService.EXTRA_COMMAND_START_SERVICE);\n                ((Intent[]) args[5])[0] = wrapperIntent;\n            }\n        } else if (intentSenderType == INTENT_SENDER_BROADCAST) {\n            // no action\n        }\n    }\n\n    protected void overridePendingTransition(Method method, Object[] args) {\n        String hostPackageName = mPluginManager.getHostContext().getPackageName();\n        args[1] = hostPackageName;\n    }\n\n}\n"
  },
  {
    "path": "CoreLibrary/src/main/java/com/didi/virtualapk/delegate/IContentProviderProxy.java",
    "content": "/*\n * Copyright (C) 2017 Beijing Didi Infinity Technology and Development Co.,Ltd. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.didi.virtualapk.delegate;\n\nimport android.content.Context;\nimport android.content.IContentProvider;\nimport android.content.pm.ProviderInfo;\nimport android.net.Uri;\nimport android.os.Bundle;\nimport android.util.Log;\n\nimport com.didi.virtualapk.PluginManager;\nimport com.didi.virtualapk.internal.Constants;\nimport com.didi.virtualapk.internal.LoadedPlugin;\nimport com.didi.virtualapk.internal.PluginContentResolver;\n\nimport java.lang.reflect.InvocationHandler;\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\nimport java.lang.reflect.Proxy;\nimport java.util.Arrays;\n\n/**\n * Created by renyugang on 16/12/8.\n */\n\npublic class IContentProviderProxy implements InvocationHandler {\n    private static final String TAG = Constants.TAG_PREFIX + \"IContentProviderProxy\";\n\n    private IContentProvider mBase;\n    private Context mContext;\n\n    private IContentProviderProxy(Context context, IContentProvider iContentProvider) {\n        mBase = iContentProvider;\n        mContext = context;\n    }\n\n    public static IContentProvider newInstance(Context context, IContentProvider iContentProvider) {\n        return (IContentProvider) Proxy.newProxyInstance(iContentProvider.getClass().getClassLoader(),\n                new Class[] { IContentProvider.class }, new IContentProviderProxy(context, iContentProvider));\n    }\n\n    @Override\n    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {\n        Log.v(TAG, method.toGenericString() + \" : \" + Arrays.toString(args));\n        wrapperUri(method, args);\n\n        try {\n            return method.invoke(mBase, args);\n        } catch (InvocationTargetException e) {\n            throw e.getTargetException();\n        }\n    }\n\n    private void wrapperUri(Method method, Object[] args) {\n        Uri uri = null;\n        int index = 0;\n        if (args != null) {\n            for (int i = 0; i < args.length; i++) {\n                if (args[i] instanceof Uri) {\n                    uri = (Uri) args[i];\n                    index = i;\n                    break;\n                }\n            }\n        }\n\n        Bundle bundleInCallMethod = null;\n        if (method.getName().equals(\"call\")) {\n            bundleInCallMethod = getBundleParameter(args);\n            if (bundleInCallMethod != null) {\n                String uriString = bundleInCallMethod.getString(RemoteContentProvider.KEY_WRAPPER_URI);\n                if (uriString != null) {\n                    uri = Uri.parse(uriString);\n                }\n            }\n        }\n\n        if (uri == null) {\n            return;\n        }\n\n        PluginManager pluginManager = PluginManager.getInstance(mContext);\n        ProviderInfo info = pluginManager.resolveContentProvider(uri.getAuthority(), 0);\n        if (info != null) {\n            String pkg = info.packageName;\n            LoadedPlugin plugin = pluginManager.getLoadedPlugin(pkg);\n            Uri wrapperUri = PluginContentResolver.wrapperUri(plugin, uri);\n            if (method.getName().equals(\"call\")) {\n                bundleInCallMethod.putString(RemoteContentProvider.KEY_WRAPPER_URI, wrapperUri.toString());\n            } else {\n                args[index] = wrapperUri;\n            }\n        }\n    }\n\n    private Bundle getBundleParameter(Object[] args) {\n        Bundle bundle = null;\n        if (args != null) {\n            for (int i = 0; i < args.length; i++) {\n                if (args[i] instanceof Bundle) {\n                    bundle = (Bundle) args[i];\n                    break;\n                }\n            }\n        }\n\n        return bundle;\n    }\n\n}\n"
  },
  {
    "path": "CoreLibrary/src/main/java/com/didi/virtualapk/delegate/LocalService.java",
    "content": "/*\n * Copyright (C) 2017 Beijing Didi Infinity Technology and Development Co.,Ltd. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.didi.virtualapk.delegate;\n\nimport android.app.ActivityThread;\nimport android.app.Application;\nimport android.app.IActivityManager;\nimport android.app.IApplicationThread;\nimport android.app.IServiceConnection;\nimport android.app.Service;\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Binder;\nimport android.os.Build;\nimport android.os.IBinder;\nimport android.util.Log;\n\nimport com.didi.virtualapk.PluginManager;\nimport com.didi.virtualapk.internal.Constants;\nimport com.didi.virtualapk.internal.LoadedPlugin;\nimport com.didi.virtualapk.internal.utils.PluginUtil;\nimport com.didi.virtualapk.utils.Reflector;\n\nimport java.lang.reflect.Method;\n\n/**\n * @author johnsonlee\n */\npublic class LocalService extends Service {\n    private static final String TAG = Constants.TAG_PREFIX + \"LocalService\";\n\n    /**\n     * The target service, usually it's a plugin service intent\n     */\n    public static final String EXTRA_TARGET = \"target\";\n    public static final String EXTRA_COMMAND = \"command\";\n    public static final String EXTRA_PLUGIN_LOCATION = \"plugin_location\";\n\n    public static final int EXTRA_COMMAND_START_SERVICE = 1;\n    public static final int EXTRA_COMMAND_STOP_SERVICE = 2;\n    public static final int EXTRA_COMMAND_BIND_SERVICE = 3;\n    public static final int EXTRA_COMMAND_UNBIND_SERVICE = 4;\n\n\n    private PluginManager mPluginManager;\n\n    @Override\n    public IBinder onBind(Intent intent) {\n        return new Binder();\n    }\n\n    @Override\n    public void onCreate() {\n        super.onCreate();\n        mPluginManager = PluginManager.getInstance(this);\n    }\n\n    @Override\n    public int onStartCommand(Intent intent, int flags, int startId) {\n        if (null == intent || !intent.hasExtra(EXTRA_TARGET) || !intent.hasExtra(EXTRA_COMMAND)) {\n            return START_STICKY;\n        }\n\n        Intent target = intent.getParcelableExtra(EXTRA_TARGET);\n        int command = intent.getIntExtra(EXTRA_COMMAND, 0);\n        if (null == target || command <= 0) {\n            return START_STICKY;\n        }\n\n        ComponentName component = target.getComponent();\n        LoadedPlugin plugin = mPluginManager.getLoadedPlugin(component);\n        \n        if (plugin == null) {\n            Log.w(TAG, \"Error target: \" + target.toURI());\n            return START_STICKY;\n        }\n        // ClassNotFoundException when unmarshalling in Android 5.1\n        target.setExtrasClassLoader(plugin.getClassLoader());\n        switch (command) {\n            case EXTRA_COMMAND_START_SERVICE: {\n                ActivityThread mainThread = ActivityThread.currentActivityThread();\n                IApplicationThread appThread = mainThread.getApplicationThread();\n                Service service;\n\n                if (this.mPluginManager.getComponentsHandler().isServiceAvailable(component)) {\n                    service = this.mPluginManager.getComponentsHandler().getService(component);\n                } else {\n                    try {\n                        service = (Service) plugin.getClassLoader().loadClass(component.getClassName()).newInstance();\n\n                        Application app = plugin.getApplication();\n                        IBinder token = appThread.asBinder();\n                        Method attach = service.getClass().getMethod(\"attach\", Context.class, ActivityThread.class, String.class, IBinder.class, Application.class, Object.class);\n                        IActivityManager am = mPluginManager.getActivityManager();\n\n                        attach.invoke(service, plugin.getPluginContext(), mainThread, component.getClassName(), token, app, am);\n                        service.onCreate();\n                        this.mPluginManager.getComponentsHandler().rememberService(component, service);\n                    } catch (Throwable t) {\n                        return START_STICKY;\n                    }\n                }\n\n                service.onStartCommand(target, 0, this.mPluginManager.getComponentsHandler().getServiceCounter(service).getAndIncrement());\n                break;\n            }\n            case EXTRA_COMMAND_BIND_SERVICE: {\n                ActivityThread mainThread = ActivityThread.currentActivityThread();\n                IApplicationThread appThread = mainThread.getApplicationThread();\n                Service service = null;\n\n                if (this.mPluginManager.getComponentsHandler().isServiceAvailable(component)) {\n                    service = this.mPluginManager.getComponentsHandler().getService(component);\n                } else {\n                    try {\n                        service = (Service) plugin.getClassLoader().loadClass(component.getClassName()).newInstance();\n\n                        Application app = plugin.getApplication();\n                        IBinder token = appThread.asBinder();\n                        Method attach = service.getClass().getMethod(\"attach\", Context.class, ActivityThread.class, String.class, IBinder.class, Application.class, Object.class);\n                        IActivityManager am = mPluginManager.getActivityManager();\n\n                        attach.invoke(service, plugin.getPluginContext(), mainThread, component.getClassName(), token, app, am);\n                        service.onCreate();\n                        this.mPluginManager.getComponentsHandler().rememberService(component, service);\n                    } catch (Throwable t) {\n                        Log.w(TAG, t);\n                    }\n                }\n                try {\n                    IBinder binder = service.onBind(target);\n                    IBinder serviceConnection = PluginUtil.getBinder(intent.getExtras(), \"sc\");\n                    IServiceConnection iServiceConnection = IServiceConnection.Stub.asInterface(serviceConnection);\n                    if (Build.VERSION.SDK_INT >= 26) {\n                        iServiceConnection.connected(component, binder, false);\n                    } else {\n                        Reflector.QuietReflector.with(iServiceConnection).method(\"connected\", ComponentName.class, IBinder.class).call(component, binder);\n                    }\n                } catch (Exception e) {\n                    Log.w(TAG, e);\n                }\n                break;\n            }\n            case EXTRA_COMMAND_STOP_SERVICE: {\n                Service service = this.mPluginManager.getComponentsHandler().forgetService(component);\n                if (null != service) {\n                    try {\n                        service.onDestroy();\n                    } catch (Exception e) {\n                        Log.e(TAG, \"Unable to stop service \" + service + \": \" + e.toString());\n                    }\n                } else {\n                    Log.i(TAG, component + \" not found\");\n                }\n                break;\n            }\n            case EXTRA_COMMAND_UNBIND_SERVICE: {\n                Service service = this.mPluginManager.getComponentsHandler().forgetService(component);\n                if (null != service) {\n                    try {\n                        service.onUnbind(target);\n                        service.onDestroy();\n                    } catch (Exception e) {\n                        Log.e(TAG, \"Unable to unbind service \" + service + \": \" + e.toString());\n                    }\n                } else {\n                    Log.i(TAG, component + \" not found\");\n                }\n                break;\n            }\n        }\n\n        return START_STICKY;\n    }\n\n}\n"
  },
  {
    "path": "CoreLibrary/src/main/java/com/didi/virtualapk/delegate/RemoteContentProvider.java",
    "content": "/*\n * Copyright (C) 2017 Beijing Didi Infinity Technology and Development Co.,Ltd. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.didi.virtualapk.delegate;\n\nimport android.content.ContentProvider;\nimport android.content.ContentProviderOperation;\nimport android.content.ContentProviderResult;\nimport android.content.ContentValues;\nimport android.content.Context;\nimport android.content.OperationApplicationException;\nimport android.content.pm.ProviderInfo;\nimport android.database.Cursor;\nimport android.net.Uri;\nimport android.os.Bundle;\nimport android.support.annotation.NonNull;\nimport android.util.Log;\n\nimport com.didi.virtualapk.PluginManager;\nimport com.didi.virtualapk.internal.Constants;\nimport com.didi.virtualapk.internal.LoadedPlugin;\nimport com.didi.virtualapk.utils.RunUtil;\n\nimport java.io.File;\nimport java.lang.reflect.Field;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * Created by renyugang on 16/12/7.\n */\n\npublic class RemoteContentProvider extends ContentProvider {\n    private static final String TAG = Constants.TAG_PREFIX + \"RemoteContentProvider\";\n\n    public static final String KEY_PKG = \"pkg\";\n    public static final String KEY_PLUGIN = \"plugin\";\n    public static final String KEY_URI = \"uri\";\n\n    public static final String KEY_WRAPPER_URI = \"wrapper_uri\";\n\n    private static Map<String, ContentProvider> sCachedProviders = new HashMap<>();\n\n    @Override\n    public boolean onCreate() {\n        Log.d(TAG, \"onCreate, current thread:\"\n                + Thread.currentThread().getName());\n        return true;\n    }\n\n    private ContentProvider getContentProvider(final Uri uri) {\n        final PluginManager pluginManager = PluginManager.getInstance(getContext());\n        Uri pluginUri = Uri.parse(uri.getQueryParameter(KEY_URI));\n        final String auth = pluginUri.getAuthority();\n        ContentProvider cachedProvider = sCachedProviders.get(auth);\n        if (cachedProvider != null) {\n            return cachedProvider;\n        }\n\n        synchronized (sCachedProviders) {\n            LoadedPlugin plugin = pluginManager.getLoadedPlugin(uri.getQueryParameter(KEY_PKG));\n            if (plugin == null) {\n                try {\n                    pluginManager.loadPlugin(new File(uri.getQueryParameter(KEY_PLUGIN)));\n                } catch (Exception e) {\n                    Log.w(TAG, e);\n                }\n            }\n\n            final ProviderInfo providerInfo = pluginManager.resolveContentProvider(auth, 0);\n            if (providerInfo != null) {\n                RunUtil.runOnUiThread(new Runnable() {\n                    @Override\n                    public void run() {\n                        try {\n                            LoadedPlugin loadedPlugin = pluginManager.getLoadedPlugin(uri.getQueryParameter(KEY_PKG));\n                            ContentProvider contentProvider = (ContentProvider) Class.forName(providerInfo.name).newInstance();\n                            contentProvider.attachInfo(loadedPlugin.getPluginContext(), providerInfo);\n                            sCachedProviders.put(auth, contentProvider);\n                        } catch (Exception e) {\n                            Log.w(TAG, e);\n                        }\n                    }\n                }, true);\n                return sCachedProviders.get(auth);\n            }\n        }\n\n        return null;\n    }\n\n    @Override\n    public String getType(Uri uri) {\n        ContentProvider provider = getContentProvider(uri);\n        Uri pluginUri = Uri.parse(uri.getQueryParameter(KEY_URI));\n        if (provider != null) {\n            return provider.getType(pluginUri);\n        }\n\n        return null;\n    }\n\n    @Override\n    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {\n        ContentProvider provider = getContentProvider(uri);\n        Uri pluginUri = Uri.parse(uri.getQueryParameter(KEY_URI));\n        if (provider != null) {\n            return provider.query(pluginUri, projection, selection, selectionArgs, sortOrder);\n        }\n\n        return null;\n    }\n\n    @Override\n    public Uri insert(Uri uri, ContentValues values) {\n        ContentProvider provider = getContentProvider(uri);\n        Uri pluginUri = Uri.parse(uri.getQueryParameter(KEY_URI));\n        if (provider != null) {\n            return provider.insert(pluginUri, values);\n        }\n\n        return uri;\n    }\n\n    @Override\n    public int delete(Uri uri, String selection, String[] selectionArgs) {\n        ContentProvider provider = getContentProvider(uri);\n        Uri pluginUri = Uri.parse(uri.getQueryParameter(KEY_URI));\n        if (provider != null) {\n            return provider.delete(pluginUri, selection, selectionArgs);\n        }\n\n        return 0;\n    }\n\n    @Override\n    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {\n        ContentProvider provider = getContentProvider(uri);\n        Uri pluginUri = Uri.parse(uri.getQueryParameter(KEY_URI));\n        if (provider != null) {\n            return provider.update(pluginUri, values, selection, selectionArgs);\n        }\n\n        return 0;\n    }\n\n    @Override\n    public int bulkInsert(Uri uri, ContentValues[] values) {\n        ContentProvider provider = getContentProvider(uri);\n        Uri pluginUri = Uri.parse(uri.getQueryParameter(KEY_URI));\n        if (provider != null) {\n            return provider.bulkInsert(pluginUri, values);\n        }\n\n        return 0;\n    }\n\n    @NonNull\n    @Override\n    public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) throws OperationApplicationException {\n        try {\n            Field uriField = ContentProviderOperation.class.getDeclaredField(\"mUri\");\n            uriField.setAccessible(true);\n            for (ContentProviderOperation operation : operations) {\n                Uri pluginUri = Uri.parse(operation.getUri().getQueryParameter(KEY_URI));\n                uriField.set(operation, pluginUri);\n            }\n        } catch (Exception e) {\n            return new ContentProviderResult[0];\n        }\n\n        if (operations.size() > 0) {\n            ContentProvider provider = getContentProvider(operations.get(0).getUri());\n            if (provider != null) {\n                return provider.applyBatch(operations);\n            }\n        }\n\n        return new ContentProviderResult[0];\n    }\n\n    @Override\n    public Bundle call(String method, String arg, Bundle extras) {\n        Log.d(TAG, \"call \" + method + \" with extras : \" + extras);\n\n        if (extras == null || extras.getString(KEY_WRAPPER_URI) == null) {\n            return null;\n        }\n\n        Uri uri = Uri.parse(extras.getString(KEY_WRAPPER_URI));\n        ContentProvider provider = getContentProvider(uri);\n        if (provider != null) {\n            return provider.call(method, arg, extras);\n        }\n\n        return null;\n    }\n\n    public static String getAuthority(Context context) {\n        return context.getPackageName() + \".VirtualAPK.Provider\";\n    }\n    \n    public static String getUri(Context context) {\n        return \"content://\" + getAuthority(context);\n    }\n    \n}\n"
  },
  {
    "path": "CoreLibrary/src/main/java/com/didi/virtualapk/delegate/RemoteService.java",
    "content": "/*\n * Copyright (C) 2017 Beijing Didi Infinity Technology and Development Co.,Ltd. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.didi.virtualapk.delegate;\n\nimport android.content.ComponentName;\nimport android.content.Intent;\nimport android.os.IBinder;\nimport android.util.Log;\n\nimport com.didi.virtualapk.PluginManager;\nimport com.didi.virtualapk.internal.Constants;\nimport com.didi.virtualapk.internal.LoadedPlugin;\n\nimport java.io.File;\n\n/**\n * @author johnsonlee\n */\npublic class RemoteService extends LocalService {\n    \n    private static final String TAG = Constants.TAG_PREFIX + \"RemoteService\";\n\n    @Override\n    public IBinder onBind(Intent intent) {\n        return null;\n    }\n\n    @Override\n    public int onStartCommand(Intent intent, int flags, int startId) {\n        if (intent == null) {\n            return super.onStartCommand(intent, flags, startId);\n        }\n\n        Intent target = intent.getParcelableExtra(EXTRA_TARGET);\n        if (target != null) {\n            String pluginLocation = intent.getStringExtra(EXTRA_PLUGIN_LOCATION);\n            ComponentName component = target.getComponent();\n            LoadedPlugin plugin = PluginManager.getInstance(this).getLoadedPlugin(component);\n            if (plugin == null && pluginLocation != null) {\n                try {\n                    PluginManager.getInstance(this).loadPlugin(new File(pluginLocation));\n                } catch (Exception e) {\n                    Log.w(TAG, e);\n                }\n            }\n        }\n\n        return super.onStartCommand(intent, flags, startId);\n    }\n\n}\n"
  },
  {
    "path": "CoreLibrary/src/main/java/com/didi/virtualapk/delegate/StubActivity.java",
    "content": "package com.didi.virtualapk.delegate;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.content.pm.ResolveInfo;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\n\n/**\n * Created by qiaopu on 2018/6/13.\n */\npublic class StubActivity extends Activity {\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n    \n        // Go to the main activity\n        Intent mainIntent = getPackageManager().getLaunchIntentForPackage(getPackageName());\n        \n        if (mainIntent == null) {\n            mainIntent = new Intent(Intent.ACTION_MAIN);\n            mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);\n            mainIntent.setPackage(getPackageName());\n    \n            ResolveInfo resolveInfo = getPackageManager().resolveActivity(mainIntent, 0);\n    \n            if (resolveInfo != null) {\n                mainIntent.setClassName(this, resolveInfo.activityInfo.name);\n            }\n        }\n    \n        startActivity(mainIntent);\n        \n        finish();\n    }\n}\n"
  },
  {
    "path": "CoreLibrary/src/main/java/com/didi/virtualapk/internal/ActivityLifecycleCallbacksProxy.java",
    "content": "package com.didi.virtualapk.internal;\n\nimport android.app.Activity;\nimport android.app.ActivityThread;\nimport android.app.Application;\nimport android.os.Bundle;\n\nimport com.didi.virtualapk.utils.Reflector;\n\nimport java.util.ArrayList;\n\n/**\n * Created by qiaopu on 2017/8/9.\n */\nclass ActivityLifecycleCallbacksProxy implements Application.ActivityLifecycleCallbacks {\n    \n    final ArrayList<Application.ActivityLifecycleCallbacks> mActivityLifecycleCallbacks =\n        Reflector.QuietReflector.with(ActivityThread.currentApplication()).field(\"mActivityLifecycleCallbacks\").get();\n    \n    Object[] collectActivityLifecycleCallbacks() {\n        if (mActivityLifecycleCallbacks == null) {\n            return null;\n        }\n        Object[] callbacks = null;\n        synchronized (mActivityLifecycleCallbacks) {\n            if (mActivityLifecycleCallbacks.size() > 0) {\n                callbacks = mActivityLifecycleCallbacks.toArray();\n            }\n        }\n        return callbacks;\n    }\n    \n    @Override\n    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {\n        Object[] callbacks = collectActivityLifecycleCallbacks();\n        if (callbacks != null) {\n            for (int i = 0; i < callbacks.length; i++) {\n                ((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityCreated(activity,\n                    savedInstanceState);\n            }\n        }\n    }\n    \n    @Override\n    public void onActivityStarted(Activity activity) {\n        Object[] callbacks = collectActivityLifecycleCallbacks();\n        if (callbacks != null) {\n            for (int i = 0; i < callbacks.length; i++) {\n                ((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityStarted(activity);\n            }\n        }\n    }\n    \n    @Override\n    public void onActivityResumed(Activity activity) {\n        Object[] callbacks = collectActivityLifecycleCallbacks();\n        if (callbacks != null) {\n            for (int i = 0; i < callbacks.length; i++) {\n                ((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityResumed(activity);\n            }\n        }\n    }\n    \n    @Override\n    public void onActivityPaused(Activity activity) {\n        Object[] callbacks = collectActivityLifecycleCallbacks();\n        if (callbacks != null) {\n            for (int i = 0; i < callbacks.length; i++) {\n                ((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityPaused(activity);\n            }\n        }\n    }\n    \n    @Override\n    public void onActivityStopped(Activity activity) {\n        Object[] callbacks = collectActivityLifecycleCallbacks();\n        if (callbacks != null) {\n            for (int i = 0; i < callbacks.length; i++) {\n                ((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityStopped(activity);\n            }\n        }\n    }\n    \n    @Override\n    public void onActivitySaveInstanceState(Activity activity, Bundle outState) {\n        Object[] callbacks = collectActivityLifecycleCallbacks();\n        if (callbacks != null) {\n            for (int i = 0; i < callbacks.length; i++) {\n                ((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivitySaveInstanceState(activity,\n                    outState);\n            }\n        }\n    }\n    \n    @Override\n    public void onActivityDestroyed(Activity activity) {\n        Object[] callbacks = collectActivityLifecycleCallbacks();\n        if (callbacks != null) {\n            for (int i = 0; i < callbacks.length; i++) {\n                ((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityDestroyed(activity);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CoreLibrary/src/main/java/com/didi/virtualapk/internal/ComponentsHandler.java",
    "content": "/*\n * Copyright (C) 2017 Beijing Didi Infinity Technology and Development Co.,Ltd. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.didi.virtualapk.internal;\n\nimport android.app.Service;\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.pm.ActivityInfo;\nimport android.content.pm.ResolveInfo;\nimport android.content.res.Resources;\nimport android.os.IBinder;\nimport android.support.v4.util.ArrayMap;\nimport android.util.Log;\n\nimport com.didi.virtualapk.PluginManager;\n\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * Created by renyugang on 17/6/7.\n */\n\npublic class ComponentsHandler {\n\n    public static final String TAG = Constants.TAG_PREFIX + \"PluginManager\";\n\n    private Context mContext;\n    private PluginManager mPluginManager;\n    private StubActivityInfo mStubActivityInfo = new StubActivityInfo();\n\n\n    private ArrayMap<ComponentName, Service> mServices = new ArrayMap<ComponentName, Service>();\n    private ArrayMap<IBinder, Intent> mBoundServices = new ArrayMap<IBinder, Intent>();\n    private ArrayMap<Service, AtomicInteger> mServiceCounters = new ArrayMap<Service, AtomicInteger>();\n\n    public ComponentsHandler(PluginManager pluginManager) {\n        mPluginManager = pluginManager;\n        mContext = pluginManager.getHostContext();\n    }\n\n    /**\n     * transform intent from implicit to explicit\n     */\n    public Intent transformIntentToExplicitAsNeeded(Intent intent) {\n        ComponentName component = intent.getComponent();\n        if (component == null\n            || component.getPackageName().equals(mContext.getPackageName())) {\n            ResolveInfo info = mPluginManager.resolveActivity(intent);\n            if (info != null && info.activityInfo != null) {\n                component = new ComponentName(info.activityInfo.packageName, info.activityInfo.name);\n                intent.setComponent(component);\n            }\n        }\n\n        return intent;\n    }\n\n    public void markIntentIfNeeded(Intent intent) {\n        if (intent.getComponent() == null) {\n            return;\n        }\n\n        String targetPackageName = intent.getComponent().getPackageName();\n        String targetClassName = intent.getComponent().getClassName();\n        // search map and return specific launchmode stub activity\n        if (!targetPackageName.equals(mContext.getPackageName()) && mPluginManager.getLoadedPlugin(targetPackageName) != null) {\n            intent.putExtra(Constants.KEY_IS_PLUGIN, true);\n            intent.putExtra(Constants.KEY_TARGET_PACKAGE, targetPackageName);\n            intent.putExtra(Constants.KEY_TARGET_ACTIVITY, targetClassName);\n            dispatchStubActivity(intent);\n        }\n    }\n\n    private void dispatchStubActivity(Intent intent) {\n        ComponentName component = intent.getComponent();\n        String targetClassName = intent.getComponent().getClassName();\n        LoadedPlugin loadedPlugin = mPluginManager.getLoadedPlugin(intent);\n        ActivityInfo info = loadedPlugin.getActivityInfo(component);\n        if (info == null) {\n            throw new RuntimeException(\"can not find \" + component);\n        }\n        int launchMode = info.launchMode;\n        Resources.Theme themeObj = loadedPlugin.getResources().newTheme();\n        themeObj.applyStyle(info.theme, true);\n        String stubActivity = mStubActivityInfo.getStubActivity(targetClassName, launchMode, themeObj);\n        Log.i(TAG, String.format(\"dispatchStubActivity,[%s -> %s]\", targetClassName, stubActivity));\n        intent.setClassName(mContext, stubActivity);\n    }\n\n\n    public AtomicInteger getServiceCounter(Service service) {\n        return this.mServiceCounters.get(service);\n    }\n\n    /**\n     * Retrieve the started service by component name\n     *\n     * @param component\n     * @return\n     */\n    public Service getService(ComponentName component) {\n        return this.mServices.get(component);\n    }\n\n    /**\n     * Put the started service into service registry, and then increase the counter associate with\n     * the service\n     *\n     * @param component\n     * @param service\n     */\n    public void rememberService(ComponentName component, Service service) {\n        synchronized (this.mServices) {\n            this.mServices.put(component, service);\n            this.mServiceCounters.put(service, new AtomicInteger(0));\n        }\n    }\n\n    /**\n     * Remove the service from service registry\n     *\n     * @param component\n     * @return\n     */\n    public Service forgetService(ComponentName component) {\n        synchronized (this.mServices) {\n            Service service = this.mServices.remove(component);\n            this.mServiceCounters.remove(service);\n            return service;\n        }\n    }\n\n    /**\n     * Remove the bound service from service registry\n     *\n     * @param iServiceConnection IServiceConnection binder when unbindService\n     * @return\n     */\n    public Intent forgetIServiceConnection(IBinder iServiceConnection) {\n        synchronized (this.mBoundServices) {\n            Intent intent = this.mBoundServices.remove(iServiceConnection);\n            return intent;\n        }\n    }\n\n    /**\n     * save the bound service\n     *\n     * @param iServiceConnection IServiceConnection binder when bindService\n     * @return\n     */\n    public void remberIServiceConnection(IBinder iServiceConnection, Intent intent) {\n        synchronized (this.mBoundServices) {\n            mBoundServices.put(iServiceConnection, intent);\n        }\n    }\n\n    /**\n     * Check if a started service with the specified component exists in the registry\n     *\n     * @param component\n     * @return\n     */\n    public boolean isServiceAvailable(ComponentName component) {\n        return this.mServices.containsKey(component);\n    }\n\n}\n"
  },
  {
    "path": "CoreLibrary/src/main/java/com/didi/virtualapk/internal/Constants.java",
    "content": "/*\n * Copyright (C) 2017 Beijing Didi Infinity Technology and Development Co.,Ltd. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.didi.virtualapk.internal;\n\n/**\n * Created by renyugang on 16/8/15.\n */\npublic class Constants {\n    public static final String KEY_IS_PLUGIN = \"isPlugin\";\n    public static final String KEY_TARGET_PACKAGE = \"target.package\";\n    public static final String KEY_TARGET_ACTIVITY = \"target.activity\";\n\n    public static final String OPTIMIZE_DIR = \"dex\";\n    public static final String NATIVE_DIR = \"valibs\";\n\n    public static final boolean COMBINE_RESOURCES = true;\n    public static final boolean COMBINE_CLASSLOADER = true;\n    public static final boolean DEBUG = true;\n    \n    public static final String TAG = \"VA\";\n    public static final String TAG_PREFIX = TAG + \".\";\n\n}\n"
  },
  {
    "path": "CoreLibrary/src/main/java/com/didi/virtualapk/internal/LoadedPlugin.java",
    "content": "/*\n * Copyright (C) 2017 Beijing Didi Infinity Technology and Development Co.,Ltd. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.didi.virtualapk.internal;\n\nimport android.annotation.TargetApi;\nimport android.app.Application;\nimport android.app.Instrumentation;\nimport android.content.BroadcastReceiver;\nimport android.content.ComponentName;\nimport android.content.ContentResolver;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.IntentFilter;\nimport android.content.pm.ActivityInfo;\nimport android.content.pm.ApplicationInfo;\nimport android.content.pm.ChangedPackages;\nimport android.content.pm.FeatureInfo;\nimport android.content.pm.InstrumentationInfo;\nimport android.content.pm.PackageInfo;\nimport android.content.pm.PackageInstaller;\nimport android.content.pm.PackageItemInfo;\nimport android.content.pm.PackageManager;\nimport android.content.pm.PackageParser;\nimport android.content.pm.PermissionGroupInfo;\nimport android.content.pm.PermissionInfo;\nimport android.content.pm.ProviderInfo;\nimport android.content.pm.ResolveInfo;\nimport android.content.pm.ServiceInfo;\nimport android.content.pm.SharedLibraryInfo;\nimport android.content.pm.VersionedPackage;\nimport android.content.res.AssetManager;\nimport android.content.res.Resources;\nimport android.content.res.XmlResourceParser;\nimport android.graphics.Rect;\nimport android.graphics.drawable.Drawable;\nimport android.os.Build;\nimport android.os.Process;\nimport android.os.UserHandle;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\n\nimport com.didi.virtualapk.PluginManager;\nimport com.didi.virtualapk.internal.utils.DexUtil;\nimport com.didi.virtualapk.internal.utils.PackageParserCompat;\nimport com.didi.virtualapk.internal.utils.PluginUtil;\nimport com.didi.virtualapk.utils.Reflector;\nimport com.didi.virtualapk.utils.RunUtil;\n\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport dalvik.system.DexClassLoader;\n\n/**\n * Created by renyugang on 16/8/9.\n */\npublic class LoadedPlugin {\n\n    public static final String TAG = Constants.TAG_PREFIX + \"LoadedPlugin\";\n\n    protected File getDir(Context context, String name) {\n        return context.getDir(name, Context.MODE_PRIVATE);\n    }\n    \n    protected ClassLoader createClassLoader(Context context, File apk, File libsDir, ClassLoader parent) throws Exception {\n        File dexOutputDir = getDir(context, Constants.OPTIMIZE_DIR);\n        String dexOutputPath = dexOutputDir.getAbsolutePath();\n        DexClassLoader loader = new DexClassLoader(apk.getAbsolutePath(), dexOutputPath, libsDir.getAbsolutePath(), parent);\n\n        if (Constants.COMBINE_CLASSLOADER) {\n            DexUtil.insertDex(loader, parent, libsDir);\n        }\n\n        return loader;\n    }\n\n    protected AssetManager createAssetManager(Context context, File apk) throws Exception {\n        AssetManager am = AssetManager.class.newInstance();\n        Reflector.with(am).method(\"addAssetPath\", String.class).call(apk.getAbsolutePath());\n        return am;\n    }\n\n    protected Resources createResources(Context context, String packageName, File apk) throws Exception {\n        if (Constants.COMBINE_RESOURCES) {\n            return ResourcesManager.createResources(context, packageName, apk);\n        } else {\n            Resources hostResources = context.getResources();\n            AssetManager assetManager = createAssetManager(context, apk);\n            return new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());\n        }\n    }\n    \n    protected PluginPackageManager createPluginPackageManager() {\n        return new PluginPackageManager();\n    }\n    \n    public PluginContext createPluginContext(Context context) {\n        if (context == null) {\n            return new PluginContext(this);\n        }\n        \n        return new PluginContext(this, context);\n    }\n\n    protected ResolveInfo chooseBestActivity(Intent intent, String s, int flags, List<ResolveInfo> query) {\n        return query.get(0);\n    }\n\n    protected final String mLocation;\n    protected PluginManager mPluginManager;\n    protected Context mHostContext;\n    protected Context mPluginContext;\n    protected final File mNativeLibDir;\n    protected final PackageParser.Package mPackage;\n    protected final PackageInfo mPackageInfo;\n    protected Resources mResources;\n    protected ClassLoader mClassLoader;\n    protected PluginPackageManager mPackageManager;\n\n    protected Map<ComponentName, ActivityInfo> mActivityInfos;\n    protected Map<ComponentName, ServiceInfo> mServiceInfos;\n    protected Map<ComponentName, ActivityInfo> mReceiverInfos;\n    protected Map<ComponentName, ProviderInfo> mProviderInfos;\n    protected Map<String, ProviderInfo> mProviders; // key is authorities of provider\n    protected Map<ComponentName, InstrumentationInfo> mInstrumentationInfos;\n\n    protected Application mApplication;\n\n    public LoadedPlugin(PluginManager pluginManager, Context context, File apk) throws Exception {\n        this.mPluginManager = pluginManager;\n        this.mHostContext = context;\n        this.mLocation = apk.getAbsolutePath();\n        this.mPackage = PackageParserCompat.parsePackage(context, apk, PackageParser.PARSE_MUST_BE_APK);\n        this.mPackage.applicationInfo.metaData = this.mPackage.mAppMetaData;\n        this.mPackageInfo = new PackageInfo();\n        this.mPackageInfo.applicationInfo = this.mPackage.applicationInfo;\n        this.mPackageInfo.applicationInfo.sourceDir = apk.getAbsolutePath();\n    \n        if (Build.VERSION.SDK_INT >= 28\n            || (Build.VERSION.SDK_INT == 27 && Build.VERSION.PREVIEW_SDK_INT != 0)) { // Android P Preview\n            try {\n                this.mPackageInfo.signatures = this.mPackage.mSigningDetails.signatures;\n            } catch (Throwable e) {\n                PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES);\n                this.mPackageInfo.signatures = info.signatures;\n            }\n        } else {\n            this.mPackageInfo.signatures = this.mPackage.mSignatures;\n        }\n        \n        this.mPackageInfo.packageName = this.mPackage.packageName;\n        if (pluginManager.getLoadedPlugin(mPackageInfo.packageName) != null) {\n            throw new RuntimeException(\"plugin has already been loaded : \" + mPackageInfo.packageName);\n        }\n        this.mPackageInfo.versionCode = this.mPackage.mVersionCode;\n        this.mPackageInfo.versionName = this.mPackage.mVersionName;\n        this.mPackageInfo.permissions = new PermissionInfo[0];\n        this.mPackageManager = createPluginPackageManager();\n        this.mPluginContext = createPluginContext(null);\n        this.mNativeLibDir = getDir(context, Constants.NATIVE_DIR);\n        this.mPackage.applicationInfo.nativeLibraryDir = this.mNativeLibDir.getAbsolutePath();\n        this.mResources = createResources(context, getPackageName(), apk);\n        this.mClassLoader = createClassLoader(context, apk, this.mNativeLibDir, context.getClassLoader());\n\n        tryToCopyNativeLib(apk);\n\n        // Cache instrumentations\n        Map<ComponentName, InstrumentationInfo> instrumentations = new HashMap<ComponentName, InstrumentationInfo>();\n        for (PackageParser.Instrumentation instrumentation : this.mPackage.instrumentation) {\n            instrumentations.put(instrumentation.getComponentName(), instrumentation.info);\n        }\n        this.mInstrumentationInfos = Collections.unmodifiableMap(instrumentations);\n        this.mPackageInfo.instrumentation = instrumentations.values().toArray(new InstrumentationInfo[instrumentations.size()]);\n\n        // Cache activities\n        Map<ComponentName, ActivityInfo> activityInfos = new HashMap<ComponentName, ActivityInfo>();\n        for (PackageParser.Activity activity : this.mPackage.activities) {\n            activity.info.metaData = activity.metaData;\n            activityInfos.put(activity.getComponentName(), activity.info);\n        }\n        this.mActivityInfos = Collections.unmodifiableMap(activityInfos);\n        this.mPackageInfo.activities = activityInfos.values().toArray(new ActivityInfo[activityInfos.size()]);\n\n        // Cache services\n        Map<ComponentName, ServiceInfo> serviceInfos = new HashMap<ComponentName, ServiceInfo>();\n        for (PackageParser.Service service : this.mPackage.services) {\n            serviceInfos.put(service.getComponentName(), service.info);\n        }\n        this.mServiceInfos = Collections.unmodifiableMap(serviceInfos);\n        this.mPackageInfo.services = serviceInfos.values().toArray(new ServiceInfo[serviceInfos.size()]);\n\n        // Cache providers\n        Map<String, ProviderInfo> providers = new HashMap<String, ProviderInfo>();\n        Map<ComponentName, ProviderInfo> providerInfos = new HashMap<ComponentName, ProviderInfo>();\n        for (PackageParser.Provider provider : this.mPackage.providers) {\n            providers.put(provider.info.authority, provider.info);\n            providerInfos.put(provider.getComponentName(), provider.info);\n        }\n        this.mProviders = Collections.unmodifiableMap(providers);\n        this.mProviderInfos = Collections.unmodifiableMap(providerInfos);\n        this.mPackageInfo.providers = providerInfos.values().toArray(new ProviderInfo[providerInfos.size()]);\n\n        // Register broadcast receivers dynamically\n        Map<ComponentName, ActivityInfo> receivers = new HashMap<ComponentName, ActivityInfo>();\n        for (PackageParser.Activity receiver : this.mPackage.receivers) {\n            receivers.put(receiver.getComponentName(), receiver.info);\n    \n            BroadcastReceiver br = BroadcastReceiver.class.cast(getClassLoader().loadClass(receiver.getComponentName().getClassName()).newInstance());\n            for (PackageParser.ActivityIntentInfo aii : receiver.intents) {\n                this.mHostContext.registerReceiver(br, aii);\n            }\n        }\n        this.mReceiverInfos = Collections.unmodifiableMap(receivers);\n        this.mPackageInfo.receivers = receivers.values().toArray(new ActivityInfo[receivers.size()]);\n    \n        // try to invoke plugin's application\n        invokeApplication();\n    }\n\n    protected void tryToCopyNativeLib(File apk) throws Exception {\n        PluginUtil.copyNativeLib(apk, mHostContext, mPackageInfo, mNativeLibDir);\n    }\n\n    public String getLocation() {\n        return this.mLocation;\n    }\n\n    public String getPackageName() {\n        return this.mPackage.packageName;\n    }\n\n    public PackageManager getPackageManager() {\n        return this.mPackageManager;\n    }\n\n    public AssetManager getAssets() {\n        return getResources().getAssets();\n    }\n\n    public Resources getResources() {\n        return this.mResources;\n    }\n\n    public void updateResources(Resources newResources) {\n        this.mResources = newResources;\n    }\n\n    public ClassLoader getClassLoader() {\n        return this.mClassLoader;\n    }\n\n    public PluginManager getPluginManager() {\n        return this.mPluginManager;\n    }\n\n    public Context getHostContext() {\n        return this.mHostContext;\n    }\n\n    public Context getPluginContext() {\n        return this.mPluginContext;\n    }\n\n    public Application getApplication() {\n        return mApplication;\n    }\n\n    public void invokeApplication() throws Exception {\n        final Exception[] temp = new Exception[1];\n        // make sure application's callback is run on ui thread.\n        RunUtil.runOnUiThread(new Runnable() {\n            @Override\n            public void run() {\n                if (mApplication != null) {\n                    return;\n                }\n                try {\n                    mApplication = makeApplication(false, mPluginManager.getInstrumentation());\n                } catch (Exception e) {\n                    temp[0] = e;\n                }\n            }\n        }, true);\n        \n        if (temp[0] != null) {\n            throw temp[0];\n        }\n    }\n\n    public String getPackageResourcePath() {\n        int myUid = Process.myUid();\n        ApplicationInfo appInfo = this.mPackage.applicationInfo;\n        return appInfo.uid == myUid ? appInfo.sourceDir : appInfo.publicSourceDir;\n    }\n\n    public String getCodePath() {\n        return this.mPackage.applicationInfo.sourceDir;\n    }\n\n    public Intent getLaunchIntent() {\n        ContentResolver resolver = this.mPluginContext.getContentResolver();\n        Intent launcher = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LAUNCHER);\n\n        for (PackageParser.Activity activity : this.mPackage.activities) {\n            for (PackageParser.ActivityIntentInfo intentInfo : activity.intents) {\n                if (intentInfo.match(resolver, launcher, false, TAG) > 0) {\n                    return Intent.makeMainActivity(activity.getComponentName());\n                }\n            }\n        }\n\n        return null;\n    }\n\n    public Intent getLeanbackLaunchIntent() {\n        ContentResolver resolver = this.mPluginContext.getContentResolver();\n        Intent launcher = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LEANBACK_LAUNCHER);\n\n        for (PackageParser.Activity activity : this.mPackage.activities) {\n            for (PackageParser.ActivityIntentInfo intentInfo : activity.intents) {\n                if (intentInfo.match(resolver, launcher, false, TAG) > 0) {\n                    Intent intent = new Intent(Intent.ACTION_MAIN);\n                    intent.setComponent(activity.getComponentName());\n                    intent.addCategory(Intent.CATEGORY_LEANBACK_LAUNCHER);\n                    return intent;\n                }\n            }\n        }\n\n        return null;\n    }\n\n    public ApplicationInfo getApplicationInfo() {\n        return this.mPackage.applicationInfo;\n    }\n\n    public PackageInfo getPackageInfo() {\n        return this.mPackageInfo;\n    }\n\n    public ActivityInfo getActivityInfo(ComponentName componentName) {\n        return this.mActivityInfos.get(componentName);\n    }\n\n    public ServiceInfo getServiceInfo(ComponentName componentName) {\n        return this.mServiceInfos.get(componentName);\n    }\n\n    public ActivityInfo getReceiverInfo(ComponentName componentName) {\n        return this.mReceiverInfos.get(componentName);\n    }\n\n    public ProviderInfo getProviderInfo(ComponentName componentName) {\n        return this.mProviderInfos.get(componentName);\n    }\n\n    public Resources.Theme getTheme() {\n        Resources.Theme theme = this.mResources.newTheme();\n        theme.applyStyle(PluginUtil.selectDefaultTheme(this.mPackage.applicationInfo.theme, Build.VERSION.SDK_INT), false);\n        return theme;\n    }\n\n    public void setTheme(int resid) {\n        Reflector.QuietReflector.with(this.mResources).field(\"mThemeResId\").set(resid);\n    }\n\n    protected Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) throws Exception {\n        if (null != this.mApplication) {\n            return this.mApplication;\n        }\n\n        String appClass = this.mPackage.applicationInfo.className;\n        if (forceDefaultAppClass || null == appClass) {\n            appClass = \"android.app.Application\";\n        }\n    \n        this.mApplication = instrumentation.newApplication(this.mClassLoader, appClass, this.getPluginContext());\n        // inject activityLifecycleCallbacks of the host application\n        mApplication.registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacksProxy());\n        instrumentation.callApplicationOnCreate(this.mApplication);\n        return this.mApplication;\n    }\n\n    public ResolveInfo resolveActivity(Intent intent, int flags) {\n        List<ResolveInfo> query = this.queryIntentActivities(intent, flags);\n        if (null == query || query.isEmpty()) {\n            return null;\n        }\n\n        ContentResolver resolver = this.mPluginContext.getContentResolver();\n        return chooseBestActivity(intent, intent.resolveTypeIfNeeded(resolver), flags, query);\n    }\n\n    public List<ResolveInfo> queryIntentActivities(Intent intent, int flags) {\n        ComponentName component = intent.getComponent();\n        List<ResolveInfo> resolveInfos = new ArrayList<ResolveInfo>();\n        ContentResolver resolver = this.mPluginContext.getContentResolver();\n\n        for (PackageParser.Activity activity : this.mPackage.activities) {\n            if (match(activity, component)) {\n                ResolveInfo resolveInfo = new ResolveInfo();\n                resolveInfo.activityInfo = activity.info;\n                resolveInfos.add(resolveInfo);\n            } else if (component == null) {\n                // only match implicit intent\n                for (PackageParser.ActivityIntentInfo intentInfo : activity.intents) {\n                    if (intentInfo.match(resolver, intent, true, TAG) >= 0) {\n                        ResolveInfo resolveInfo = new ResolveInfo();\n                        resolveInfo.activityInfo = activity.info;\n                        resolveInfos.add(resolveInfo);\n                        break;\n                    }\n                }\n            }\n        }\n\n        return resolveInfos;\n    }\n\n    public ResolveInfo resolveService(Intent intent, int flags) {\n        List<ResolveInfo> query = this.queryIntentServices(intent, flags);\n        if (null == query || query.isEmpty()) {\n            return null;\n        }\n\n        ContentResolver resolver = this.mPluginContext.getContentResolver();\n        return chooseBestActivity(intent, intent.resolveTypeIfNeeded(resolver), flags, query);\n    }\n\n    public List<ResolveInfo> queryIntentServices(Intent intent, int flags) {\n        ComponentName component = intent.getComponent();\n        List<ResolveInfo> resolveInfos = new ArrayList<ResolveInfo>();\n        ContentResolver resolver = this.mPluginContext.getContentResolver();\n\n        for (PackageParser.Service service : this.mPackage.services) {\n            if (match(service, component)) {\n                ResolveInfo resolveInfo = new ResolveInfo();\n                resolveInfo.serviceInfo = service.info;\n                resolveInfos.add(resolveInfo);\n            } else if (component == null) {\n                // only match implicit intent\n                for (PackageParser.ServiceIntentInfo intentInfo : service.intents) {\n                    if (intentInfo.match(resolver, intent, true, TAG) >= 0) {\n                        ResolveInfo resolveInfo = new ResolveInfo();\n                        resolveInfo.serviceInfo = service.info;\n                        resolveInfos.add(resolveInfo);\n                        break;\n                    }\n                }\n            }\n        }\n\n        return resolveInfos;\n    }\n\n    public List<ResolveInfo> queryBroadcastReceivers(Intent intent, int flags) {\n        ComponentName component = intent.getComponent();\n        List<ResolveInfo> resolveInfos = new ArrayList<ResolveInfo>();\n        ContentResolver resolver = this.mPluginContext.getContentResolver();\n\n        for (PackageParser.Activity receiver : this.mPackage.receivers) {\n            if (receiver.getComponentName().equals(component)) {\n                ResolveInfo resolveInfo = new ResolveInfo();\n                resolveInfo.activityInfo = receiver.info;\n                resolveInfos.add(resolveInfo);\n            } else if (component == null) {\n                // only match implicit intent\n                for (PackageParser.ActivityIntentInfo intentInfo : receiver.intents) {\n                    if (intentInfo.match(resolver, intent, true, TAG) >= 0) {\n                        ResolveInfo resolveInfo = new ResolveInfo();\n                        resolveInfo.activityInfo = receiver.info;\n                        resolveInfos.add(resolveInfo);\n                        break;\n                    }\n                }\n            }\n        }\n\n        return resolveInfos;\n    }\n\n    public ProviderInfo resolveContentProvider(String name, int flags) {\n        return this.mProviders.get(name);\n    }\n\n    protected boolean match(PackageParser.Component component, ComponentName target) {\n        ComponentName source = component.getComponentName();\n        if (source == target) return true;\n        if (source != null && target != null\n                && source.getClassName().equals(target.getClassName())\n                && (source.getPackageName().equals(target.getPackageName())\n                || mHostContext.getPackageName().equals(target.getPackageName()))) {\n            return true;\n        }\n        return false;\n    }\n\n    /**\n     * @author johnsonlee\n     */\n    protected class PluginPackageManager extends PackageManager {\n\n        protected PackageManager mHostPackageManager = mHostContext.getPackageManager();\n\n        @Override\n        public PackageInfo getPackageInfo(String packageName, int flags) throws NameNotFoundException {\n\n            LoadedPlugin plugin = mPluginManager.getLoadedPlugin(packageName);\n            if (null != plugin) {\n                return plugin.mPackageInfo;\n            }\n\n            return this.mHostPackageManager.getPackageInfo(packageName, flags);\n        }\n    \n        @TargetApi(Build.VERSION_CODES.O)\n        @Override\n        public PackageInfo getPackageInfo(VersionedPackage versionedPackage, int i) throws NameNotFoundException {\n\n            LoadedPlugin plugin = mPluginManager.getLoadedPlugin(versionedPackage.getPackageName());\n            if (null != plugin) {\n                return plugin.mPackageInfo;\n            }\n\n            return this.mHostPackageManager.getPackageInfo(versionedPackage, i);\n        }\n    \n        @Override\n        public String[] currentToCanonicalPackageNames(String[] names) {\n            return this.mHostPackageManager.currentToCanonicalPackageNames(names);\n        }\n\n        @Override\n        public String[] canonicalToCurrentPackageNames(String[] names) {\n            return this.mHostPackageManager.canonicalToCurrentPackageNames(names);\n        }\n\n        @Override\n        public Intent getLaunchIntentForPackage(@NonNull String packageName) {\n            LoadedPlugin plugin = mPluginManager.getLoadedPlugin(packageName);\n            if (null != plugin) {\n                return plugin.getLaunchIntent();\n            }\n\n            return this.mHostPackageManager.getLaunchIntentForPackage(packageName);\n        }\n\n        @TargetApi(Build.VERSION_CODES.LOLLIPOP)\n        @Override\n        public Intent getLeanbackLaunchIntentForPackage(@NonNull String packageName) {\n            LoadedPlugin plugin = mPluginManager.getLoadedPlugin(packageName);\n            if (null != plugin) {\n                return plugin.getLeanbackLaunchIntent();\n            }\n\n            return this.mHostPackageManager.getLeanbackLaunchIntentForPackage(packageName);\n        }\n\n        @Override\n        public int[] getPackageGids(@NonNull String packageName) throws NameNotFoundException {\n            return this.mHostPackageManager.getPackageGids(packageName);\n        }\n    \n        @TargetApi(Build.VERSION_CODES.N)\n        @Override\n        public int[] getPackageGids(String packageName, int flags) throws NameNotFoundException {\n            return this.mHostPackageManager.getPackageGids(packageName, flags);\n        }\n    \n        @TargetApi(Build.VERSION_CODES.N)\n        @Override\n        public int getPackageUid(String packageName, int flags) throws NameNotFoundException {\n            return this.mHostPackageManager.getPackageUid(packageName, flags);\n        }\n\n        @Override\n        public PermissionInfo getPermissionInfo(String name, int flags) throws NameNotFoundException {\n            return this.mHostPackageManager.getPermissionInfo(name, flags);\n        }\n\n        @Override\n        public List<PermissionInfo> queryPermissionsByGroup(String group, int flags) throws NameNotFoundException {\n            return this.mHostPackageManager.queryPermissionsByGroup(group, flags);\n        }\n\n        @Override\n        public PermissionGroupInfo getPermissionGroupInfo(String name, int flags) throws NameNotFoundException {\n            return this.mHostPackageManager.getPermissionGroupInfo(name, flags);\n        }\n\n        @Override\n        public List<PermissionGroupInfo> getAllPermissionGroups(int flags) {\n            return this.mHostPackageManager.getAllPermissionGroups(flags);\n        }\n\n        @Override\n        public ApplicationInfo getApplicationInfo(String packageName, int flags) throws NameNotFoundException {\n            LoadedPlugin plugin = mPluginManager.getLoadedPlugin(packageName);\n            if (null != plugin) {\n                return plugin.getApplicationInfo();\n            }\n\n            return this.mHostPackageManager.getApplicationInfo(packageName, flags);\n        }\n\n        @Override\n        public ActivityInfo getActivityInfo(ComponentName component, int flags) throws NameNotFoundException {\n            LoadedPlugin plugin = mPluginManager.getLoadedPlugin(component);\n            if (null != plugin) {\n                return plugin.mActivityInfos.get(component);\n            }\n\n            return this.mHostPackageManager.getActivityInfo(component, flags);\n        }\n\n        @Override\n        public ActivityInfo getReceiverInfo(ComponentName component, int flags) throws NameNotFoundException {\n            LoadedPlugin plugin = mPluginManager.getLoadedPlugin(component);\n            if (null != plugin) {\n                return plugin.mReceiverInfos.get(component);\n            }\n\n            return this.mHostPackageManager.getReceiverInfo(component, flags);\n        }\n\n        @Override\n        public ServiceInfo getServiceInfo(ComponentName component, int flags) throws NameNotFoundException {\n            LoadedPlugin plugin = mPluginManager.getLoadedPlugin(component);\n            if (null != plugin) {\n                return plugin.mServiceInfos.get(component);\n            }\n\n            return this.mHostPackageManager.getServiceInfo(component, flags);\n        }\n\n        @Override\n        public ProviderInfo getProviderInfo(ComponentName component, int flags) throws NameNotFoundException {\n            LoadedPlugin plugin = mPluginManager.getLoadedPlugin(component);\n            if (null != plugin) {\n                return plugin.mProviderInfos.get(component);\n            }\n\n            return this.mHostPackageManager.getProviderInfo(component, flags);\n        }\n\n        @Override\n        public List<PackageInfo> getInstalledPackages(int flags) {\n            return this.mHostPackageManager.getInstalledPackages(flags);\n        }\n\n        @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)\n        @Override\n        public List<PackageInfo> getPackagesHoldingPermissions(String[] permissions, int flags) {\n            return this.mHostPackageManager.getPackagesHoldingPermissions(permissions, flags);\n        }\n\n        @Override\n        public int checkPermission(String permName, String pkgName) {\n            return this.mHostPackageManager.checkPermission(permName, pkgName);\n        }\n\n        @TargetApi(Build.VERSION_CODES.M)\n        @Override\n        public boolean isPermissionRevokedByPolicy(@NonNull String permName, @NonNull String pkgName) {\n            return this.mHostPackageManager.isPermissionRevokedByPolicy(permName, pkgName);\n        }\n\n        @Override\n        public boolean addPermission(PermissionInfo info) {\n            return this.mHostPackageManager.addPermission(info);\n        }\n\n        @Override\n        public boolean addPermissionAsync(PermissionInfo info) {\n            return this.mHostPackageManager.addPermissionAsync(info);\n        }\n\n        @Override\n        public void removePermission(String name) {\n            this.mHostPackageManager.removePermission(name);\n        }\n\n        @Override\n        public int checkSignatures(String pkg1, String pkg2) {\n            return this.mHostPackageManager.checkSignatures(pkg1, pkg2);\n        }\n\n        @Override\n        public int checkSignatures(int uid1, int uid2) {\n            return this.mHostPackageManager.checkSignatures(uid1, uid2);\n        }\n\n        @Override\n        public String[] getPackagesForUid(int uid) {\n            return this.mHostPackageManager.getPackagesForUid(uid);\n        }\n\n        @Override\n        public String getNameForUid(int uid) {\n            return this.mHostPackageManager.getNameForUid(uid);\n        }\n\n        @Override\n        public List<ApplicationInfo> getInstalledApplications(int flags) {\n            return this.mHostPackageManager.getInstalledApplications(flags);\n        }\n    \n        @TargetApi(Build.VERSION_CODES.O)\n        @Override\n        public boolean isInstantApp() {\n            return this.mHostPackageManager.isInstantApp();\n        }\n    \n        @TargetApi(Build.VERSION_CODES.O)\n        @Override\n        public boolean isInstantApp(String packageName) {\n            return this.mHostPackageManager.isInstantApp(packageName);\n        }\n    \n        @TargetApi(Build.VERSION_CODES.O)\n        @Override\n        public int getInstantAppCookieMaxBytes() {\n            return this.mHostPackageManager.getInstantAppCookieMaxBytes();\n        }\n    \n        @TargetApi(Build.VERSION_CODES.O)\n        @NonNull\n        @Override\n        public byte[] getInstantAppCookie() {\n            return this.mHostPackageManager.getInstantAppCookie();\n        }\n    \n        @TargetApi(Build.VERSION_CODES.O)\n        @Override\n        public void clearInstantAppCookie() {\n            this.mHostPackageManager.clearInstantAppCookie();\n        }\n    \n        @TargetApi(Build.VERSION_CODES.O)\n        @Override\n        public void updateInstantAppCookie(@Nullable byte[] cookie) {\n            this.mHostPackageManager.updateInstantAppCookie(cookie);\n        }\n    \n        @Override\n        public String[] getSystemSharedLibraryNames() {\n            return this.mHostPackageManager.getSystemSharedLibraryNames();\n        }\n    \n        @TargetApi(Build.VERSION_CODES.O)\n        @NonNull\n        @Override\n        public List<SharedLibraryInfo> getSharedLibraries(int flags) {\n            return this.mHostPackageManager.getSharedLibraries(flags);\n        }\n    \n        @TargetApi(Build.VERSION_CODES.O)\n        @Nullable\n        @Override\n        public ChangedPackages getChangedPackages(int sequenceNumber) {\n            return this.mHostPackageManager.getChangedPackages(sequenceNumber);\n        }\n    \n        @Override\n        public FeatureInfo[] getSystemAvailableFeatures() {\n            return this.mHostPackageManager.getSystemAvailableFeatures();\n        }\n\n        @Override\n        public boolean hasSystemFeature(String name) {\n            return this.mHostPackageManager.hasSystemFeature(name);\n        }\n\n        @TargetApi(Build.VERSION_CODES.N)\n        @Override\n        public boolean hasSystemFeature(String name, int version) {\n            return this.mHostPackageManager.hasSystemFeature(name, version);\n        }\n    \n        @Override\n        public ResolveInfo resolveActivity(Intent intent, int flags) {\n            ResolveInfo resolveInfo = mPluginManager.resolveActivity(intent, flags);\n            if (null != resolveInfo) {\n                return resolveInfo;\n            }\n\n            return this.mHostPackageManager.resolveActivity(intent, flags);\n        }\n\n        @Override\n        public List<ResolveInfo> queryIntentActivities(Intent intent, int flags) {\n            ComponentName component = intent.getComponent();\n            if (null == component) {\n                if (intent.getSelector() != null) {\n                    intent = intent.getSelector();\n                    component = intent.getComponent();\n                }\n            }\n\n            if (null != component) {\n                LoadedPlugin plugin = mPluginManager.getLoadedPlugin(component);\n                if (null != plugin) {\n                    ActivityInfo activityInfo = plugin.getActivityInfo(component);\n                    if (activityInfo != null) {\n                        ResolveInfo resolveInfo = new ResolveInfo();\n                        resolveInfo.activityInfo = activityInfo;\n                        return Arrays.asList(resolveInfo);\n                    }\n                }\n            }\n\n            List<ResolveInfo> all = new ArrayList<ResolveInfo>();\n\n            List<ResolveInfo> pluginResolveInfos = mPluginManager.queryIntentActivities(intent, flags);\n            if (null != pluginResolveInfos && pluginResolveInfos.size() > 0) {\n                all.addAll(pluginResolveInfos);\n            }\n\n            List<ResolveInfo> hostResolveInfos = this.mHostPackageManager.queryIntentActivities(intent, flags);\n            if (null != hostResolveInfos && hostResolveInfos.size() > 0) {\n                all.addAll(hostResolveInfos);\n            }\n\n            return all;\n        }\n\n        @Override\n        public List<ResolveInfo> queryIntentActivityOptions(ComponentName caller, Intent[] specifics, Intent intent, int flags) {\n            return this.mHostPackageManager.queryIntentActivityOptions(caller, specifics, intent, flags);\n        }\n\n        @Override\n        public List<ResolveInfo> queryBroadcastReceivers(Intent intent, int flags) {\n            ComponentName component = intent.getComponent();\n            if (null == component) {\n                if (intent.getSelector() != null) {\n                    intent = intent.getSelector();\n                    component = intent.getComponent();\n                }\n            }\n\n            if (null != component) {\n                LoadedPlugin plugin = mPluginManager.getLoadedPlugin(component);\n                if (null != plugin) {\n                    ActivityInfo activityInfo = plugin.getReceiverInfo(component);\n                    if (activityInfo != null) {\n                        ResolveInfo resolveInfo = new ResolveInfo();\n                        resolveInfo.activityInfo = activityInfo;\n                        return Arrays.asList(resolveInfo);\n                    }\n                }\n            }\n\n            List<ResolveInfo> all = new ArrayList<>();\n\n            List<ResolveInfo> pluginResolveInfos = mPluginManager.queryBroadcastReceivers(intent, flags);\n            if (null != pluginResolveInfos && pluginResolveInfos.size() > 0) {\n                all.addAll(pluginResolveInfos);\n            }\n\n            List<ResolveInfo> hostResolveInfos = this.mHostPackageManager.queryBroadcastReceivers(intent, flags);\n            if (null != hostResolveInfos && hostResolveInfos.size() > 0) {\n                all.addAll(hostResolveInfos);\n            }\n\n            return all;\n        }\n\n        @Override\n        public ResolveInfo resolveService(Intent intent, int flags) {\n            ResolveInfo resolveInfo = mPluginManager.resolveService(intent, flags);\n            if (null != resolveInfo) {\n                return resolveInfo;\n            }\n\n            return this.mHostPackageManager.resolveService(intent, flags);\n        }\n\n        @Override\n        public List<ResolveInfo> queryIntentServices(Intent intent, int flags) {\n            ComponentName component = intent.getComponent();\n            if (null == component) {\n                if (intent.getSelector() != null) {\n                    intent = intent.getSelector();\n                    component = intent.getComponent();\n                }\n            }\n\n            if (null != component) {\n                LoadedPlugin plugin = mPluginManager.getLoadedPlugin(component);\n                if (null != plugin) {\n                    ServiceInfo serviceInfo = plugin.getServiceInfo(component);\n                    if (serviceInfo != null) {\n                        ResolveInfo resolveInfo = new ResolveInfo();\n                        resolveInfo.serviceInfo = serviceInfo;\n                        return Arrays.asList(resolveInfo);\n                    }\n                }\n            }\n\n            List<ResolveInfo> all = new ArrayList<ResolveInfo>();\n\n            List<ResolveInfo> pluginResolveInfos = mPluginManager.queryIntentServices(intent, flags);\n            if (null != pluginResolveInfos && pluginResolveInfos.size() > 0) {\n                all.addAll(pluginResolveInfos);\n            }\n\n            List<ResolveInfo> hostResolveInfos = this.mHostPackageManager.queryIntentServices(intent, flags);\n            if (null != hostResolveInfos && hostResolveInfos.size() > 0) {\n                all.addAll(hostResolveInfos);\n            }\n\n            return all;\n        }\n\n        @Override\n        @TargetApi(Build.VERSION_CODES.KITKAT)\n        public List<ResolveInfo> queryIntentContentProviders(Intent intent, int flags) {\n            return this.mHostPackageManager.queryIntentContentProviders(intent, flags);\n        }\n\n        @Override\n        public ProviderInfo resolveContentProvider(String name, int flags) {\n            ProviderInfo providerInfo = mPluginManager.resolveContentProvider(name, flags);\n            if (null != providerInfo) {\n                return providerInfo;\n            }\n\n            return this.mHostPackageManager.resolveContentProvider(name, flags);\n        }\n\n        @Override\n        public List<ProviderInfo> queryContentProviders(String processName, int uid, int flags) {\n            return this.mHostPackageManager.queryContentProviders(processName, uid, flags);\n        }\n\n        @Override\n        public InstrumentationInfo getInstrumentationInfo(ComponentName component, int flags) throws NameNotFoundException {\n            LoadedPlugin plugin = mPluginManager.getLoadedPlugin(component);\n            if (null != plugin) {\n                return plugin.mInstrumentationInfos.get(component);\n            }\n\n            return this.mHostPackageManager.getInstrumentationInfo(component, flags);\n        }\n\n        @Override\n        public List<InstrumentationInfo> queryInstrumentation(String targetPackage, int flags) {\n            return this.mHostPackageManager.queryInstrumentation(targetPackage, flags);\n        }\n\n        @Override\n        public Drawable getDrawable(String packageName, int resid, ApplicationInfo appInfo) {\n            LoadedPlugin plugin = mPluginManager.getLoadedPlugin(packageName);\n            if (null != plugin) {\n                return plugin.mResources.getDrawable(resid);\n            }\n\n            return this.mHostPackageManager.getDrawable(packageName, resid, appInfo);\n        }\n\n        @Override\n        public Drawable getActivityIcon(ComponentName component) throws NameNotFoundException {\n            LoadedPlugin plugin = mPluginManager.getLoadedPlugin(component);\n            if (null != plugin) {\n                return plugin.mResources.getDrawable(plugin.mActivityInfos.get(component).icon);\n            }\n\n            return this.mHostPackageManager.getActivityIcon(component);\n        }\n\n        @Override\n        public Drawable getActivityIcon(Intent intent) throws NameNotFoundException {\n            ResolveInfo ri = mPluginManager.resolveActivity(intent);\n            if (null != ri) {\n                LoadedPlugin plugin = mPluginManager.getLoadedPlugin(ri.resolvePackageName);\n                return plugin.mResources.getDrawable(ri.activityInfo.icon);\n            }\n\n            return this.mHostPackageManager.getActivityIcon(intent);\n        }\n\n        @TargetApi(Build.VERSION_CODES.KITKAT_WATCH)\n        @Override\n        public Drawable getActivityBanner(ComponentName component) throws NameNotFoundException {\n            LoadedPlugin plugin = mPluginManager.getLoadedPlugin(component);\n            if (null != plugin) {\n                return plugin.mResources.getDrawable(plugin.mActivityInfos.get(component).banner);\n            }\n\n            return this.mHostPackageManager.getActivityBanner(component);\n        }\n\n        @TargetApi(Build.VERSION_CODES.KITKAT_WATCH)\n        @Override\n        public Drawable getActivityBanner(Intent intent) throws NameNotFoundException {\n            ResolveInfo ri = mPluginManager.resolveActivity(intent);\n            if (null != ri) {\n                LoadedPlugin plugin = mPluginManager.getLoadedPlugin(ri.resolvePackageName);\n                return plugin.mResources.getDrawable(ri.activityInfo.banner);\n            }\n\n            return this.mHostPackageManager.getActivityBanner(intent);\n        }\n\n        @Override\n        public Drawable getDefaultActivityIcon() {\n            return this.mHostPackageManager.getDefaultActivityIcon();\n        }\n\n        @Override\n        public Drawable getApplicationIcon(ApplicationInfo info) {\n            LoadedPlugin plugin = mPluginManager.getLoadedPlugin(info.packageName);\n            if (null != plugin) {\n                return plugin.mResources.getDrawable(info.icon);\n            }\n\n            return this.mHostPackageManager.getApplicationIcon(info);\n        }\n\n        @Override\n        public Drawable getApplicationIcon(String packageName) throws NameNotFoundException {\n            LoadedPlugin plugin = mPluginManager.getLoadedPlugin(packageName);\n            if (null != plugin) {\n                return plugin.mResources.getDrawable(plugin.mPackage.applicationInfo.icon);\n            }\n\n            return this.mHostPackageManager.getApplicationIcon(packageName);\n        }\n\n        @TargetApi(Build.VERSION_CODES.KITKAT_WATCH)\n        @Override\n        public Drawable getApplicationBanner(ApplicationInfo info) {\n            LoadedPlugin plugin = mPluginManager.getLoadedPlugin(info.packageName);\n            if (null != plugin) {\n                return plugin.mResources.getDrawable(info.banner);\n            }\n\n            return this.mHostPackageManager.getApplicationBanner(info);\n        }\n\n        @TargetApi(Build.VERSION_CODES.KITKAT_WATCH)\n        @Override\n        public Drawable getApplicationBanner(String packageName) throws NameNotFoundException {\n            LoadedPlugin plugin = mPluginManager.getLoadedPlugin(packageName);\n            if (null != plugin) {\n                return plugin.mResources.getDrawable(plugin.mPackage.applicationInfo.banner);\n            }\n\n            return this.mHostPackageManager.getApplicationBanner(packageName);\n        }\n\n        @Override\n        public Drawable getActivityLogo(ComponentName component) throws NameNotFoundException {\n            LoadedPlugin plugin = mPluginManager.getLoadedPlugin(component);\n            if (null != plugin) {\n                return plugin.mResources.getDrawable(plugin.mActivityInfos.get(component).logo);\n            }\n\n            return this.mHostPackageManager.getActivityLogo(component);\n        }\n\n        @Override\n        public Drawable getActivityLogo(Intent intent) throws NameNotFoundException {\n            ResolveInfo ri = mPluginManager.resolveActivity(intent);\n            if (null != ri) {\n                LoadedPlugin plugin = mPluginManager.getLoadedPlugin(ri.resolvePackageName);\n                return plugin.mResources.getDrawable(ri.activityInfo.logo);\n            }\n\n            return this.mHostPackageManager.getActivityLogo(intent);\n        }\n\n        @Override\n        public Drawable getApplicationLogo(ApplicationInfo info) {\n            LoadedPlugin plugin = mPluginManager.getLoadedPlugin(info.packageName);\n            if (null != plugin) {\n                return plugin.mResources.getDrawable(0 != info.logo ? info.logo : android.R.drawable.sym_def_app_icon);\n            }\n\n            return this.mHostPackageManager.getApplicationLogo(info);\n        }\n\n        @Override\n        public Drawable getApplicationLogo(String packageName) throws NameNotFoundException {\n            LoadedPlugin plugin = mPluginManager.getLoadedPlugin(packageName);\n            if (null != plugin) {\n                return plugin.mResources.getDrawable(0 != plugin.mPackage.applicationInfo.logo ? plugin.mPackage.applicationInfo.logo : android.R.drawable.sym_def_app_icon);\n            }\n\n            return this.mHostPackageManager.getApplicationLogo(packageName);\n        }\n\n        @TargetApi(Build.VERSION_CODES.LOLLIPOP)\n        @Override\n        public Drawable getUserBadgedIcon(Drawable icon, UserHandle user) {\n            return this.mHostPackageManager.getUserBadgedIcon(icon, user);\n        }\n\n        @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)\n        public Drawable getUserBadgeForDensity(UserHandle user, int density) {\n            try {\n                return Reflector.with(this.mHostPackageManager)\n                    .method(\"getUserBadgeForDensity\", UserHandle.class, int.class)\n                    .call(user, density);\n            } catch (Exception e) {\n                throw new RuntimeException(e);\n            }\n        }\n\n        @TargetApi(Build.VERSION_CODES.LOLLIPOP)\n        @Override\n        public Drawable getUserBadgedDrawableForDensity(Drawable drawable, UserHandle user, Rect badgeLocation, int badgeDensity) {\n            return this.mHostPackageManager.getUserBadgedDrawableForDensity(drawable, user, badgeLocation, badgeDensity);\n        }\n\n        @TargetApi(Build.VERSION_CODES.LOLLIPOP)\n        @Override\n        public CharSequence getUserBadgedLabel(CharSequence label, UserHandle user) {\n            return this.mHostPackageManager.getUserBadgedLabel(label, user);\n        }\n\n        @Override\n        public CharSequence getText(String packageName, int resid, ApplicationInfo appInfo) {\n            LoadedPlugin plugin = mPluginManager.getLoadedPlugin(packageName);\n            if (null != plugin) {\n                return plugin.mResources.getText(resid);\n            }\n\n            return this.mHostPackageManager.getText(packageName, resid, appInfo);\n        }\n\n        @Override\n        public XmlResourceParser getXml(String packageName, int resid, ApplicationInfo appInfo) {\n            LoadedPlugin plugin = mPluginManager.getLoadedPlugin(packageName);\n            if (null != plugin) {\n                return plugin.mResources.getXml(resid);\n            }\n\n            return this.mHostPackageManager.getXml(packageName, resid, appInfo);\n        }\n\n        @Override\n        public CharSequence getApplicationLabel(ApplicationInfo info) {\n            LoadedPlugin plugin = mPluginManager.getLoadedPlugin(info.packageName);\n            if (null != plugin) {\n                try {\n                    return plugin.mResources.getText(info.labelRes);\n                } catch (Resources.NotFoundException e) {\n                    // ignored.\n                }\n            }\n\n            return this.mHostPackageManager.getApplicationLabel(info);\n        }\n\n        @Override\n        public Resources getResourcesForActivity(ComponentName component) throws NameNotFoundException {\n            LoadedPlugin plugin = mPluginManager.getLoadedPlugin(component);\n            if (null != plugin) {\n                return plugin.mResources;\n            }\n\n            return this.mHostPackageManager.getResourcesForActivity(component);\n        }\n\n        @Override\n        public Resources getResourcesForApplication(ApplicationInfo app) throws NameNotFoundException {\n            LoadedPlugin plugin = mPluginManager.getLoadedPlugin(app.packageName);\n            if (null != plugin) {\n                return plugin.mResources;\n            }\n\n            return this.mHostPackageManager.getResourcesForApplication(app);\n        }\n\n        @Override\n        public Resources getResourcesForApplication(String appPackageName) throws NameNotFoundException {\n            LoadedPlugin plugin = mPluginManager.getLoadedPlugin(appPackageName);\n            if (null != plugin) {\n                return plugin.mResources;\n            }\n\n            return this.mHostPackageManager.getResourcesForApplication(appPackageName);\n        }\n\n        @Override\n        public void verifyPendingInstall(int id, int verificationCode) {\n            this.mHostPackageManager.verifyPendingInstall(id, verificationCode);\n        }\n\n        @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)\n        @Override\n        public void extendVerificationTimeout(int id, int verificationCodeAtTimeout, long millisecondsToDelay) {\n            this.mHostPackageManager.extendVerificationTimeout(id, verificationCodeAtTimeout, millisecondsToDelay);\n        }\n\n        @Override\n        public void setInstallerPackageName(String targetPackage, String installerPackageName) {\n            LoadedPlugin plugin = mPluginManager.getLoadedPlugin(targetPackage);\n            if (null != plugin) {\n                return;\n            }\n\n            this.mHostPackageManager.setInstallerPackageName(targetPackage, installerPackageName);\n        }\n\n        @Override\n        public String getInstallerPackageName(String packageName) {\n            LoadedPlugin plugin = mPluginManager.getLoadedPlugin(packageName);\n            if (null != plugin) {\n                return mHostContext.getPackageName();\n            }\n\n            return this.mHostPackageManager.getInstallerPackageName(packageName);\n        }\n\n        @Override\n        public void addPackageToPreferred(String packageName) {\n            this.mHostPackageManager.addPackageToPreferred(packageName);\n        }\n\n        @Override\n        public void removePackageFromPreferred(String packageName) {\n            this.mHostPackageManager.removePackageFromPreferred(packageName);\n        }\n\n        @Override\n        public List<PackageInfo> getPreferredPackages(int flags) {\n            return this.mHostPackageManager.getPreferredPackages(flags);\n        }\n\n        @Override\n        public void addPreferredActivity(IntentFilter filter, int match, ComponentName[] set, ComponentName activity) {\n            this.mHostPackageManager.addPreferredActivity(filter, match, set, activity);\n        }\n\n        @Override\n        public void clearPackagePreferredActivities(String packageName) {\n            this.mHostPackageManager.clearPackagePreferredActivities(packageName);\n        }\n\n        @Override\n        public int getPreferredActivities(@NonNull List<IntentFilter> outFilters, @NonNull List<ComponentName> outActivities, String packageName) {\n            return this.mHostPackageManager.getPreferredActivities(outFilters, outActivities, packageName);\n        }\n\n        @Override\n        public void setComponentEnabledSetting(ComponentName component, int newState, int flags) {\n            this.mHostPackageManager.setComponentEnabledSetting(component, newState, flags);\n        }\n\n        @Override\n        public int getComponentEnabledSetting(ComponentName component) {\n            return this.mHostPackageManager.getComponentEnabledSetting(component);\n        }\n\n        @Override\n        public void setApplicationEnabledSetting(String packageName, int newState, int flags) {\n            this.mHostPackageManager.setApplicationEnabledSetting(packageName, newState, flags);\n        }\n\n        @Override\n        public int getApplicationEnabledSetting(String packageName) {\n            return this.mHostPackageManager.getApplicationEnabledSetting(packageName);\n        }\n\n        @Override\n        public boolean isSafeMode() {\n            return this.mHostPackageManager.isSafeMode();\n        }\n    \n        @TargetApi(Build.VERSION_CODES.O)\n        @Override\n        public void setApplicationCategoryHint(@NonNull String packageName, int categoryHint) {\n            this.mHostPackageManager.setApplicationCategoryHint(packageName, categoryHint);\n        }\n    \n        @TargetApi(Build.VERSION_CODES.LOLLIPOP)\n        @Override\n        public @NonNull PackageInstaller getPackageInstaller() {\n            return this.mHostPackageManager.getPackageInstaller();\n        }\n    \n        @TargetApi(Build.VERSION_CODES.O)\n        @Override\n        public boolean canRequestPackageInstalls() {\n            return this.mHostPackageManager.canRequestPackageInstalls();\n        }\n    \n        public Drawable loadItemIcon(PackageItemInfo itemInfo, ApplicationInfo appInfo) {\n            if (itemInfo == null) {\n                return null;\n            }\n            return itemInfo.loadIcon(this.mHostPackageManager);\n        }\n    }\n\n}\n"
  },
  {
    "path": "CoreLibrary/src/main/java/com/didi/virtualapk/internal/PluginContentResolver.java",
    "content": "/*\n * Copyright (C) 2017 Beijing Didi Infinity Technology and Development Co.,Ltd. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.didi.virtualapk.internal;\n\nimport android.annotation.TargetApi;\nimport android.content.ContentResolverWrapper;\nimport android.content.Context;\nimport android.content.IContentProvider;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.support.annotation.Keep;\n\nimport com.didi.virtualapk.PluginManager;\nimport com.didi.virtualapk.delegate.RemoteContentProvider;\n\n/**\n * Created by renyugang on 16/12/7.\n */\n\npublic class PluginContentResolver extends ContentResolverWrapper {\n    private PluginManager mPluginManager;\n\n    public PluginContentResolver(Context context) {\n        super(context);\n        mPluginManager = PluginManager.getInstance(context);\n    }\n    \n    @Override\n    protected IContentProvider acquireProvider(Context context, String auth) {\n        if (mPluginManager.resolveContentProvider(auth, 0) != null) {\n            return mPluginManager.getIContentProvider();\n        }\n        return super.acquireProvider(context, auth);\n    }\n\n    @Override\n    protected IContentProvider acquireExistingProvider(Context context, String auth) {\n        if (mPluginManager.resolveContentProvider(auth, 0) != null) {\n            return mPluginManager.getIContentProvider();\n        }\n        return super.acquireExistingProvider(context, auth);\n    }\n    \n    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)\n    @Override\n    protected IContentProvider acquireUnstableProvider(Context context, String auth) {\n        if (mPluginManager.resolveContentProvider(auth, 0) != null) {\n            return mPluginManager.getIContentProvider();\n        }\n        return super.acquireUnstableProvider(context, auth);\n    }\n\n    @Override\n    public boolean releaseProvider(IContentProvider provider) {\n        return true;\n    }\n    \n    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)\n    @Override\n    public boolean releaseUnstableProvider(IContentProvider icp) {\n        return true;\n    }\n    \n    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)\n    @Override\n    public void unstableProviderDied(IContentProvider icp) {\n    }\n\n    @TargetApi(Build.VERSION_CODES.KITKAT_WATCH)\n    @Override\n    public void appNotRespondingViaProvider(IContentProvider icp) {\n    }\n\n    protected int resolveUserIdFromAuthority(String auth) {\n        return 0;\n    }\n\n    @Keep\n    public static Uri wrapperUri(LoadedPlugin loadedPlugin, Uri pluginUri) {\n        String pkg = loadedPlugin.getPackageName();\n        String pluginUriString = Uri.encode(pluginUri.toString());\n        StringBuilder builder = new StringBuilder(RemoteContentProvider.getUri(loadedPlugin.getHostContext()));\n        builder.append(\"/?plugin=\" + loadedPlugin.getLocation());\n        builder.append(\"&pkg=\" + pkg);\n        builder.append(\"&uri=\" + pluginUriString);\n        Uri wrapperUri = Uri.parse(builder.toString());\n        return wrapperUri;\n    }\n\n    @Deprecated\n    public static String getAuthority(Context context) {\n        return RemoteContentProvider.getAuthority(context);\n    }\n\n    @Deprecated\n    public static String getUri(Context context) {\n        return RemoteContentProvider.getUri(context);\n    }\n\n    @Keep\n    public static Bundle getBundleForCall(Uri uri) {\n        Bundle bundle = new Bundle();\n        bundle.putString(RemoteContentProvider.KEY_WRAPPER_URI, uri.toString());\n        return bundle;\n    }\n\n}\n"
  },
  {
    "path": "CoreLibrary/src/main/java/com/didi/virtualapk/internal/PluginContext.java",
    "content": "/*\n * Copyright (C) 2017 Beijing Didi Infinity Technology and Development Co.,Ltd. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.didi.virtualapk.internal;\n\nimport android.content.ContentResolver;\nimport android.content.Context;\nimport android.content.ContextWrapper;\nimport android.content.Intent;\nimport android.content.pm.ApplicationInfo;\nimport android.content.pm.PackageManager;\nimport android.content.res.AssetManager;\nimport android.content.res.Resources;\n\n/**\n * Created by renyugang on 16/8/12.\n */\nclass PluginContext extends ContextWrapper {\n\n    private final LoadedPlugin mPlugin;\n\n    public PluginContext(LoadedPlugin plugin) {\n        super(plugin.getPluginManager().getHostContext());\n        this.mPlugin = plugin;\n    }\n    \n    public PluginContext(LoadedPlugin plugin, Context base) {\n        super(base);\n        this.mPlugin = plugin;\n    }\n\n    @Override\n    public Context getApplicationContext() {\n        return this.mPlugin.getApplication();\n    }\n\n//    @Override\n//    public ApplicationInfo getApplicationInfo() {\n//        return this.mPlugin.getApplicationInfo();\n//    }\n\n    private Context getHostContext() {\n        return getBaseContext();\n    }\n\n    @Override\n    public ContentResolver getContentResolver() {\n        return new PluginContentResolver(getHostContext());\n    }\n\n    @Override\n    public ClassLoader getClassLoader() {\n        return this.mPlugin.getClassLoader();\n    }\n\n//    @Override\n//    public String getPackageName() {\n//        return this.mPlugin.getPackageName();\n//    }\n\n//    @Override\n//    public String getPackageResourcePath() {\n//        return this.mPlugin.getPackageResourcePath();\n//    }\n\n//    @Override\n//    public String getPackageCodePath() {\n//        return this.mPlugin.getCodePath();\n//    }\n\n    @Override\n    public PackageManager getPackageManager() {\n        return this.mPlugin.getPackageManager();\n    }\n\n    @Override\n    public Object getSystemService(String name) {\n        // intercept CLIPBOARD_SERVICE,NOTIFICATION_SERVICE\n        if (name.equals(Context.CLIPBOARD_SERVICE)) {\n            return getHostContext().getSystemService(name);\n        } else if (name.equals(Context.NOTIFICATION_SERVICE)) {\n            return getHostContext().getSystemService(name);\n        }\n\n        return super.getSystemService(name);\n    }\n\n    @Override\n    public Resources getResources() {\n        return this.mPlugin.getResources();\n    }\n\n    @Override\n    public AssetManager getAssets() {\n        return this.mPlugin.getAssets();\n    }\n\n    @Override\n    public Resources.Theme getTheme() {\n        return this.mPlugin.getTheme();\n    }\n\n    @Override\n    public void startActivity(Intent intent) {\n        ComponentsHandler componentsHandler = mPlugin.getPluginManager().getComponentsHandler();\n        componentsHandler.transformIntentToExplicitAsNeeded(intent);\n        super.startActivity(intent);\n    }\n\n}\n"
  },
  {
    "path": "CoreLibrary/src/main/java/com/didi/virtualapk/internal/ResourcesManager.java",
    "content": "/*\n * Copyright (C) 2017 Beijing Didi Infinity Technology and Development Co.,Ltd. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.didi.virtualapk.internal;\n\nimport android.annotation.TargetApi;\nimport android.app.Activity;\nimport android.app.ActivityThread;\nimport android.app.LoadedApk;\nimport android.content.Context;\nimport android.content.pm.ApplicationInfo;\nimport android.content.res.AssetManager;\nimport android.content.res.Configuration;\nimport android.content.res.Resources;\nimport android.content.res.ResourcesImpl;\nimport android.content.res.ResourcesKey;\nimport android.os.Build;\nimport android.util.ArrayMap;\nimport android.util.DisplayMetrics;\nimport android.util.Log;\n\nimport com.didi.virtualapk.PluginManager;\nimport com.didi.virtualapk.utils.Reflector;\n\nimport java.io.File;\nimport java.lang.ref.WeakReference;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\n\n/**\n * Created by renyugang on 16/8/9.\n */\nclass ResourcesManager {\n    \n    public static final String TAG = Constants.TAG_PREFIX + \"LoadedPlugin\";\n\n    private static Configuration mDefaultConfiguration;\n    \n    public static synchronized Resources createResources(Context hostContext, String packageName, File apk) throws Exception {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n            return createResourcesForN(hostContext, packageName, apk);\n        }\n        \n        Resources resources = ResourcesManager.createResourcesSimple(hostContext, apk.getAbsolutePath());\n        ResourcesManager.hookResources(hostContext, resources);\n        return resources;\n    }\n    \n    private static Resources createResourcesSimple(Context hostContext, String apk) throws Exception {\n        Resources hostResources = hostContext.getResources();\n        Resources newResources = null;\n        AssetManager assetManager;\n        Reflector reflector = Reflector.on(AssetManager.class).method(\"addAssetPath\", String.class);\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {\n            assetManager = AssetManager.class.newInstance();\n            reflector.bind(assetManager);\n            final int cookie1 = reflector.call(hostContext.getApplicationInfo().sourceDir);;\n            if (cookie1 == 0) {\n                throw new RuntimeException(\"createResources failed, can't addAssetPath for \" + hostContext.getApplicationInfo().sourceDir);\n            }\n        } else {\n            assetManager = hostResources.getAssets();\n            reflector.bind(assetManager);\n        }\n        final int cookie2 = reflector.call(apk);\n        if (cookie2 == 0) {\n            throw new RuntimeException(\"createResources failed, can't addAssetPath for \" + apk);\n        }\n        List<LoadedPlugin> pluginList = PluginManager.getInstance(hostContext).getAllLoadedPlugins();\n        for (LoadedPlugin plugin : pluginList) {\n            final int cookie3 = reflector.call(plugin.getLocation());\n            if (cookie3 == 0) {\n                throw new RuntimeException(\"createResources failed, can't addAssetPath for \" + plugin.getLocation());\n            }\n        }\n        if (isMiUi(hostResources)) {\n            newResources = MiUiResourcesCompat.createResources(hostResources, assetManager);\n        } else if (isVivo(hostResources)) {\n            newResources = VivoResourcesCompat.createResources(hostContext, hostResources, assetManager);\n        } else if (isNubia(hostResources)) {\n            newResources = NubiaResourcesCompat.createResources(hostResources, assetManager);\n        } else if (isNotRawResources(hostResources)) {\n            newResources = AdaptationResourcesCompat.createResources(hostResources, assetManager);\n        } else {\n            // is raw android resources\n            newResources = new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());\n        }\n        // lastly, sync all LoadedPlugin to newResources\n        for (LoadedPlugin plugin : pluginList) {\n            plugin.updateResources(newResources);\n        }\n        \n        return newResources;\n    }\n\n    public static void hookResources(Context base, Resources resources) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n            return;\n        }\n        try {\n            Reflector reflector = Reflector.with(base);\n            reflector.field(\"mResources\").set(resources);\n            Object loadedApk = reflector.field(\"mPackageInfo\").get();\n            Reflector.with(loadedApk).field(\"mResources\").set(resources);\n\n            Object activityThread = ActivityThread.currentActivityThread();\n            Object resManager;\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {\n                resManager = android.app.ResourcesManager.getInstance();\n            } else {\n                resManager = Reflector.with(activityThread).field(\"mResourcesManager\").get();\n            }\n            Map<Object, WeakReference<Resources>> map = Reflector.with(resManager).field(\"mActiveResources\").get();\n            Object key = map.keySet().iterator().next();\n            map.put(key, new WeakReference<>(resources));\n        } catch (Exception e) {\n            Log.w(TAG, e);\n        }\n    }\n    \n    /**\n     * Use System Apis to update all existing resources.\n     * <br/>\n     * 1. Update ApplicationInfo.splitSourceDirs and LoadedApk.mSplitResDirs\n     * <br/>\n     * 2. Replace all keys of ResourcesManager.mResourceImpls to new ResourcesKey\n     * <br/>\n     * 3. Use ResourcesManager.appendLibAssetForMainAssetPath(appInfo.publicSourceDir, \"${packageName}.vastub\") to update all existing resources.\n     * <br/>\n     *\n     * see android.webkit.WebViewDelegate.addWebViewAssetPath(Context)\n     */\n    @TargetApi(Build.VERSION_CODES.N)\n    private static Resources createResourcesForN(Context context, String packageName, File apk) throws Exception {\n        long startTime = System.currentTimeMillis();\n        String newAssetPath = apk.getAbsolutePath();\n        ApplicationInfo info = context.getApplicationInfo();\n        String baseResDir = info.publicSourceDir;\n        \n        info.splitSourceDirs = append(info.splitSourceDirs, newAssetPath);\n        LoadedApk loadedApk = Reflector.with(context).field(\"mPackageInfo\").get();\n    \n        Reflector rLoadedApk = Reflector.with(loadedApk).field(\"mSplitResDirs\");\n        String[] splitResDirs = rLoadedApk.get();\n        rLoadedApk.set(append(splitResDirs, newAssetPath));\n    \n        final android.app.ResourcesManager resourcesManager = android.app.ResourcesManager.getInstance();\n        ArrayMap<ResourcesKey, WeakReference<ResourcesImpl>> originalMap = Reflector.with(resourcesManager).field(\"mResourceImpls\").get();\n    \n        synchronized (resourcesManager) {\n            HashMap<ResourcesKey, WeakReference<ResourcesImpl>> resolvedMap = new HashMap<>();\n    \n            if (Build.VERSION.SDK_INT >= 28\n                || (Build.VERSION.SDK_INT == 27 && Build.VERSION.PREVIEW_SDK_INT != 0)) { // P Preview\n                ResourcesManagerCompatForP.resolveResourcesImplMap(originalMap, resolvedMap, context, loadedApk);\n\n            } else {\n                ResourcesManagerCompatForN.resolveResourcesImplMap(originalMap, resolvedMap, baseResDir, newAssetPath);\n            }\n    \n            originalMap.clear();\n            originalMap.putAll(resolvedMap);\n        }\n    \n        android.app.ResourcesManager.getInstance().appendLibAssetForMainAssetPath(baseResDir, packageName + \".vastub\");\n    \n        Resources newResources = context.getResources();\n    \n        // lastly, sync all LoadedPlugin to newResources\n        for (LoadedPlugin plugin : PluginManager.getInstance(context).getAllLoadedPlugins()) {\n            plugin.updateResources(newResources);\n        }\n    \n        Log.d(TAG, \"createResourcesForN cost time: +\" + (System.currentTimeMillis() - startTime) + \"ms\");\n        return newResources;\n    }\n    \n    private static String[] append(String[] paths, String newPath) {\n        if (contains(paths, newPath)) {\n            return paths;\n        }\n        \n        final int newPathsCount = 1 + (paths != null ? paths.length : 0);\n        final String[] newPaths = new String[newPathsCount];\n        if (paths != null) {\n            System.arraycopy(paths, 0, newPaths, 0, paths.length);\n        }\n        newPaths[newPathsCount - 1] = newPath;\n        return newPaths;\n    }\n    \n    @TargetApi(Build.VERSION_CODES.KITKAT)\n    private static boolean contains(String[] array, String value) {\n        if (array == null) {\n            return false;\n        }\n        for (int i = 0; i < array.length; i++) {\n            if (Objects.equals(array[i], value)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    private static boolean isMiUi(Resources resources) {\n        return resources.getClass().getName().equals(\"android.content.res.MiuiResources\");\n    }\n\n    private static boolean isVivo(Resources resources) {\n        return resources.getClass().getName().equals(\"android.content.res.VivoResources\");\n    }\n\n    private static boolean isNubia(Resources resources) {\n        return resources.getClass().getName().equals(\"android.content.res.NubiaResources\");\n    }\n\n    private static boolean isNotRawResources(Resources resources) {\n        return !resources.getClass().getName().equals(\"android.content.res.Resources\");\n    }\n\n    private static final class MiUiResourcesCompat {\n        private static Resources createResources(Resources hostResources, AssetManager assetManager) throws Exception {\n            Reflector reflector = Reflector.on(\"android.content.res.MiuiResources\");\n            Resources newResources = reflector.constructor(AssetManager.class, DisplayMetrics.class, Configuration.class)\n                .newInstance(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());\n            return newResources;\n        }\n    }\n\n    private static final class VivoResourcesCompat {\n        private static Resources createResources(Context hostContext, Resources hostResources, AssetManager assetManager) throws Exception {\n            Reflector reflector = Reflector.on(\"android.content.res.VivoResources\");\n            Resources newResources = reflector.constructor(AssetManager.class, DisplayMetrics.class, Configuration.class)\n                .newInstance(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());\n            reflector.method(\"init\", String.class).callByCaller(newResources, hostContext.getPackageName());\n            reflector.field(\"mThemeValues\");\n            reflector.set(newResources, reflector.get(hostResources));\n            return newResources;\n        }\n    }\n\n    private static final class NubiaResourcesCompat {\n        private static Resources createResources(Resources hostResources, AssetManager assetManager) throws Exception {\n            Reflector reflector = Reflector.on(\"android.content.res.NubiaResources\");\n            Resources newResources = reflector.constructor(AssetManager.class, DisplayMetrics.class, Configuration.class)\n                .newInstance(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());\n            return newResources;\n        }\n    }\n\n    private static final class AdaptationResourcesCompat {\n        private static Resources createResources(Resources hostResources, AssetManager assetManager) throws Exception {\n            Resources newResources;\n            try {\n                Reflector reflector = Reflector.with(hostResources);\n                newResources = reflector.constructor(AssetManager.class, DisplayMetrics.class, Configuration.class)\n                    .newInstance(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());\n            } catch (Exception e) {\n                newResources = new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());\n            }\n\n            return newResources;\n        }\n    }\n\n    private static final class ResourcesManagerCompatForN {\n        \n        @TargetApi(Build.VERSION_CODES.KITKAT)\n        public static void resolveResourcesImplMap(Map<ResourcesKey, WeakReference<ResourcesImpl>> originalMap, Map<ResourcesKey, WeakReference<ResourcesImpl>> resolvedMap, String baseResDir, String newAssetPath) throws Exception {\n            for (Map.Entry<ResourcesKey, WeakReference<ResourcesImpl>> entry : originalMap.entrySet()) {\n                ResourcesKey key = entry.getKey();\n                if (Objects.equals(key.mResDir, baseResDir)) {\n                    resolvedMap.put(new ResourcesKey(key.mResDir,\n                        append(key.mSplitResDirs, newAssetPath),\n                        key.mOverlayDirs,\n                        key.mLibDirs,\n                        key.mDisplayId,\n                        key.mOverrideConfiguration,\n                        key.mCompatInfo), entry.getValue());\n                } else {\n                    resolvedMap.put(key, entry.getValue());\n                }\n            }\n        }\n    }\n    \n    private static final class ResourcesManagerCompatForP {\n        \n        @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)\n        public static void resolveResourcesImplMap(Map<ResourcesKey, WeakReference<ResourcesImpl>> originalMap, Map<ResourcesKey, WeakReference<ResourcesImpl>> resolvedMap, Context context, LoadedApk loadedApk) throws Exception {\n            HashMap<ResourcesImpl, Context> newResImplMap = new HashMap<>();\n            Map<ResourcesImpl, ResourcesKey> resKeyMap = new HashMap<>();\n            Resources newRes;\n        \n            // Recreate the resImpl of the context\n        \n            // See LoadedApk.getResources()\n            if (mDefaultConfiguration == null) {\n                mDefaultConfiguration = new Configuration();\n            }\n            newRes = context.createConfigurationContext(mDefaultConfiguration).getResources();\n            newResImplMap.put(newRes.getImpl(), context);\n        \n            // Recreate the ResImpl of the activity\n            for (WeakReference<Activity> ref : PluginManager.getInstance(context).getInstrumentation().getActivities()) {\n                Activity activity = ref.get();\n                if (activity != null) {\n                    newRes = activity.createConfigurationContext(activity.getResources().getConfiguration()).getResources();\n                    newResImplMap.put(newRes.getImpl(), activity);\n                }\n            }\n        \n            // Mapping all resKey and resImpl\n            for (Map.Entry<ResourcesKey, WeakReference<ResourcesImpl>> entry : originalMap.entrySet()) {\n                ResourcesImpl resImpl = entry.getValue().get();\n                if (resImpl != null) {\n                    resKeyMap.put(resImpl, entry.getKey());\n                }\n                resolvedMap.put(entry.getKey(), entry.getValue());\n            }\n        \n            // Replace the resImpl to the new resKey and remove the origin resKey\n            for (Map.Entry<ResourcesImpl, Context> entry : newResImplMap.entrySet()) {\n                ResourcesKey newKey = resKeyMap.get(entry.getKey());\n                ResourcesImpl originResImpl = entry.getValue().getResources().getImpl();\n            \n                resolvedMap.put(newKey, new WeakReference<>(originResImpl));\n                resolvedMap.remove(resKeyMap.get(originResImpl));\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "CoreLibrary/src/main/java/com/didi/virtualapk/internal/StubActivityInfo.java",
    "content": "/*\n * Copyright (C) 2017 Beijing Didi Infinity Technology and Development Co.,Ltd. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.didi.virtualapk.internal;\n\nimport android.content.pm.ActivityInfo;\nimport android.content.res.TypedArray;\nimport android.util.Log;\nimport android.content.res.Resources.Theme;\n\nimport java.util.HashMap;\n\n/**\n * Created by renyugang on 16/8/15.\n */\nclass StubActivityInfo {\n    public static final int MAX_COUNT_STANDARD = 1;\n    public static final int MAX_COUNT_SINGLETOP = 8;\n    public static final int MAX_COUNT_SINGLETASK = 8;\n    public static final int MAX_COUNT_SINGLEINSTANCE = 8;\n\n    public static final String corePackage = \"com.didi.virtualapk.core\";\n    public static final String STUB_ACTIVITY_STANDARD = \"%s.A$%d\";\n    public static final String STUB_ACTIVITY_SINGLETOP = \"%s.B$%d\";\n    public static final String STUB_ACTIVITY_SINGLETASK = \"%s.C$%d\";\n    public static final String STUB_ACTIVITY_SINGLEINSTANCE = \"%s.D$%d\";\n\n    public final int usedStandardStubActivity = 1;\n    public int usedSingleTopStubActivity = 0;\n    public int usedSingleTaskStubActivity = 0;\n    public int usedSingleInstanceStubActivity = 0;\n\n    private HashMap<String, String> mCachedStubActivity = new HashMap<>();\n\n    public String getStubActivity(String className, int launchMode, Theme theme) {\n        String stubActivity= mCachedStubActivity.get(className);\n        if (stubActivity != null) {\n            return stubActivity;\n        }\n\n        TypedArray array = theme.obtainStyledAttributes(new int[]{\n                android.R.attr.windowIsTranslucent,\n                android.R.attr.windowBackground\n        });\n        boolean windowIsTranslucent = array.getBoolean(0, false);\n        array.recycle();\n        if (Constants.DEBUG) {\n            Log.d(Constants.TAG_PREFIX + \"StubActivityInfo\", \"getStubActivity, is transparent theme ? \" + windowIsTranslucent);\n        }\n        stubActivity = String.format(STUB_ACTIVITY_STANDARD, corePackage, usedStandardStubActivity);\n        switch (launchMode) {\n            case ActivityInfo.LAUNCH_MULTIPLE: {\n                stubActivity = String.format(STUB_ACTIVITY_STANDARD, corePackage, usedStandardStubActivity);\n                if (windowIsTranslucent) {\n                    stubActivity = String.format(STUB_ACTIVITY_STANDARD, corePackage, 2);\n                }\n                break;\n            }\n            case ActivityInfo.LAUNCH_SINGLE_TOP: {\n                usedSingleTopStubActivity = usedSingleTopStubActivity % MAX_COUNT_SINGLETOP + 1;\n                stubActivity = String.format(STUB_ACTIVITY_SINGLETOP, corePackage, usedSingleTopStubActivity);\n                break;\n            }\n            case ActivityInfo.LAUNCH_SINGLE_TASK: {\n                usedSingleTaskStubActivity = usedSingleTaskStubActivity % MAX_COUNT_SINGLETASK + 1;\n                stubActivity = String.format(STUB_ACTIVITY_SINGLETASK, corePackage, usedSingleTaskStubActivity);\n                break;\n            }\n            case ActivityInfo.LAUNCH_SINGLE_INSTANCE: {\n                usedSingleInstanceStubActivity = usedSingleInstanceStubActivity % MAX_COUNT_SINGLEINSTANCE + 1;\n                stubActivity = String.format(STUB_ACTIVITY_SINGLEINSTANCE, corePackage, usedSingleInstanceStubActivity);\n                break;\n            }\n\n            default:break;\n        }\n\n        mCachedStubActivity.put(className, stubActivity);\n        return stubActivity;\n    }\n\n}\n"
  },
  {
    "path": "CoreLibrary/src/main/java/com/didi/virtualapk/internal/VAInstrumentation.java",
    "content": "/*\n * Copyright (C) 2017 Beijing Didi Infinity Technology and Development Co.,Ltd. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.didi.virtualapk.internal;\n\nimport android.annotation.TargetApi;\nimport android.app.Activity;\nimport android.app.Application;\nimport android.app.Fragment;\nimport android.app.Instrumentation;\nimport android.content.ActivityNotFoundException;\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.pm.ActivityInfo;\nimport android.content.pm.ApplicationInfo;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.os.IBinder;\nimport android.os.Message;\nimport android.os.PersistableBundle;\nimport android.util.Log;\n\nimport com.didi.virtualapk.PluginManager;\nimport com.didi.virtualapk.delegate.StubActivity;\nimport com.didi.virtualapk.internal.utils.PluginUtil;\nimport com.didi.virtualapk.utils.Reflector;\n\nimport java.lang.ref.WeakReference;\nimport java.util.ArrayList;\nimport java.util.List;\n\n\n/**\n * Created by renyugang on 16/8/10.\n */\npublic class VAInstrumentation extends Instrumentation implements Handler.Callback {\n    public static final String TAG = Constants.TAG_PREFIX + \"VAInstrumentation\";\n    public static final int LAUNCH_ACTIVITY         = 100;\n\n    protected Instrumentation mBase;\n    \n    protected final ArrayList<WeakReference<Activity>> mActivities = new ArrayList<>();\n\n    protected PluginManager mPluginManager;\n\n    public VAInstrumentation(PluginManager pluginManager, Instrumentation base) {\n        this.mPluginManager = pluginManager;\n        this.mBase = base;\n    }\n\n    @Override\n    public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode) {\n        injectIntent(intent);\n        return mBase.execStartActivity(who, contextThread, token, target, intent, requestCode);\n    }\n\n    @Override\n    public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) {\n        injectIntent(intent);\n        return mBase.execStartActivity(who, contextThread, token, target, intent, requestCode, options);\n    }\n\n    @Override\n    public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Fragment target, Intent intent, int requestCode, Bundle options) {\n        injectIntent(intent);\n        return mBase.execStartActivity(who, contextThread, token, target, intent, requestCode, options);\n    }\n\n    @Override\n    public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, String target, Intent intent, int requestCode, Bundle options) {\n        injectIntent(intent);\n        return mBase.execStartActivity(who, contextThread, token, target, intent, requestCode, options);\n    }\n    \n    protected void injectIntent(Intent intent) {\n        mPluginManager.getComponentsHandler().transformIntentToExplicitAsNeeded(intent);\n        // null component is an implicitly intent\n        if (intent.getComponent() != null) {\n            Log.i(TAG, String.format(\"execStartActivity[%s : %s]\", intent.getComponent().getPackageName(), intent.getComponent().getClassName()));\n            // resolve intent with Stub Activity if needed\n            this.mPluginManager.getComponentsHandler().markIntentIfNeeded(intent);\n        }\n    }\n\n    @Override\n    public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {\n        try {\n            cl.loadClass(className);\n            Log.i(TAG, String.format(\"newActivity[%s]\", className));\n            \n        } catch (ClassNotFoundException e) {\n            ComponentName component = PluginUtil.getComponent(intent);\n            \n            if (component == null) {\n                return newActivity(mBase.newActivity(cl, className, intent));\n            }\n    \n            String targetClassName = component.getClassName();\n            Log.i(TAG, String.format(\"newActivity[%s : %s/%s]\", className, component.getPackageName(), targetClassName));\n    \n            LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(component);\n    \n            if (plugin == null) {\n                // Not found then goto stub activity.\n                boolean debuggable = false;\n                try {\n                    Context context = this.mPluginManager.getHostContext();\n                    debuggable = (context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;\n                } catch (Throwable ex) {\n        \n                }\n    \n                if (debuggable) {\n                    throw new ActivityNotFoundException(\"error intent: \" + intent.toURI());\n                }\n                \n                Log.i(TAG, \"Not found. starting the stub activity: \" + StubActivity.class);\n                return newActivity(mBase.newActivity(cl, StubActivity.class.getName(), intent));\n            }\n            \n            Activity activity = mBase.newActivity(plugin.getClassLoader(), targetClassName, intent);\n            activity.setIntent(intent);\n    \n            // for 4.1+\n            Reflector.QuietReflector.with(activity).field(\"mResources\").set(plugin.getResources());\n    \n            return newActivity(activity);\n        }\n\n        return newActivity(mBase.newActivity(cl, className, intent));\n    }\n    \n    @Override\n    public Application newApplication(ClassLoader cl, String className, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException {\n        return mBase.newApplication(cl, className, context);\n    }\n\n    @Override\n    public void callActivityOnCreate(Activity activity, Bundle icicle) {\n        injectActivity(activity);\n        mBase.callActivityOnCreate(activity, icicle);\n    }\n    \n    @TargetApi(Build.VERSION_CODES.LOLLIPOP)\n    @Override\n    public void callActivityOnCreate(Activity activity, Bundle icicle, PersistableBundle persistentState) {\n        injectActivity(activity);\n        mBase.callActivityOnCreate(activity, icicle, persistentState);\n    }\n    \n    protected void injectActivity(Activity activity) {\n        final Intent intent = activity.getIntent();\n        if (PluginUtil.isIntentFromPlugin(intent)) {\n            Context base = activity.getBaseContext();\n            try {\n                LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(intent);\n                Reflector.with(base).field(\"mResources\").set(plugin.getResources());\n                Reflector reflector = Reflector.with(activity);\n                reflector.field(\"mBase\").set(plugin.createPluginContext(activity.getBaseContext()));\n                reflector.field(\"mApplication\").set(plugin.getApplication());\n\n                // set screenOrientation\n                ActivityInfo activityInfo = plugin.getActivityInfo(PluginUtil.getComponent(intent));\n                if (activityInfo.screenOrientation != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {\n                    activity.setRequestedOrientation(activityInfo.screenOrientation);\n                }\n    \n                // for native activity\n                ComponentName component = PluginUtil.getComponent(intent);\n                Intent wrapperIntent = new Intent(intent);\n                wrapperIntent.setClassName(component.getPackageName(), component.getClassName());\n                activity.setIntent(wrapperIntent);\n                \n            } catch (Exception e) {\n                Log.w(TAG, e);\n            }\n        }\n    }\n\n    @Override\n    public boolean handleMessage(Message msg) {\n        if (msg.what == LAUNCH_ACTIVITY) {\n            // ActivityClientRecord r\n            Object r = msg.obj;\n            try {\n                Reflector reflector = Reflector.with(r);\n                Intent intent = reflector.field(\"intent\").get();\n                intent.setExtrasClassLoader(mPluginManager.getHostContext().getClassLoader());\n                ActivityInfo activityInfo = reflector.field(\"activityInfo\").get();\n\n                if (PluginUtil.isIntentFromPlugin(intent)) {\n                    int theme = PluginUtil.getTheme(mPluginManager.getHostContext(), intent);\n                    if (theme != 0) {\n                        Log.i(TAG, \"resolve theme, current theme:\" + activityInfo.theme + \"  after :0x\" + Integer.toHexString(theme));\n                        activityInfo.theme = theme;\n                    }\n                }\n            } catch (Exception e) {\n                Log.w(TAG, e);\n            }\n        }\n\n        return false;\n    }\n\n    @Override\n    public Context getContext() {\n        return mBase.getContext();\n    }\n\n    @Override\n    public Context getTargetContext() {\n        return mBase.getTargetContext();\n    }\n\n    @Override\n    public ComponentName getComponentName() {\n        return mBase.getComponentName();\n    }\n\n    protected Activity newActivity(Activity activity) {\n        synchronized (mActivities) {\n            for (int i = mActivities.size() - 1; i >= 0; i--) {\n                if (mActivities.get(i).get() == null) {\n                    mActivities.remove(i);\n                }\n            }\n            mActivities.add(new WeakReference<>(activity));\n        }\n        return activity;\n    }\n\n    List<WeakReference<Activity>> getActivities() {\n        synchronized (mActivities) {\n            return new ArrayList<>(mActivities);\n        }\n    }\n}\n"
  },
  {
    "path": "CoreLibrary/src/main/java/com/didi/virtualapk/internal/utils/DexUtil.java",
    "content": "/*\n * Copyright (C) 2017 Beijing Didi Infinity Technology and Development Co.,Ltd. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.didi.virtualapk.internal.utils;\n\nimport android.app.ActivityThread;\nimport android.content.Context;\nimport android.os.Build;\n\nimport com.didi.virtualapk.internal.Constants;\nimport com.didi.virtualapk.utils.Reflector;\n\nimport java.io.File;\nimport java.lang.reflect.Array;\nimport java.lang.reflect.Field;\nimport java.util.List;\n\nimport dalvik.system.DexClassLoader;\n\npublic class DexUtil {\n    private static boolean sHasInsertedNativeLibrary = false;\n\n    public static void insertDex(DexClassLoader dexClassLoader, ClassLoader baseClassLoader, File nativeLibsDir) throws Exception {\n        Object baseDexElements = getDexElements(getPathList(baseClassLoader));\n        Object newDexElements = getDexElements(getPathList(dexClassLoader));\n        Object allDexElements = combineArray(baseDexElements, newDexElements);\n        Object pathList = getPathList(baseClassLoader);\n        Reflector.with(pathList).field(\"dexElements\").set(allDexElements);\n\n        insertNativeLibrary(dexClassLoader, baseClassLoader, nativeLibsDir);\n    }\n\n    private static Object getDexElements(Object pathList) throws Exception {\n        return Reflector.with(pathList).field(\"dexElements\").get();\n    }\n\n    private static Object getPathList(ClassLoader baseDexClassLoader) throws Exception {\n        return Reflector.with(baseDexClassLoader).field(\"pathList\").get();\n    }\n\n    private static Object combineArray(Object firstArray, Object secondArray) {\n        Class<?> localClass = firstArray.getClass().getComponentType();\n        int firstArrayLength = Array.getLength(firstArray);\n        int secondArrayLength = Array.getLength(secondArray);\n        Object result = Array.newInstance(localClass, firstArrayLength + secondArrayLength);\n        System.arraycopy(firstArray, 0, result, 0, firstArrayLength);\n        System.arraycopy(secondArray, 0, result, firstArrayLength, secondArrayLength);\n        return result;\n    }\n\n    private static synchronized void insertNativeLibrary(DexClassLoader dexClassLoader, ClassLoader baseClassLoader, File nativeLibsDir) throws Exception {\n        if (sHasInsertedNativeLibrary) {\n            return;\n        }\n        sHasInsertedNativeLibrary = true;\n\n        Context context = ActivityThread.currentApplication();\n        Object basePathList = getPathList(baseClassLoader);\n        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1) {\n            Reflector reflector = Reflector.with(basePathList);\n            List<File> nativeLibraryDirectories = reflector.field(\"nativeLibraryDirectories\").get();\n            nativeLibraryDirectories.add(nativeLibsDir);\n\n            Object baseNativeLibraryPathElements = reflector.field(\"nativeLibraryPathElements\").get();\n            final int baseArrayLength = Array.getLength(baseNativeLibraryPathElements);\n\n            Object newPathList = getPathList(dexClassLoader);\n            Object newNativeLibraryPathElements = reflector.get(newPathList);\n            Class<?> elementClass = newNativeLibraryPathElements.getClass().getComponentType();\n            Object allNativeLibraryPathElements = Array.newInstance(elementClass, baseArrayLength + 1);\n            System.arraycopy(baseNativeLibraryPathElements, 0, allNativeLibraryPathElements, 0, baseArrayLength);\n\n            Field soPathField;\n            if (Build.VERSION.SDK_INT >= 26) {\n                soPathField = elementClass.getDeclaredField(\"path\");\n            } else {\n                soPathField = elementClass.getDeclaredField(\"dir\");\n            }\n            soPathField.setAccessible(true);\n            final int newArrayLength = Array.getLength(newNativeLibraryPathElements);\n            for (int i = 0; i < newArrayLength; i++) {\n                Object element = Array.get(newNativeLibraryPathElements, i);\n                String dir = ((File)soPathField.get(element)).getAbsolutePath();\n                if (dir.contains(Constants.NATIVE_DIR)) {\n                    Array.set(allNativeLibraryPathElements, baseArrayLength, element);\n                    break;\n                }\n            }\n\n            reflector.set(allNativeLibraryPathElements);\n        } else {\n            Reflector reflector = Reflector.with(basePathList).field(\"nativeLibraryDirectories\");\n            File[] nativeLibraryDirectories = reflector.get();\n            final int N = nativeLibraryDirectories.length;\n            File[] newNativeLibraryDirectories = new File[N + 1];\n            System.arraycopy(nativeLibraryDirectories, 0, newNativeLibraryDirectories, 0, N);\n            newNativeLibraryDirectories[N] = nativeLibsDir;\n            reflector.set(newNativeLibraryDirectories);\n        }\n    }\n\n}"
  },
  {
    "path": "CoreLibrary/src/main/java/com/didi/virtualapk/internal/utils/PackageParserCompat.java",
    "content": "/*\n * Copyright (C) 2017 Beijing Didi Infinity Technology and Development Co.,Ltd. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.didi.virtualapk.internal.utils;\n\nimport android.content.Context;\nimport android.content.pm.PackageParser;\nimport android.os.Build;\n\nimport com.didi.virtualapk.utils.Reflector;\n\nimport java.io.File;\n\n/**\n * @author johnsonlee\n */\npublic final class PackageParserCompat {\n\n    public static final PackageParser.Package parsePackage(final Context context, final File apk, final int flags) {\n        try {\n            if (Build.VERSION.SDK_INT >= 28\n                || (Build.VERSION.SDK_INT == 27 && Build.VERSION.PREVIEW_SDK_INT != 0)) { // Android P Preview\n                return PackageParserPPreview.parsePackage(context, apk, flags);\n            } else if (Build.VERSION.SDK_INT >= 24) {\n                return PackageParserV24.parsePackage(context, apk, flags);\n            } else if (Build.VERSION.SDK_INT >= 21) {\n                return PackageParserLollipop.parsePackage(context, apk, flags);\n            } else {\n                return PackageParserLegacy.parsePackage(context, apk, flags);\n            }\n    \n        } catch (Throwable e) {\n            throw new RuntimeException(\"error\", e);\n        }\n    }\n\n    private static final class PackageParserPPreview {\n\n        static final PackageParser.Package parsePackage(Context context, File apk, int flags) throws Throwable {\n            PackageParser parser = new PackageParser();\n            PackageParser.Package pkg = parser.parsePackage(apk, flags);\n            Reflector.with(parser)\n                .method(\"collectCertificates\", PackageParser.Package.class, boolean.class)\n                .call(pkg, false);\n            return pkg;\n        }\n    }\n\n    private static final class PackageParserV24 {\n\n        static final PackageParser.Package parsePackage(Context context, File apk, int flags) throws Throwable {\n            PackageParser parser = new PackageParser();\n            PackageParser.Package pkg = parser.parsePackage(apk, flags);\n            Reflector.with(parser)\n                .method(\"collectCertificates\", PackageParser.Package.class, int.class)\n                .call(pkg, flags);\n            return pkg;\n        }\n    }\n\n    private static final class PackageParserLollipop {\n\n        static final PackageParser.Package parsePackage(final Context context, final File apk, final int flags) throws Throwable {\n            PackageParser parser = new PackageParser();\n            PackageParser.Package pkg = parser.parsePackage(apk, flags);\n            parser.collectCertificates(pkg, flags);\n            return pkg;\n        }\n\n    }\n\n    private static final class PackageParserLegacy {\n\n        static final PackageParser.Package parsePackage(Context context, File apk, int flags) throws Throwable {\n            PackageParser parser = new PackageParser(apk.getAbsolutePath());\n            PackageParser.Package pkg = parser.parsePackage(apk, apk.getAbsolutePath(), context.getResources().getDisplayMetrics(), flags);\n            Reflector.with(parser)\n                .method(\"collectCertificates\", PackageParser.Package.class, int.class)\n                .call(pkg, flags);\n            return pkg;\n        }\n\n    }\n\n}"
  },
  {
    "path": "CoreLibrary/src/main/java/com/didi/virtualapk/internal/utils/PluginUtil.java",
    "content": "/*\n * Copyright (C) 2017 Beijing Didi Infinity Technology and Development Co.,Ltd. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.didi.virtualapk.internal.utils;\n\nimport android.app.Activity;\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.pm.ActivityInfo;\nimport android.content.pm.ApplicationInfo;\nimport android.content.pm.PackageInfo;\nimport android.content.pm.ServiceInfo;\nimport android.content.res.Resources;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.os.IBinder;\nimport android.text.TextUtils;\nimport android.util.Log;\n\nimport com.didi.virtualapk.PluginManager;\nimport com.didi.virtualapk.internal.Constants;\nimport com.didi.virtualapk.internal.LoadedPlugin;\nimport com.didi.virtualapk.utils.Reflector;\n\nimport java.io.BufferedInputStream;\nimport java.io.BufferedOutputStream;\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.util.Enumeration;\nimport java.util.zip.ZipEntry;\nimport java.util.zip.ZipFile;\n\n/**\n * Created by renyugang on 16/8/15.\n */\npublic class PluginUtil {\n    \n    public static final String TAG = Constants.TAG_PREFIX + \"NativeLib\";\n    \n    public static ComponentName getComponent(Intent intent) {\n        if (intent == null) {\n            return null;\n        }\n        if (isIntentFromPlugin(intent)) {\n            return new ComponentName(intent.getStringExtra(Constants.KEY_TARGET_PACKAGE),\n                intent.getStringExtra(Constants.KEY_TARGET_ACTIVITY));\n        }\n        \n        return intent.getComponent();\n    }\n\n    public static boolean isIntentFromPlugin(Intent intent) {\n        if (intent == null) {\n            return false;\n        }\n        return intent.getBooleanExtra(Constants.KEY_IS_PLUGIN, false);\n    }\n\n    public static int getTheme(Context context, Intent intent) {\n        return PluginUtil.getTheme(context, PluginUtil.getComponent(intent));\n    }\n\n    public static int getTheme(Context context, ComponentName component) {\n        LoadedPlugin loadedPlugin = PluginManager.getInstance(context).getLoadedPlugin(component);\n\n        if (null == loadedPlugin) {\n            return 0;\n        }\n\n        ActivityInfo info = loadedPlugin.getActivityInfo(component);\n        if (null == info) {\n            return 0;\n        }\n\n        if (0 != info.theme) {\n            return info.theme;\n        }\n\n        ApplicationInfo appInfo = info.applicationInfo;\n        if (null != appInfo && appInfo.theme != 0) {\n            return appInfo.theme;\n        }\n\n        return selectDefaultTheme(0, Build.VERSION.SDK_INT);\n    }\n\n    public static int selectDefaultTheme(final int curTheme, final int targetSdkVersion) {\n        return selectSystemTheme(curTheme, targetSdkVersion,\n                android.R.style.Theme,\n                android.R.style.Theme_Holo,\n                android.R.style.Theme_DeviceDefault,\n                android.R.style.Theme_DeviceDefault_Light_DarkActionBar);\n    }\n\n    public static int selectSystemTheme(final int curTheme, final int targetSdkVersion, final int orig, final int holo, final int dark, final int deviceDefault) {\n        if (curTheme != 0) {\n            return curTheme;\n        }\n\n        if (targetSdkVersion < 11 /* Build.VERSION_CODES.HONEYCOMB */) {\n            return orig;\n        }\n\n        if (targetSdkVersion < 14 /* Build.VERSION_CODES.ICE_CREAM_SANDWICH */) {\n            return holo;\n        }\n\n        if (targetSdkVersion < 24 /* Build.VERSION_CODES.N */) {\n            return dark;\n        }\n\n        return deviceDefault;\n    }\n\n    public static void hookActivityResources(Activity activity, String packageName) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && isVivo(activity.getResources())) {\n            // for 5.0+ vivo\n            return;\n        }\n\n        // designed for 5.0 - only, but some bad phones not work, eg:letv\n        try {\n            Context base = activity.getBaseContext();\n            final LoadedPlugin plugin = PluginManager.getInstance(activity).getLoadedPlugin(packageName);\n            final Resources resources = plugin.getResources();\n            if (resources != null) {\n                Reflector.with(base).field(\"mResources\").set(resources);\n\n                // copy theme\n                Resources.Theme theme = resources.newTheme();\n                theme.setTo(activity.getTheme());\n                Reflector reflector = Reflector.with(activity);\n                int themeResource = reflector.field(\"mThemeResource\").get();\n                theme.applyStyle(themeResource, true);\n                reflector.field(\"mTheme\").set(theme);\n\n                reflector.field(\"mResources\").set(resources);\n            }\n        } catch (Exception e) {\n            Log.w(Constants.TAG, e);\n        }\n    }\n\n    public static final boolean isLocalService(final ServiceInfo serviceInfo) {\n        return TextUtils.isEmpty(serviceInfo.processName) || serviceInfo.applicationInfo.packageName.equals(serviceInfo.processName);\n    }\n\n    public static boolean isVivo(Resources resources) {\n        return resources.getClass().getName().equals(\"android.content.res.VivoResources\");\n    }\n\n    public static void putBinder(Bundle bundle, String key, IBinder value) {\n        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {\n            bundle.putBinder(key, value);\n        } else {\n            Reflector.QuietReflector.with(bundle).method(\"putIBinder\", String.class, IBinder.class).call(key, value);\n        }\n    }\n\n    public static IBinder getBinder(Bundle bundle, String key) {\n        if (bundle == null) {\n            return null;\n        }\n        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {\n            return bundle.getBinder(key);\n        } else {\n            return (IBinder) Reflector.QuietReflector.with(bundle)\n                .method(\"getIBinder\", String.class).call(key);\n        }\n    }\n    \n    public static void copyNativeLib(File apk, Context context, PackageInfo packageInfo, File nativeLibDir) throws Exception {\n        long startTime = System.currentTimeMillis();\n        ZipFile zipfile = new ZipFile(apk.getAbsolutePath());\n    \n        try {\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n                for (String cpuArch : Build.SUPPORTED_ABIS) {\n                    if (findAndCopyNativeLib(zipfile, context, cpuArch, packageInfo, nativeLibDir)) {\n                        return;\n                    }\n                }\n                \n            } else {\n                if (findAndCopyNativeLib(zipfile, context, Build.CPU_ABI, packageInfo, nativeLibDir)) {\n                    return;\n                }\n            }\n            \n            findAndCopyNativeLib(zipfile, context, \"armeabi\", packageInfo, nativeLibDir);\n    \n        } finally {\n            zipfile.close();\n            Log.d(TAG, \"Done! +\" + (System.currentTimeMillis() - startTime) + \"ms\");\n        }\n    }\n    \n    private static boolean findAndCopyNativeLib(ZipFile zipfile, Context context, String cpuArch, PackageInfo packageInfo, File nativeLibDir) throws Exception {\n        Log.d(TAG, \"Try to copy plugin's cup arch: \" + cpuArch);\n        boolean findLib = false;\n        boolean findSo = false;\n        byte buffer[] = null;\n        String libPrefix = \"lib/\" + cpuArch + \"/\";\n        ZipEntry entry;\n        Enumeration e = zipfile.entries();\n        \n        while (e.hasMoreElements()) {\n            entry = (ZipEntry) e.nextElement();\n            String entryName = entry.getName();\n            \n            if (entryName.charAt(0) < 'l') {\n                continue;\n            }\n            if (entryName.charAt(0) > 'l') {\n                break;\n            }\n            if (!findLib && !entryName.startsWith(\"lib/\")) {\n                continue;\n            }\n            findLib = true;\n            if (!entryName.endsWith(\".so\") || !entryName.startsWith(libPrefix)) {\n                continue;\n            }\n    \n            if (buffer == null) {\n                findSo = true;\n                Log.d(TAG, \"Found plugin's cup arch dir: \" + cpuArch);\n                buffer = new byte[8192];\n            }\n            \n            String libName = entryName.substring(entryName.lastIndexOf('/') + 1);\n            Log.d(TAG, \"verify so \" + libName);\n            File libFile = new File(nativeLibDir, libName);\n            String key = packageInfo.packageName + \"_\" + libName;\n            if (libFile.exists()) {\n                int VersionCode = Settings.getSoVersion(context, key);\n                if (VersionCode == packageInfo.versionCode) {\n                    Log.d(TAG, \"skip existing so : \" + entry.getName());\n                    continue;\n                }\n            }\n            FileOutputStream fos = new FileOutputStream(libFile);\n            Log.d(TAG, \"copy so \" + entry.getName() + \" of \" + cpuArch);\n            copySo(buffer, zipfile.getInputStream(entry), fos);\n            Settings.setSoVersion(context, key, packageInfo.versionCode);\n        }\n        \n        if (!findLib) {\n            Log.d(TAG, \"Fast skip all!\");\n            return true;\n        }\n        \n        return findSo;\n    }\n    \n    private static void copySo(byte[] buffer, InputStream input, OutputStream output) throws IOException {\n        BufferedInputStream bufferedInput = new BufferedInputStream(input);\n        BufferedOutputStream bufferedOutput = new BufferedOutputStream(output);\n        int count;\n        \n        while ((count = bufferedInput.read(buffer)) > 0) {\n            bufferedOutput.write(buffer, 0, count);\n        }\n        bufferedOutput.flush();\n        bufferedOutput.close();\n        output.close();\n        bufferedInput.close();\n        input.close();\n    }\n\n}\n"
  },
  {
    "path": "CoreLibrary/src/main/java/com/didi/virtualapk/internal/utils/Settings.java",
    "content": "/*\n * Copyright (C) 2017 Beijing Didi Infinity Technology and Development Co.,Ltd. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.didi.virtualapk.internal.utils;\n\nimport android.content.Context;\nimport android.content.SharedPreferences;\n\n/**\n * Created by renyugang on 16/8/23.\n */\npublic class Settings {\n\n    private static final String FILE_NAME = \"VirtualAPK_Settings\";\n\n    public static void setSoVersion(Context context, String name, int version) {\n        SharedPreferences preferences = context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE);\n        SharedPreferences.Editor editor = preferences.edit();\n        editor.putInt(name, version);\n        editor.commit();\n    }\n\n    public static int getSoVersion(Context context, String name) {\n        SharedPreferences preferences = context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE);\n        return preferences.getInt(name, 0);\n    }\n\n}\n"
  },
  {
    "path": "CoreLibrary/src/main/java/com/didi/virtualapk/utils/Reflector.java",
    "content": "package com.didi.virtualapk.utils;\n\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\nimport android.util.Log;\n\nimport com.didi.virtualapk.internal.Constants;\n\nimport java.lang.reflect.Constructor;\nimport java.lang.reflect.Field;\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Member;\nimport java.lang.reflect.Method;\nimport java.lang.reflect.Modifier;\n\n/**\n * Created by qiaopu on 2018/4/26.\n */\npublic class Reflector {\n    \n    public static final String LOG_TAG = Constants.TAG_PREFIX + \"Reflector\";\n    \n    protected Class<?> mType;\n    protected Object mCaller;\n    protected Constructor mConstructor;\n    protected Field mField;\n    protected Method mMethod;\n    \n    public static class ReflectedException extends Exception {\n    \n        public ReflectedException(String message) {\n            super(message);\n        }\n        public ReflectedException(String message, Throwable cause) {\n            super(message, cause);\n        }\n    \n    }\n    public static Reflector on(@NonNull String name) throws ReflectedException {\n        return on(name, true, Reflector.class.getClassLoader());\n    }\n    \n    public static Reflector on(@NonNull String name, boolean initialize) throws ReflectedException {\n        return on(name, initialize, Reflector.class.getClassLoader());\n    }\n    \n    public static Reflector on(@NonNull String name, boolean initialize, @Nullable ClassLoader loader) throws ReflectedException {\n        try {\n            return on(Class.forName(name, initialize, loader));\n        } catch (Throwable e) {\n            throw new ReflectedException(\"Oops!\", e);\n        }\n    }\n    \n    public static Reflector on(@NonNull Class<?> type) {\n        Reflector reflector = new Reflector();\n        reflector.mType = type;\n        return reflector;\n    }\n    \n    public static Reflector with(@NonNull Object caller) throws ReflectedException {\n        return on(caller.getClass()).bind(caller);\n    }\n    \n    protected Reflector() {\n    \n    }\n    \n    public Reflector constructor(@Nullable Class<?>... parameterTypes) throws ReflectedException {\n        try {\n            mConstructor = mType.getDeclaredConstructor(parameterTypes);\n            mConstructor.setAccessible(true);\n            mField = null;\n            mMethod = null;\n            return this;\n        } catch (Throwable e) {\n            throw new ReflectedException(\"Oops!\", e);\n        }\n    }\n    \n    @SuppressWarnings(\"unchecked\")\n    public <R> R newInstance(@Nullable Object ... initargs) throws ReflectedException {\n        if (mConstructor == null) {\n            throw new ReflectedException(\"Constructor was null!\");\n        }\n        try {\n            return (R) mConstructor.newInstance(initargs);\n        } catch (InvocationTargetException e) {\n            throw new ReflectedException(\"Oops!\", e.getTargetException());\n        } catch (Throwable e) {\n            throw new ReflectedException(\"Oops!\", e);\n        }\n    }\n    \n    protected Object checked(@Nullable Object caller) throws ReflectedException {\n        if (caller == null || mType.isInstance(caller)) {\n            return caller;\n        }\n        throw new ReflectedException(\"Caller [\" + caller + \"] is not a instance of type [\" + mType + \"]!\");\n    }\n    \n    protected void check(@Nullable Object caller, @Nullable Member member, @NonNull String name) throws ReflectedException {\n        if (member == null) {\n            throw new ReflectedException(name + \" was null!\");\n        }\n        if (caller == null && !Modifier.isStatic(member.getModifiers())) {\n            throw new ReflectedException(\"Need a caller!\");\n        }\n        checked(caller);\n    }\n    \n    public Reflector bind(@Nullable Object caller) throws ReflectedException {\n        mCaller = checked(caller);\n        return this;\n    }\n    \n    public Reflector unbind() {\n        mCaller = null;\n        return this;\n    }\n    \n    public Reflector field(@NonNull String name) throws ReflectedException {\n        try {\n            mField = findField(name);\n            mField.setAccessible(true);\n            mConstructor = null;\n            mMethod = null;\n            return this;\n        } catch (Throwable e) {\n            throw new ReflectedException(\"Oops!\", e);\n        }\n    }\n    \n    protected Field findField(@NonNull String name) throws NoSuchFieldException {\n        try {\n            return mType.getField(name);\n        } catch (NoSuchFieldException e) {\n            for (Class<?> cls = mType; cls != null; cls = cls.getSuperclass()) {\n                try {\n                    return cls.getDeclaredField(name);\n                } catch (NoSuchFieldException ex) {\n                    // Ignored\n                }\n            }\n            throw e;\n        }\n    }\n    \n    @SuppressWarnings(\"unchecked\")\n    public <R> R get() throws ReflectedException {\n        return get(mCaller);\n    }\n    \n    @SuppressWarnings(\"unchecked\")\n    public <R> R get(@Nullable Object caller) throws ReflectedException {\n        check(caller, mField, \"Field\");\n        try {\n            return (R) mField.get(caller);\n        } catch (Throwable e) {\n            throw new ReflectedException(\"Oops!\", e);\n        }\n    }\n    \n    public Reflector set(@Nullable Object value) throws ReflectedException {\n        return set(mCaller, value);\n    }\n    \n    public Reflector set(@Nullable Object caller, @Nullable Object value) throws ReflectedException {\n        check(caller, mField, \"Field\");\n        try {\n            mField.set(caller, value);\n            return this;\n        } catch (Throwable e) {\n            throw new ReflectedException(\"Oops!\", e);\n        }\n    }\n    \n    public Reflector method(@NonNull String name, @Nullable Class<?>... parameterTypes) throws ReflectedException {\n        try {\n            mMethod = findMethod(name, parameterTypes);\n            mMethod.setAccessible(true);\n            mConstructor = null;\n            mField = null;\n            return this;\n        } catch (NoSuchMethodException e) {\n            throw new ReflectedException(\"Oops!\", e);\n        }\n    }\n    \n    protected Method findMethod(@NonNull String name, @Nullable Class<?>... parameterTypes) throws NoSuchMethodException {\n        try {\n            return mType.getMethod(name, parameterTypes);\n        } catch (NoSuchMethodException e) {\n            for (Class<?> cls = mType; cls != null; cls = cls.getSuperclass()) {\n                try {\n                    return cls.getDeclaredMethod(name, parameterTypes);\n                } catch (NoSuchMethodException ex) {\n                    // Ignored\n                }\n            }\n            throw e;\n        }\n    }\n    \n    public <R> R call(@Nullable Object... args) throws ReflectedException {\n        return callByCaller(mCaller, args);\n    }\n    \n    @SuppressWarnings(\"unchecked\")\n    public <R> R callByCaller(@Nullable Object caller, @Nullable Object... args) throws ReflectedException {\n        check(caller, mMethod, \"Method\");\n        try {\n            return (R) mMethod.invoke(caller, args);\n        } catch (InvocationTargetException e) {\n            throw new ReflectedException(\"Oops!\", e.getTargetException());\n        } catch (Throwable e) {\n            throw new ReflectedException(\"Oops!\", e);\n        }\n    }\n    \n    public static class QuietReflector extends Reflector {\n        \n        protected Throwable mIgnored;\n    \n        public static QuietReflector on(@NonNull String name) {\n            return on(name, true, QuietReflector.class.getClassLoader());\n        }\n    \n        public static QuietReflector on(@NonNull String name, boolean initialize) {\n            return on(name, initialize, QuietReflector.class.getClassLoader());\n        }\n    \n        public static QuietReflector on(@NonNull String name, boolean initialize, @Nullable ClassLoader loader) {\n            Class<?> cls = null;\n            try {\n                cls = Class.forName(name, initialize, loader);\n                return on(cls, null);\n            } catch (Throwable e) {\n//                Log.w(LOG_TAG, \"Oops!\", e);\n                return on(cls, e);\n            }\n        }\n    \n        public static QuietReflector on(@Nullable Class<?> type) {\n            return on(type, (type == null) ? new ReflectedException(\"Type was null!\") : null);\n        }\n    \n        private static QuietReflector on(@Nullable Class<?> type, @Nullable Throwable ignored) {\n            QuietReflector reflector = new QuietReflector();\n            reflector.mType = type;\n            reflector.mIgnored = ignored;\n            return reflector;\n        }\n    \n        public static QuietReflector with(@Nullable Object caller) {\n            if (caller == null) {\n                return on((Class<?>) null);\n            }\n            return on(caller.getClass()).bind(caller);\n        }\n        \n        protected QuietReflector() {\n            \n        }\n    \n        public Throwable getIgnored() {\n            return mIgnored;\n        }\n    \n        protected boolean skip() {\n            return skipAlways() || mIgnored != null;\n        }\n        \n        protected boolean skipAlways() {\n            return mType == null;\n        }\n    \n        @Override\n        public QuietReflector constructor(@Nullable Class<?>... parameterTypes) {\n            if (skipAlways()) {\n                return this;\n            }\n            try {\n                mIgnored = null;\n                super.constructor(parameterTypes);\n            } catch (Throwable e) {\n                mIgnored = e;\n//                Log.w(LOG_TAG, \"Oops!\", e);\n            }\n            return this;\n        }\n    \n        @Override\n        public <R> R newInstance(@Nullable Object... initargs) {\n            if (skip()) {\n                return null;\n            }\n            try {\n                mIgnored = null;\n                return super.newInstance(initargs);\n            } catch (Throwable e) {\n                mIgnored = e;\n//                Log.w(LOG_TAG, \"Oops!\", e);\n            }\n            return null;\n        }\n    \n        @Override\n        public QuietReflector bind(@Nullable Object obj) {\n            if (skipAlways()) {\n                return this;\n            }\n            try {\n                mIgnored = null;\n                super.bind(obj);\n            } catch (Throwable e) {\n                mIgnored = e;\n//                Log.w(LOG_TAG, \"Oops!\", e);\n            }\n            return this;\n        }\n    \n        @Override\n        public QuietReflector unbind() {\n            super.unbind();\n            return this;\n        }\n    \n        @Override\n        public QuietReflector field(@NonNull String name) {\n            if (skipAlways()) {\n                return this;\n            }\n            try {\n                mIgnored = null;\n                super.field(name);\n            } catch (Throwable e) {\n                mIgnored = e;\n//                Log.w(LOG_TAG, \"Oops!\", e);\n            }\n            return this;\n        }\n    \n        @Override\n        public <R> R get() {\n            if (skip()) {\n                return null;\n            }\n            try {\n                mIgnored = null;\n                return super.get();\n            } catch (Throwable e) {\n                mIgnored = e;\n//                Log.w(LOG_TAG, \"Oops!\", e);\n            }\n            return null;\n        }\n    \n        @Override\n        public <R> R get(@Nullable Object caller) {\n            if (skip()) {\n                return null;\n            }\n            try {\n                mIgnored = null;\n                return super.get(caller);\n            } catch (Throwable e) {\n                mIgnored = e;\n//                Log.w(LOG_TAG, \"Oops!\", e);\n            }\n            return null;\n        }\n    \n        @Override\n        public QuietReflector set(@Nullable Object value) {\n            if (skip()) {\n                return this;\n            }\n            try {\n                mIgnored = null;\n                super.set(value);\n            } catch (Throwable e) {\n                mIgnored = e;\n//                Log.w(LOG_TAG, \"Oops!\", e);\n            }\n            return this;\n        }\n    \n        @Override\n        public QuietReflector set(@Nullable Object caller, @Nullable Object value) {\n            if (skip()) {\n                return this;\n            }\n            try {\n                mIgnored = null;\n                super.set(caller, value);\n            } catch (Throwable e) {\n                mIgnored = e;\n//                Log.w(LOG_TAG, \"Oops!\", e);\n            }\n            return this;\n        }\n    \n        @Override\n        public QuietReflector method(@NonNull String name, @Nullable Class<?>... parameterTypes) {\n            if (skipAlways()) {\n                return this;\n            }\n            try {\n                mIgnored = null;\n                super.method(name, parameterTypes);\n            } catch (Throwable e) {\n                mIgnored = e;\n//                Log.w(LOG_TAG, \"Oops!\", e);\n            }\n            return this;\n        }\n    \n        @Override\n        public <R> R call(@Nullable Object... args)  {\n            if (skip()) {\n                return null;\n            }\n            try {\n                mIgnored = null;\n                return super.call(args);\n            } catch (Throwable e) {\n                mIgnored = e;\n//                Log.w(LOG_TAG, \"Oops!\", e);\n            }\n            return null;\n        }\n    \n        @Override\n        public <R> R callByCaller(@Nullable Object caller, @Nullable Object... args) {\n            if (skip()) {\n                return null;\n            }\n            try {\n                mIgnored = null;\n                return super.callByCaller(caller, args);\n            } catch (Throwable e) {\n                mIgnored = e;\n//                Log.w(LOG_TAG, \"Oops!\", e);\n            }\n            return null;\n        }\n    }\n}\n"
  },
  {
    "path": "CoreLibrary/src/main/java/com/didi/virtualapk/utils/RunUtil.java",
    "content": "/*\n * Copyright (C) 2017 Beijing Didi Infinity Technology and Development Co.,Ltd. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.didi.virtualapk.utils;\n\nimport android.app.ActivityManager;\nimport android.content.Context;\nimport android.os.AsyncTask;\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.os.Message;\nimport android.os.Process;\nimport android.util.Log;\nimport android.util.Pair;\n\nimport com.didi.virtualapk.internal.Constants;\n\nimport java.util.List;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.Executor;\n\n/**\n * Created by renyugang on 16/11/10.\n */\npublic class RunUtil {\n\n    private static final int MESSAGE_RUN_ON_UITHREAD = 0x1;\n\n    private static Handler sHandler;\n\n    /**\n     * execute a runnable on ui thread, then return immediately. see also {@link #runOnUiThread(Runnable, boolean)}\n     * @param runnable the runnable prepared to run\n     */\n    public static void runOnUiThread(Runnable runnable) {\n        runOnUiThread(runnable, false);\n    }\n\n    /**\n     * execute a runnable on ui thread\n     * @param runnable the runnable prepared to run\n     * @param waitUtilDone if set true, the caller thread will wait until the specific runnable finished.\n     */\n    public static void runOnUiThread(Runnable runnable, boolean waitUtilDone) {\n        if (Thread.currentThread() == Looper.getMainLooper().getThread()) {\n            runnable.run();\n            return;\n        }\n\n        CountDownLatch countDownLatch = null;\n        if (waitUtilDone) {\n            countDownLatch = new CountDownLatch(1);\n        }\n        Pair<Runnable, CountDownLatch> pair = new Pair<>(runnable, countDownLatch);\n        getHandler().obtainMessage(MESSAGE_RUN_ON_UITHREAD, pair).sendToTarget();\n        if (waitUtilDone) {\n            try {\n                countDownLatch.await();\n            } catch (InterruptedException e) {\n                Log.w(Constants.TAG, e);\n            }\n        }\n    }\n\n    public static Executor getThreadPool() {\n        return AsyncTask.THREAD_POOL_EXECUTOR;\n    }\n\n    private static String getProcessNameByPid(Context context, int pid) {\n        try {\n            ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);\n            List<ActivityManager.RunningAppProcessInfo> appProcessList = manager.getRunningAppProcesses();\n            if (appProcessList != null) {\n                for (ActivityManager.RunningAppProcessInfo appProcessInfo : appProcessList) {\n                    if (pid == appProcessInfo.pid) {\n                        return appProcessInfo.processName;\n                    }\n                }\n            }\n    \n        } catch (Throwable e) {\n            Log.w(Constants.TAG, e);\n        }\n        \n        return null;\n    }\n\n    public static boolean isMainProcess(Context context) {\n        String processName = getProcessNameByPid(context, Process.myPid());\n        if (context.getPackageName().equals(processName)) {\n            return true;\n        }\n\n        return false;\n    }\n\n    private static Handler getHandler() {\n        synchronized (RunUtil.class) {\n            if (sHandler == null) {\n                sHandler = new InternalHandler();\n            }\n            return sHandler;\n        }\n    }\n\n    private static class InternalHandler extends Handler {\n        public InternalHandler() {\n            super(Looper.getMainLooper());\n        }\n\n        @Override\n        public void handleMessage(Message msg) {\n            if (msg.what == MESSAGE_RUN_ON_UITHREAD) {\n                Pair<Runnable, CountDownLatch> pair = (Pair<Runnable, CountDownLatch>) msg.obj;\n                try {\n                    Runnable runnable = pair.first;\n                    runnable.run();\n    \n                } finally {\n                    if (pair.second != null) {\n                        pair.second.countDown();\n                    }\n                }\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "CoreLibrary/src/main/java/com/didi/virtualapk/utils/ZipVerifyUtil.java",
    "content": "/*\n * Copyright (C) 2017 Beijing Didi Infinity Technology and Development Co.,Ltd. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.didi.virtualapk.utils;\n\nimport android.content.Context;\nimport android.util.Base64;\nimport android.util.Log;\n\nimport com.didi.virtualapk.internal.Constants;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.security.cert.Certificate;\nimport java.security.cert.CertificateException;\nimport java.security.cert.CertificateFactory;\nimport java.util.Enumeration;\nimport java.util.HashSet;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.jar.Attributes;\nimport java.util.jar.Manifest;\nimport java.util.zip.ZipEntry;\nimport java.util.zip.ZipFile;\n\n/**\n * Created by renyugang on 16/5/12.\n */\n\n/**\n * verify signature of zip file<br>\n * usage: boolean valid = ZipVerifyUtil.verifyZip(context, zipPath)\n */\npublic class ZipVerifyUtil {\n\n    public static boolean verifyZip(Context context, String zipPath) {\n        return verifyZip(context, zipPath, \"test.cer\");\n    }\n    \n    public static boolean verifyZip(Context context, String zipPath, String cerName) {\n        try {\n            CertificateFactory certificateFactory = CertificateFactory.getInstance(\"X.509\");\n            InputStream in = context.getAssets().open(cerName);\n            Certificate certificate = certificateFactory.generateCertificate(in);\n            in.close();\n            return verifyZip(zipPath, certificate);\n        } catch (IOException | CertificateException e) {\n            Log.w(Constants.TAG, e);\n            return false;\n        }\n    }\n\n    public static boolean verifyZip(String zipPath, Certificate remoteCertificate) {\n        try {\n            String certPath = checkZipFileForCertificate(zipPath);\n            Certificate certificate = getCertificateFromZip(zipPath, certPath);\n            remoteCertificate.verify(certificate.getPublicKey());\n            return true;\n        } catch (Exception e) {\n            Log.w(Constants.TAG, e);\n            return false;\n        }\n    }\n\n    public static Certificate getCertificateFromZip(String zipPath, String certPath) throws Exception {\n        CertificateFactory certificateFactory = CertificateFactory.getInstance(\"X.509\");\n        ZipFile zip = new ZipFile(new File(zipPath));\n        InputStream in = zip.getInputStream(zip.getEntry(certPath));\n        Certificate certificate = certificateFactory.generateCertificates(in).iterator().next();\n        in.close();\n        zip.close();\n        return certificate;\n    }\n\n    public static String checkZipFileForCertificate(String zipPath) throws IOException {\n        String certPath = \"\";\n        ZipFile zip = new ZipFile(new File(zipPath));\n\n        // This call will throw a java.lang.SecurityException if someone has tampered\n        // with the signature of _any_ element of the JAR file.\n        // Alas, it will proceed without a problem if the JAR file is not signed at all\n        InputStream is = zip.getInputStream(zip.getEntry(\"META-INF/MANIFEST.MF\"));\n        Manifest man = new Manifest(is);\n        is.close();\n\n        Set<String> signed = new HashSet();\n        for (Map.Entry<String, Attributes> entry : man.getEntries().entrySet()) {\n            for (Object attrkey : entry.getValue().keySet()) {\n                if (attrkey instanceof Attributes.Name\n                        && ((Attributes.Name) attrkey).toString().indexOf(\"-Digest\") != -1)\n                    signed.add(entry.getKey());\n            }\n        }\n\n        Set<String> entries = new HashSet<String>();\n        for (Enumeration<ZipEntry> entry = (Enumeration<ZipEntry>) zip.entries(); entry.hasMoreElements();) {\n            ZipEntry ze = entry.nextElement();\n            if (!ze.isDirectory()) {\n                String name = ze.getName();\n                if (!name.startsWith(\"META-INF/\")) {\n                    entries.add(name);\n                } else if (name.endsWith(\".RSA\") || name.endsWith(\".DSA\")) {\n                    certPath = name;\n                }\n            }\n        }\n\n        // contains all entries in the Manifest that are not signed.\n        // Ususally, this contains:\n        // * MANIFEST.MF itself\n        // * *.SF files containing the signature of MANIFEST.MF\n        // * *.DSA files containing public keys of the signer\n\n        Set<String> unsigned = new HashSet<String>(entries);\n        unsigned.removeAll(signed);\n\n        // contains all the entries with a signature that are not present in the JAR\n        Set<String> missing = new HashSet<String>(signed);\n        missing.removeAll(entries);\n        zip.close();\n        if (unsigned.isEmpty() && missing.isEmpty()) {\n            return certPath;\n        }\n        return null;\n    }\n\n    public static Certificate getCertificate(String certificatePath) throws Exception {\n        CertificateFactory certificateFactory = CertificateFactory.getInstance(\"X.509\");\n        FileInputStream in = new FileInputStream(certificatePath);\n        Certificate certificate = certificateFactory.generateCertificate(in);\n        in.close();\n        return certificate;\n    }\n\n    private static byte[] decode(String base64) throws Exception {\n        return Base64.decode(base64.getBytes(), Base64.DEFAULT);\n    }\n\n}\n"
  },
  {
    "path": "CoreLibrary/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">CoreLibrary</string>\n</resources>\n"
  },
  {
    "path": "CoreLibrary/src/test/java/com/didi/virtualapk/core/ExampleUnitTest.java",
    "content": "package com.didi.virtualapk.core;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\n/**\n * To work on unit tests, switch the Test Artifact in the Build Variants view.\n */\npublic class ExampleUnitTest {\n    @Test\n    public void addition_isCorrect() throws Exception {\n        assertEquals(4, 2 + 2);\n    }\n}"
  },
  {
    "path": "CoreLibrary/upload.gradle",
    "content": "apply plugin: 'com.github.dcendents.android-maven'\napply plugin: 'com.jfrog.bintray'\n\ndef GROUP_ID = 'com.didi.virtualapk'\ndef ARTIFACT_ID = 'core'\n\n\ndef siteUrl = 'https://github.com/didi/VirtualAPK' // 项目的主页\ndef gitUrl = 'https://github.com/didi/VirtualAPK' // Git仓库的url\n\ngroup = GROUP_ID\narchivesBaseName = 'core'\n\nversion = \"0.9.8\"\n\n\ninstall {\n    repositories.mavenInstaller {\n        // This generates POM.xml with proper parameters\n        pom {\n            artifactId = ARTIFACT_ID\n\n            project {\n                packaging 'aar'\n                // Add your description here\n                name 'A powerful but lightweight plugin framework for Android' //项目描述\n                url siteUrl\n                // Set your license\n                licenses {\n                    license {\n                        name 'Apache License 2.0'\n                        url 'http://www.apache.org/licenses/LICENSE-2.0'\n                    }\n                }\n                developers {\n                    developer {\n                        id 'singwhatiwanna'    //填写的一些基本信息\n                        name 'DiDi'\n                        email 'singwhatiwanna@gmail.com'\n                    }\n                }\n                scm {\n                    connection gitUrl\n                    developerConnection gitUrl\n                    url siteUrl\n                }\n            }\n        }\n    }\n}\n\ntask sourcesJar(type: Jar) {\n    from android.sourceSets.main.java.srcDirs\n    classifier = 'sources'\n}\ntask javadoc(type: Javadoc) {\n    source = android.sourceSets.main.java.srcDirs\n    classpath += project.files(android.getBootClasspath().join(File.pathSeparator))\n}\ntask javadocJar(type: Jar, dependsOn: javadoc) {\n    classifier = 'javadoc'\n    from javadoc.destinationDir\n}\nartifacts {\n    //archives javadocJar\n    archives sourcesJar\n}\n\nProperties properties = new Properties()\nproperties.load(project.rootProject.file('local.properties').newDataInputStream())\nbintray {\n    user = properties.getProperty(\"bintray.user\")\n    key = properties.getProperty(\"bintray.apikey\")\n    configurations = ['archives']\n    pkg {\n        repo = \"maven\"\n        name = \"${GROUP_ID}:${ARTIFACT_ID}\"    //发布到JCenter上的项目名字\n        websiteUrl = siteUrl\n        vcsUrl = gitUrl\n        licenses = [\"Apache-2.0\"]\n        publish = true\n    }\n\n}"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"{}\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright (C) 2017 Beijing Didi Infinity Technology and Development Co.,Ltd. All rights reserved.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "PluginDemo/app/build.gradle",
    "content": "apply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion VERSION_COMPILE_SDK\n    buildToolsVersion VERSION_BUILD_TOOLS\n\n    defaultConfig {\n        applicationId \"com.didi.virtualapk.demo\"\n        minSdkVersion VERSION_MIN_SDK\n        targetSdkVersion VERSION_TARGET_SDK\n        versionName \"1.0.0\"\n        versionCode 1\n    }\n    compileOptions {\n        sourceCompatibility SOURCE_COMPATIBILITY\n    }\n\n    flavorDimensions \"demo\"\n    productFlavors {\n        beijing {\n            dimension \"demo\"\n            applicationId 'com.didi.virtualapk.demo'\n        }\n        shanghai {\n            dimension \"demo\"\n            applicationId 'com.didi.virtualapk.demo'\n        }\n    }\n\n    signingConfigs {\n        release {\n            storeFile file(\"../../keystore/test.keystore\")\n            storePassword \"test123456\"\n            keyAlias \"test\"\n            keyPassword \"test123456\"\n        }\n    }\n\n    buildTypes {\n        debug {\n            minifyEnabled false\n            shrinkResources false\n        }\n        release {\n            minifyEnabled true\n            shrinkResources true\n            signingConfig signingConfigs.release\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n}\n\ndependencies {\n    // the following aars are also compiled in host project, so they will be filterd when build plugin apk.\n    // but, wo can still visit their Class and Resources.\n    implementation 'com.android.support:appcompat-v7:23.4.0'\n//    implementation 'com.didi.virtualapk:core:0.9.8'\n}\n\napply plugin: 'com.didi.virtualapk.plugin'\n\nvirtualApk {\n    packageId = 0x6f // the package id of Resources.\n    targetHost = '../../VirtualAPK/app' // the path of application module in host project.\n    applyHostMapping = true //optional, default value: true.\n}"
  },
  {
    "path": "PluginDemo/app/gradle.properties",
    "content": "#android.enableD8=false\nandroid.useDexArchive=false"
  },
  {
    "path": "PluginDemo/app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /Users/didi/Library/Android/sdk/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n-dontusemixedcaseclassnames\n-dontskipnonpubliclibraryclasses\n-dontpreverify\n-verbose\n-dontoptimize\n-dontshrink\n-allowaccessmodification\n-keepattributes *Annotation*\n-keepattributes Exceptions\n-keepattributes JavascriptInterface\n-keepattributes LineNumberTable\n-keepattributes Signature\n-keepattributes SourceFile\n\n-keep public class * extends android.app.Activity\n-keep public class * extends android.app.Application\n-keep public class * extends android.app.Service\n-keep public class * extends android.content.BroadcastReceiver\n-keep public class * extends android.content.ContentProvider\n-keep public class * extends android.app.backup.BackupAgentHelper\n-keep public class * extends android.preference.Preference\n-keep public class com.android.vending.licensing.ILicensingService\n-dontnote com.android.vending.licensing.ILicensingService\n\n-keepclasseswithmembernames class * {\n    native <methods>;\n}\n\n-keepclassmembers enum * {\n    public static <fields>;\n    public static **[] values();\n    public static ** valueOf(java.lang.String);\n}\n\n-keep class com.google.**{*;}\n-keep class sun.misc.Unsafe { *; }\n-keepnames class * implements java.io.Serializable\n-keepclassmembers class * implements java.io.Serializable {\n    static final long serialVersionUID;\n    private static final java.io.ObjectStreamField[] serialPersistentFields;\n    !static !transient <fields>;\n    private void writeObject(java.io.ObjectOutputStream);\n    private void readObject(java.io.ObjectInputStream);\n    java.lang.Object writeReplace();\n    java.lang.Object readResolve();\n}\n\n-keep class * extends java.util.ListResourceBundle {\n    protected Object[][] getContents();\n}\n\n-keepclassmembers class * extends android.view.View {\n    public <init>(android.content.Context);\n    public <init>(android.content.Context);\n    public <init>(android.content.Context, android.util.AttributeSet);\n    public <init>(android.content.Context, android.util.AttributeSet, int);\n    void set*(***);\n    *** get*();\n}\n\n-keepclassmembers class * extends android.app.Activity {\n    public void *(android.view.View);\n}\n\n-keep class * implements android.os.Parcelable {\n    public static final android.os.Parcelable$Creator *;\n}\n\n-keep class **.R$* {\n    public static <fields>;\n}\n\n-keep @android.support.annotation.Keep class *\n-keep @android.support.annotation.Keep interface *\n\n-keepclassmembers class * {\n    @android.support.annotation.Keep <methods>;\n}\n\n-keepclassmembers class * {\n    @android.support.annotation.Keep <fields>;\n}\n\n-keepclassmembers interface * {\n    @android.support.annotation.Keep <methods>;\n}\n\n-keepclassmembers class * {\n    @android.webkit.JavascriptInterface <methods>;\n}\n\n-assumenosideeffects class android.util.Log {\n    public static boolean isLoggable(java.lang.String,int);\n    public static int v(...);\n    public static int i(...);\n    public static int w(...);\n    public static int d(...);\n    public static int e(...);\n}\n\n\n#######################################################################\n-keep class com.didi.virtualapk.internal.VAInstrumentation { *; }\n-keep class com.didi.virtualapk.internal.PluginContentResolver { *; }\n\n-dontwarn com.didi.virtualapk.**\n-dontwarn android.**\n-keep class android.** { *; }"
  },
  {
    "path": "PluginDemo/app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.didi.virtualapk.demo\"\n    android:versionCode=\"1\"\n    android:versionName=\"1.0\" >\n\n    <uses-sdk\n        android:minSdkVersion=\"15\"\n        android:targetSdkVersion=\"19\" />\n\n    <permission\n        android:name=\"com.didi.virtualapk.demo.permission.ACCESS_BOOK_SERVICE\"\n        android:protectionLevel=\"normal\" />\n    <permission\n        android:name=\"com.didi.virtualapk.demo.PROVIDER\"\n        android:protectionLevel=\"normal\" />\n\n    <uses-permission android:name=\"com.didi.virtualapk.demo.PROVIDER\" />\n    <uses-permission android:name=\"com.didi.virtualapk.demo.permission.ACCESS_BOOK_SERVICE\" />\n\n    <uses-permission android:name=\"android.permission.SET_WALLPAPER_HINTS\" />\n    <uses-permission android:name=\"android.permission.SET_WALLPAPER\" />\n    <uses-permission android:name=\"android.permission.BATTERY_STATS\" />\n    <uses-permission android:name=\"android.permission.GET_PACKAGE_SIZE\" />\n    <uses-permission android:name=\"android.permission.GET_TASKS\" />\n    <uses-permission android:name=\"android.permission.RESTART_PACKAGES\" />\n    <uses-permission android:name=\"android.permission.READ_PHONE_STATE\" />\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n    <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />\n    <uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\" />\n    <uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\" />\n    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />\n    <uses-permission android:name=\"android.permission.READ_LOGS\" />\n    <uses-permission android:name=\"android.permission.READ_SMS\" />\n    <uses-permission android:name=\"android.permission.READ_CONTACTS\" />\n    <uses-permission android:name=\"android.permission.WRITE_SMS\" />\n    <uses-permission android:name=\"android.permission.CHANGE_WIFI_MULTICAST_STATE\" />\n    <uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\" />\n    <uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\" />\n    <uses-permission android:name=\"android.permission.KILL_BACKGROUND_PROCESSES\" />\n    <uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\" />\n    <uses-permission android:name=\"android.permission.RECORD_AUDIO\" />\n    <uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\" />\n    <uses-permission android:name=\"android.permission.CHANGE_CONFIGURATION\" />\n    <uses-permission android:name=\"com.android.launcher.permission.INSTALL_SHORTCUT\" />\n    <uses-permission android:name=\"android.permission.EXPAND_STATUS_BAR\" />\n\n    <application\n        android:name=\"com.didi.virtualapk.demo.MyApplication\"\n        android:allowBackup=\"true\"\n        android:icon=\"@drawable/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:theme=\"@style/AppTheme\" >\n        <activity\n            android:name=\"com.didi.virtualapk.demo.MainActivity\"\n            android:configChanges=\"orientation|screenSize\"\n            android:label=\"A\"\n            android:launchMode=\"standard\" >\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n                <category android:name=\"android.intent.category._LAUNCHER\" />\n            </intent-filter>\n        </activity>\n        <activity\n            android:name=\"com.didi.virtualapk.demo.SecondActivity\"\n            android:configChanges=\"screenLayout\"\n            android:launchMode=\"singleInstance\"\n            android:label=\"B\" />\n        <activity\n            android:name=\"com.didi.virtualapk.demo.ThirdActivity\"\n            android:configChanges=\"screenLayout\"\n            android:label=\"C\" />\n        <activity\n            android:name=\"com.didi.virtualapk.demo.aidl.BookManagerActivity\"\n            android:label=\"@string/title_activity_book_manager\" >\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n        <activity\n            android:name=\"com.didi.virtualapk.demo.messenger.MessengerActivity\"\n            android:label=\"@string/title_activity_messenger\" >\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n            </intent-filter>\n        </activity>\n\n        <service\n            android:name=\"com.didi.virtualapk.demo.messenger.MessengerService\"\n            android:process=\":remote\" >\n            <intent-filter>\n                <action android:name=\"com.didi.MessengerService.launch\" />\n            </intent-filter>\n        </service>\n        <service\n            android:name=\"com.didi.virtualapk.demo.aidl.BookManagerService\"\n            android:process=\":remote\" >\n        </service>\n\n        <activity\n            android:name=\"com.didi.virtualapk.demo.provider.ProviderActivity\"\n            android:label=\"@string/title_activity_provider\" >\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category._LAUNCHER\" />\n            </intent-filter>\n        </activity>\n\n        <provider\n            android:name=\"com.didi.virtualapk.demo.provider.BookProvider\"\n            android:authorities=\"com.didi.virtualapk.demo.book.provider\"\n            android:permission=\"com.didi.virtualapk.demo.PROVIDER\"\n            android:process=\":provider\" >\n        </provider>\n\n        <service\n            android:name=\"com.didi.virtualapk.demo.socket.TCPServerService\"\n            android:process=\":remote\" />\n\n        <activity\n            android:name=\"com.didi.virtualapk.demo.socket.TCPClientActivity\"\n            android:label=\"@string/title_activity_tcpclient\"\n            android:windowSoftInputMode=\"adjustResize\" >\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category._LAUNCHER\" />\n            </intent-filter>\n        </activity>\n\n        <service\n            android:name=\"com.didi.virtualapk.demo.binderpool.BinderPoolService\"\n            android:process=\":remote\" />\n\n        <activity\n            android:name=\"com.didi.virtualapk.demo.binderpool.BinderPoolActivity\"\n            android:label=\"@string/title_activity_binder_pool\" >\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category._LAUNCHER\" />\n            </intent-filter>\n        </activity>\n    </application>\n\n</manifest>"
  },
  {
    "path": "PluginDemo/app/src/main/aidl/com/didi/virtualapk/demo/aidl/Book.aidl",
    "content": "package com.didi.virtualapk.demo.aidl;\n\nparcelable Book;"
  },
  {
    "path": "PluginDemo/app/src/main/aidl/com/didi/virtualapk/demo/aidl/IBookManager.aidl",
    "content": "package com.didi.virtualapk.demo.aidl;\n\nimport com.didi.virtualapk.demo.aidl.Book;\nimport com.didi.virtualapk.demo.aidl.IOnNewBookArrivedListener;\n\ninterface IBookManager {\n     List<Book> getBookList();\n     void addBook(in Book book);\n     void registerListener(IOnNewBookArrivedListener listener);\n     void unregisterListener(IOnNewBookArrivedListener listener);\n}"
  },
  {
    "path": "PluginDemo/app/src/main/aidl/com/didi/virtualapk/demo/aidl/IOnNewBookArrivedListener.aidl",
    "content": "package com.didi.virtualapk.demo.aidl;\n\nimport com.didi.virtualapk.demo.aidl.Book;\n\ninterface IOnNewBookArrivedListener {\n    void onNewBookArrived(in Book newBook);\n}\n"
  },
  {
    "path": "PluginDemo/app/src/main/aidl/com/didi/virtualapk/demo/binderpool/IBinderPool.aidl",
    "content": "package com.didi.virtualapk.demo.binderpool;\n\ninterface IBinderPool {\n\n    /**\n     * @param binderCode, the unique token of specific Binder<br/>\n     * @return specific Binder who's token is binderCode.\n     */\n    IBinder queryBinder(int binderCode);\n}"
  },
  {
    "path": "PluginDemo/app/src/main/aidl/com/didi/virtualapk/demo/binderpool/ICompute.aidl",
    "content": "package com.didi.virtualapk.demo.binderpool;\n\ninterface ICompute {\n    int add(int a, int b);\n}"
  },
  {
    "path": "PluginDemo/app/src/main/aidl/com/didi/virtualapk/demo/binderpool/ISecurityCenter.aidl",
    "content": "package com.didi.virtualapk.demo.binderpool;\n\ninterface ISecurityCenter {\n    String encrypt(String content);\n    String decrypt(String password);\n}"
  },
  {
    "path": "PluginDemo/app/src/main/aidl/com/didi/virtualapk/demo/manualbinder/Book.aidl",
    "content": "package com.didi.virtualapk.demo.manualbinder;\n\nparcelable Book;"
  },
  {
    "path": "PluginDemo/app/src/main/java/com/didi/virtualapk/demo/MainActivity.java",
    "content": "package com.didi.virtualapk.demo;\n\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.ObjectOutputStream;\nimport java.io.Serializable;\n\nimport com.didi.virtualapk.demo.R;\nimport com.didi.virtualapk.demo.aidl.Book;\nimport com.didi.virtualapk.demo.manager.UserManager;\nimport com.didi.virtualapk.demo.model.User;\nimport com.didi.virtualapk.demo.utils.MyConstants;\nimport com.didi.virtualapk.demo.utils.MyUtils;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.view.View;\nimport android.view.View.OnClickListener;\n\npublic class MainActivity extends Activity {\n\n    private static final String TAG = \"MainActivity\";\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n        UserManager.sUserId = 2;\n        findViewById(R.id.button1).setOnClickListener(new OnClickListener() {\n\n            @Override\n            public void onClick(View v) {\n                Intent intent = new Intent();\n                intent.setClass(MainActivity.this, SecondActivity.class);\n                User user = new User(0, \"jake\", true);\n                user.book = new Book();\n                intent.putExtra(\"extra_user\", (Serializable) user);\n                startActivity(intent);\n            }\n        });\n    }\n\n    @Override\n    protected void onResume() {\n        Log.d(TAG, \"UserManage.sUserId=\" + UserManager.sUserId);\n        persistToFile();\n\n        super.onResume();\n    }\n\n    private void persistToFile() {\n        new Thread(new Runnable() {\n\n            @Override\n            public void run() {\n                User user = new User(1, \"hello world\", false);\n                File dir = new File(MyConstants.CHAPTER_2_PATH);\n                if (!dir.exists()) {\n                    dir.mkdirs();\n                }\n                File cachedFile = new File(MyConstants.CACHE_FILE_PATH);\n                ObjectOutputStream objectOutputStream = null;\n                try {\n                    objectOutputStream = new ObjectOutputStream(\n                            new FileOutputStream(cachedFile));\n                    objectOutputStream.writeObject(user);\n                    Log.d(TAG, \"persist user:\" + user);\n                } catch (IOException e) {\n                    e.printStackTrace();\n                } finally {\n                    MyUtils.close(objectOutputStream);\n                }\n            }\n        }).start();\n    }\n}\n"
  },
  {
    "path": "PluginDemo/app/src/main/java/com/didi/virtualapk/demo/MyApplication.java",
    "content": "package com.didi.virtualapk.demo;\n\nimport com.didi.virtualapk.demo.utils.MyUtils;\n\nimport android.app.Application;\nimport android.os.Process;\nimport android.util.Log;\n\npublic class MyApplication extends Application {\n\n    private static final String TAG = \"MyApplication\";\n\n    @Override\n    public void onCreate() {\n        super.onCreate();\n        String processName = MyUtils.getProcessName(getApplicationContext(),\n                Process.myPid());\n        Log.d(TAG, \"application start, process name:\" + processName);\n        new Thread(new Runnable() {\n\n            @Override\n            public void run() {\n                doWorkInBackground();\n            }\n        }).start();\n    }\n\n    private void doWorkInBackground() {\n        // init binder pool\n        //BinderPool.getInsance(getApplicationContext());\n    }\n}\n"
  },
  {
    "path": "PluginDemo/app/src/main/java/com/didi/virtualapk/demo/SecondActivity.java",
    "content": "package com.didi.virtualapk.demo;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.io.ObjectInputStream;\n\nimport com.didi.virtualapk.demo.R;\nimport com.didi.virtualapk.demo.model.User;\nimport com.didi.virtualapk.demo.utils.MyConstants;\nimport com.didi.virtualapk.demo.utils.MyUtils;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.view.View;\nimport android.view.View.OnClickListener;\n\npublic class SecondActivity extends Activity {\n    private static final String TAG = \"SecondActivity\";\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_second);\n        findViewById(R.id.button1).setOnClickListener(new OnClickListener() {\n\n            @Override\n            public void onClick(View v) {\n                Intent intent = new Intent();\n                intent.setClass(SecondActivity.this, ThirdActivity.class);\n                intent.putExtra(\"time\", System.currentTimeMillis());\n                startActivity(intent);\n            }\n        });\n        Log.d(TAG, \"onCreate\");\n    }\n\n    @Override\n    protected void onNewIntent(Intent intent) {\n        super.onNewIntent(intent);\n        Log.d(TAG, \"onNewIntent\");\n    }\n\n    @Override\n    protected void onResume() {\n        super.onResume();\n        //User user = (User) getIntent().getSerializableExtra(\"extra_user\");\n        //Log.d(TAG, \"user:\" + user.toString());\n        // Log.d(TAG, \"UserManage.sUserId=\" + UserManager.sUserId);\n        //recoverFromFile();\n    }\n\n    private void recoverFromFile() {\n        new Thread(new Runnable() {\n\n            @Override\n            public void run() {\n                User user = null;\n                File cachedFile = new File(MyConstants.CACHE_FILE_PATH);\n                if (cachedFile.exists()) {\n                    ObjectInputStream objectInputStream = null;\n                    try {\n                        objectInputStream = new ObjectInputStream(\n                                new FileInputStream(cachedFile));\n                        user = (User) objectInputStream.readObject();\n                        Log.d(TAG, \"recover user:\" + user);\n                    } catch (IOException e) {\n                        e.printStackTrace();\n                    } catch (ClassNotFoundException e) {\n                        e.printStackTrace();\n                    } finally {\n                        MyUtils.close(objectInputStream);\n                    }\n                }\n            }\n        }).start();\n    }\n}\n"
  },
  {
    "path": "PluginDemo/app/src/main/java/com/didi/virtualapk/demo/ThirdActivity.java",
    "content": "package com.didi.virtualapk.demo;\n\nimport com.didi.virtualapk.demo.R;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.view.View;\nimport android.view.View.OnClickListener;\n\npublic class ThirdActivity extends Activity {\n    private static final String TAG = \"ThirdActivity\";\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        // TODO Auto-generated method stub\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_third);\n        findViewById(R.id.button1).setOnClickListener(new OnClickListener() {\n\n            @Override\n            public void onClick(View v) {\n                Intent intent = new Intent();\n                intent.setClass(ThirdActivity.this, SecondActivity.class);\n                intent.putExtra(\"time\", System.currentTimeMillis());\n                startActivity(intent);\n            }\n        });\n        Log.d(TAG, \"onCreate\");\n    }\n\n    @Override\n    protected void onStart() {\n        // TODO Auto-generated method stub\n        super.onStart();\n        Log.d(TAG, \"onStart\");\n    }\n\n    @Override\n    protected void onResume() {\n        // TODO Auto-generated method stub\n        super.onResume();\n        Log.d(TAG, \"onResume\");\n    }\n\n    @Override\n    protected void onSaveInstanceState(Bundle outState) {\n        super.onSaveInstanceState(outState);\n        Log.d(TAG, \"onSaveInstanceState\");\n    }\n\n    @Override\n    protected void onRestoreInstanceState(Bundle savedInstanceState) {\n        super.onRestoreInstanceState(savedInstanceState);\n        Log.d(TAG, \"onRestoreInstanceState\");\n    }\n}\n"
  },
  {
    "path": "PluginDemo/app/src/main/java/com/didi/virtualapk/demo/aidl/Book.java",
    "content": "package com.didi.virtualapk.demo.aidl;\n\nimport android.os.Parcel;\nimport android.os.Parcelable;\n\npublic class Book implements Parcelable {\n\n    public int bookId;\n    public String bookName;\n\n    public Book() {\n\n    }\n\n    public Book(int bookId, String bookName) {\n        this.bookId = bookId;\n        this.bookName = bookName;\n    }\n\n    public int describeContents() {\n        return 0;\n    }\n\n    public void writeToParcel(Parcel out, int flags) {\n        out.writeInt(bookId);\n        out.writeString(bookName);\n    }\n\n    public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>() {\n        public Book createFromParcel(Parcel in) {\n            return new Book(in);\n        }\n\n        public Book[] newArray(int size) {\n            return new Book[size];\n        }\n    };\n\n    private Book(Parcel in) {\n        bookId = in.readInt();\n        bookName = in.readString();\n    }\n\n    @Override\n    public String toString() {\n        return String.format(\"[bookId:%s, bookName:%s]\", bookId, bookName);\n    }\n\n}\n"
  },
  {
    "path": "PluginDemo/app/src/main/java/com/didi/virtualapk/demo/aidl/BookManagerActivity.java",
    "content": "package com.didi.virtualapk.demo.aidl;\n\nimport java.util.List;\nimport com.didi.virtualapk.demo.R;\nimport com.didi.virtualapk.demo.socket.TCPClientActivity;\n\nimport android.annotation.SuppressLint;\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.ServiceConnection;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.os.IBinder;\nimport android.os.Message;\nimport android.os.RemoteException;\nimport android.support.v7.app.AppCompatActivity;\nimport android.util.Log;\nimport android.view.View;\nimport android.widget.EditText;\n\npublic class BookManagerActivity extends AppCompatActivity {\n\n    private static final String TAG = \"BookManagerActivity\";\n    private static final int MESSAGE_NEW_BOOK_ARRIVED = 1;\n\n    private IBookManager mRemoteBookManager;\n    private EditText mEditText;\n\n    @SuppressLint(\"HandlerLeak\")\n    private Handler mHandler = new Handler() {\n        @Override\n        public void handleMessage(Message msg) {\n            switch (msg.what) {\n            case MESSAGE_NEW_BOOK_ARRIVED:\n                Log.d(TAG, \"receive new book :\" + msg.obj);\n                mEditText.append(msg.obj.toString() + \"\\n\");\n                break;\n            default:\n                super.handleMessage(msg);\n            }\n        }\n    };\n\n    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {\n        @Override\n        public void binderDied() {\n            Log.d(TAG, \"binder died. tname:\" + Thread.currentThread().getName());\n            if (mRemoteBookManager == null)\n                return;\n            mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);\n            mRemoteBookManager = null;\n            // TODO:这里重新绑定远程Service\n        }\n    };\n\n    private ServiceConnection mConnection = new ServiceConnection() {\n        public void onServiceConnected(ComponentName className, IBinder service) {\n            IBookManager bookManager = IBookManager.Stub.asInterface(service);\n            mRemoteBookManager = bookManager;\n            try {\n                mRemoteBookManager.asBinder().linkToDeath(mDeathRecipient, 0);\n                List<Book> list = bookManager.getBookList();\n                Log.i(TAG, \"query book list, list type:\"\n                        + list.getClass().getCanonicalName());\n                Log.i(TAG, \"query book list:\" + list.toString());\n                Book newBook = new Book(3, \"Android进阶\");\n                bookManager.addBook(newBook);\n                Log.i(TAG, \"add book:\" + newBook);\n                List<Book> newList = bookManager.getBookList();\n                Log.i(TAG, \"query book list:\" + newList.toString());\n                bookManager.registerListener(mOnNewBookArrivedListener);\n            } catch (RemoteException e) {\n                e.printStackTrace();\n            }\n        }\n\n        public void onServiceDisconnected(ComponentName className) {\n            mRemoteBookManager = null;\n            Log.d(TAG, \"onServiceDisconnected. tname:\" + Thread.currentThread().getName());\n        }\n    };\n\n    private IOnNewBookArrivedListener mOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() {\n\n        @Override\n        public void onNewBookArrived(Book newBook) throws RemoteException {\n            mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, newBook)\n                    .sendToTarget();\n        }\n    };\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_book_manager);\n        mEditText = (EditText)findViewById(R.id.editText);\n        Intent intent = new Intent(this, BookManagerService.class);\n        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);\n    }\n\n    public void onButton1Click(View view) {\n        new Thread(new Runnable() {\n\n            @Override\n            public void run() {\n                if (mRemoteBookManager != null) {\n                    try {\n                        List<Book> newList = mRemoteBookManager.getBookList();\n                    } catch (RemoteException e) {\n                        e.printStackTrace();\n                    }\n                }\n            }\n        }).start();\n\n        Intent intent = new Intent(this, TCPClientActivity.class);\n        startActivity(intent);\n    }\n\n    @Override\n    protected void onDestroy() {\n        if (mRemoteBookManager != null\n                && mRemoteBookManager.asBinder().isBinderAlive()) {\n            try {\n                Log.i(TAG, \"unregister listener:\" + mOnNewBookArrivedListener);\n                mRemoteBookManager\n                        .unregisterListener(mOnNewBookArrivedListener);\n            } catch (RemoteException e) {\n                e.printStackTrace();\n            }\n        }\n        unbindService(mConnection);\n        super.onDestroy();\n    }\n\n}\n"
  },
  {
    "path": "PluginDemo/app/src/main/java/com/didi/virtualapk/demo/aidl/BookManagerService.java",
    "content": "package com.didi.virtualapk.demo.aidl;\n\nimport java.util.List;\nimport java.util.concurrent.CopyOnWriteArrayList;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport android.app.Service;\nimport android.content.Intent;\nimport android.content.pm.PackageManager;\nimport android.os.Binder;\nimport android.os.IBinder;\nimport android.os.Parcel;\nimport android.os.RemoteCallbackList;\nimport android.os.RemoteException;\nimport android.util.Log;\n\npublic class BookManagerService extends Service {\n\n    private static final String TAG = \"BMS\";\n\n    private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false);\n\n    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>();\n    // private CopyOnWriteArrayList<IOnNewBookArrivedListener> mListenerList =\n    // new CopyOnWriteArrayList<IOnNewBookArrivedListener>();\n\n    private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<IOnNewBookArrivedListener>();\n\n    private Binder mBinder = new IBookManager.Stub() {\n\n        @Override\n        public List<Book> getBookList() throws RemoteException {\n//            SystemClock.sleep(5000);\n            return mBookList;\n        }\n\n        @Override\n        public void addBook(Book book) throws RemoteException {\n            mBookList.add(book);\n        }\n\n        public boolean onTransact(int code, Parcel data, Parcel reply, int flags)\n                throws RemoteException {\n            int check = checkCallingOrSelfPermission(\"com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE\");\n            Log.d(TAG, \"check=\" + check);\n            if (check == PackageManager.PERMISSION_DENIED) {\n                //return false;\n            }\n\n            String packageName = null;\n            String[] packages = getPackageManager().getPackagesForUid(\n                    getCallingUid());\n            if (packages != null && packages.length > 0) {\n                packageName = packages[0];\n            }\n            Log.d(TAG, \"onTransact: \" + packageName);\n            if (!packageName.startsWith(\"com.ryg\")) {\n                //return false;\n            }\n\n            return super.onTransact(code, data, reply, flags);\n        }\n\n        @Override\n        public void registerListener(IOnNewBookArrivedListener listener)\n                throws RemoteException {\n            mListenerList.register(listener);\n\n            final int N = mListenerList.beginBroadcast();\n            mListenerList.finishBroadcast();\n            Log.d(TAG, \"registerListener, current size:\" + N);\n        }\n\n        @Override\n        public void unregisterListener(IOnNewBookArrivedListener listener)\n                throws RemoteException {\n            boolean success = mListenerList.unregister(listener);\n\n            if (success) {\n                Log.d(TAG, \"unregister success.\");\n            } else {\n                Log.d(TAG, \"not found, can not unregister.\");\n            }\n            final int N = mListenerList.beginBroadcast();\n            mListenerList.finishBroadcast();\n            Log.d(TAG, \"unregisterListener, current size:\" + N);\n        };\n\n    };\n\n    @Override\n    public void onCreate() {\n        super.onCreate();\n        mBookList.add(new Book(1, \"Android\"));\n        mBookList.add(new Book(2, \"Ios\"));\n        new Thread(new ServiceWorker()).start();\n    }\n\n    @Override\n    public IBinder onBind(Intent intent) {\n        return mBinder;\n    }\n\n    @Override\n    public void onDestroy() {\n        mIsServiceDestoryed.set(true);\n        super.onDestroy();\n    }\n\n    private void onNewBookArrived(Book book) throws RemoteException {\n        mBookList.add(book);\n        final int N = mListenerList.beginBroadcast();\n        for (int i = 0; i < N; i++) {\n            IOnNewBookArrivedListener l = mListenerList.getBroadcastItem(i);\n            if (l != null) {\n                try {\n                    l.onNewBookArrived(book);\n                } catch (RemoteException e) {\n                    e.printStackTrace();\n                }\n            }\n        }\n        mListenerList.finishBroadcast();\n    }\n\n    private class ServiceWorker implements Runnable {\n        @Override\n        public void run() {\n            // do background processing here.....\n            while (!mIsServiceDestoryed.get()) {\n                try {\n                    Thread.sleep(5000);\n                } catch (InterruptedException e) {\n                    e.printStackTrace();\n                }\n                int bookId = mBookList.size() + 1;\n                Book newBook = new Book(bookId, \"new book#\" + bookId);\n                try {\n                    onNewBookArrived(newBook);\n                } catch (RemoteException e) {\n                    e.printStackTrace();\n                }\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "PluginDemo/app/src/main/java/com/didi/virtualapk/demo/binderpool/BinderPool.java",
    "content": "package com.didi.virtualapk.demo.binderpool;\n\nimport java.util.concurrent.CountDownLatch;\n\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.ServiceConnection;\nimport android.os.IBinder;\nimport android.os.RemoteException;\nimport android.util.Log;\n\npublic class BinderPool {\n    private static final String TAG = \"BinderPool\";\n    public static final int BINDER_NONE = -1;\n    public static final int BINDER_COMPUTE = 0;\n    public static final int BINDER_SECURITY_CENTER = 1;\n\n    private Context mContext;\n    private IBinderPool mBinderPool;\n    private static volatile BinderPool sInstance;\n    private CountDownLatch mConnectBinderPoolCountDownLatch;\n\n    private BinderPool(Context context) {\n        mContext = context.getApplicationContext();\n        connectBinderPoolService();\n    }\n\n    public static BinderPool getInsance(Context context) {\n        if (sInstance == null) {\n            synchronized (BinderPool.class) {\n                if (sInstance == null) {\n                    sInstance = new BinderPool(context);\n                }\n            }\n        }\n        return sInstance;\n    }\n\n    private synchronized void connectBinderPoolService() {\n        mConnectBinderPoolCountDownLatch = new CountDownLatch(1);\n        Intent service = new Intent(mContext, BinderPoolService.class);\n        mContext.bindService(service, mBinderPoolConnection,\n                Context.BIND_AUTO_CREATE);\n        try {\n            mConnectBinderPoolCountDownLatch.await();\n        } catch (InterruptedException e) {\n            e.printStackTrace();\n        }\n    }\n\n    /**\n     * query binder by binderCode from binder pool\n     * \n     * @param binderCode\n     *            the unique token of binder\n     * @return binder who's token is binderCode<br>\n     *         return null when not found or BinderPoolService died.\n     */\n    public IBinder queryBinder(int binderCode) {\n        IBinder binder = null;\n        try {\n            if (mBinderPool != null) {\n                binder = mBinderPool.queryBinder(binderCode);\n            }\n        } catch (RemoteException e) {\n            e.printStackTrace();\n        }\n        return binder;\n    }\n\n    private ServiceConnection mBinderPoolConnection = new ServiceConnection() {\n\n        @Override\n        public void onServiceDisconnected(ComponentName name) {\n            // ignored.\n        }\n\n        @Override\n        public void onServiceConnected(ComponentName name, IBinder service) {\n            mBinderPool = IBinderPool.Stub.asInterface(service);\n            try {\n                mBinderPool.asBinder().linkToDeath(mBinderPoolDeathRecipient, 0);\n            } catch (RemoteException e) {\n                e.printStackTrace();\n            }\n            mConnectBinderPoolCountDownLatch.countDown();\n        }\n    };\n\n    private IBinder.DeathRecipient mBinderPoolDeathRecipient = new IBinder.DeathRecipient() {\n        @Override\n        public void binderDied() {\n            Log.w(TAG, \"binder died.\");\n            mBinderPool.asBinder().unlinkToDeath(mBinderPoolDeathRecipient, 0);\n            mBinderPool = null;\n            connectBinderPoolService();\n        }\n    };\n\n    public static class BinderPoolImpl extends IBinderPool.Stub {\n\n        public BinderPoolImpl() {\n            super();\n        }\n\n        @Override\n        public IBinder queryBinder(int binderCode) throws RemoteException {\n            IBinder binder = null;\n            switch (binderCode) {\n            case BINDER_SECURITY_CENTER: {\n                binder = new SecurityCenterImpl();\n                break;\n            }\n            case BINDER_COMPUTE: {\n                binder = new ComputeImpl();\n                break;\n            }\n            default:\n                break;\n            }\n\n            return binder;\n        }\n    }\n\n}\n"
  },
  {
    "path": "PluginDemo/app/src/main/java/com/didi/virtualapk/demo/binderpool/BinderPoolActivity.java",
    "content": "package com.didi.virtualapk.demo.binderpool;\n\nimport com.didi.virtualapk.demo.R;\nimport android.app.Activity;\nimport android.os.Bundle;\nimport android.os.IBinder;\nimport android.os.RemoteException;\nimport android.util.Log;\n\npublic class BinderPoolActivity extends Activity {\n    private static final String TAG = \"BinderPoolActivity\";\n\n    private ISecurityCenter mSecurityCenter;\n    private ICompute mCompute;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_binder_pool);\n        new Thread(new Runnable() {\n\n            @Override\n            public void run() {\n                doWork();\n            }\n        }).start();\n    }\n\n    private void doWork() {\n        BinderPool binderPool = BinderPool.getInsance(BinderPoolActivity.this);\n        IBinder securityBinder = binderPool\n                .queryBinder(BinderPool.BINDER_SECURITY_CENTER);\n        ;\n        mSecurityCenter = (ISecurityCenter) SecurityCenterImpl\n                .asInterface(securityBinder);\n        Log.d(TAG, \"visit ISecurityCenter\");\n        String msg = \"helloworld-安卓\";\n        System.out.println(\"content:\" + msg);\n        try {\n            String password = mSecurityCenter.encrypt(msg);\n            System.out.println(\"encrypt:\" + password);\n            System.out.println(\"decrypt:\" + mSecurityCenter.decrypt(password));\n        } catch (RemoteException e) {\n            e.printStackTrace();\n        }\n\n        Log.d(TAG, \"visit ICompute\");\n        IBinder computeBinder = binderPool\n                .queryBinder(BinderPool.BINDER_COMPUTE);\n        ;\n        mCompute = ComputeImpl.asInterface(computeBinder);\n        try {\n            System.out.println(\"3+5=\" + mCompute.add(3, 5));\n        } catch (RemoteException e) {\n            e.printStackTrace();\n        }\n    }\n\n}\n"
  },
  {
    "path": "PluginDemo/app/src/main/java/com/didi/virtualapk/demo/binderpool/BinderPoolService.java",
    "content": "package com.didi.virtualapk.demo.binderpool;\n\nimport android.app.Service;\nimport android.content.Intent;\nimport android.os.Binder;\nimport android.os.IBinder;\nimport android.util.Log;\n\npublic class BinderPoolService extends Service {\n\n    private static final String TAG = \"BinderPoolService\";\n\n    private Binder mBinderPool = new BinderPool.BinderPoolImpl();\n\n    @Override\n    public void onCreate() {\n        super.onCreate();\n    }\n\n    @Override\n    public IBinder onBind(Intent intent) {\n        Log.d(TAG, \"onBind\");\n        return mBinderPool;\n    }\n\n    @Override\n    public void onDestroy() {\n        super.onDestroy();\n    }\n\n}\n"
  },
  {
    "path": "PluginDemo/app/src/main/java/com/didi/virtualapk/demo/binderpool/ComputeImpl.java",
    "content": "package com.didi.virtualapk.demo.binderpool;\n\nimport android.os.RemoteException;\n\npublic class ComputeImpl extends ICompute.Stub {\n\n    @Override\n    public int add(int a, int b) throws RemoteException {\n        return a + b;\n    }\n\n}\n"
  },
  {
    "path": "PluginDemo/app/src/main/java/com/didi/virtualapk/demo/binderpool/SecurityCenterImpl.java",
    "content": "package com.didi.virtualapk.demo.binderpool;\n\nimport android.os.RemoteException;\n\npublic class SecurityCenterImpl extends ISecurityCenter.Stub {\n\n    private static final char SECRET_CODE = '^';\n\n    @Override\n    public String encrypt(String content) throws RemoteException {\n        char[] chars = content.toCharArray();\n        for (int i = 0; i < chars.length; i++) {\n            chars[i] ^= SECRET_CODE;\n        }\n        return new String(chars);\n    }\n\n    @Override\n    public String decrypt(String password) throws RemoteException {\n        return encrypt(password);\n    }\n\n}\n"
  },
  {
    "path": "PluginDemo/app/src/main/java/com/didi/virtualapk/demo/manager/BookManager.java",
    "content": "package com.didi.virtualapk.demo.manager;\n\nimport com.didi.virtualapk.demo.aidl.IBookManager;\n\nimport android.os.IBinder;\n\npublic class BookManager {\n\n    private IBookManager mBookManager;\n\n    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {\n        @Override\n        public void binderDied() {\n            if (mBookManager == null)\n                return;\n            mBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);\n            mBookManager = null;\n            // TODO:这里重新绑定远程Service\n        }\n    };\n}\n"
  },
  {
    "path": "PluginDemo/app/src/main/java/com/didi/virtualapk/demo/manager/UserManager.java",
    "content": "package com.didi.virtualapk.demo.manager;\n\npublic class UserManager {\n\n    public static int sUserId = 1;\n\n}\n"
  },
  {
    "path": "PluginDemo/app/src/main/java/com/didi/virtualapk/demo/manualbinder/Book.java",
    "content": "package com.didi.virtualapk.demo.manualbinder;\n\nimport android.os.Parcel;\nimport android.os.Parcelable;\n\npublic class Book implements Parcelable {\n\n    public int bookId;\n    public String bookName;\n\n    public Book() {\n\n    }\n\n    public Book(int bookId, String bookName) {\n        this.bookId = bookId;\n        this.bookName = bookName;\n    }\n\n    public int describeContents() {\n        return 0;\n    }\n\n    public void writeToParcel(Parcel out, int flags) {\n        out.writeInt(bookId);\n        out.writeString(bookName);\n    }\n\n    public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>() {\n        public Book createFromParcel(Parcel in) {\n            return new Book(in);\n        }\n\n        public Book[] newArray(int size) {\n            return new Book[size];\n        }\n    };\n\n    private Book(Parcel in) {\n        bookId = in.readInt();\n        bookName = in.readString();\n    }\n\n}\n"
  },
  {
    "path": "PluginDemo/app/src/main/java/com/didi/virtualapk/demo/manualbinder/BookManagerImpl.java",
    "content": "package com.didi.virtualapk.demo.manualbinder;\n\nimport java.util.List;\n\nimport android.os.Binder;\nimport android.os.IBinder;\nimport android.os.Parcel;\nimport android.os.RemoteException;\n\npublic class BookManagerImpl extends Binder implements IBookManager {\n\n    /** Construct the stub at attach it to the interface. */\n    public BookManagerImpl() {\n        this.attachInterface(this, DESCRIPTOR);\n    }\n\n    /**\n     * Cast an IBinder object into an IBookManager interface, generating a proxy\n     * if needed.\n     */\n    public static IBookManager asInterface(IBinder obj) {\n        if ((obj == null)) {\n            return null;\n        }\n        android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);\n        if (((iin != null) && (iin instanceof IBookManager))) {\n            return ((IBookManager) iin);\n        }\n        return new BookManagerImpl.Proxy(obj);\n    }\n\n    @Override\n    public IBinder asBinder() {\n        return this;\n    }\n\n    @Override\n    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)\n            throws RemoteException {\n        switch (code) {\n        case INTERFACE_TRANSACTION: {\n            reply.writeString(DESCRIPTOR);\n            return true;\n        }\n        case TRANSACTION_getBookList: {\n            data.enforceInterface(DESCRIPTOR);\n            List<Book> result = this.getBookList();\n            reply.writeNoException();\n            reply.writeTypedList(result);\n            return true;\n        }\n        case TRANSACTION_addBook: {\n            data.enforceInterface(DESCRIPTOR);\n            Book arg0;\n            if ((0 != data.readInt())) {\n                arg0 = Book.CREATOR.createFromParcel(data);\n            } else {\n                arg0 = null;\n            }\n            this.addBook(arg0);\n            reply.writeNoException();\n            return true;\n        }\n        }\n        return super.onTransact(code, data, reply, flags);\n    }\n\n    @Override\n    public List<Book> getBookList() throws RemoteException {\n        // TODO 待实现\n        return null;\n    }\n\n    @Override\n    public void addBook(Book book) throws RemoteException {\n        // TODO 待实现\n    }\n\n    private static class Proxy implements IBookManager {\n        private IBinder mRemote;\n\n        Proxy(IBinder remote) {\n            mRemote = remote;\n        }\n\n        @Override\n        public IBinder asBinder() {\n            return mRemote;\n        }\n\n        public java.lang.String getInterfaceDescriptor() {\n            return DESCRIPTOR;\n        }\n\n        @Override\n        public List<Book> getBookList() throws RemoteException {\n            Parcel data = Parcel.obtain();\n            Parcel reply = Parcel.obtain();\n            List<Book> result;\n            try {\n                data.writeInterfaceToken(DESCRIPTOR);\n                mRemote.transact(TRANSACTION_getBookList, data, reply, 0);\n                reply.readException();\n                result = reply.createTypedArrayList(Book.CREATOR);\n            } finally {\n                reply.recycle();\n                data.recycle();\n            }\n            return result;\n        }\n\n        @Override\n        public void addBook(Book book) throws RemoteException {\n            Parcel data = Parcel.obtain();\n            Parcel reply = Parcel.obtain();\n            try {\n                data.writeInterfaceToken(DESCRIPTOR);\n                if ((book != null)) {\n                    data.writeInt(1);\n                    book.writeToParcel(data, 0);\n                } else {\n                    data.writeInt(0);\n                }\n                mRemote.transact(TRANSACTION_addBook, data, reply, 0);\n                reply.readException();\n            } finally {\n                reply.recycle();\n                data.recycle();\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "PluginDemo/app/src/main/java/com/didi/virtualapk/demo/manualbinder/IBookManager.java",
    "content": "package com.didi.virtualapk.demo.manualbinder;\n\nimport java.util.List;\n\nimport android.os.IBinder;\nimport android.os.IInterface;\nimport android.os.RemoteException;\n\npublic interface IBookManager extends IInterface {\n\n    static final String DESCRIPTOR = \"com.ryg.chapter_2.manualbinder.IBookManager\";\n\n    static final int TRANSACTION_getBookList = (IBinder.FIRST_CALL_TRANSACTION + 0);\n    static final int TRANSACTION_addBook = (IBinder.FIRST_CALL_TRANSACTION + 1);\n\n    public List<Book> getBookList() throws RemoteException;\n\n    public void addBook(Book book) throws RemoteException;\n}\n"
  },
  {
    "path": "PluginDemo/app/src/main/java/com/didi/virtualapk/demo/messenger/MessengerActivity.java",
    "content": "package com.didi.virtualapk.demo.messenger;\n\nimport com.didi.virtualapk.demo.R;\nimport com.didi.virtualapk.demo.R.layout;\nimport com.didi.virtualapk.demo.utils.MyConstants;\n\nimport android.app.Activity;\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.ServiceConnection;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.os.IBinder;\nimport android.os.Message;\nimport android.os.Messenger;\nimport android.os.RemoteException;\nimport android.util.Log;\n\npublic class MessengerActivity extends Activity {\n\n    private static final String TAG = \"MessengerActivity\";\n\n    private Messenger mService;\n    private Messenger mGetReplyMessenger = new Messenger(new MessengerHandler());\n    \n    private static class MessengerHandler extends Handler {\n        @Override\n        public void handleMessage(Message msg) {\n            switch (msg.what) {\n            case MyConstants.MSG_FROM_SERVICE:\n                Log.i(TAG, \"receive msg from Service:\" + msg.getData().getString(\"reply\"));\n                break;\n            default:\n                super.handleMessage(msg);\n            }\n        }\n    }\n\n    private ServiceConnection mConnection = new ServiceConnection() {\n        public void onServiceConnected(ComponentName className, IBinder service) {\n            mService = new Messenger(service);\n            Log.d(TAG, \"bind service\");\n            Message msg = Message.obtain(null, MyConstants.MSG_FROM_CLIENT);\n            Bundle data = new Bundle();\n            data.putString(\"msg\", \"hello, this is client.\");\n            msg.setData(data);\n            msg.replyTo = mGetReplyMessenger;\n            try {\n                mService.send(msg);\n            } catch (RemoteException e) {\n                e.printStackTrace();\n            }\n        }\n\n        public void onServiceDisconnected(ComponentName className) {\n        }\n    };\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_messenger);\n        Intent intent = new Intent(\"com.ryg.MessengerService.launch\");\n        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);\n    }\n    \n    @Override\n    protected void onDestroy() {\n        unbindService(mConnection);\n        super.onDestroy();\n    }\n}\n"
  },
  {
    "path": "PluginDemo/app/src/main/java/com/didi/virtualapk/demo/messenger/MessengerService.java",
    "content": "package com.didi.virtualapk.demo.messenger;\n\nimport com.didi.virtualapk.demo.utils.MyConstants;\n\nimport android.app.Service;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.os.IBinder;\nimport android.os.Message;\nimport android.os.Messenger;\nimport android.os.RemoteException;\nimport android.util.Log;\n\npublic class MessengerService extends Service {\n\n    private static final String TAG = \"MessengerService\";\n\n    private static class MessengerHandler extends Handler {\n        @Override\n        public void handleMessage(Message msg) {\n            switch (msg.what) {\n            case MyConstants.MSG_FROM_CLIENT:\n                Log.i(TAG, \"receive msg from Client:\" + msg.getData().getString(\"msg\"));\n                Messenger client = msg.replyTo;\n                Message relpyMessage = Message.obtain(null, MyConstants.MSG_FROM_SERVICE);\n                Bundle bundle = new Bundle();\n                bundle.putString(\"reply\", \"嗯，你的消息我已经收到，稍后会回复你。\");\n                relpyMessage.setData(bundle);\n                try {\n                    client.send(relpyMessage);\n                } catch (RemoteException e) {\n                    e.printStackTrace();\n                }\n                break;\n            default:\n                super.handleMessage(msg);\n            }\n        }\n    }\n\n    private final Messenger mMessenger = new Messenger(new MessengerHandler());\n\n    @Override\n    public IBinder onBind(Intent intent) {\n        return mMessenger.getBinder();\n    }\n\n    @Override\n    public void onCreate() {\n        super.onCreate();\n    }\n\n    @Override\n    public int onStartCommand(Intent intent, int flags, int startId) {\n        return super.onStartCommand(intent, flags, startId);\n    }\n\n}\n"
  },
  {
    "path": "PluginDemo/app/src/main/java/com/didi/virtualapk/demo/model/User.java",
    "content": "package com.didi.virtualapk.demo.model;\n\nimport java.io.Serializable;\n\nimport com.didi.virtualapk.demo.aidl.Book;\n\nimport android.os.Parcel;\nimport android.os.Parcelable;\n\npublic class User implements Parcelable, Serializable {\n    private static final long serialVersionUID = 519067123721295773L;\n\n    public int userId;\n    public String userName;\n    public boolean isMale;\n\n    public Book book;\n\n    public User() {\n    }\n\n    public User(int userId, String userName, boolean isMale) {\n        this.userId = userId;\n        this.userName = userName;\n        this.isMale = isMale;\n    }\n\n    public int describeContents() {\n        return 0;\n    }\n\n    public void writeToParcel(Parcel out, int flags) {\n        out.writeInt(userId);\n        out.writeString(userName);\n        out.writeInt(isMale ? 1 : 0);\n        out.writeParcelable(book, 0);\n    }\n\n    public static final Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>() {\n        public User createFromParcel(Parcel in) {\n            return new User(in);\n        }\n\n        public User[] newArray(int size) {\n            return new User[size];\n        }\n    };\n\n    private User(Parcel in) {\n        userId = in.readInt();\n        userName = in.readString();\n        isMale = in.readInt() == 1;\n        book = in\n                .readParcelable(Thread.currentThread().getContextClassLoader());\n    }\n\n    @Override\n    public String toString() {\n        return String.format(\n                \"User:{userId:%s, userName:%s, isMale:%s}, with child:{%s}\",\n                userId, userName, isMale, book);\n    }\n\n}\n"
  },
  {
    "path": "PluginDemo/app/src/main/java/com/didi/virtualapk/demo/provider/BookProvider.java",
    "content": "package com.didi.virtualapk.demo.provider;\n\nimport android.content.ContentProvider;\nimport android.content.ContentValues;\nimport android.content.Context;\nimport android.content.UriMatcher;\nimport android.database.Cursor;\nimport android.database.sqlite.SQLiteDatabase;\nimport android.net.Uri;\nimport android.util.Log;\n\npublic class BookProvider extends ContentProvider {\n\n    private static final String TAG = \"BookProvider\";\n\n    public static final String AUTHORITY = \"com.didi.virtualapk.demo.book.provider\";\n\n    public static final Uri BOOK_CONTENT_URI = Uri.parse(\"content://\"\n            + AUTHORITY + \"/book\");\n    public static final Uri USER_CONTENT_URI = Uri.parse(\"content://\"\n            + AUTHORITY + \"/user\");\n\n    public static final int BOOK_URI_CODE = 0;\n    public static final int USER_URI_CODE = 1;\n    private static final UriMatcher sUriMatcher = new UriMatcher(\n            UriMatcher.NO_MATCH);\n\n    static {\n        sUriMatcher.addURI(AUTHORITY, \"book\", BOOK_URI_CODE);\n        sUriMatcher.addURI(AUTHORITY, \"user\", USER_URI_CODE);\n    }\n\n    private Context mContext;\n    private SQLiteDatabase mDb;\n\n    @Override\n    public boolean onCreate() {\n        Log.d(TAG, \"onCreate, current thread:\"\n                + Thread.currentThread().getName());\n        mContext = getContext();\n        initProviderData();\n        return true;\n    }\n\n    private void initProviderData() {\n        mDb = new DbOpenHelper(mContext).getWritableDatabase();\n        mDb.execSQL(\"delete from \" + DbOpenHelper.BOOK_TABLE_NAME);\n        mDb.execSQL(\"delete from \" + DbOpenHelper.USER_TALBE_NAME);\n        mDb.execSQL(\"insert into book values(3,'Android');\");\n        mDb.execSQL(\"insert into book values(4,'Ios');\");\n        mDb.execSQL(\"insert into book values(5,'Html5');\");\n        mDb.execSQL(\"insert into user values(1,'jake',1);\");\n        mDb.execSQL(\"insert into user values(2,'jasmine',0);\");\n    }\n\n    @Override\n    public Cursor query(Uri uri, String[] projection, String selection,\n            String[] selectionArgs, String sortOrder) {\n        Log.d(TAG, \"query, current thread:\" + Thread.currentThread().getName());\n        String table = getTableName(uri);\n        if (table == null) {\n            throw new IllegalArgumentException(\"Unsupported URI: \" + uri);\n        }\n        return mDb.query(table, projection, selection, selectionArgs, null, null, sortOrder, null);\n    }\n\n    @Override\n    public String getType(Uri uri) {\n        Log.d(TAG, \"getType\");\n        return null;\n    }\n\n    @Override\n    public Uri insert(Uri uri, ContentValues values) {\n        Log.d(TAG, \"insert\");\n        String table = getTableName(uri);\n        if (table == null) {\n            throw new IllegalArgumentException(\"Unsupported URI: \" + uri);\n        }\n        mDb.insert(table, null, values);\n        mContext.getContentResolver().notifyChange(uri, null);\n        return uri;\n    }\n\n    @Override\n    public int delete(Uri uri, String selection, String[] selectionArgs) {\n        Log.d(TAG, \"delete\");\n        String table = getTableName(uri);\n        if (table == null) {\n            throw new IllegalArgumentException(\"Unsupported URI: \" + uri);\n        }\n        int count = mDb.delete(table, selection, selectionArgs);\n        if (count > 0) {\n            getContext().getContentResolver().notifyChange(uri, null);\n        }\n        return count;\n    }\n\n    @Override\n    public int update(Uri uri, ContentValues values, String selection,\n            String[] selectionArgs) {\n        Log.d(TAG, \"update\");\n        String table = getTableName(uri);\n        if (table == null) {\n            throw new IllegalArgumentException(\"Unsupported URI: \" + uri);\n        }\n        int row = mDb.update(table, values, selection, selectionArgs);\n        if (row > 0) {\n            getContext().getContentResolver().notifyChange(uri, null);\n        }\n        return row;\n    }\n\n    private String getTableName(Uri uri) {\n        String tableName = null;\n        switch (sUriMatcher.match(uri)) {\n        case BOOK_URI_CODE:\n            tableName = DbOpenHelper.BOOK_TABLE_NAME;\n            break;\n        case USER_URI_CODE:\n            tableName = DbOpenHelper.USER_TALBE_NAME;\n            break;\n            default:break;\n        }\n\n        return tableName;\n    }\n}\n"
  },
  {
    "path": "PluginDemo/app/src/main/java/com/didi/virtualapk/demo/provider/DbOpenHelper.java",
    "content": "package com.didi.virtualapk.demo.provider;\n\nimport android.content.Context;\nimport android.database.sqlite.SQLiteDatabase;\nimport android.database.sqlite.SQLiteOpenHelper;\n\npublic class DbOpenHelper extends SQLiteOpenHelper {\n\n    private static final String DB_NAME = \"book_provider.db\";\n    public static final String BOOK_TABLE_NAME = \"book\";\n    public static final String USER_TALBE_NAME = \"user\";\n\n    private static final int DB_VERSION = 3;\n\n    private String CREATE_BOOK_TABLE = \"CREATE TABLE IF NOT EXISTS \"\n            + BOOK_TABLE_NAME + \"(_id INTEGER PRIMARY KEY,\" + \"name TEXT)\";\n\n    private String CREATE_USER_TABLE = \"CREATE TABLE IF NOT EXISTS \"\n            + USER_TALBE_NAME + \"(_id INTEGER PRIMARY KEY,\" + \"name TEXT,\"\n            + \"sex INT)\";\n\n    public DbOpenHelper(Context context) {\n        super(context, DB_NAME, null, DB_VERSION);\n    }\n\n    @Override\n    public void onCreate(SQLiteDatabase db) {\n        db.execSQL(CREATE_BOOK_TABLE);\n        db.execSQL(CREATE_USER_TABLE);\n    }\n\n    @Override\n    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {\n        // TODO ignored\n    }\n\n}\n"
  },
  {
    "path": "PluginDemo/app/src/main/java/com/didi/virtualapk/demo/provider/ProviderActivity.java",
    "content": "package com.didi.virtualapk.demo.provider;\n\nimport com.didi.virtualapk.demo.R;\nimport com.didi.virtualapk.demo.aidl.Book;\nimport com.didi.virtualapk.demo.model.User;\n\nimport android.app.Activity;\nimport android.content.ContentValues;\nimport android.database.Cursor;\nimport android.net.Uri;\nimport android.os.Bundle;\nimport android.util.Log;\n\npublic class ProviderActivity extends Activity {\n    private static final String TAG = \"ProviderActivity\";\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_provider);\n        // Uri uri = Uri.parse(\"content://com.ryg.chapter_2.book.provider\");\n        // getContentResolver().query(uri, null, null, null, null);\n        // getContentResolver().query(uri, null, null, null, null);\n        // getContentResolver().query(uri, null, null, null, null);\n\n        Uri bookUri = Uri.parse(\"content://com.ryg.chapter_2.book.provider/book\");\n        ContentValues values = new ContentValues();\n        values.put(\"_id\", 6);\n        values.put(\"name\", \"程序设计的艺术\");\n        getContentResolver().insert(bookUri, values);\n        Cursor bookCursor = getContentResolver().query(bookUri, new String[]{\"_id\", \"name\"}, null, null, null);\n        while (bookCursor.moveToNext()) {\n            Book book = new Book();\n            book.bookId = bookCursor.getInt(0);\n            book.bookName = bookCursor.getString(1);\n            Log.d(TAG, \"query book:\" + book.toString());\n        }\n        bookCursor.close();\n\n        Uri userUri = Uri.parse(\"content://com.ryg.chapter_2.book.provider/user\");\n        Cursor userCursor = getContentResolver().query(userUri, new String[]{\"_id\", \"name\", \"sex\"}, null, null, null);\n        while (userCursor.moveToNext()) {\n            User user = new User();\n            user.userId = userCursor.getInt(0);\n            user.userName = userCursor.getString(1);\n            user.isMale = userCursor.getInt(2) == 1;\n            Log.d(TAG, \"query user:\" + user.toString());\n        }\n        userCursor.close();\n    }\n}\n"
  },
  {
    "path": "PluginDemo/app/src/main/java/com/didi/virtualapk/demo/socket/TCPClientActivity.java",
    "content": "package com.didi.virtualapk.demo.socket;\n\nimport java.io.*;\nimport java.net.Socket;\nimport java.sql.Date;\nimport java.text.SimpleDateFormat;\n\nimport com.didi.virtualapk.demo.R;\nimport com.didi.virtualapk.demo.utils.MyUtils;\n\nimport android.annotation.SuppressLint;\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.os.Message;\nimport android.os.SystemClock;\nimport android.support.v4.app.FragmentActivity;\nimport android.text.TextUtils;\nimport android.view.View;\nimport android.view.View.OnClickListener;\nimport android.widget.Button;\nimport android.widget.EditText;\nimport android.widget.TextView;\n\npublic class TCPClientActivity extends FragmentActivity implements OnClickListener {\n\n    private static final int MESSAGE_RECEIVE_NEW_MSG = 1;\n    private static final int MESSAGE_SOCKET_CONNECTED = 2;\n\n    private Button mSendButton;\n    private TextView mMessageTextView;\n    private EditText mMessageEditText;\n\n    private PrintWriter mPrintWriter;\n    private Socket mClientSocket;\n\n    @SuppressLint(\"HandlerLeak\")\n    private Handler mHandler = new Handler() {\n        @Override\n        public void handleMessage(Message msg) {\n            switch (msg.what) {\n            case MESSAGE_RECEIVE_NEW_MSG: {\n                mMessageTextView.setText(mMessageTextView.getText()\n                        + (String) msg.obj);\n                break;\n            }\n            case MESSAGE_SOCKET_CONNECTED: {\n                mSendButton.setEnabled(true);\n                break;\n            }\n            default:\n                break;\n            }\n        }\n    };\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_tcpclient);\n        mMessageTextView = (TextView) findViewById(R.id.msg_container);\n        mSendButton = (Button) findViewById(R.id.send);\n        mSendButton.setOnClickListener(this);\n        mMessageEditText = (EditText) findViewById(R.id.msg);\n        Intent service = new Intent(this, TCPServerService.class);\n        startService(service);\n        new Thread() {\n            @Override\n            public void run() {\n                connectTCPServer();\n            }\n        }.start();\n    }\n\n    @Override\n    protected void onDestroy() {\n        if (mClientSocket != null) {\n            try {\n                mClientSocket.shutdownInput();\n                mClientSocket.close();\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n        super.onDestroy();\n    }\n\n    @Override\n    public void onClick(View v) {\n        if (v == mSendButton) {\n            final String msg = mMessageEditText.getText().toString();\n            if (!TextUtils.isEmpty(msg) && mPrintWriter != null) {\n                String time = formatDateTime(System.currentTimeMillis());\n                final String showedMsg = \"self \" + time + \":\" + msg + \"\\n\";\n                mMessageEditText.setText(\"\");\n                mMessageTextView.setText(mMessageTextView.getText() + showedMsg);\n\n                new Thread(new Runnable() {\n                    @Override\n                    public void run() {\n                        mPrintWriter.println(msg);\n                    }\n                }).start();\n            }\n        }\n    }\n\n    @SuppressLint(\"SimpleDateFormat\")\n    private String formatDateTime(long time) {\n        return new SimpleDateFormat(\"(HH:mm:ss)\").format(new Date(time));\n    }\n\n    private void connectTCPServer() {\n        Socket socket = null;\n        while (socket == null) {\n            try {\n                socket = new Socket(\"localhost\", 8688);\n                mClientSocket = socket;\n                mPrintWriter = new PrintWriter(new BufferedWriter(\n                        new OutputStreamWriter(socket.getOutputStream())), true);\n                mHandler.sendEmptyMessage(MESSAGE_SOCKET_CONNECTED);\n                System.out.println(\"connect server success\");\n            } catch (IOException e) {\n                SystemClock.sleep(1000);\n                System.out.println(\"connect tcp server failed, retry...\");\n            }\n        }\n\n        try {\n            // 接收服务器端的消息\n            BufferedReader br = new BufferedReader(new InputStreamReader(\n                    socket.getInputStream()));\n            while (!TCPClientActivity.this.isFinishing()) {\n                String msg = br.readLine();\n                System.out.println(\"receive :\" + msg);\n                if (msg != null) {\n                    String time = formatDateTime(System.currentTimeMillis());\n                    final String showedMsg = \"server \" + time + \":\" + msg\n                            + \"\\n\";\n                    mHandler.obtainMessage(MESSAGE_RECEIVE_NEW_MSG, showedMsg)\n                            .sendToTarget();\n                }\n            }\n            System.out.println(\"quit...\");\n            MyUtils.close(mPrintWriter);\n            MyUtils.close(br);\n            socket.close();\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n    }\n}\n"
  },
  {
    "path": "PluginDemo/app/src/main/java/com/didi/virtualapk/demo/socket/TCPServerService.java",
    "content": "package com.didi.virtualapk.demo.socket;\n\nimport java.io.BufferedReader;\nimport java.io.BufferedWriter;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.io.OutputStreamWriter;\nimport java.io.PrintWriter;\nimport java.net.ServerSocket;\nimport java.net.Socket;\nimport java.util.Random;\n\nimport com.didi.virtualapk.demo.utils.MyUtils;\n\nimport android.app.Service;\nimport android.content.Intent;\nimport android.os.IBinder;\n\npublic class TCPServerService extends Service {\n\n    private boolean mIsServiceDestoryed = false;\n    private String[] mDefinedMessages = new String[] {\n            \"你好啊，哈哈\",\n            \"请问你叫什么名字呀？\",\n            \"今天北京天气不错啊，shy\",\n            \"你知道吗？我可是可以和多个人同时聊天的哦\",\n            \"给你讲个笑话吧：据说爱笑的人运气不会太差，不知道真假。\"\n    };\n\n    @Override\n    public void onCreate() {\n        new Thread(new TcpServer()).start();\n        super.onCreate();\n    }\n\n    @Override\n    public IBinder onBind(Intent intent) {\n        return null;\n    }\n\n    @Override\n    public void onDestroy() {\n        mIsServiceDestoryed = true;\n        super.onDestroy();\n    }\n\n    private class TcpServer implements Runnable {\n\n        @SuppressWarnings(\"resource\")\n        @Override\n        public void run() {\n            ServerSocket serverSocket = null;\n            try {\n                serverSocket = new ServerSocket(8688);\n            } catch (IOException e) {\n                System.err.println(\"establish tcp server failed, port:8688\");\n                e.printStackTrace();\n                return;\n            }\n\n            while (!mIsServiceDestoryed) {\n                try {\n                    // 接受客户端请求\n                    final Socket client = serverSocket.accept();\n                    System.out.println(\"accept\");\n                    new Thread() {\n                        @Override\n                        public void run() {\n                            try {\n                                responseClient(client);\n                            } catch (IOException e) {\n                                e.printStackTrace();\n                            }\n                        };\n                    }.start();\n\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n            }\n        }\n    }\n\n    private void responseClient(Socket client) throws IOException {\n        // 用于接收客户端消息\n        BufferedReader in = new BufferedReader(new InputStreamReader(\n                client.getInputStream()));\n        // 用于向客户端发送消息\n        PrintWriter out = new PrintWriter(new BufferedWriter(\n                new OutputStreamWriter(client.getOutputStream())), true);\n        out.println(\"欢迎来到聊天室！\");\n        while (!mIsServiceDestoryed) {\n            String str = in.readLine();\n            System.out.println(\"msg from client:\" + str);\n            if (str == null) {\n                break;\n            }\n            int i = new Random().nextInt(mDefinedMessages.length);\n            String msg = mDefinedMessages[i];\n            out.println(msg);\n            System.out.println(\"send :\" + msg);\n        }\n        System.out.println(\"client quit.\");\n        // 关闭流\n        MyUtils.close(out);\n        MyUtils.close(in);\n        client.close();\n    }\n\n}\n"
  },
  {
    "path": "PluginDemo/app/src/main/java/com/didi/virtualapk/demo/utils/MyConstants.java",
    "content": "package com.didi.virtualapk.demo.utils;\n\nimport android.os.Environment;\n\npublic class MyConstants {\n    public static final String CHAPTER_2_PATH = Environment\n            .getExternalStorageDirectory().getPath()\n            + \"/singwhatiwanna/chapter_2/\";\n\n    public static final String CACHE_FILE_PATH = CHAPTER_2_PATH + \"usercache\";\n\n    public static final int MSG_FROM_CLIENT = 0;\n    public static final int MSG_FROM_SERVICE = 1;\n\n}\n"
  },
  {
    "path": "PluginDemo/app/src/main/java/com/didi/virtualapk/demo/utils/MyUtils.java",
    "content": "package com.didi.virtualapk.demo.utils;\n\nimport java.io.Closeable;\nimport java.io.IOException;\nimport java.util.List;\n\nimport android.app.ActivityManager;\nimport android.app.ActivityManager.RunningAppProcessInfo;\nimport android.content.Context;\n\npublic class MyUtils {\n\n    public static String getProcessName(Context cxt, int pid) {\n        ActivityManager am = (ActivityManager) cxt\n                .getSystemService(Context.ACTIVITY_SERVICE);\n        List<RunningAppProcessInfo> runningApps = am.getRunningAppProcesses();\n        if (runningApps == null) {\n            return null;\n        }\n        for (RunningAppProcessInfo procInfo : runningApps) {\n            if (procInfo.pid == pid) {\n                return procInfo.processName;\n            }\n        }\n        return null;\n    }\n\n    public static void close(Closeable closeable) {\n        try {\n            if (closeable != null) {\n                closeable.close();\n            }\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n    }\n\n    public static void executeInThread(Runnable runnable) {\n        new Thread(runnable).start();\n    }\n\n}\n"
  },
  {
    "path": "PluginDemo/app/src/main/res/drawable/edit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    <item>\n        <shape android:shape=\"rectangle\" >\n            <solid android:color=\"#0ac39e\" />\n        </shape>\n    </item>\n\n    <item android:bottom=\"6dp\">\n        <shape android:shape=\"rectangle\" >\n            <solid android:color=\"#ffffff\" />\n        </shape>\n    </item>\n\n    <item\n        android:bottom=\"1dp\"\n        android:left=\"1dp\"\n        android:right=\"1dp\">\n        <shape android:shape=\"rectangle\" >\n            <solid android:color=\"#ffffff\" />\n        </shape>\n    </item>\n\n</layer-list>"
  },
  {
    "path": "PluginDemo/app/src/main/res/layout/activity_binder_pool.xml",
    "content": "<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    tools:context=\"${relativePackage}.${activityClass}\" >\n\n    <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/hello_world\" />\n\n</RelativeLayout>\n"
  },
  {
    "path": "PluginDemo/app/src/main/res/layout/activity_book_manager.xml",
    "content": "<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:padding=\"10dp\"\n    tools:context=\"${relativePackage}.${activityClass}\" >\n\n    <TextView\n        android:id=\"@+id/textView1\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/hello_world\" />\n\n    <Button\n        android:id=\"@+id/button1\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@+id/textView1\"\n        android:onClick=\"onButton1Click\"\n        android:text=\"打开另一个插件Activity\" />\n\n    <ScrollView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_below=\"@+id/button1\"\n        android:layout_centerHorizontal=\"true\">\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\" >\n        <EditText\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:inputType=\"textMultiLine\"\n            android:ems=\"10\"\n            android:clickable=\"false\"\n            android:layout_alignParentLeft=\"true\"\n            android:id=\"@+id/editText\" />\n        </LinearLayout>\n    </ScrollView>\n\n\n\n</RelativeLayout>"
  },
  {
    "path": "PluginDemo/app/src/main/res/layout/activity_main.xml",
    "content": "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"#ffffff\"\n    android:orientation=\"vertical\" >\n\n    <Button\n        android:id=\"@+id/button1\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"open activity B\" />\n\n    <EditText\n        android:id=\"@+id/editText1\"\n        android:layout_width=\"150dp\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"A\"\n        android:ems=\"10\" >\n\n        <requestFocus />\n    </EditText>\n\n    <TextView\n        android:text=\"ActivityA\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:id=\"@+id/textView\" />\n\n</LinearLayout>"
  },
  {
    "path": "PluginDemo/app/src/main/res/layout/activity_messenger.xml",
    "content": "<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    tools:context=\"${relativePackage}.${activityClass}\" >\n\n    <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/hello_world\" />\n\n</RelativeLayout>\n"
  },
  {
    "path": "PluginDemo/app/src/main/res/layout/activity_provider.xml",
    "content": "<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    tools:context=\"${relativePackage}.${activityClass}\" >\n\n    <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/hello_world\" />\n\n</RelativeLayout>\n"
  },
  {
    "path": "PluginDemo/app/src/main/res/layout/activity_second.xml",
    "content": "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"#ffffff\"\n    android:orientation=\"vertical\" >\n\n    <Button\n        android:id=\"@+id/button1\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"open activity C\" />\n\n    <TextView\n        android:id=\"@+id/textView1\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"Activity B\" />\n\n</LinearLayout>"
  },
  {
    "path": "PluginDemo/app/src/main/res/layout/activity_tcpclient.xml",
    "content": "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"#ffffff\"\n    android:orientation=\"vertical\"\n    android:padding=\"5dp\"\n    tools:context=\"${relativePackage}.${activityClass}\" >\n\n    <ScrollView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\" >\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\" >\n\n            <TextView\n                android:id=\"@+id/msg_container\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\" />\n        </LinearLayout>\n    </ScrollView>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\" >\n\n        <EditText\n            android:id=\"@+id/msg\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"35dp\"\n            android:layout_weight=\"1\"\n            android:background=\"@drawable/edit\"\n            android:ems=\"10\"\n            android:padding=\"5dp\" />\n\n        <Button\n            android:id=\"@+id/send\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:enabled=\"false\"\n            android:focusable=\"false\"\n            android:text=\"发送\" />\n    </LinearLayout>\n\n</LinearLayout>"
  },
  {
    "path": "PluginDemo/app/src/main/res/layout/activity_third.xml",
    "content": "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"#ffffff\"\n    android:orientation=\"vertical\" >\n\n    <Button\n        android:id=\"@+id/button1\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"open activity B\" />\n\n    <TextView\n        android:id=\"@+id/textView1\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"Activity C\" />\n\n</LinearLayout>"
  },
  {
    "path": "PluginDemo/app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <color name=\"reveal_color\">#1b000000</color>\n\n</resources>"
  },
  {
    "path": "PluginDemo/app/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <string name=\"app_name\">Chapter_2</string>\n    <string name=\"hello_world\">Hello world!</string>\n    <string name=\"title_activity_messenger\">MessengerActivity</string>\n    <string name=\"title_activity_book_manager\">BookManagerActivity</string>\n    <string name=\"title_activity_provider\">ProviderActivity</string>\n    <string name=\"title_activity_tcpserver\">TCPServerActivity</string>\n    <string name=\"title_activity_tcpclient\">TCPClientActivity</string>\n    <string name=\"title_activity_binder_pool\">BinderPoolActivity</string>\n\n</resources>\n"
  },
  {
    "path": "PluginDemo/app/src/main/res/values/styles.xml",
    "content": "<resources>\r\n\r\n    <!--\r\n        Base application theme, dependent on API level. This theme is replaced\r\n        by AppBaseTheme from res/values-vXX/styles.xml on newer devices.\r\n    -->\r\n    <style name=\"AppBaseTheme\" parent=\"Theme.AppCompat.Light.DarkActionBar\">\r\n        <!--\r\n            Theme customizations available in newer API levels can go in\r\n            res/values-vXX/styles.xml, while customizations related to\r\n            backward-compatibility can go here.\r\n        -->\r\n    </style>\r\n\r\n    <!-- Application theme. -->\r\n    <style name=\"AppTheme\" parent=\"AppBaseTheme\">\r\n        <!-- All customizations that are NOT specific to a particular API-level can go here. -->\r\n    </style>\r\n\r\n    <!-- Common button styles -->\r\n    <style name=\"AppTheme.Button\">\r\n        <item name=\"android:layout_width\">match_parent</item>\r\n        <item name=\"android:layout_height\">44dp</item>\r\n        <item name=\"android:textSize\">18dp</item>\r\n    </style>\r\n\r\n    <style name=\"AppTheme.Button.Green\">\r\n        <item name=\"android:background\">#0ac39e</item>\r\n        <item name=\"android:textColor\">#ffffff</item>\r\n    </style>\r\n\r\n    <style name=\"AppTheme.Button.Highlight\">\r\n        <item name=\"android:background\">#4185f2</item>\r\n        <item name=\"android:textColor\">#ffffff</item>\r\n    </style>\r\n\r\n\r\n</resources>"
  },
  {
    "path": "PluginDemo/build.gradle",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\nbuildscript {\n    repositories {\n        google()\n        jcenter()\n    }\n    dependencies {\n        classpath 'com.android.tools.build:gradle:3.0.0'\n        classpath 'com.didi.virtualapk:gradle:0.9.8.6'\n    }\n}\n\next {\n    VERSION_COMPILE_SDK = 27\n    VERSION_BUILD_TOOLS = '26.0.2'\n\n    VERSION_MIN_SDK = 15\n    VERSION_TARGET_SDK = 25\n\n    SOURCE_COMPATIBILITY = JavaVersion.VERSION_1_7\n}\n\nallprojects {\n    repositories {\n        google()\n        jcenter()\n    }\n}\n"
  },
  {
    "path": "PluginDemo/gradle/wrapper/gradle-wrapper.properties",
    "content": "#Mon Dec 28 10:00:20 PST 2015\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-4.6-all.zip\n"
  },
  {
    "path": "PluginDemo/gradlew",
    "content": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn ( ) {\n    echo \"$*\"\n}\n\ndie ( ) {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\nesac\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules\nfunction splitJvmOpts() {\n    JVM_OPTS=(\"$@\")\n}\neval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\nJVM_OPTS[${#JVM_OPTS[*]}]=\"-Dorg.gradle.appname=$APP_BASE_NAME\"\n\nexec \"$JAVACMD\" \"${JVM_OPTS[@]}\" -classpath \"$CLASSPATH\" org.gradle.wrapper.GradleWrapperMain \"$@\"\n"
  },
  {
    "path": "PluginDemo/gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:init\r\n@rem Get command-line arguments, handling Windowz variants\r\n\r\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\r\nif \"%@eval[2+2]\" == \"4\" goto 4NT_args\r\n\r\n:win9xME_args\r\n@rem Slurp the command line arguments.\r\nset CMD_LINE_ARGS=\r\nset _SKIP=2\r\n\r\n:win9xME_args_slurp\r\nif \"x%~1\" == \"x\" goto execute\r\n\r\nset CMD_LINE_ARGS=%*\r\ngoto execute\r\n\r\n:4NT_args\r\n@rem Get arguments from the 4NT Shell from JP Software\r\nset CMD_LINE_ARGS=%$\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "PluginDemo/make.sh",
    "content": "./gradlew clean assemblePlugin\nadb push app/build/outputs/apk/app-beijing-release-unsigned.apk /sdcard/Test.apk\nadb shell am force-stop com.didi.virtualapk\nadb shell am start -n com.didi.virtualapk/com.didi.virtualapk.MainActivity\n"
  },
  {
    "path": "PluginDemo/settings.gradle",
    "content": "include ':app'\n"
  },
  {
    "path": "README.md",
    "content": "# Android 9.0 supported! Please use [the lastest release](https://github.com/didi/VirtualAPK/releases).\n\n# <img src=\"imgs/va-logo.png\" width=\"200px\" align=\"center\" alt=\"VirtualAPK\"/>\n[![license](http://img.shields.io/badge/license-Apache2.0-brightgreen.svg?style=flat)](https://github.com/didi/VirtualAPK/blob/master/LICENSE)\n[![Release Version](https://img.shields.io/badge/release-0.9.8-red.svg)](https://github.com/didi/VirtualAPK/releases)\n[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/didi/VirtualAPK/pulls)\n\nVirtualAPK is a powerful yet lightweight plugin framework for Android. It can dynamically load and run an APK file (we call it `LoadedPlugin`) seamlessly as an installed application. Developers can use any Class, Resources, Activity, Service, Receiver and Provider in `LoadedPlugin` as if they are registered in app's manifest file.\n\n![VirtualAPK](imgs/va.png)\n\n# Supported Features\n\n| Feature | Detail |\n|:-------------|:-------------:|\n| Supported components | Activity, Service, Receiver and Provider |\n| Manually register components in AndroidManifest.xml | No need |\n| Access host app classes and resources | Supported |\n| PendingIntent | Supported |\n| Supported Android features | Almost all features |\n| Compatibility | Almost all devices |\n| Building system | Gradle plugin |\n| Supported Android versions | API Level 15+ |\n\n# Getting started\n\n## Host Project\n\nAdd a dependency in `build.gradle` in root of host project as following.\n\n``` java\ndependencies {\n    classpath 'com.didi.virtualapk:gradle:0.9.8.6'\n}\n```\n\nApply plugin in application module of `build.gradle`.\n\n```\napply plugin: 'com.didi.virtualapk.host'\n```\n\nCompile VirtualAPK in application module of `build.gradle`.\n\n``` java\ncompile 'com.didi.virtualapk:core:0.9.8'\n```\n\nInitialize `PluginManager` in `YourApplication::attachBaseContext()`.\n\n``` java\n@Override\nprotected void attachBaseContext(Context base) {\n    super.attachBaseContext(base);\n    PluginManager.getInstance(base).init();\n}\n```\n\nModify proguard rules to keep VirtualAPK related files.\n\n```\n-keep class com.didi.virtualapk.internal.VAInstrumentation { *; }\n-keep class com.didi.virtualapk.internal.PluginContentResolver { *; }\n\n-dontwarn com.didi.virtualapk.**\n-dontwarn android.**\n-keep class android.** { *; }\n```\n\nFinally, load an APK and have fun!\n\n``` java\nString pluginPath = Environment.getExternalStorageDirectory().getAbsolutePath().concat(\"/Test.apk\");\nFile plugin = new File(pluginPath);\nPluginManager.getInstance(base).loadPlugin(plugin);\n\n// Given \"com.didi.virtualapk.demo\" is the package name of plugin APK, \n// and there is an activity called `MainActivity`.\nIntent intent = new Intent();\nintent.setClassName(\"com.didi.virtualapk.demo\", \"com.didi.virtualapk.demo.MainActivity\");\nstartActivity(intent);\n```\n\n## Plugin Project\n\nAdd a dependency in `build.gradle` in root of plugin project as following.\n\n``` java\ndependencies {\n    classpath 'com.didi.virtualapk:gradle:0.9.8.6'\n}\n```\n\nApply plugin in application module of `build.gradle`.\n\n```\napply plugin: 'com.didi.virtualapk.plugin'\n```\n\nConfig VirtualAPK. Remember to put following lines at the end of `build.gradle`.\n\n```\nvirtualApk {\n    packageId = 0x6f             // The package id of Resources.\n    targetHost='source/host/app' // The path of application module in host project.\n    applyHostMapping = true      // [Optional] Default value is true. \n}\n```\n\n# Developer guide\n\n* API document [wiki](https://github.com/didi/VirtualAPK/wiki)\n* Sample project [PluginDemo](https://github.com/didi/VirtualAPK/tree/master/PluginDemo)\n* Read [core library source code](https://github.com/didi/VirtualAPK/tree/master/CoreLibrary)\n* Read [Release notes](RELEASE-NOTES.md)\n\n# Known issues\n\n* Notifications with custom layout are not supported in plugin.\n* Transition animations with animation resources are not supported in plugin.\n\n# Contributing\n\nWelcome to contribute by creating issues or sending pull requests. See [Contributing Guide](CONTRIBUTING.md) for guidelines.\n\n# Who is using VirtualAPK?\n\n<img src=\"imgs/didi.png\" width=\"78px\" align=\"center\" alt=\"滴滴出行\"/> <img src=\"imgs/uber-china.png\" width=\"78px\" align=\"center\" alt=\"Uber中国\"/>\n\n# License\n\nVirtualAPK is licensed under the Apache License 2.0. See the [LICENSE](LICENSE) file.\n"
  },
  {
    "path": "RELEASE-NOTES.md",
    "content": "# Release Notes\n\n## com.didi.virtualapk:core:0.9.0\n开源的第一个版本，支持了几乎所有 Android 特性，目前已经被大家广泛使用。\n\n## com.didi.virtualapk:core:0.9.1\n##### 1. 最初，为了让程序更加健壮，当我们通过无效的 Intent 来启动插件中的 Activity，这个时候程序不会报错。尽管我们的初衷是好的，但现在我们觉得这种方式太过友好了，不利于bug的排查。\n\n```改动```：现在，启动无效的 Activity 会直接抛出 ActivityNotFoundException。\n\n##### 2. 最初，为了性能考虑，当启动插件中的四大组件时，要求 Intent 中的包名必须和目标插件一致，否则就会抛出 RuntimeException。从反馈来看，这貌似给大家造成了一些困扰，所以我们优化了这一块，采用全局搜索策略，牺牲一点点效率来解决这个问题。\n\n```改动```：现在，启动插件中的组件将不再依赖 Intent 中的包名。\n\n```新增```：现在，如果一个组件（Activity/Service/Receiver）在宿主和插件中同时存在，那么只有插件中的组件会被唤起。\n\n##### 3. 现在 VirtualAPK 将全面支持 Android O。\n\n```改动```：兼容 Android O。\n\n##### 4. 修复了一个 bug，该bug曾经导致：如果先后加载了A和B两个插件，并且存在通过A的Resources对象来访问B中资源这种情形，这个时候资源访问就会失败，造成 Crash。\n\n```改动```：我们修复了这个bug，使得通过任何 Resources 对象均可以访问所有插件以及宿主的资源。\n\n##### 5. 现在 VirtualAPK 将开始 hook Android N 的 Resources 对象，尽管这个改动是多余的。事实上，从Android L开始，仅仅替换ContextImpl和PluginContext中的资源就可以满足绝大多数使用场景，为了避免开发者心存疑问，我们统一了这一行为，任何版本都hook资源。\n\n```改动```：hook Android N 的资源，尽管是多余的。\n\n## com.didi.virtualapk:core:0.9.5\n1. 修复多个bug，强烈建议升级至此版本，以前版本不再维护。\n2. 与 com.didi.virtualapk:gradle:0.9.8.2及以上版本 搭配使用，支持官方 Data Binding。\n\n## com.didi.virtualapk:core:0.9.6\n1. 修复部分空指针问题。\n\n## com.didi.virtualapk:core:0.9.6\n1. 代码重构。\n2. 修复部分空指针bug。\n3. 适配Android P。\n4. 修复webview初始化后插件资源不可用的bug。\n\n## VirtualAPK 的构建部分已经开源了，![点击这里查看](https://github.com/didi/VirtualAPK/tree/master/virtualapk-gradle-plugin)\n\n## com.didi.virtualapk:gradle:0.9.8.2\n1. 适配android gradle 3.0.0\n2. 修复多个bug，强烈建议升级至此版本，以前版本不再维护。\n3. 插件工程需要定义 productFlavors。\n\n## com.didi.virtualapk:gradle:0.9.8.3\n1. 兼容不定义 productFlavors 的配置。\n\n## com.didi.virtualapk:gradle:0.9.8.4\n1. 修复当插件依赖library module时构建失败的bug。\n2. 修复依赖本地aar时构建失败的bug。\n3. 修复当插件自定义attr属性时id错误的bug。\n\n## com.didi.virtualapk:gradle:0.9.8.6\n1. 适配com.android.tools.build:gradle:3.1.0。\n2. 修复当插件未定义attr资源时anim资源找不到的bug。\n"
  },
  {
    "path": "app/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "app/build.gradle",
    "content": "apply plugin: 'com.android.application'\napply plugin: 'com.didi.virtualapk.host'\n\nandroid {\n    compileSdkVersion VERSION_COMPILE_SDK\n    buildToolsVersion VERSION_BUILD_TOOLS\n\n    defaultConfig {\n        applicationId \"com.didi.virtualapk\"\n        minSdkVersion VERSION_MIN_SDK\n        targetSdkVersion VERSION_TARGET_SDK\n        versionCode 3\n        versionName \"1.0.0\"\n    }\n    compileOptions {\n        sourceCompatibility SOURCE_COMPATIBILITY\n    }\n\n    signingConfigs {\n        release {\n            storeFile file(\"../keystore/test.keystore\")\n            storePassword \"test123456\"\n            keyAlias \"test\"\n            keyPassword \"test123456\"\n        }\n    }\n\n    buildTypes {\n        debug {\n            minifyEnabled false\n            shrinkResources false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n        release {\n            minifyEnabled true\n            shrinkResources true\n            signingConfig signingConfigs.release\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n\n    lintOptions {\n        abortOnError false\n    }\n}\n\ndependencies {\n    implementation fileTree(dir: 'libs', include: ['*.jar'])\n    testImplementation 'junit:junit:4.12'\n\n    implementation 'com.android.support:appcompat-v7:23.4.0'\n    implementation 'com.didi.virtualapk:core:0.9.8'\n//    implementation project (':CoreLibrary')\n\n}"
  },
  {
    "path": "app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /Users/didi/Library/Android/sdk/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n-dontusemixedcaseclassnames\n-dontskipnonpubliclibraryclasses\n-dontpreverify\n-verbose\n-dontoptimize\n-dontshrink\n-allowaccessmodification\n-keepattributes *Annotation*\n-keepattributes Exceptions\n-keepattributes JavascriptInterface\n-keepattributes LineNumberTable\n-keepattributes Signature\n-keepattributes SourceFile\n\n-keep public class * extends android.app.Activity\n-keep public class * extends android.app.Application\n-keep public class * extends android.app.Service\n-keep public class * extends android.content.BroadcastReceiver\n-keep public class * extends android.content.ContentProvider\n-keep public class * extends android.app.backup.BackupAgentHelper\n-keep public class * extends android.preference.Preference\n-keep public class com.android.vending.licensing.ILicensingService\n-dontnote com.android.vending.licensing.ILicensingService\n\n-keepclasseswithmembernames class * {\n    native <methods>;\n}\n\n-keepclassmembers enum * {\n    public static <fields>;\n    public static **[] values();\n    public static ** valueOf(java.lang.String);\n}\n\n-keep class com.google.**{*;}\n-keep class sun.misc.Unsafe { *; }\n-keepnames class * implements java.io.Serializable\n-keepclassmembers class * implements java.io.Serializable {\n    static final long serialVersionUID;\n    private static final java.io.ObjectStreamField[] serialPersistentFields;\n    !static !transient <fields>;\n    private void writeObject(java.io.ObjectOutputStream);\n    private void readObject(java.io.ObjectInputStream);\n    java.lang.Object writeReplace();\n    java.lang.Object readResolve();\n}\n\n-keep class * extends java.util.ListResourceBundle {\n    protected Object[][] getContents();\n}\n\n-keepclassmembers class * extends android.view.View {\n    public <init>(android.content.Context);\n    public <init>(android.content.Context);\n    public <init>(android.content.Context, android.util.AttributeSet);\n    public <init>(android.content.Context, android.util.AttributeSet, int);\n    void set*(***);\n    *** get*();\n}\n\n-keepclassmembers class * extends android.app.Activity {\n    public void *(android.view.View);\n}\n\n-keep class * implements android.os.Parcelable {\n    public static final android.os.Parcelable$Creator *;\n}\n\n-keep class **.R$* {\n    public static <fields>;\n}\n\n-keep @android.support.annotation.Keep class *\n-keep @android.support.annotation.Keep interface *\n\n-keepclassmembers class * {\n    @android.support.annotation.Keep <methods>;\n}\n\n-keepclassmembers class * {\n    @android.support.annotation.Keep <fields>;\n}\n\n-keepclassmembers interface * {\n    @android.support.annotation.Keep <methods>;\n}\n\n-keepclassmembers class * {\n    @android.webkit.JavascriptInterface <methods>;\n}\n\n-assumenosideeffects class android.util.Log {\n    public static boolean isLoggable(java.lang.String,int);\n    public static int v(...);\n    public static int i(...);\n    public static int w(...);\n    public static int d(...);\n    public static int e(...);\n}\n\n#######################################################################\n-keep class com.didi.virtualapk.internal.VAInstrumentation { *; }\n-keep class com.didi.virtualapk.internal.PluginContentResolver { *; }\n\n-dontwarn com.didi.virtualapk.**\n-dontwarn android.**\n-keep class android.** { *; }"
  },
  {
    "path": "app/src/androidTest/java/com/didi/virtualapk/ApplicationTest.java",
    "content": "package com.didi.virtualapk;\n\nimport android.app.Application;\nimport android.test.ApplicationTestCase;\n\n/**\n * <a href=\"http://d.android.com/tools/testing/testing_android.html\">Testing Fundamentals</a>\n */\npublic class ApplicationTest extends ApplicationTestCase<Application> {\n    public ApplicationTest() {\n        super(Application.class);\n    }\n}"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.didi.virtualapk\">\n\n    <uses-permission android:name=\"android.permission.SET_WALLPAPER_HINTS\" />\n    <uses-permission android:name=\"android.permission.SET_WALLPAPER\" />\n    <uses-permission android:name=\"android.permission.BATTERY_STATS\" />\n    <uses-permission android:name=\"android.permission.GET_PACKAGE_SIZE\" />\n    <uses-permission android:name=\"android.permission.GET_TASKS\" />\n    <uses-permission android:name=\"android.permission.RESTART_PACKAGES\" />\n    <uses-permission android:name=\"android.permission.READ_PHONE_STATE\" />\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n    <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />\n    <uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\" />\n    <uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\" />\n    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />\n    <uses-permission android:name=\"android.permission.READ_LOGS\" />\n    <uses-permission android:name=\"android.permission.READ_SMS\" />\n    <uses-permission android:name=\"android.permission.READ_CONTACTS\" />\n    <uses-permission android:name=\"android.permission.WRITE_SMS\" />\n    <uses-permission android:name=\"android.permission.CHANGE_WIFI_MULTICAST_STATE\" />\n    <uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\" />\n    <uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\" />\n    <uses-permission android:name=\"android.permission.KILL_BACKGROUND_PROCESSES\" />\n    <uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\" />\n    <uses-permission android:name=\"android.permission.RECORD_AUDIO\" />\n    <uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\" />\n    <uses-permission android:name=\"android.permission.CHANGE_CONFIGURATION\" />\n    <uses-permission android:name=\"com.android.launcher.permission.INSTALL_SHORTCUT\" />\n    <uses-permission android:name=\"android.permission.EXPAND_STATUS_BAR\" />\n\n    <application\n        android:name=\".VAApplication\"\n        android:allowBackup=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/HostTheme\">\n        <activity android:name=\".MainActivity\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "app/src/main/java/com/didi/virtualapk/MainActivity.java",
    "content": "package com.didi.virtualapk;\n\nimport android.Manifest;\nimport android.app.AlertDialog;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.pm.PackageManager;\nimport android.database.Cursor;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.os.Environment;\nimport android.support.annotation.NonNull;\nimport android.support.v7.app.AppCompatActivity;\nimport android.util.Log;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.webkit.WebView;\nimport android.webkit.WebViewClient;\nimport android.widget.LinearLayout;\nimport android.widget.TextView;\nimport android.widget.Toast;\n\nimport com.didi.virtualapk.internal.LoadedPlugin;\nimport com.didi.virtualapk.internal.PluginContentResolver;\n\nimport java.io.File;\n\npublic class MainActivity extends AppCompatActivity {\n\n    private static final int PERMISSION_REQUEST_CODE_STORAGE = 20171222;\n\n    private static final String TAG = \"MainActivity\";\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n        TextView textView = (TextView)findViewById(R.id.textView);\n        String cpuArch;\n        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {\n            cpuArch = Build.SUPPORTED_ABIS[0];\n        } else {\n            cpuArch = Build.CPU_ABI;\n        }\n        textView.setText(cpuArch);\n        Log.d(\"ryg\", \"onCreate cpu arch is \"+ cpuArch);\n        Log.d(\"ryg\", \"onCreate classloader is \"+ getClassLoader());\n\n        if (hasPermission()) {\n            Log.d(TAG,\"loadPlugin\");\n\n            this.loadPlugin(this);\n        } else {\n            requestPermission();\n        }\n    }\n\n    @Override\n    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {\n        if (PERMISSION_REQUEST_CODE_STORAGE == requestCode) {\n            if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {\n                requestPermission();\n            } else {\n                this.loadPlugin(this);\n            }\n            return;\n        }\n        super.onRequestPermissionsResult(requestCode, permissions, grantResults);\n    }\n\n\n    private boolean hasPermission() {\n\n        Log.d(TAG,\"hasPermission\");\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n            return checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;\n        }\n        return true;\n    }\n\n    private void requestPermission() {\n\n        Log.d(TAG,\"requestPermission\");\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n            requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_REQUEST_CODE_STORAGE);\n        }\n    }\n\n\n    public void onButtonClick(View v) {\n        if (v.getId() == R.id.button) {\n            final String pkg = \"com.didi.virtualapk.demo\";\n            if (PluginManager.getInstance(this).getLoadedPlugin(pkg) == null) {\n                Toast.makeText(this, \"plugin [com.didi.virtualapk.demo] not loaded\", Toast.LENGTH_SHORT).show();\n                return;\n            }\n\n            // test Activity and Service\n            Intent intent = new Intent();\n            intent.setClassName(this, \"com.didi.virtualapk.demo.aidl.BookManagerActivity\");\n            startActivity(intent);\n\n            // test ContentProvider\n            Uri bookUri = Uri.parse(\"content://com.didi.virtualapk.demo.book.provider/book\");\n            LoadedPlugin plugin = PluginManager.getInstance(this).getLoadedPlugin(pkg);\n            bookUri = PluginContentResolver.wrapperUri(plugin, bookUri);\n\n            Cursor bookCursor = getContentResolver().query(bookUri, new String[]{\"_id\", \"name\"}, null, null, null);\n            if (bookCursor != null) {\n                while (bookCursor.moveToNext()) {\n                    int bookId = bookCursor.getInt(0);\n                    String bookName = bookCursor.getString(1);\n                    Log.d(\"ryg\", \"query book:\" + bookId + \", \" + bookName);\n                }\n                bookCursor.close();\n            }\n        } else if (v.getId() == R.id.about) {\n            showAbout();\n        } else if (v.getId() == R.id.webview) {\n            LinearLayout linearLayout = (LinearLayout) v.getParent();\n            WebView webView = new WebView(this);\n            linearLayout.addView(webView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1));\n            webView.setWebViewClient(new WebViewClient());\n            webView.loadUrl(\"http://github.com/didi/VirtualAPK\");\n        }\n    }\n\n    private void loadPlugin(Context base) {\n        if (!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {\n            Toast.makeText(this, \"sdcard was NOT MOUNTED!\", Toast.LENGTH_SHORT).show();\n        }\n        PluginManager pluginManager = PluginManager.getInstance(base);\n        File apk = new File(Environment.getExternalStorageDirectory(), \"Test.apk\");\n        if (apk.exists()) {\n            try {\n                pluginManager.loadPlugin(apk);\n                Log.i(TAG, \"Loaded plugin from apk: \" + apk);\n            } catch (Exception e) {\n                e.printStackTrace();\n            }\n        } else {\n            try {\n                File file = new File(base.getFilesDir(), \"Test.apk\");\n                java.io.InputStream inputStream = base.getAssets().open(\"Test.apk\", 2);\n                java.io.FileOutputStream outputStream = new java.io.FileOutputStream(file);\n                byte[] buf = new byte[1024];\n                int len;\n    \n                while ((len = inputStream.read(buf)) > 0) {\n                    outputStream.write(buf, 0, len);\n                }\n    \n                outputStream.close();\n                inputStream.close();\n                pluginManager.loadPlugin(file);\n                Log.i(TAG, \"Loaded plugin from assets: \" + file);\n            } catch (Exception e) {\n                e.printStackTrace();\n            }\n        }\n    }\n\n    private void showAbout() {\n        AlertDialog.Builder builder = new AlertDialog.Builder(this);\n        builder.setMessage(R.string.about_detail);\n        builder.setTitle(\"关于\");\n        builder.setNegativeButton(\"好的\", null);\n        builder.show();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/didi/virtualapk/VAApplication.java",
    "content": "package com.didi.virtualapk;\n\nimport android.app.Application;\nimport android.content.Context;\nimport android.util.Log;\n\n/**\n * Created by renyugang on 16/8/10.\n */\npublic class VAApplication extends Application {\n\n    @Override\n    protected void attachBaseContext(Context base) {\n        super.attachBaseContext(base);\n        long start = System.currentTimeMillis();\n        PluginManager.getInstance(base).init();\n        Log.d(\"ryg\", \"use time:\" + (System.currentTimeMillis() - start));\n    }\n\n    @Override\n    public void onCreate() {\n        super.onCreate();\n    }\n}\n"
  },
  {
    "path": "app/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    android:paddingBottom=\"@dimen/activity_vertical_margin\"\n    android:paddingLeft=\"@dimen/activity_horizontal_margin\"\n    android:paddingRight=\"@dimen/activity_horizontal_margin\"\n    android:paddingTop=\"@dimen/activity_vertical_margin\"\n    tools:context=\"com.didi.virtualapk.MainActivity\">\n\n    <TextView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"Hello World!\"\n        android:id=\"@+id/textView\" />\n\n    <Button\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/open_plugin\"\n        android:id=\"@+id/button\"\n        android:onClick=\"onButtonClick\"\n        android:layout_marginTop=\"10dp\" />\n\n    <Button\n        android:text=\"@string/about\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:onClick=\"onButtonClick\"\n        android:layout_marginTop=\"10dp\"\n        android:id=\"@+id/about\" />\n\n    <Button\n        android:text=\"@string/webview\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:onClick=\"onButtonClick\"\n        android:layout_marginTop=\"10dp\"\n        android:id=\"@+id/webview\" />\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#3F51B5</color>\n    <color name=\"colorPrimaryDark\">#303F9F</color>\n    <color name=\"colorAccent\">#FF4081</color>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/dimens.xml",
    "content": "<resources>\n    <!-- Default screen margins, per the Android Design guidelines. -->\n    <dimen name=\"activity_horizontal_margin\">16dp</dimen>\n    <dimen name=\"activity_vertical_margin\">16dp</dimen>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">VirtualAPK</string>\n    <string name=\"open_plugin\">加载插件</string>\n    <string name=\"about\">关于</string>\n    <string name=\"webview\">Append WebView</string>\n    <string name=\"about_detail\">VirtualAPK 是一款由滴滴出行研发的 Android 插件化框架，项目地址：https://github.com/didi/VirtualAPK</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"HostTheme\" parent=\"Theme.AppCompat.Light.DarkActionBar\">\n        <!-- Customize your theme here. -->\n        <item name=\"colorPrimary\">@color/colorPrimary</item>\n        <item name=\"colorPrimaryDark\">@color/colorPrimaryDark</item>\n        <item name=\"colorAccent\">@color/colorAccent</item>\n    </style>\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-en/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">VirtualAPK-EN</string>\n    <string name=\"open_plugin\">open plugin</string>\n    <string name=\"about\">about</string>\n    <string name=\"webview\">Append WebView</string>\n    <string name=\"about_detail\">VirtualAPK is a plugin framework powered by DiDi company for Android，see the source code : https://github.com/didi/VirtualAPK</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-w820dp/dimens.xml",
    "content": "<resources>\n    <!-- Example customization of dimensions originally defined in res/values/dimens.xml\n         (such as screen margins) for screens with more than 820dp of available width. This\n         would include 7\" and 10\" devices in landscape (~960dp and ~1280dp respectively). -->\n    <dimen name=\"activity_horizontal_margin\">64dp</dimen>\n</resources>\n"
  },
  {
    "path": "app/src/test/java/com/didi/virtualapk/ExampleUnitTest.java",
    "content": "package com.didi.virtualapk;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\n/**\n * To work on unit tests, switch the Test Artifact in the Build Variants view.\n */\npublic class ExampleUnitTest {\n    @Test\n    public void addition_isCorrect() throws Exception {\n        assertEquals(4, 2 + 2);\n    }\n}"
  },
  {
    "path": "build.gradle",
    "content": "buildscript {\n    System.properties['com.android.build.gradle.overrideVersionCheck'] = 'true'\n\n    repositories {\n        jcenter()\n        google()\n    }\n    dependencies {\n        classpath 'com.android.tools.build:gradle:3.0.0'\n        classpath 'com.didi.virtualapk:gradle:0.9.8.6'\n\n        classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.6'\n        classpath 'com.github.dcendents:android-maven-gradle-plugin:1.4.1'\n    }\n}\n\next {\n    VERSION_COMPILE_SDK = 27\n    VERSION_BUILD_TOOLS = '26.0.2'\n\n    VERSION_MIN_SDK = 15\n    VERSION_TARGET_SDK = 25\n\n    SOURCE_COMPATIBILITY = JavaVersion.VERSION_1_7\n}\n\nallprojects {\n    repositories {\n        mavenCentral()\n        jcenter()\n        google()\n    }\n}\n\ntask clean(type: Delete) {\n    delete rootProject.buildDir\n}\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "#Mon Dec 05 15:48:51 CST 2016\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-4.6-all.zip\n"
  },
  {
    "path": "gradle.properties",
    "content": "## Project-wide Gradle settings.\n#\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n#\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\n# Default value: -Xmx1024m -XX:MaxPermSize=256m\n# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8\n#\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\n#Mon Jun 12 18:17:16 CST 2017"
  },
  {
    "path": "gradlew",
    "content": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn ( ) {\n    echo \"$*\"\n}\n\ndie ( ) {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\nesac\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules\nfunction splitJvmOpts() {\n    JVM_OPTS=(\"$@\")\n}\neval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\nJVM_OPTS[${#JVM_OPTS[*]}]=\"-Dorg.gradle.appname=$APP_BASE_NAME\"\n\nexec \"$JAVACMD\" \"${JVM_OPTS[@]}\" -classpath \"$CLASSPATH\" org.gradle.wrapper.GradleWrapperMain \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:init\r\n@rem Get command-line arguments, handling Windowz variants\r\n\r\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\r\nif \"%@eval[2+2]\" == \"4\" goto 4NT_args\r\n\r\n:win9xME_args\r\n@rem Slurp the command line arguments.\r\nset CMD_LINE_ARGS=\r\nset _SKIP=2\r\n\r\n:win9xME_args_slurp\r\nif \"x%~1\" == \"x\" goto execute\r\n\r\nset CMD_LINE_ARGS=%*\r\ngoto execute\r\n\r\n:4NT_args\r\n@rem Get arguments from the 4NT Shell from JP Software\r\nset CMD_LINE_ARGS=%$\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "keystore/test.cer",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDXTCCAkWgAwIBAgIEWWZdhTANBgkqhkiG9w0BAQsFADBfMQ4wDAYDVQQGDAV6aF9jbjEQMA4G\r\nA1UECBMHYmVpamluZzEQMA4GA1UEBxMHYmVpamluZzENMAsGA1UEChMEZGlkaTEMMAoGA1UECxMD\r\nYXBwMQwwCgYDVQQDEwNyeWcwHhcNMTcwNTA4MDkxOTM0WhcNNDQwOTIzMDkxOTM0WjBfMQ4wDAYD\r\nVQQGDAV6aF9jbjEQMA4GA1UECBMHYmVpamluZzEQMA4GA1UEBxMHYmVpamluZzENMAsGA1UEChME\r\nZGlkaTEMMAoGA1UECxMDYXBwMQwwCgYDVQQDEwNyeWcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw\r\nggEKAoIBAQC6d9Pq0+qedUEuHgYhIuROidDWVMSECAq2+MTlX1kn1gGgAKiAt7P6c864YlFCB8Mq\r\n4Q8R8HR9SMprTD4NPUt7nsf3jQiFSOH0f9PvOI3fWMLNCpUq64GthcwHd3bVqOy6r5h7f3Jd9Pi3\r\nPmkhFs6FwCNaFoPkQioDdnE+8bNKOC53rsx5gqHFzs0xpYYZLz7+tP7OSm9KQe19VBwPOI3MxgPD\r\n6dbEWRNI4FPk6yNC3k9Fq8HrtCzlfapmEsdxpBjrQ3DUeegvtfQhqZtLujj9X894cuoCR05eowoi\r\n6mamKRdRErQ7JMhkUYMwJQvKYLuLr1JElEYHJ5iT46Ph8FVzAgMBAAGjITAfMB0GA1UdDgQWBBQ2\r\nyNaLVuMnjEKQiMSz+/FjbjPYHTANBgkqhkiG9w0BAQsFAAOCAQEAnFXbS07Zu8vphohhZ1nHPeNN\r\n8GGcbSllO7xwbONPng+uTFGldIUhPP9lmKkw/5bA1T/57tjbfmNph9li6MfuwO4CECkG2WTQNpr9\r\nY8ZcvM0sh3unx9wHkPJQ43Cvp6vCxsaor/ALT4UbTTHtWRpwcZUBxsgdNQDpwM1u0ye5uMd5E/P6\r\nhlsdI0GdtnMdyZrCInwhQbBEHYpSerVyRpCRq9pa72kP865fDB9zCyLSekctvfZBYkEKEMX9QVfs\r\nd9tJ7vxqABxUjNPYWlAxLlRPf/Dz4NDyeYHuTjEZW46NErqJ8GQGqvS+MG4OehqV1SCIQoHkCJQ2\r\no7w3hv2XVmdozw==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "settings.gradle",
    "content": "include ':app'\ninclude ':CoreLibrary'\ninclude ':AndroidStub'"
  },
  {
    "path": "virtualapk-gradle-plugin/build.gradle",
    "content": "buildscript {\n    repositories {\n        mavenLocal()\n        google()\n        mavenCentral()\n        jcenter()\n    }\n    dependencies {\n        classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.6'\n        classpath 'com.github.dcendents:android-maven-gradle-plugin:1.4.1'\n    }\n}\n\napply plugin: 'java'\napply plugin: 'groovy'\n\ntasks.withType(GroovyCompile) {\n    sourceCompatibility = '1.7'\n    targetCompatibility = '1.7'\n}\n\nrepositories {\n    mavenLocal()\n    google()\n    mavenCentral()\n    jcenter()\n}\n\nconfigurations {\n    provided\n}\n\nsourceSets {\n    main {\n        compileClasspath += configurations.provided\n    }\n}\n\ndependencies {\n    compile gradleApi()\n    compile localGroovy()\n    compile 'com.google.guava:guava:19.0'\n    compile 'commons-io:commons-io:1.4'\n    compile 'commons-codec:commons-codec:1.6'\n    compile 'org.ow2.asm:asm:4.0'\n    compile 'org.javassist:javassist:3.18.2-GA'\n    compile 'com.android.tools.build:gradle:3.0.0'\n}\n\n\n\napply from: 'upload.gradle'"
  },
  {
    "path": "virtualapk-gradle-plugin/gradle/wrapper/gradle-wrapper.properties",
    "content": "#Wed Sep 27 15:30:56 CST 2017\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-4.1-all.zip\n"
  },
  {
    "path": "virtualapk-gradle-plugin/gradle.properties",
    "content": "GROUP_ID=com.didi.virtualapk\nARTIFACT_ID=gradle\nVERSION=0.9.8.6\n"
  },
  {
    "path": "virtualapk-gradle-plugin/gradlew",
    "content": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn ( ) {\n    echo \"$*\"\n}\n\ndie ( ) {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\nesac\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules\nfunction splitJvmOpts() {\n    JVM_OPTS=(\"$@\")\n}\neval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\nJVM_OPTS[${#JVM_OPTS[*]}]=\"-Dorg.gradle.appname=$APP_BASE_NAME\"\n\nexec \"$JAVACMD\" \"${JVM_OPTS[@]}\" -classpath \"$CLASSPATH\" org.gradle.wrapper.GradleWrapperMain \"$@\"\n"
  },
  {
    "path": "virtualapk-gradle-plugin/gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:init\r\n@rem Get command-line arguments, handling Windowz variants\r\n\r\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\r\nif \"%@eval[2+2]\" == \"4\" goto 4NT_args\r\n\r\n:win9xME_args\r\n@rem Slurp the command line arguments.\r\nset CMD_LINE_ARGS=\r\nset _SKIP=2\r\n\r\n:win9xME_args_slurp\r\nif \"x%~1\" == \"x\" goto execute\r\n\r\nset CMD_LINE_ARGS=%*\r\ngoto execute\r\n\r\n:4NT_args\r\n@rem Get arguments from the 4NT Shell from JP Software\r\nset CMD_LINE_ARGS=%$\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/groovy/com.didi.virtualapk/BasePlugin.groovy",
    "content": "package com.didi.virtualapk\n\nimport com.android.build.gradle.AppExtension\nimport com.android.build.gradle.AppPlugin\nimport com.android.build.gradle.internal.TaskFactory\nimport com.android.build.gradle.internal.TaskManager\nimport com.android.build.gradle.internal.api.ApplicationVariantImpl\nimport com.android.build.gradle.internal.variant.VariantFactory\nimport com.android.builder.core.VariantType\nimport com.didi.virtualapk.tasks.AssemblePlugin\nimport com.didi.virtualapk.utils.Log\nimport com.didi.virtualapk.utils.Reflect\nimport org.gradle.api.Action\nimport org.gradle.api.Plugin\nimport org.gradle.api.Project\nimport org.gradle.api.Task\nimport org.gradle.internal.reflect.Instantiator\nimport org.gradle.tooling.provider.model.ToolingModelBuilderRegistry\nimport org.gradle.util.NameMatcher\n\nimport javax.inject.Inject\nimport java.lang.reflect.InvocationHandler\nimport java.lang.reflect.Method\nimport java.lang.reflect.Proxy\n\n/**\n * Base class of VirtualApk plugin, we create assemblePlugin task here\n * @author zhengtao\n */\npublic abstract class BasePlugin implements Plugin<Project> {\n\n    protected Project project\n    protected Instantiator instantiator\n    protected TaskFactory taskFactory\n\n    boolean checkVariantFactoryInvoked\n\n    @Inject\n    public BasePlugin(Instantiator instantiator, ToolingModelBuilderRegistry registry) {\n        this.instantiator = instantiator\n    }\n\n    @Override\n    public void apply(Project project) {\n        this.project = project\n        project.ext.set(Constants.GRADLE_3_1_0, false)\n\n        try {\n            Class.forName('com.android.builder.core.VariantConfiguration')\n        } catch (Throwable e) {\n            // com.android.tools.build:gradle:3.1.0\n            project.ext.set(Constants.GRADLE_3_1_0, true)\n        }\n\n        AppPlugin appPlugin = project.plugins.findPlugin(AppPlugin)\n\n        Reflect reflect = Reflect.on(appPlugin.variantManager)\n\n        VariantFactory variantFactory = Proxy.newProxyInstance(this.class.classLoader, [VariantFactory.class] as Class[],\n                new InvocationHandler() {\n                    Object delegate = reflect.get('variantFactory')\n\n                    @Override\n                    Object invoke(Object proxy, Method method, Object[] args) throws Throwable {\n                        if ('preVariantWork' == method.name) {\n                            checkVariantFactoryInvoked = true\n                            Log.i 'VAPlugin', \"Evaluating VirtualApk's configurations...\"\n                            boolean isBuildingPlugin = evaluateBuildingPlugin(appPlugin, project)\n                            beforeCreateAndroidTasks(isBuildingPlugin)\n                        }\n\n                        return method.invoke(delegate, args)\n                    }\n                })\n        reflect.set('variantFactory', variantFactory)\n\n        project.extensions.create('virtualApk', VAExtention)\n\n        if (project.extensions.extraProperties.get(Constants.GRADLE_3_1_0)) {\n            TaskManager taskManager = Reflect.on(appPlugin).field('taskManager').get()\n            taskFactory = taskManager.getTaskFactory()\n        } else {\n            taskFactory = Reflect.on('com.android.build.gradle.internal.TaskContainerAdaptor')\n                    .create(project.tasks)\n                    .get()\n        }\n        project.afterEvaluate {\n\n            if (!checkVariantFactoryInvoked) {\n                throw new RuntimeException('Evaluating VirtualApk\\'s configurations has failed!')\n            }\n\n            android.applicationVariants.each { ApplicationVariantImpl variant ->\n                if ('release' == variant.buildType.name) {\n                    String variantAssembleTaskName = variant.variantData.scope.getTaskName('assemble', 'Plugin')\n                    def final variantPluginTaskName = createPluginTaskName(variantAssembleTaskName)\n                    final def configAction = new AssemblePlugin.ConfigAction(project, variant)\n\n                    taskFactory.create(variantPluginTaskName, AssemblePlugin, configAction)\n\n                    Action action = new Action<Task>() {\n                        @Override\n                        void execute(Task task) {\n                            task.dependsOn(variantPluginTaskName)\n                        }\n                    }\n\n                    if (project.extensions.extraProperties.get(Constants.GRADLE_3_1_0)) {\n                        taskFactory.configure(\"assemblePlugin\", action)\n                    } else {\n                        taskFactory.named(\"assemblePlugin\", action)\n                    }\n                }\n            }\n        }\n\n        project.task('assemblePlugin', dependsOn: \"assembleRelease\", group: 'build', description: 'Build plugin apk')\n    }\n\n    String createPluginTaskName(String name) {\n        if (name == 'assembleReleasePlugin') {\n            return '_assemblePlugin'\n        }\n        return name.replace('Release', '')\n    }\n\n    private boolean evaluateBuildingPlugin(AppPlugin appPlugin, Project project) {\n        def startParameter = project.gradle.startParameter\n        def targetTasks = startParameter.taskNames\n\n        def pluginTasks = ['assemblePlugin'] as List<String>\n\n        appPlugin.variantManager.buildTypes.each {\n            def buildType = it.value.buildType\n            if ('release' != buildType.name) {\n                return\n            }\n            if (appPlugin.variantManager.productFlavors.isEmpty()) {\n                return\n            }\n\n            appPlugin.variantManager.productFlavors.each {\n                String variantName\n                if (project.extensions.extraProperties.get(Constants.GRADLE_3_1_0)) {\n                    variantName = Reflect.on('com.android.build.gradle.internal.core.VariantConfiguration')\n                            .call('computeFullName', it.key, buildType, VariantType.DEFAULT, null)\n                            .get()\n                } else {\n                    variantName = Reflect.on('com.android.builder.core.VariantConfiguration')\n                            .call('computeFullName', it.key, buildType, VariantType.DEFAULT, null)\n                            .get()\n                }\n                def variantPluginTaskName = createPluginTaskName(\"assemble${variantName.capitalize()}Plugin\".toString())\n                pluginTasks.add(variantPluginTaskName)\n            }\n        }\n\n//        pluginTasks.each {\n//            Log.i 'VAPlugin', \"pluginTask: ${it}\"\n//        }\n\n        boolean isBuildingPlugin = false\n        NameMatcher nameMatcher = new NameMatcher()\n        targetTasks.every {\n            int index = it.lastIndexOf(\":\");\n            String task = index >= 0 ? it.substring(index + 1) : it\n            String taskName = nameMatcher.find(task, pluginTasks)\n            if (taskName != null) {\n//                Log.i 'VAPlugin', \"Found task name '${taskName}' by given name '${it}'\"\n                isBuildingPlugin = true\n                return false\n            }\n            return true\n        }\n\n        return isBuildingPlugin\n    }\n\n    protected abstract void beforeCreateAndroidTasks(boolean isBuildingPlugin)\n\n    protected final VAExtention getVirtualApk() {\n        return this.project.virtualApk\n    }\n\n    protected final AppExtension getAndroid() {\n        return this.project.android\n    }\n}\n"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/groovy/com.didi.virtualapk/Constants.groovy",
    "content": "package com.didi.virtualapk\n\npublic final class Constants {\n    public static final String GRADLE_3_1_0 = 'va.gradle.3.1.0'\n}"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/groovy/com.didi.virtualapk/VAExtention.groovy",
    "content": "package com.didi.virtualapk\n\nimport com.android.build.gradle.internal.scope.VariantScope\nimport com.didi.virtualapk.collector.dependence.AarDependenceInfo\nimport com.didi.virtualapk.collector.dependence.DependenceInfo\nimport com.didi.virtualapk.utils.CheckList\nimport com.didi.virtualapk.utils.Log\n\n/**\n * VirtualApk extension for plugin projects.\n *\n * @author zhengtao\n */\npublic class VAExtention {\n\n    /** Custom defined resource package Id **/\n    private int packageId\n    /** Local host application directory or Jenkins build number, fetch config files from here **/\n    private String targetHost\n    /** Apply Host Proguard Mapping or not**/\n    private boolean applyHostMapping = true\n    /** Exclude dependent aar or jar **/\n    private Collection<String> excludes = new HashSet<>()\n    private boolean forceUseHostDependences\n    private ArrayList<String> warningList = new ArrayList<>()\n    /**  host dependence file - version.txt*/\n    public File hostDependenceFile\n    //group:artifact -> group:artifact:version\n    private Map hostDependencies\n\n    private HashSet<String> flagTable = new HashSet<>()\n\n    private final Map<String, VAContext> vaContextMap = [] as HashMap\n\n    public VAContext getVaContext(String variantName) {\n        synchronized (vaContextMap) {\n            VAContext vaContext = vaContextMap.get(variantName)\n            if (vaContext == null) {\n                vaContext = new VAContext(variantName)\n                vaContextMap.put(variantName, vaContext)\n            }\n            return vaContext\n        }\n    }\n\n    public int getPackageId() {\n        return packageId\n    }\n\n    public void setPackageId(int packageId) {\n        this.packageId = packageId\n    }\n\n    public String getTargetHost() {\n        return targetHost\n    }\n\n    public void setTargetHost(String targetHost) {\n        this.targetHost = targetHost\n    }\n\n    public boolean getApplyHostMapping() {\n        return applyHostMapping\n    }\n\n    public void setApplyHostMapping(boolean applyHostMapping) {\n        this.applyHostMapping = applyHostMapping\n    }\n\n    Collection<String> getExcludes() {\n        return excludes\n    }\n\n    public void setExcludes(final String...filters) {\n        if (null != filters) {\n            for (final String filter :filters) {\n                this.excludes.add(filter)\n            }\n        }\n    }\n\n    public boolean getForceUseHostDependences() {\n        return forceUseHostDependences\n    }\n\n    public void setForceUseHostDependences(boolean forceUseHostDependences) {\n        this.forceUseHostDependences = forceUseHostDependences\n    }\n\n    public Map getHostDependencies() {\n        if (hostDependencies == null) {\n            hostDependencies = [] as LinkedHashMap\n            hostDependenceFile.splitEachLine('\\\\s+', { columns ->\n                String id = columns[0]\n                int index1 = id.indexOf(':')\n                int index2 = id.lastIndexOf(':')\n                def module = [group: 'unspecified', name: 'unspecified', version: 'unspecified']\n\n                if (index1 < 0 || index2 < 0 || index1 == index2) {\n                    Log.e('Dependencies', \"Parsed error: [${id}] -> ${module}\")\n                    return\n                }\n\n                if (index1 > 0) {\n                    module.group = id.substring(0, index1)\n                }\n                if (index2 - index1 > 0) {\n                    module.name = id.substring(index1 + 1, index2)\n                }\n                if (id.length() - index2 > 1) {\n                    module.version = id.substring(index2 + 1)\n                }\n\n                hostDependencies.put(\"${module.group}:${module.name}\", module)\n            })\n        }\n        return hostDependencies\n    }\n\n    public void addWarning(String detail) {\n        warningList.add(detail)\n    }\n\n    public void printWarning(String tag) {\n        warningList.each {\n            Log.i(tag, it)\n        }\n    }\n\n    public void setFlag(String key, boolean value) {\n        if (value) {\n            flagTable.add(key)\n        } else {\n            flagTable.remove(key)\n        }\n    }\n\n    public boolean getFlag(String key) {\n        return flagTable.contains(key)\n    }\n\n    public static class VAContext {\n\n        /**  host Symbol file - Host_R.txt */\n        public File hostSymbolFile\n\n        public Collection<DependenceInfo> stripDependencies = []\n        public Collection<AarDependenceInfo> retainedAarLibs = []\n\n        /** Variant application id */\n        public String packageName\n\n        /** Package path for java classes */\n        public String packagePath\n\n        /** File of split R.java */\n        public File splitRJavaFile\n\n        public final CheckList checkList\n\n        VAContext(String variantName) {\n            checkList = new CheckList(variantName)\n        }\n\n        public File getBuildDir(VariantScope scope) {\n            return new File(scope.getGlobalScope().getIntermediatesDir(),\n                    \"virtualapk/\" + scope.getVariantConfiguration().getDirName())\n        }\n\n    }\n}"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/groovy/com.didi.virtualapk/VAHostPlugin.groovy",
    "content": "package com.didi.virtualapk\n\nimport com.android.build.gradle.api.ApplicationVariant\nimport com.android.build.gradle.internal.api.ApplicationVariantImpl\nimport com.android.build.gradle.internal.ide.ArtifactDependencyGraph\nimport com.android.build.gradle.internal.pipeline.TransformTask\nimport com.android.build.gradle.internal.publishing.AndroidArtifacts\nimport com.android.build.gradle.internal.transforms.ProGuardTransform\nimport com.android.build.gradle.tasks.ProcessAndroidResources\nimport com.didi.virtualapk.utils.FileUtil\nimport com.didi.virtualapk.utils.Log\nimport com.didi.virtualapk.utils.Reflect\nimport com.google.common.collect.ImmutableMap\nimport org.gradle.api.Plugin\nimport org.gradle.api.Project\nimport org.gradle.api.artifacts.component.ComponentIdentifier\nimport org.gradle.api.artifacts.component.ModuleComponentIdentifier\nimport org.gradle.api.artifacts.component.ProjectComponentIdentifier\n\n/**\n * VirtualAPK gradle plugin for host project,\n * The primary role of this class is to save the\n * information needed to build the plugin apk.\n *\n * @author zhengtao\n */\npublic class VAHostPlugin implements Plugin<Project> {\n\n    public static final String TAG = 'VAHostPlugin'\n    Project project\n    File vaHostDir\n\n    @Override\n    public void apply(Project project) {\n\n        this.project = project\n        project.ext.set(Constants.GRADLE_3_1_0, false)\n\n        try {\n            Class.forName('com.android.builder.core.VariantConfiguration')\n        } catch (Throwable e) {\n            // com.android.tools.build:gradle:3.1.0\n            project.ext.set(Constants.GRADLE_3_1_0, true)\n        }\n        \n        //The target project must be a android application module\n        if (!project.plugins.hasPlugin('com.android.application')) {\n            Log.e(TAG, \"application required!\")\n            return;\n        }\n\n        vaHostDir = new File(project.getBuildDir(), \"VAHost\")\n\n        project.afterEvaluate {\n\n            project.android.applicationVariants.each { ApplicationVariantImpl variant ->\n                generateDependencies(variant)\n                backupHostR(variant)\n                backupProguardMapping(variant)\n                //keepResourceIds(variant)\n            }\n        }\n\n    }\n\n    /**\n     * Generate ${project.buildDir}/VAHost/versions.txt\n     */\n    def generateDependencies(ApplicationVariantImpl applicationVariant) {\n\n        applicationVariant.javaCompile.doLast {\n\n            FileUtil.saveFile(vaHostDir, \"allVersions\", {\n                List<String> deps = new ArrayList<String>()\n                project.configurations.each {\n                    String configName = it.name\n\n                    if (!it.canBeResolved) {\n                        deps.add(\"${configName} -> NOT READY\")\n                        return\n                    }\n\n                    try {\n                        it.resolvedConfiguration.resolvedArtifacts.each {\n                            deps.add(\"${configName} -> id: ${it.moduleVersion.id}, type: ${it.type}, ext: ${it.extension}\")\n                        }\n\n                    } catch (Exception e) {\n                        deps.add(\"${configName} -> ${e}\")\n                    }\n                }\n                Collections.sort(deps)\n                return deps\n            })\n\n            FileUtil.saveFile(vaHostDir, \"versions\", {\n                List<String> deps = new ArrayList<String>()\n                Log.i TAG, \"Used compileClasspath: ${applicationVariant.name}\"\n                Set<ArtifactDependencyGraph.HashableResolvedArtifactResult> compileArtifacts\n                if (project.extensions.extraProperties.get(Constants.GRADLE_3_1_0)) {\n                    ImmutableMap<String, String> buildMapping = Reflect.on('com.android.build.gradle.internal.ide.ModelBuilder')\n                            .call('computeBuildMapping', project.gradle)\n                            .get()\n                    compileArtifacts = ArtifactDependencyGraph.getAllArtifacts(\n                            applicationVariant.variantData.scope, AndroidArtifacts.ConsumedConfigType.COMPILE_CLASSPATH, null, buildMapping)\n                } else {\n                    compileArtifacts = ArtifactDependencyGraph.getAllArtifacts(\n                            applicationVariant.variantData.scope, AndroidArtifacts.ConsumedConfigType.COMPILE_CLASSPATH, null)\n                }\n\n                compileArtifacts.each { ArtifactDependencyGraph.HashableResolvedArtifactResult artifact ->\n                    ComponentIdentifier id = artifact.id.componentIdentifier\n                    if (id instanceof ProjectComponentIdentifier) {\n                        deps.add(\"${id.projectPath.replace(':', '')}:${ArtifactDependencyGraph.getVariant(artifact)}:unspecified ${artifact.file.length()}\")\n\n                    } else if (id instanceof ModuleComponentIdentifier) {\n                        deps.add(\"${id.group}:${id.module}:${id.version} ${artifact.file.length()}\")\n\n                    } else {\n                        deps.add(\"${artifact.id.displayName.replace(':', '')}:unspecified:unspecified ${artifact.file.length()}\")\n                    }\n                }\n\n                Collections.sort(deps)\n                return deps\n            })\n        }\n\n    }\n\n    /**\n     * Save R symbol file\n     */\n    def backupHostR(ApplicationVariant applicationVariant) {\n\n        final ProcessAndroidResources aaptTask = this.project.tasks[\"process${applicationVariant.name.capitalize()}Resources\"]\n\n        aaptTask.doLast {\n            project.copy {\n                from aaptTask.textSymbolOutputFile\n                into vaHostDir\n                rename { \"Host_R.txt\" }\n            }\n        }\n    }\n\n    /**\n     * Save proguard mapping\n     */\n    def backupProguardMapping(ApplicationVariant applicationVariant) {\n\n        if (applicationVariant.buildType.minifyEnabled) {\n            TransformTask proguardTask = project.tasks[\"transformClassesAndResourcesWithProguardFor${applicationVariant.name.capitalize()}\"]\n\n            ProGuardTransform proguardTransform = proguardTask.transform\n            File mappingFile = proguardTransform.mappingFile\n\n            proguardTask.doLast {\n                project.copy {\n                    from mappingFile\n                    into vaHostDir\n                }\n            }\n        }\n\n    }\n\n    /**\n     * Keep the host app resource id same with last publish, in order to compatible with the published plugin\n     */\n     def keepResourceIds(variant) {\n\n\n        def VIRTUAL_APK_DIR = new File([project.rootDir, 'virtualapk'].join(File.separator))\n        System.println(\"keepResource start\")\n        def mergeResourceTask = project.tasks[\"merge${variant.name.capitalize()}Resources\"]\n        def vaDir = new File(VIRTUAL_APK_DIR, \"${variant.dirName}\")\n\n        def rSymbole = new File(vaDir, 'Host-R.txt')\n        if (!rSymbole.exists()) {\n            return\n        }\n\n        File resDir = new File(project.projectDir, ['src', 'main', 'res'].join(File.separator))\n        File mergedValuesDir = new File(mergeResourceTask.outputDir, 'values')\n\n        mergeResourceTask.doFirst {\n            generateIdsXml(rSymbole, resDir)\n        }\n\n        mergeResourceTask.doLast {\n\n            def mergeXml = new File(variant.mergeResources.incrementalFolder, 'merger.xml')\n            def typeEntries = [:] as Map<String, Set>\n\n            collectResourceEntries(mergeXml, resDir.path, typeEntries)\n\n            generatePublicXml(rSymbole, mergedValuesDir, typeEntries)\n\n            new File(resDir, 'values/ids.xml').delete()\n        }\n    }\n\n\n    def collectResourceEntries(final File mergeXml, final String projectResDir, final Map typeEntries) {\n\n        collectAarResourceEntries(null, projectResDir, mergeXml, typeEntries)\n\n        File aarDir = new File(project.buildDir, \"intermediates/exploded-aar\")\n\n        project.configurations.compile.resolvedConfiguration.resolvedArtifacts.each {\n            if (it.extension == 'aar') {\n                def moduleVersion = it.moduleVersion.id\n                def resPath = new File(aarDir,\"${moduleVersion.group}/${moduleVersion.name}/${moduleVersion.version}/res\")\n                collectAarResourceEntries(moduleVersion.version, resPath.path, mergeXml, typeEntries)\n            }\n        }\n    }\n\n\n    def collectAarResourceEntries(String aarVersion, String resPath, File mergeXml, final Map typeEntries) {\n        final def merger = new XmlParser().parse(mergeXml)\n        def filter = aarVersion == null ? {\n            it.@config == 'main' || it.@config == 'release'\n        } : {\n            it.@config = aarVersion\n        }\n        def dataSets = merger.dataSet.findAll filter\n        dataSets.each {\n            it.source.each {\n                if (it.@path != resPath) {\n                    return\n                }\n                it.file.each {\n                    def String type = it.@type\n                    if (type != null) {\n                        def entrySet = getEntriesSet(type, typeEntries)\n                        if (!entrySet.contains(it.@name)) {\n                            entrySet.add(it.@name)\n                        }\n                    } else {\n                        it.children().each {\n                            type = it.name()\n                            def name = it.@name\n                            if (type.endsWith('-array')) {\n                                type = 'array'\n                            } else if (type == 'item'){\n                                type = it.@type\n                            } else if (type == 'declare-styleable'){\n                                return\n                            }\n                            def entrySet = getEntriesSet(type, typeEntries)\n                            if (!entrySet.contains(name)) {\n                                entrySet.add(name)\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    def generatePublicXml(rSymboleFile, destDir, hostResourceEntries) {\n        def styleNameMap = [:] as Map\n        def styleEntries = hostResourceEntries['style']\n        styleEntries.each {\n            def _styleName = it.replaceAll('\\\\.', '_')\n            styleNameMap.put(_styleName, it)\n        }\n\n        def lastSplitType\n        new File(destDir, \"public.xml\").withPrintWriter { pw ->\n            pw.println \"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?>\"\n            pw.println \"<resources>\"\n            rSymboleFile.eachLine { line ->\n                def values = line.split(' ')\n                def type = values[1]\n                if (type == 'styleable') {\n                    return\n                }\n                if (type == 'style') {\n                    if (styleNameMap.containsKey(values[2])) {\n                        pw.println \"\\t<public type=\\\"${type}\\\" name=\\\"${styleNameMap.get(values[2])}\\\" id=\\\"${values[3]}\\\" />\"\n                    }\n                    return\n                }\n                //ID does not filter and remains redundant\n                if (type == 'id') {\n                    pw.println \"\\t<public type=\\\"${type}\\\" name=\\\"${values[2]}\\\" id=\\\"${values[3]}\\\" />\"\n                    return\n                }\n\n                //Only keep resources' Id that are present in the current project\n                Set entries = hostResourceEntries[type]\n                if (entries != null && entries.contains(values[2])) {\n                    pw.println \"\\t<public type=\\\"${type}\\\" name=\\\"${values[2]}\\\" id=\\\"${values[3]}\\\" />\"\n                } else {\n                    if (entries == null) {\n                        if (type != lastSplitType) {\n                            lastSplitType = type\n                            println \">>>> ${type} is splited\"\n                        }\n\n                    } else {\n                        if (type != 'attr'){\n                            println \">>>> ${type} : ${values[2]} is deleted\"\n                        }\n\n                    }\n                }\n\n            }\n            pw.print \"</resources>\"\n        }\n    }\n\n    def generateIdsXml(rSymboleFile, resDir) {\n        new File(resDir, \"values/ids.xml\").withPrintWriter { pw ->\n            pw.println \"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?>\"\n            pw.println \"<resources>\"\n            rSymboleFile.eachLine { line ->\n                def values = line.split(' ')\n                if (values[1] == 'id')\n                    pw.println \"\\t<item type=\\\"id\\\" name=\\\"${values[2]}\\\"/>\"\n            }\n            pw.print \"</resources>\"\n        }\n    }\n\n\n    def Set<String> getEntriesSet (final String type, final Map typeEntries) {\n        def entries = typeEntries[type]\n        if (entries == null) {\n            entries = [] as Set<String>\n            typeEntries[type] = entries\n        }\n        return entries\n    }\n\n\n}\n"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/groovy/com.didi.virtualapk/VAPlugin.groovy",
    "content": "package com.didi.virtualapk\n\nimport com.android.build.gradle.AppPlugin\nimport com.android.build.gradle.internal.api.ApplicationVariantImpl\nimport com.android.build.gradle.options.BooleanOption\nimport com.android.build.gradle.options.ProjectOptions\nimport com.didi.virtualapk.hooker.*\nimport com.didi.virtualapk.transform.StripClassAndResTransform\nimport com.didi.virtualapk.utils.FileBinaryCategory\nimport com.didi.virtualapk.utils.Log\nimport com.didi.virtualapk.utils.Reflect\nimport org.gradle.api.InvalidUserDataException\nimport org.gradle.api.Project\nimport org.gradle.api.artifacts.Configuration\nimport org.gradle.api.artifacts.DependencyResolveDetails\nimport org.gradle.api.artifacts.ResolutionStrategy\nimport org.gradle.internal.reflect.Instantiator\nimport org.gradle.tooling.provider.model.ToolingModelBuilderRegistry\n\nimport javax.inject.Inject\n\n/**\n * VirtualAPK gradle plugin for plugin project\n *\n * @author zhengtao\n */\n\nclass VAPlugin extends BasePlugin {\n\n    //Files be retained after host apk build\n    //private def hostFileNames = ['versions', 'R.txt', 'mapping.txt', 'versions.txt', 'Host_R.txt'] as Set\n\n    /**\n     * Stores files generated by the host side and is used when building plugin apk\n     */\n    private def hostDir\n\n    protected boolean isBuildingPlugin = false\n    private boolean checked\n\n    /**\n     * TaskHooker manager, registers hookers when apply invoked\n     */\n    private TaskHookerManager taskHookerManager\n\n    private StripClassAndResTransform stripClassAndResTransform\n\n    @Inject\n    public VAPlugin(Instantiator instantiator, ToolingModelBuilderRegistry registry) {\n        super(instantiator, registry)\n    }\n\n    @Override\n    protected void beforeCreateAndroidTasks(boolean isBuildingPlugin) {\n        this.isBuildingPlugin = isBuildingPlugin\n        if (!isBuildingPlugin) {\n            Log.i 'VAPlugin', \"Skipped all VirtualApk's configurations!\"\n            return\n        }\n\n        checkConfig()\n\n        stripClassAndResTransform = new StripClassAndResTransform(project)\n        android.registerTransform(stripClassAndResTransform)\n\n        android.defaultConfig.buildConfigField(\"int\", \"PACKAGE_ID\", \"0x\" + Integer.toHexString(virtualApk.packageId))\n\n        // Force using the versions of host dependencies\n        // check current project was the first subproject\n//        if (project.rootProject.subprojects.first() != project) {\n//            throw new RuntimeException(\"The project ':${project.name}' must be the first subproject when enabled \\\"virtualApk.forceUseHostDependences\\\" . \" +\n//                    \"You should modify the project's configuration explicitly in \\\"settings.gradle\\\" like this:\\n\\n\" +\n//                    \"include ':_${project.name}'\\n\" +\n//                    \"project(':_${project.name}').projectDir = new File('./${project.name}')\")\n//        }\n        HashSet<String> replacedSet = [] as HashSet\n        project.rootProject.subprojects { Project p ->\n            p.configurations.all { Configuration configuration ->\n                configuration.resolutionStrategy { ResolutionStrategy resolutionStrategy ->\n                    resolutionStrategy.eachDependency { DependencyResolveDetails details ->\n                        if (!isBuildingPlugin) {\n                            return\n                        }\n\n                        checkConfig()\n\n                        def hostDependency = virtualApk.hostDependencies.get(\"${details.requested.group}:${details.requested.name}\")\n                        if (hostDependency != null) {\n                            if (\"${details.requested.version}\" != \"${hostDependency['version']}\") {\n                                String key = \"${p.name}:${details.requested}\"\n                                if (!replacedSet.contains(key)) {\n                                    replacedSet.add(key)\n                                    if (virtualApk.forceUseHostDependences) {\n                                        Log.i 'Dependencies', \"ATTENTION: Replaced module [${details.requested}] in project(:${p.name})'s configuration to host version: [${hostDependency['version']}]!\"\n                                    } else {\n                                        virtualApk.addWarning \"WARNING: [${details.requested}] in project(:${p.name})'s configuration will be occupied by Host App! Please change it to host version: [${hostDependency['group']}:${hostDependency['name']}:${hostDependency['version']}].\"\n                                        virtualApk.setFlag('tip.forceUseHostDependences', true)\n                                    }\n                                }\n\n                                if (virtualApk.forceUseHostDependences) {\n                                    details.useVersion(hostDependency['version'])\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    File getJarPath() {\n        URL url = this.class.getResource(\"\")\n        int index = url.path.indexOf('!')\n        if (index < 0) {\n            index = url.path.length()\n        }\n        return project.file(url.path.substring(0, index))\n    }\n\n    @Override\n    void apply(final Project project) {\n        super.apply(project)\n\n        hostDir = new File(project.rootDir, \"host\")\n        if (!hostDir.exists()) {\n            hostDir.mkdirs()\n        }\n\n        virtualApk.hostDependenceFile = new File(hostDir, \"versions.txt\")\n\n        project.afterEvaluate {\n            if (!isBuildingPlugin) {\n                return\n            }\n\n            stripClassAndResTransform.onProjectAfterEvaluate()\n            taskHookerManager = new VATaskHookerManager(project, instantiator)\n            taskHookerManager.registerTaskHookers()\n\n            if (android.dataBinding.enabled) {\n                project.dependencies.add('annotationProcessor', project.files(jarPath.absolutePath))\n            }\n\n            android.applicationVariants.each { ApplicationVariantImpl variant ->\n\n                virtualApk.with {\n                    VAExtention.VAContext vaContext = getVaContext(variant.name)\n                    vaContext.packageName = variant.applicationId\n                    vaContext.packagePath = vaContext.packageName.replace('.'.charAt(0), File.separatorChar)\n                    vaContext.hostSymbolFile = new File(hostDir, \"Host_R.txt\")\n                }\n            }\n        }\n    }\n\n    /**\n     * Check the plugin apk related config infos\n     */\n    private void checkConfig() {\n        if (checked) {\n            return\n        }\n        checked = true\n\n        int packageId = virtualApk.packageId\n        if (packageId == 0) {\n            def err = new StringBuilder('you should set the packageId in build.gradle,\\n ')\n            err.append('please declare it in application project build.gradle:\\n')\n            err.append('    virtualApk {\\n')\n            err.append('        packageId = 0xXX \\n')\n            err.append('    }\\n')\n            err.append('apply for the value of packageId.\\n')\n            throw new InvalidUserDataException(err.toString())\n        }\n        if (packageId >= 0x7f || packageId <= 0x01) {\n            throw new IllegalArgumentException('the packageId must be in [0x02, 0x7E].')\n        }\n\n        String targetHost = virtualApk.targetHost\n        if (!targetHost) {\n            def err = new StringBuilder('\\nyou should specify the targetHost in build.gradle, e.g.: \\n')\n            err.append('    virtualApk {\\n')\n            err.append('        //when target Host in local machine, value is host application directory\\n')\n            err.append('        targetHost = ../xxxProject/app \\n')\n            err.append('    }\\n')\n            throw new InvalidUserDataException(err.toString())\n        }\n\n        File hostLocalDir = new File(targetHost)\n        if (!hostLocalDir.exists()) {\n            def err = \"The directory of host application doesn't exist! Dir: ${hostLocalDir.canonicalPath}\"\n            throw new InvalidUserDataException(err)\n        }\n\n        File hostR = new File(hostLocalDir, \"build/VAHost/Host_R.txt\")\n        if (hostR.exists()) {\n            def dst = new File(hostDir, \"Host_R.txt\")\n            use(FileBinaryCategory) {\n                dst << hostR\n            }\n        } else {\n            def err = new StringBuilder(\"Can't find ${hostR.canonicalPath}, please check up your host application\\n\")\n            err.append(\"  need apply com.didi.virtualapk.host in build.gradle of host application\\n \")\n            throw new InvalidUserDataException(err.toString())\n        }\n\n        File hostVersions = new File(hostLocalDir, \"build/VAHost/versions.txt\")\n        if (hostVersions.exists()) {\n            def dst = new File(hostDir, \"versions.txt\")\n            use(FileBinaryCategory) {\n                dst << hostVersions\n            }\n        } else {\n            def err = new StringBuilder(\"Can't find ${hostVersions.canonicalPath}, please check up your host application\\n\")\n            err.append(\"  need apply com.didi.virtualapk.host in build.gradle of host application \\n\")\n            throw new InvalidUserDataException(err.toString())\n        }\n\n        File hostMapping = new File(hostLocalDir, \"build/VAHost/mapping.txt\")\n        if (hostMapping.exists()) {\n            def dst = new File(hostDir, \"mapping.txt\")\n            use(FileBinaryCategory) {\n                dst << hostMapping\n            }\n        }\n\n        AppPlugin appPlugin = project.plugins.findPlugin(AppPlugin)\n        ProjectOptions projectOptions = Reflect.on(appPlugin).field('projectOptions').get()\n        if (projectOptions.get(BooleanOption.ENABLE_DEX_ARCHIVE)) {\n            throw new InvalidUserDataException(\"Can't using incremental dexing mode, please add 'android.useDexArchive=false' in gradle.properties of :${project.name}.\")\n        }\n//        project.ext.set('android.useDexArchive', false)\n        \n    }\n\n    static class VATaskHookerManager extends TaskHookerManager {\n\n        VATaskHookerManager(Project project, Instantiator instantiator) {\n            super(project, instantiator)\n        }\n\n        @Override\n        void registerTaskHookers() {\n            android.applicationVariants.all { ApplicationVariantImpl appVariant ->\n                if (!appVariant.buildType.name.equalsIgnoreCase(\"release\")) {\n                    return\n                }\n\n                registerTaskHooker(instantiator.newInstance(PrepareDependenciesHooker, project, appVariant))\n                registerTaskHooker(instantiator.newInstance(MergeAssetsHooker, project, appVariant))\n                registerTaskHooker(instantiator.newInstance(MergeManifestsHooker, project, appVariant))\n                registerTaskHooker(instantiator.newInstance(MergeJniLibsHooker, project, appVariant))\n//                registerTaskHooker(instantiator.newInstance(ShrinkResourcesHooker, project, appVariant))\n                registerTaskHooker(instantiator.newInstance(ProcessResourcesHooker, project, appVariant))\n                registerTaskHooker(instantiator.newInstance(ProguardHooker, project, appVariant))\n                registerTaskHooker(instantiator.newInstance(DxTaskHooker, project, appVariant))\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/groovy/com.didi.virtualapk/aapt/AXmlEditor.groovy",
    "content": "package com.didi.virtualapk.aapt\n\n/**\n * Class to edit aapt-generated hex xml file\n */\npublic class AXmlEditor extends AssetEditor {\n\n    private static final ATTR_BEFORE_ID_LENGTH = 16\n\n    AXmlEditor(final File file) {\n        super(file)\n    }\n\n    // To support plugin JNI, we carry the ABIs flag to extract the exactly JNIs\n    // under the supported ABI at runtime. (#87, #79)\n    //\n    // In addition, we find that if `addAssetPath` with an non-resources apk to `AssetManager`,\n    // Small will crash on the asset manager's `getPooledString` method under android M. (#62)\n    // So we also carry a flag to specify whether the plugin apk has resources.\n    //\n    // The above flag will be merged into an integer and write to `platformBuildVersion`\n    def setSmallFlags(final int flags) {\n        def xml = readChunkHeader()\n        if (xml.type != ResType.RES_XML_TYPE) {\n            return\n        }\n\n        def sp = readStringPool()\n        byte[] targetBytes = [ // platformBuildVersionCode\n                'p',0,'l',0,'a',0,'t',0,'f',0,'o',0,'r',0,'m',0,'B',0,'u',0,'i',0,'l',0,'d',0,\n                'V',0,'e',0,'r',0,'s',0,'i',0,'o',0,'n',0,'C',0,'o',0,'d',0,'e',0 ]\n        int targetIndex = -1\n        final int N = sp.stringCount\n        for (int i = 0; i < N; i++) {\n            def bytes = sp.strings[i]\n            if (Arrays.equals(bytes, targetBytes)) {\n                targetIndex = i\n                break\n            }\n        }\n\n        if (targetIndex == -1) {\n            return\n        }\n\n        while (tellp() < xml.size) {\n            def chunk = readChunkHeader()\n            if (chunk.type != ResType.RES_XML_START_ELEMENT_TYPE) {\n                skipChunk(chunk)\n                continue\n            }\n\n            // The first element: <manifest ...\n            def node = readNode()\n            for (int i = 0; i < node.attributeCount; i++) {\n                skip(4)\n                int nameIndex = readInt()\n                if (nameIndex == targetIndex) { // platformBuildVersionCode\n                    skip(8)\n\n                    final int versionCode = readInt()\n\n                    seek(tellp() - 4)\n\n                    // The flag bits are:\n                    //  F    F    F    F    F    F    F    F\n                    // 1111 1111 1111 1111 1111 1111 1111 1111\n                    // ^^^^ ^^^^ ^^^^ ^^^^ ^^^^\n                    //       ABI Flags (20)\n                    //                          ^\n                    //                 nonResources Flag (1)\n                    //                           ^^^ ^^^^ ^^^^\n                    //                     platformBuildVersionCode (11) => MAX=0x7FF=4095\n                    final int newFlag = (flags << 11) | versionCode\n                    writeInt(newFlag)\n                    close()\n                    return true\n                } else {\n                    skip(12)\n                }\n            }\n\n            break\n        }\n\n        close()\n        return false\n    }\n\n    def setPackageId(final int pp, final Map idMaps) {\n        def xml = readChunkHeader()\n        if (xml.type != ResType.RES_XML_TYPE) {\n            return false\n        }\n\n        setPackageIdRecursive(pp, idMaps, xml.size)\n        close()\n        return edited\n    }\n\n    private def setPackageIdRecursive(final int pp, final Map idMaps, final long size) {\n        if (tellp() >= size) {\n            return\n        }\n\n        def chunk = readChunkHeader()\n        if (chunk.type == ResType.RES_XML_RESOURCE_MAP_TYPE) {\n            def idCount = (chunk.size - chunk.headerSize) / 4\n            for (int i = 0; i < idCount; i++) {\n                checkToRewritePackageId(pp, idMaps)\n            }\n        } else if (chunk.type == ResType.RES_XML_START_ELEMENT_TYPE) {\n            // Parse element, reset package id\n            def node = readNode()\n            for (int i = 0; i < node.attributeCount; i++) {\n                skip(ATTR_BEFORE_ID_LENGTH)\n                checkToRewritePackageId(pp, idMaps)\n            }\n        } else {\n            skip(chunk.size - CHUNK_HEADER_SIZE)\n        }\n\n        setPackageIdRecursive(pp, idMaps, size)\n    }\n\n    /** Read struct ResXMLTree_node and ResXMLTree_attrExt */\n    private def readNode() {\n        def node = [:]\n        // Skip struct ResXMLTree_node: lineNumber(4), comment(4) and part of struct\n        // ResXMLTree_attrExt: ns(4), name(4), attributeStart(2), attributeSize(2)\n        skip(20)\n        node.attributeCount = readShort()\n        // skip tail of struct ResXMLTree_attrExt: idIndex(2), classIndex(2), styleIndex(2)\n        skip(6)\n        return node\n    }\n\n    def createAndroidManefist(Map options) {\n        def size = 0\n        def xml = [\n            header: [type: ResType.RES_XML_TYPE, headerSize: 8, size: size],\n            stringPool: []\n        ]\n    }\n\n    public void dumpXmlTree() {\n        def xml = readXmlTree()\n        // TODO\n    }\n\n    private def readXmlTree() {\n        def xml = [:]\n        xml.header = readChunkHeader()\n        assert (xml.header.type == ResType.RES_XML_TYPE)\n\n        def size = xml.header.size\n\n        println \"pos1: ${tellp()}\"\n\n        xml.stringPool = readStringPool()\n        println xml.stringPool.flags\n\n        dumpStringPool(xml.stringPool)\n\n        readChunks(xml)\n//        println xml\n//        xml.namespaces.each {\n//            println it\n//        }\n//        println xml.namespaces\n    }\n\n    private def readChunks(xml) {\n        def size = length()\n        def currNamespace = null\n        def currNode = null\n        def pos\n        while ((pos = tellp()) < size) {\n            def header = readChunkHeader()\n            println header\n            switch (header.type) {\n                case ResType.RES_XML_RESOURCE_MAP_TYPE:\n                    def mapCount = (header.size - header.headerSize) / 4\n                    def maps = []\n                    for (int i = 0; i < mapCount; i++) {\n                        maps.add(readInt())\n                    }\n                    xml.attrMap = [header: header, ids: maps]\n                    break\n                case ResType.RES_XML_START_NAMESPACE_TYPE:\n                    def ns = [header: header]\n                    ns.startLine = readInt()\n                    ns.headComment = readInt()\n                    ns.prefix = readInt()\n                    ns.uri = readInt()\n                    ns.nodes = []\n                    def nss = xml.namespaces\n                    if (nss == null) {\n                        xml.namespaces = nss = []\n                    }\n                    nss.add(ns)\n                    ns.currNode = ns\n                    currNamespace = ns\n                    break\n                case ResType.RES_XML_START_ELEMENT_TYPE:\n                    def node = [:]\n                    node.startLine = readInt()\n                    node.headComment = readInt()\n                    node.ns = readInt()\n                    node.name = readInt()\n                    node.attributeStart = readShort()\n                    node.attributeSize = readShort()\n                    node.attributeCount = readShort()\n                    node.idIndex = readShort()\n                    node.classIndex = readShort()\n                    node.styleIndex = readShort()\n                    node.attrs = []\n                    for (int i = 0; i < node.attributeCount; i++) {\n                        node.attrs.add(readAttribute())\n                    }\n                    node.nodes = []\n                    def parent = currNamespace.currNode\n                    println \"- parent: ${parent}\"\n                    parent.nodes.add(node)\n                    println \"- parent: ${parent}\"\n                    node.parent = parent\n                    println \"- 333\"\n                    currNamespace.currNode = node\n                    println \"- 444\"\n                    break\n                case ResType.RES_XML_END_NAMESPACE_TYPE:\n                    currNamespace.endLine = readInt()\n                    currNamespace.tailComment = readInt()\n                    skip(8) // Bypass ns and name, ignore tag check\n                    currNamespace = null\n                    break\n                case ResType.RES_XML_END_ELEMENT_TYPE:\n                    println \"- 555\"\n                    currNode.endLine = readInt()\n                    currNode.tailComment = readInt()\n                    skip(8) // Ignore tag check\n                    println \"- 666 $currNode\"\n                    currNode = currNode.parent\n                    println \"- 777 $currNode\"\n                    break\n                default:\n//                    pos += header.size - CHUNK_HEADER_SIZE\n                    println \"-- left: ${header.size + pos - tellp()}\"\n                    dumpBytes(header.size + pos - tellp())\n//                    pos = tellp()\n//                    seek(pos)\n                    break\n            }\n        }\n    }\n\n    private def readAttribute() {\n        def at = [:]\n        at.ns = readInt()\n        at.name = readInt()\n        at.rawValue = readInt()\n        at.typedValue = readResValue()\n    }\n}\n"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/groovy/com.didi.virtualapk/aapt/Aapt.groovy",
    "content": "package com.didi.virtualapk.aapt\n\nimport com.didi.virtualapk.collector.res.ResourceEntry\nimport com.didi.virtualapk.collector.res.StyleableEntry\nimport com.google.common.collect.ListMultimap\nimport com.google.common.io.Files\nimport groovy.io.FileType\nimport org.gradle.api.Project\n\n/**\n * Class to expand aapt function\n */\npublic class Aapt {\n\n    public static final int ID_DELETED = -1\n    public static final int ID_NO_ATTR = -2\n\n    public static final String RESOURCES_ARSC = 'resources.arsc'\n\n    private static final String ENTRY_SEPARATOR = '/'\n\n    private final File assetDir\n    private final File symbolFile\n    private final def toolsRevision\n\n    Aapt(final File assetDir, File symbolFile, toolsRevision) {\n        this.assetDir = assetDir\n        this.symbolFile = symbolFile\n        this.toolsRevision = toolsRevision\n    }\n\n    /**\n     * Filter package assets by specific types\n     *\n     * @param retainedTypes\n     *            the resource types to retain\n     * @param pp\n     *            new package id\n     * @param idMaps\n     */\n    void filterPackage(final List<?> retainedTypes, final List<?> retainedStyleables, final int pp, final Map<?, ?> idMaps, final Map<?, ?> libRefTable, final Set<String> outUpdatedResources) {\n        final File arscFile = new File(this.assetDir, RESOURCES_ARSC)\n        final def arscEditor = new ArscEditor(arscFile, toolsRevision)\n\n        // Filter R.txt\n        if (this.symbolFile != null) {\n            this.filterRTxt(this.symbolFile, retainedTypes, retainedStyleables)\n        }\n\n        arscEditor.slice(pp, idMaps, libRefTable, retainedTypes)\n        outUpdatedResources.add(RESOURCES_ARSC)\n        this.resetAllXmlPackageId(this.assetDir, pp, idMaps, outUpdatedResources)\n    }\n\n\n    /**\n     * Filter resources with specific types\n     *\n     * @param retainedTypes\n     */\n    void filterResources(final List<?> retainedTypes, final Set<String> outFilteredResources) {\n        def resDir = new File(assetDir, 'res')\n        resDir.listFiles().each { typeDir ->\n            def type = retainedTypes.find { typeDir.name.startsWith(it.name) }\n            if (type == null) {\n                typeDir.listFiles().each {\n                    outFilteredResources.add(\"res/$typeDir.name/$it.name\")\n                }\n\n                typeDir.deleteDir()\n                return\n            }\n\n            def entryFiles = typeDir.listFiles()\n            def retainedEntryCount = entryFiles.size()\n\n            entryFiles.each { entryFile ->\n                def entry = type.entries.find { entryFile.name.startsWith(\"${it.name}.\") }\n                if (entry == null) {\n                    outFilteredResources.add(\"res/$typeDir.name/$entryFile.name\")\n                    entryFile.delete()\n                    retainedEntryCount--\n                }\n            }\n\n            if (retainedEntryCount == 0) {\n                typeDir.deleteDir()\n            }\n        }\n    }\n\n\n    /**\n     * Reset package id for *.xml\n     */\n    private static void resetAllXmlPackageId(final File dir, final int pp, final Map<?, ?> idMaps, final Set<String> outUpdatedResources) {\n        int len = dir.canonicalPath.length() + 1 // bypass '/'\n        def isWindows = (File.separator != ENTRY_SEPARATOR)\n\n        dir.eachFileRecurse(FileType.FILES) { file ->\n            if ('xml'.equalsIgnoreCase(Files.getFileExtension(file.name))) {\n                new AXmlEditor(file).setPackageId(pp, idMaps)\n\n                if (outUpdatedResources != null) {\n                    def path = file.canonicalPath.substring(len)\n                    if (isWindows) { // compat for windows\n                        path = path.replaceAll('\\\\\\\\', ENTRY_SEPARATOR)\n                    }\n\n                    outUpdatedResources.add(path)\n                }\n            }\n        }\n    }\n\n    public static void generateRJava(File dest, String pkg, ListMultimap<String, ResourceEntry> resources, List<StyleableEntry> styleables) {\n        if (!dest.parentFile.exists()) {\n            dest.parentFile.mkdirs()\n        }\n\n        if (!dest.exists()) {\n            dest.createNewFile()\n        }\n\n        dest.withPrintWriter { pw ->\n            pw.println \"/* AUTO-GENERATED FILE.  DO NOT MODIFY.\"\n            pw.println \" * \"\n            pw.println \" * This class was automatically generated by the\"\n            pw.println \" * aapt tool from the resource data it found.  It\"\n            pw.println \" * should not be modified by hand.\"\n            pw.println \" */\"\n            pw.println \"package ${pkg};\"\n            pw.println \"public final class R {\"\n\n            resources.keySet().each { type ->\n                pw.println \"    public static final class ${type} {\"\n                resources.get(type).each { entry ->\n                    pw.println \"        public static final int ${entry.resourceName} = ${entry.hexNewResourceId};\"\n                }\n                pw.println \"    }\"\n            }\n\n            pw.println \"    public static final class styleable {\"\n            styleables.each { styleable ->\n                    pw.println \"        public static final ${styleable.valueType} ${styleable.name} = ${styleable.value};\"\n            }\n            pw.println \"    }\"\n            pw.println \"}\"\n        }\n    }\n\n\n    /**\n     * Filter specify types for R.txt\n     *\n     * @param rTxt\n     *            The R.txt\n     * @param retainedTypes\n     */\n    private static def filterRTxt(final File rTxt, final List<?> retainedTypes, final List<?> retainedStyleables) {\n        rTxt.write('')\n        rTxt.withPrintWriter { pw ->\n            retainedTypes.each { t ->\n                t.entries.each { e ->\n                    pw.println(\"${t.type} ${t.name} ${e.name} ${e._vs}\")\n                }\n            }\n            retainedStyleables.each {\n                pw.println(\"${it.vtype} ${it.type} ${it.key} ${it.idStr}\")\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/groovy/com.didi.virtualapk/aapt/ArscEditor.groovy",
    "content": "package com.didi.virtualapk.aapt\n\n/**\n * Class to edit aapt-generated resources.arsc file\n */\npublic class ArscEditor extends AssetEditor {\n    /*      Arsc struct\n     *  +-----------------------+\n     *  | Table Header          |\n     *  +-----------------------+\n     *  | Res string pool       |\n     *  +-----------------------+\n     *  | Package Header        | <-- rewrite entry 1: package id\n     *  +-----------------------+\n     *  | Type strings          |\n     *  +-----------------------+\n     *  | Key strings           |\n     *  +-----------------------+\n     *  | DynamicRefTable chunk | <-- insert entry (for 5.0+)\n     *  +-----------------------+\n     *  | Type spec             |\n     *  |                  * N  |\n     *  | Type info  * M        | <-- rewrite entry 2: entry value\n     *  +-----------------------+\n     */\n\n    private static final boolean DEBUG_NOISY = false // log verbose\n\n    private static def LIBRARY_HEADER_SIZE = 0x0C\n    private static def LIBRARY_ENTRY_SIZE = 260 // packageId(4), packageName(256)\n    private static def LIBRARY_CHUNK_SIZE = 272 // ResTable_lib_header & ResTable_lib_entry\n    private static def TABLE_SIZE_POS = 4\n\n    private int mTableConfigSize = 52 // sizeof(ResTable_config)\n\n    ArscEditor(File file, def v) {\n        super(file, v)\n        if (version != null && version.major >= 24) {\n            mTableConfigSize = 56\n        }\n    }\n\n    /**\n     * Slice asset package and reset resource package ids\n     * @param pp new resource package id\n     * @param idMaps\n     * @param retainedTypes the resource types to retain\n     * @return\n     */\n    def slice(int pp, Map idMaps, Map libRefTable, List retainedTypes) {\n        def t = readTable()\n\n        def retainedTypeSpecs = []\n        def retainedStringIds = []\n        def retainedTypeIds = []\n        def retainedKeyIds = []\n        def retainedEntries = []\n        def libPackageIds = []\n\n        if (t.typeList.specs.size() == 0) {\n            println \"\\t -- There was no res.\"\n            return\n        }\n\n        // Ensure there is an `attr' typeSpec\n        if (retainedTypes[0].id == Aapt.ID_NO_ATTR) { // attr type id is always at first\n            def attrSpec = t.typeList.specs[0]\n            attrSpec.entryCount = 0\n            attrSpec.configs = []\n            attrSpec.flags = []\n            attrSpec.header.size = attrSpec.header.headerSize // id(1) res0(1) res1(2) entryCount(4)\n            retainedTypeIds.add(attrSpec.id - 1)\n            retainedTypeSpecs.add(attrSpec)\n            println \"\\t -- There was no attr.\"\n        }\n\n        def index = 0\n        while (index < t.stringPool.styleCount) {\n            retainedStringIds.add(index++)\n        }\n\n        // Create the mapping of type ids\n        LinkedHashMap<Object, Integer> typeIdMap = new LinkedHashMap<>()\n        t.typeList.specs.eachWithIndex { it, i ->\n            typeIdMap.put(it.id.intValue(), i)\n        }\n\n        // Filter typeSpecs\n        retainedTypes.each {\n            if (it.id == Aapt.ID_DELETED) {\n                // TODO: Add empty entry to default config\n                throw new UnsupportedOperationException(\"No support deleting resources on lib.* now\")\n            }\n\n            if (it.id == Aapt.ID_NO_ATTR) {\n                return\n            }\n\n            def specIndex = typeIdMap.get(it.id)\n            def ts = t.typeList.specs[specIndex]\n            def es = it.entries\n            def newEntryCount = es.size()\n            def d = (ts.entryCount - newEntryCount) * 4\n            ts.entryCount = newEntryCount\n            // Filter flags\n            def flags = []\n            es.each { e ->\n                def flag = (e.id == Aapt.ID_DELETED) ? 0 : ts.flags[e.id]\n                flags.add(flag)\n            }\n            ts.flags = flags\n            ts.header.size -= d\n            ts.id = retainedTypeSpecs.size() + 1\n            // Filter config entries\n            def configs = []\n            ts.configs.each {\n                def entries = []\n                def offsets = []\n                int offset = 0\n                def emptyCount = 0\n                es.each { e ->\n                    if (e.id == Aapt.ID_DELETED) {\n                        // TODO: Add empty entry to default config\n                        throw new UnsupportedOperationException(\"No support deleting resources on lib.* now\")\n                    }\n\n                    def entry = it.entries[e.id]\n                    if (entry == null) {\n                        throw new Exception(\"Missing entry at ${e} on ${it}!\")\n                    }\n\n                    entries.add(entry)\n                    if (entry.isEmpty()) {\n                        offsets.add(-1)\n                        emptyCount++\n                        return\n                    }\n\n                    def ename = new String(t.keyStringPool.strings[entry.key]).replaceAll('\\\\.', '_')\n                    if (e.name != ename) {\n                        throw new Exception(\"Required entry '${e.name}' but got '$ename', This \" +\n                                \"is seems to unsupport the buildToolsRevision: ${version}.\")\n                    }\n\n                    offsets.add(offset)\n                    offset += entry.allSize\n                    if (!retainedKeyIds.contains(entry.key)) retainedKeyIds.add(entry.key)\n                    retainedEntries.add(entry)\n                    int dataType\n                    if (entry.value != null) {\n                        // Reset entry ids\n                        dataType = entry.value.dataType\n                        if (dataType == ResValueDataType.TYPE_STRING) {\n                            // String reference\n                            def oldId = entry.value.data\n                            def newId = retainedStringIds.indexOf(oldId)\n                            if (newId < 0) {\n                                retainedStringIds.add(oldId)\n                                newId = retainedStringIds.size() - 1\n                            }\n                            entry.value.data = newId\n                        } else if (dataType == ResValueDataType.TYPE_REFERENCE) {\n                            def id = idMaps.get(entry.value.data)\n                            if (id != null) {\n                                if (DEBUG_NOISY) println \"\\t -- map ResTable_entry.value: \" +\n                                        \"${String.format('0x%08x', entry.value.data)} -> \" +\n                                        \"${String.format('0x%08x', id)}\"\n                                entry.value.data = id\n                            }\n                        }\n                    } else if (entry.maps != null) {\n                        // Reset entry parent\n                        def id = idMaps.get(entry.parent)\n                        if (id != null) {\n                            if (DEBUG_NOISY) println \"\\t -- map ResTable_map_entry.parent: \" +\n                                    \"${String.format('0x%08x', entry.parent)} -> \" +\n                                    \"${String.format('0x%08x', id)}\"\n                            entry.parent = id\n                        }\n                        entry.maps.each {\n                            // Reset map ids\n                            id = idMaps.get(it.name)\n                            if (id != null) {\n                                if (DEBUG_NOISY) println \"\\t -- map ResTable_map.name: \" +\n                                        \"${String.format('0x%08x', it.name)} -> \" +\n                                        \"${String.format('0x%08x', id)}\"\n                                it.name = id\n                            }\n                            dataType = it.value.dataType\n                            if (dataType == ResValueDataType.TYPE_STRING) {\n                                // String reference\n                                def oldId = it.value.data\n                                def newId = retainedStringIds.indexOf(oldId)\n                                if (newId < 0) {\n                                    retainedStringIds.add(oldId)\n                                    newId = retainedStringIds.size() - 1\n                                }\n                                it.value.data = newId\n                            } else if (dataType == ResValueDataType.TYPE_REFERENCE) {\n                                id = idMaps.get(it.value.data)\n                                if (id != null) {\n                                    if (DEBUG_NOISY) println \"\\t -- map ResTable_map.value: \" +\n                                            \"${String.format('0x%08x', it.value.data)} -> \" +\n                                            \"${String.format('0x%08x', id)}\"\n                                    it.value.data = id\n\n                                    int pid = (id >> 24)\n                                    if (pid != 0x7f && pid != 0x01 && pid != pp) {\n                                        libPackageIds.add(pid)\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n\n                if (emptyCount == ts.entryCount) return\n\n                it.entries = entries\n                it.entryOffsets = offsets\n                it.entryCount = ts.entryCount\n                it.entriesStart -= d\n                it.header.size -= d + it.entriesSize - offset\n                it.id = ts.id\n                configs.add(it)\n            }\n\n            ts.configs = configs\n            retainedTypeSpecs.add(ts)\n            retainedTypeIds.add(it.id - 1)\n        }\n\n        // Reset entry keys (reference to keyStringPool index)\n        def keyMaps = [:]\n        retainedKeyIds.eachWithIndex { key, newKey ->\n            keyMaps.put(key, newKey)\n        }\n        retainedEntries.each { e ->\n            e.key = keyMaps[e.key]\n        }\n        t.typeList.specs = retainedTypeSpecs\n\n\n        // Filter string pools\n        filterStringPool(t.stringPool, retainedStringIds)\n        filterStringPool(t.typeStringPool, retainedTypeIds)\n        filterStringPool(t.keyStringPool, retainedKeyIds)\n\n        // Add dynamic ref table for 5.0+\n        def lib = t.typeList.lib\n        if (lib == null) {\n            lib = [:]\n            lib.header = [:]\n            lib.header.type = ResType.RES_TABLE_LIBRARY_TYPE\n            lib.header.headerSize = LIBRARY_HEADER_SIZE\n            lib.header.size = LIBRARY_HEADER_SIZE + LIBRARY_ENTRY_SIZE\n            lib.count = 1\n            lib.entries = []\n            t.typeList.lib = lib\n        } else {\n            lib.count ++\n            lib.header.size += LIBRARY_ENTRY_SIZE\n        }\n        def libEntry = [:]\n        libEntry.packageId = pp\n        libEntry.packageName = t.package.name\n        lib.entries.add(libEntry)\n\n        // more dynamic ref table from related libraries\n        libPackageIds.each { pid ->\n            def pname = libRefTable[pid]\n            if (pname == null) {\n                def err = \"Failed to resolve package: ${String.format('0x%02x', pid)}\\n\"\n                libRefTable.each { id, name ->\n                    err += \"  [${String.format('0x%02x', id)}] -> $name\\n\"\n                }\n                throw new RuntimeException(err)\n            }\n\n            lib.count ++\n            lib.header.size += LIBRARY_ENTRY_SIZE\n            lib.entries.add([packageId: pid,\n                             packageName: getUtf16String(pname, 256)])\n        }\n\n        // Reset sizes & offsets\n        int size = lib.header.size\n        t.typeList.specs.each { ts ->\n            size += ts.header.size\n            ts.configs.each {\n                size += it.header.size\n            }\n        }\n        size += t.typeStringPool.header.size + t.keyStringPool.header.size +\n                t.package.header.headerSize\n        t.package.header.size = size\n        size += t.stringPool.header.size + t.header.headerSize\n        t.header.size = size\n        t.package.keyStrings = t.package.typeStrings + t.typeStringPool.header.size\n        t.package.lastPublicType = t.typeStringPool.strings.size()\n        t.package.lastPublicKey = t.keyStringPool.strings.size()\n\n        // Rewrite\n        t.package.id = pp\n        setLength(t.header.size)\n        seek(0)\n        writeTable(t)\n\n        if (DEBUG_NOISY) dumpTable()\n\n        close()\n    }\n\n    /**\n     * Reset resource package ids\n     * @param pp new resource package id\n     * @param idMaps\n     * @return\n     */\n    def reset(int pp, Map idMaps) {\n        def table = [:]\n        table.header = readChunkHeader()\n        assert (table.header.type == ResType.RES_TABLE_TYPE)\n\n        table.packageCount = readInt()\n\n        // Skip ResStringPool\n        def packageIdPos = table.header.headerSize\n        def strChunk = this.readChunkHeader()\n        assert (strChunk.type == ResType.RES_STRING_POOL_TYPE)\n\n        packageIdPos += strChunk.size\n        seek(packageIdPos)\n\n        // Enter ResTable_package\n        def pkgChunk = this.readChunkHeader()\n        assert (pkgChunk.type == ResType.RES_TABLE_PACKAGE_TYPE)\n        def pkgSizePos = tellp() - 4 // for adding size after insert dynamicRefTable\n\n        // Rewrite package id\n        writeInt(pp)\n        def packageName = readBytes(256)\n        skip(20) // skip other fields of ResTable_package\n\n        // Skip typeStrings\n        def chunk = readChunkHeader()\n        assert (chunk.type == ResType.RES_STRING_POOL_TYPE)\n        skip(chunk.size - CHUNK_HEADER_SIZE)\n\n        // Skip keyStrings\n        chunk = readChunkHeader()\n        assert (chunk.type == ResType.RES_STRING_POOL_TYPE)\n        skip(chunk.size - CHUNK_HEADER_SIZE)\n\n        def dynamicRefPos = tellp()\n        def offset = dynamicRefPos\n        def type = readTableType()\n        while (offset < table.header.size) {\n//            println \"-- type: $type\"\n            if (type.isConfig) { // ResTable_type\n                def entryOffsets = []\n                for (int i = 0; i < type.entryCount; i++) {\n                    entryOffsets[i] = readInt()\n                }\n                def entriesStart = tellp()\n                for (int i = 0; i < type.entryCount; i++) {\n                    def entryOffset = entryOffsets[i]\n                    if (entryOffset == ResTableType.NO_ENTRY) continue\n\n                    def entryPos = entriesStart + entryOffset\n                    if (table.header.size - entryPos < 16) break // requires 16 bytes at least\n                    seek(entryPos)\n                    def entry = _readTableEntry()\n                    if (entry.flags == 0) {\n                        checkToRewriteTypedValueId(pp, idMaps)// Res_value\n                    } else if (entry.flags & ResTableEntry.FLAG_COMPLEX) { // ResTable_mapEntry\n                        checkToRewritePackageId(pp, idMaps) // mapEntry.parent\n                        def count1 = readInt()\n                        def count = count1 & 0x00ffff // fix aapt v22 bug?\n                        for (int j = 0; j < count; j++) {\n                            checkToRewritePackageId(pp, idMaps) // map.name\n                            checkToRewriteTypedValueId(pp, idMaps) // map.value\n                        }\n                    }\n                }\n            }\n            offset += type.header.size\n            seek(offset)\n            type = readTableType()\n        }\n\n        // for 5.0+\n        insertDynamicRefTable(packageName, pp, dynamicRefPos)\n        // Add table size\n        seek(TABLE_SIZE_POS)\n        writeInt(table.header.size + LIBRARY_CHUNK_SIZE)\n        seek(pkgSizePos)\n        writeInt(pkgChunk.size + LIBRARY_CHUNK_SIZE)\n\n        close()\n    }\n\n    /** Read all data of resources.arsc */\n    def readTable() {\n        def header = readChunkHeader()\n        assert (header.type == ResType.RES_TABLE_TYPE)\n\n        def t = [:]\n        t.header = header\n        t.packageCount = readInt()\n        t.stringPool = readStringPool()\n        t.package = readPackage()\n        t.typeStringPool = readStringPool()\n        t.keyStringPool = readStringPool()\n        t.typeList = readTypeList()\n        return t\n    }\n    /** Write all data of resources.arsc */\n    def writeTable(t) {\n        writeChunkHeader(t.header)\n        writeInt(t.packageCount)\n        writeStringPool(t.stringPool)\n        writePackage(t.package)\n        writeStringPool(t.typeStringPool)\n        writeStringPool(t.keyStringPool)\n        writeTypeList(t.typeList)\n    }\n\n    /** Read struct ResTable_package */\n    def readPackage() {\n        def header = readChunkHeader()\n        assert (header.type == ResType.RES_TABLE_PACKAGE_TYPE)\n\n        def p = [:]\n        p.header = header\n        p.id = readInt()\n        p.name = readBytes(256)\n        p.typeStrings = readInt()\n        p.lastPublicType = readInt()\n        p.keyStrings = readInt()\n        p.lastPublicKey = readInt()\n        p.typeIdOffset = readInt()\n        return p\n    }\n    /** Write struct ResTable_package */\n    def writePackage(p) {\n        writeChunkHeader(p.header)\n        writeInt(p.id)\n        writeBytes(p.name)\n        writeInt(p.typeStrings)\n        writeInt(p.lastPublicType)\n        writeInt(p.keyStrings)\n        writeInt(p.lastPublicKey)\n        writeInt(p.typeIdOffset)\n    }\n\n    /** Read 1 x ResTable_lib + (ResTable_typeSpec + ResTable_type x M) x N */\n    def readTypeList() {\n        def offset = tellp()\n        def length = length()\n        def lib = null // ResTable_lib\n        def specs = [] // ResTable_typeSpec\n        def currTypeSpec = null\n        while (offset < length) {\n            def tt = [:]\n            tt.header = readChunkHeader()\n            switch (tt.header.type) {\n                case ResType.RES_TABLE_TYPE_SPEC_TYPE:\n                    tt.id = readByte()\n                    tt.res0 = readByte()\n                    tt.res1 = readShort()\n                    tt.entryCount = readInt()\n                    tt.flags = []\n                    for (int i = 0; i < tt.entryCount; i++) {\n                        tt.flags[i] = readInt()\n                    }\n                    currTypeSpec = tt\n                    specs.add(tt)\n                    break\n                case ResType.RES_TABLE_TYPE_TYPE:\n                    tt.id = readByte()\n                    tt.res0 = readByte()\n                    tt.res1 = readShort()\n                    tt.entryCount = readInt()\n                    tt.entriesStart = readInt()\n                    tt.config = readTableConfig()\n                    tt.entryOffsets = []\n                    tt.entries = []\n                    for (int i = 0; i < tt.entryCount; i++) {\n                        tt.entryOffsets.add(readInt())\n                    }\n                    int start = offset + tt.entriesStart\n                    tt.entriesSize = 0\n                    for (int i = 0; i < tt.entryCount; i++) {\n                        int pos = tt.entryOffsets[i]\n                        if (pos == -1) {\n                            tt.entries.add([:])\n                            continue\n                        }\n                        pos += start\n                        seek(pos)\n                        def entry = readTableEntry()\n                        entry.allSize = tellp() - pos\n                        tt.entries.add(entry)\n                        tt.entriesSize += entry.allSize\n                    }\n                    if (currTypeSpec.configs == null) currTypeSpec.configs = []\n                    currTypeSpec.configs.add(tt)\n                    break\n                case ResType.RES_TABLE_LIBRARY_TYPE:\n                    tt.count = readInt()\n                    tt.entries = []\n                    for (int i = 0; i < tt.count; i++) {\n                        def entry = [:]\n                        entry.packageId = readInt()\n                        entry.packageName = readBytes(256)\n                        tt.entries.add(entry)\n                    }\n                    lib = tt\n                    break\n                default:\n                    println \"!!!Unkown type: ${String.format('0x%04x', tt.header.type)}\"\n                    seek(tellp() - 8)\n                    dumpBytes(32)\n                    assert(false)\n            }\n            offset += tt.header.size\n            seek(offset)\n        }\n        return [specs:specs, lib:lib]\n    }\n    /** Write 1 x ResTable_lib + (ResTable_typeSpec + ResTable_type x M) x N */\n    def writeTypeList(tl) {\n        if (tl.lib) {\n            // ResTable_lib (5.0+)\n            writeChunkHeader(tl.lib.header)\n            writeInt(tl.lib.count)\n            tl.lib.entries.each {\n                writeInt(it.packageId)\n                writeBytes(it.packageName)\n            }\n        }\n        tl.specs.each { ts ->\n            // ResTable_typeSpec\n            writeChunkHeader(ts.header)\n            writeByte(ts.id)\n            writeByte(ts.res0)\n            writeShort(ts.res1)\n            writeInt(ts.entryCount)\n            ts.flags.each {\n                writeInt(it ?: 0)\n            }\n            ts.configs.each { c ->\n                // ResTable_type\n                writeChunkHeader(c.header)\n                writeByte(c.id)\n                writeByte(c.res0)\n                writeShort(c.res1)\n                writeInt(c.entryCount)\n                writeInt(c.entriesStart)\n                writeTableConfig(c.config)\n                c.entryOffsets.each {\n                    writeInt(it)\n                }\n                c.entries.each { e ->\n                    if (e.isEmpty()) return\n                    writeTableEntry(e)\n                }\n            }\n        }\n    }\n\n    /** Read struct ResTable_entry */\n    def _readTableEntry() {\n        def e = [:]\n        e.size = readShort()\n        e.flags = readShort()\n        e.key = readInt()\n        return e\n    }\n    /** Read struct ResTable_entry or ResTable_map_entry */\n    def readTableEntry() {\n        def e = _readTableEntry()\n        if (e.flags & ResTableEntry.FLAG_COMPLEX) {\n            // ResTable_map_entry\n            e.parent = readInt()\n            e.count = readInt()\n            e.maps = []\n            for (int i = 0; i < e.count; i++) {\n                e.maps.add(readTableMap())\n            }\n        } else {\n            e.value = readResValue()\n        }\n        return e\n    }\n    /** Write struct ResTable_entry or ResTable_map_entry */\n    def writeTableEntry(e) {\n        writeShort(e.size)\n        writeShort(e.flags)\n        writeInt(e.key)\n        if (e.maps != null) {\n            writeInt(e.parent)\n            writeInt(e.count)\n            e.maps.each {\n                writeTableMap(it)\n            }\n        } else {\n            writeResValue(e.value)\n        }\n    }\n\n    /** Read struct ResTable_map */\n    def readTableMap() {\n        def m = [:]\n        m.name = readInt()\n        m.value = readResValue()\n        return m\n    }\n    /** Write struct ResTable_map */\n    def writeTableMap(m) {\n        writeInt(m.name)\n        writeResValue(m.value)\n    }\n\n    /** Read struct ResTable_config */\n    def readTableConfig() {\n        def c = [:]\n//        c.size = readInt()\n//        c.imsi = [:]\n//        c.imsi.mcc = readShort()\n//        c.imsi.mnc = readShort()\n//        c.locale = [:]\n//        c.locale.language = readBytes(2)\n//        c.locale.country = readBytes(2)\n//        c.screenType = [:]\n//        c.screenType.orientation = readByte()\n//        c.screenType.touchscreen = readByte()\n//        c.screenType.density = readShort()\n//        c.input = [:]\n//        c.input.keyboard = readByte()\n//        c.input.navigation = readByte()\n//        c.input.inputFlags = readByte()\n//        c.input.inputPad0 = readByte()\n//        c.screenSize = [:]\n//        c.screenSize.screenWidth = readShort()\n//        c.screenSize.screenHeight = readShort()\n//        c.version = [:]\n//        c.version.sdkVersion = readShort()\n//        c.version.minorVersion = readShort()\n//        c.screenConfig = [:]\n//        c.screenConfig.screenLayout = readByte()\n//        c.screenConfig.uiMode = readByte()\n//        c.screenConfig.smallestScreenWidthDp = readShort()\n//        c.screenSizeDp = [:]\n//        c.screenSizeDp.screenWidthDp = readShort()\n//        c.screenSizeDp.screenHeightDp = readShort()\n//        c.localeScript = readBytes(4)\n//        c.localeVariant = readBytes(8)\n//        c.screenConfig2 = [:]\n//        c.screenConfig2.screenLayout2 = readByte()\n//        c.screenConfig2.screenConfigPad1 = readByte()\n//        c.screenConfig2.screenConfigPad2 = readShort()\n        c.ignored = readBytes(mTableConfigSize)\n        return c\n    }\n    /** Write struct ResTable_config */\n    def writeTableConfig(c) {\n        writeBytes(c.ignored)\n    }\n\n    /** Read struct ResTable_typeSpec or ResTable_type */\n    private def readTableType() {\n        def type = [:]\n        type.header = readChunkHeader()\n        if (type.header.type == ResType.RES_TABLE_TYPE_SPEC_TYPE) {\n            type.isConfig = false\n            skip(4) // id(1), res0(1), res1(2)\n            type.entryCount = readInt()\n        } else if (type.header.type == ResType.RES_TABLE_TYPE_TYPE) {\n            type.isConfig = true\n            skip(4) // id(1), res0(1), res1(2)\n            type.entryCount= readInt()\n            type.entriesStart = readInt()\n            skip(mTableConfigSize) // ResTable_type.config: struct ResTable_config\n        }\n        return type\n    }\n\n    private def insertDynamicRefTable(byte[] packageName, int pp, long pos) {\n        // Cut data after pos to clip channel\n        clipLaterData(pos)\n\n        // write ResTable_lib_header\n        seek(pos)\n        writeShort(ResType.RES_TABLE_LIBRARY_TYPE) // header.type (2)\n        writeShort(LIBRARY_HEADER_SIZE) // header.headerSize (2)\n        writeInt(LIBRARY_CHUNK_SIZE) // header.size (4)\n        writeInt(0x1) // count (4)\n        // write ResTable_lib_entry\n        writeInt(pp) // packageId (4)\n        writeBytes(packageName) // packageName (256)\n\n        // Paste data from clip channel\n        pasteLaterData(pos)\n    }\n\n    private static def getConfigName(c) {\n        def s = ''\n//        switch (c.screenType.density) {\n//            case 160: s += 'mdpi'; break\n//            case 240: s += 'hdpi'; break\n//            case 320: s += 'xhdpi'; break\n//            case 480: s += 'xxhdpi'; break\n//            case 640: s += 'xxxhdpi'; break\n//        }\n//        if (c.screenSizeDp.screenWidthDp != 0) s += \"w${c.screenSizeDp.screenWidthDp}dp\"\n//        if (c.version.sdkVersion != 0) s += \"-v${c.version.sdkVersion}\"\n        if (s == '') s = '(default)'\n        return s\n    }\n\n    private def dumpTable() {\n        seek(0)\n        def t = readTable()\n        dumpTable(t)\n    }\n\n    /** Dump table as `aapt d resources' */\n    private def dumpTable(t) {\n        println \"String Pool:\"\n        dumpStringPool(t.stringPool)\n        println \"Type String Pool:\"\n        dumpStringPool(t.typeStringPool)\n        println \"Key String Pool:\"\n        dumpStringPool(t.keyStringPool)\n\n        def pname = getUtf8String(t.package.name)\n        def pid = t.package.id << 24\n        def pidStr = \"0x${Integer.toHexString(t.package.id)}\"\n\n        println \"Package Groups (${t.packageCount})\"\n        println \"Package Group 0 id=$pidStr packageCount=${t.packageCount} name=$pname\"\n        def lib = t.typeList.lib\n        if (lib != null) {\n            println \"  DynamicRefTable entryCount=${lib.count}\"\n            lib.entries.each{ e ->\n                println \"    0x${Integer.toHexString(e.packageId)} -> \" +\n                        \"${getUtf8String(e.packageName)}\"\n            }\n            println ''\n        }\n\n        println \"  Package 0 id=$pidStr name=$pname\"\n\n        def keyId = 0\n        t.typeList.specs.each { ts ->\n            if (ts.configs == null) return\n            def configCount = ts.configs.size()\n            if (configCount == 0) return\n            def entryCount = ts.entryCount\n            if (entryCount == 0) return\n            def type = new String(t.typeStringPool.strings[ts.id - 1])\n            println \"    type ${ts.id - 1} configCount=$configCount entryCount=$entryCount\"\n            for (int ei = 0; ei < entryCount; ei++) {\n                def id = Integer.toHexString(pid | (ts.id << 16) | ei)\n                def keyBuff = t.keyStringPool.strings[keyId]\n                def key = keyBuff == null ? 'null' : new String(keyBuff)\n                print \"      spec resource 0x$id $pname:$type/$key: flags=0x\"\n                println String.format('%08x', ts.flags[ei])\n                keyId++\n            }\n            ts.configs.each {\n                println \"      config ${getConfigName(it.config)}:\"\n                it.entries.eachWithIndex { e, ei ->\n                    if (e.key == null) return\n\n                    def id = Integer.toHexString(pid | (ts.id << 16) | ei)\n                    def keyBuff = t.keyStringPool.strings[e.key]\n                    def key = keyBuff == null ? 'null' : new String(keyBuff)\n                    print \"        resource 0x$id $pname:$type/$key: \"\n                    if (e.value != null) {\n                        print String.format('t=0x%02x d=0x%08x (s=0x%04x r=0x%02x)',\n                                e.value.dataType, e.value.data, e.value.size, e.value.res0)\n                    } else {\n                        print '<bag>'\n                    }\n                    if (e.flags & ResTableEntry.FLAG_PUBLIC) print ' (PUBLIC)'\n                    println ''\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/groovy/com.didi.virtualapk/aapt/AssetEditor.groovy",
    "content": "package com.didi.virtualapk.aapt\n\nimport javax.management.RuntimeMBeanException\n\n/**\n * Class to edit aapt-generated asset file\n */\npublic class AssetEditor extends CppHexEditor {\n\n    public static final CHUNK_HEADER_SIZE = 8\n\n    protected def version // com.android.sdklib.repository.FullRevision \"major.minor.micro preview\"\n\n    public AssetEditor(final File file) {\n        this(file, null)\n    }\n\n    public AssetEditor(final File file, final def v) {\n        super(file)\n        this.version = v\n    }\n\n    /** Read struct ResChunk_header */\n    protected def readChunkHeader() {\n        def header = [:]\n        header.type = readShort()\n        header.headerSize = readShort()\n        header.size = readInt()\n        return header\n    }\n\n    /** Write struct ResChunk_header */\n    protected def writeChunkHeader(h) {\n        writeShort(h.type)\n        writeShort(h.headerSize)\n        writeInt(h.size)\n    }\n\n    /** Read struct Res_value */\n    protected def readResValue() {\n        def v = [:]\n        v.size = readShort()\n        v.res0 = readByte()\n        v.dataType = readByte()\n        v.data = readInt()\n        return v\n    }\n    /** Write struct Res_value */\n    protected def writeResValue(v) {\n        writeShort(v.size)\n        writeByte(v.res0)\n        writeByte(v.dataType)\n        writeInt(v.data)\n    }\n\n    protected def skipChunk(c) {\n        skip(c.size - CHUNK_HEADER_SIZE)\n    }\n\n    /**\n     * Rewrite package id on incoming uint32_t\n     * @param pp high bits of resource id\n     */\n    protected def checkToRewritePackageId(int pp, Map idMaps) {\n        def pos = tellp()\n        int id = readInt()\n        if (id >> 24 != 0x7f) return\n        if (idMaps != null && idMaps.containsKey(id)) {\n            id = idMaps.get(id) // use library resource id\n        } else {\n            id = ((pp << 24) | (id & 0x00ffffff)) // replace pp\n        }\n        seek(pos)\n        writeInt(id)\n    }\n\n    /**\n     * Rewrite package id on typed value (Res_value: 8 bytes)\n     * @param pp\n     */\n    protected def checkToRewriteTypedValueId(int pp, Map idMaps) {\n        skip(4)\n        checkToRewritePackageId(pp, idMaps)\n    }\n\n    /** Read struct ResStringPool_header and following string data */\n    protected def readStringPool() {\n        def pos = tellp()\n        def s = [:]\n        s.header = readChunkHeader() // string pool\n        assert (s.header.type == ResType.RES_STRING_POOL_TYPE)\n\n        // Read header\n        s.stringCount = readInt()\n        s.styleCount = readInt()\n\n        s.flags = readInt()\n        s.stringsStart = readInt()\n        s.stylesStart = readInt()\n        s.stringOffsets = []\n        s.styleOffsets = []\n        s.strings = [] // byte[][]\n        s.styles = [] // [{name, firstChar, lastChar}]\n        s.stringsSize = 0\n        s.stylesSize = 0\n        s.stringLens = []\n        s.styleLens = []\n        s.styleStringIds = [] as HashSet //string pool index of array item b ...\n        s.isUtf8 = (s.flags & ResStringFlag.UTF8_FLAG) != 0\n        byte[] eof = new byte[s.isUtf8 ? 1 : 2]\n        Arrays.fill(eof, (byte) 0x0)\n        s.stringsEOF = eof\n\n        // Read offsets\n        for (int i = 0; i < s.stringCount; i++) {\n            s.stringOffsets.add(readInt())\n        }\n        for (int i = 0; i < s.styleCount; i++) {\n            s.styleOffsets.add(readInt())\n        }\n\n        // Read strings\n        def start = s.stringsStart + pos\n        for (int i = 0; i < s.stringCount; i++) {\n            seek(start + s.stringOffsets[i])\n            def len = decodeLength(s.isUtf8)\n            s.stringLens[i] = len.data\n            s.strings[i] = readBytes(len.value)\n            s.stringsSize += len.value + len.data.length + eof.length // len for 0x0\n            skip(eof.length) // 0x0\n        }\n\n        def endPos = pos + s.header.size\n        def curPos = tellp()\n        def noStyles = (s.stylesStart == 0)\n        if (noStyles) {\n            s.stringPadding = endPos - curPos\n        } else {\n            start = s.stylesStart + pos\n            s.stringPadding = start - curPos\n        }\n\n        // Skip string padding\n        if (s.stringPadding != 0) {\n            skip(s.stringPadding)\n        }\n\n        if (noStyles) return s\n\n\n        // Read styles\n        for (int i = 0; i < s.styleCount; i++) {\n            seek(start + s.styleOffsets[i])\n            def style = [];\n            def styleLen =  0;\n\n            def name = readInt()\n            while (name != ResStringPoolSpan.END) {\n                def span  = [:]\n                span.name = name\n                span.firstChar = readInt()\n                span.lastChar = readInt()\n\n                if (!s.styleStringIds.contains(name)) {\n                    s.styleStringIds.add(name)\n                }\n\n                styleLen += 4 * 3\n\n                style.add(span)\n\n                name = readInt()\n            }\n\n            styleLen += 4\n\n            s.styles[i] = style;\n            s.styleLens[i] = styleLen\n        }\n\n        // Validate styles end span\n        s.styleEnd = readBytes(8)\n        s.styleSize = endPos - start\n\n        assert (Arrays.equals(s.styleEnd, ResStringPoolSpan.END_SPAN))\n\n        return s\n    }\n\n    /** Write struct ResStringPool_header and following string data */\n    protected def writeStringPool(s) {\n        // Write header\n        writeChunkHeader(s.header)\n\n        writeInt(s.stringCount)\n        writeInt(s.styleCount)\n        writeInt(s.flags)\n        writeInt(s.stringsStart)\n        writeInt(s.stylesStart)\n\n        // Write offsets\n        for (int i = 0; i < s.stringCount; i++) {\n            writeInt(s.stringOffsets[i])\n        }\n        for (int i = 0; i < s.styleCount; i++) {\n            writeInt(s.styleOffsets[i])\n        }\n\n        // Write strings\n        s.strings.eachWithIndex { it, i ->\n            writeBytes(s.stringLens[i])\n            writeBytes(it)\n            writeBytes(s.stringsEOF)\n        }\n        if (s.stringPadding > 0) writeBytes(new byte[s.stringPadding])\n\n\n        if (s.styleCount > 0) {\n            // Write styles\n            s.styles.each { style ->\n                style.each { span ->\n                    writeInt(span.name)\n                    writeInt(span.firstChar)\n                    writeInt(span.lastChar)\n                }\n\n                writeInt(ResStringPoolSpan.END);\n            }\n\n            writeBytes(s.styleEnd)\n        }\n    }\n//    /** Make ResStringPool */\n//    protected static def makeStringPool(u8strs) {\n//        def s = [:]\n//\n//        def size = 0\n//        def strings = []\n//        u8strs.each {\n//            def str = [:]\n//            str\n//        }\n//        s.header = [type: ResType.RES_STRING_POOL_TYPE, headerSize: 0x1C, size: size]\n//\n//    }\n\n    /** Read struct ResStringPool_span */\n    protected def readStringPoolSpan() {\n        def ss = [:]\n        ss.name = readInt()\n        ss.firstChar = readInt()\n        ss.lastChar = readInt()\n        skip(4) // END: 0xFFFFFFFF\n        return ss\n    }\n\n    /** Get utf-8 from utf-16 */\n    protected static def getUtf8String(u16str) {\n        int len16 = u16str.size()\n        int len = len16 / 2\n        def buffer = new char[len]\n        int i = 0;\n        for (int j = 0; j < len16; j+=2) {\n            char c = (char)u16str[j]\n            if (c == 0) {\n                buffer[i] = '\\0'\n                break\n            }\n            buffer[i++] = c\n        }\n        return String.copyValueOf(buffer, 0, i)\n    }\n\n    /** Get utf-16 from utf-8 */\n    protected static def getUtf16String(String u8str, int size) {\n        byte[] str = new byte[size]\n        int N = Math.min(u8str.length(), size)\n        int i = 0\n        int j = 0\n        for (; i < N; i++) {\n            str[j++] = u8str.charAt(i)\n            str[j++] = 0\n        }\n        for (; j < size; j++) {\n            str[j] = 0\n        }\n        return str\n    }\n\n    /**\n     * see https://github.com/android/platform_frameworks_base/blob/d59921149bb5948ffbcb9a9e832e9ac1538e05a0/libs/androidfw/ResourceTypes.cpp\n     * @param isUtf8\n     * @return\n     */\n    private Map decodeLength(isUtf8) {\n        if (isUtf8) {\n            // *u16len = decodeLength(&u8str); @ResourceTypes.cpp#722, seems to unused here\n            def bytes = []\n            short hb = readByte()\n            bytes.add(hb)\n            if (hb & 0x80) {\n                bytes.add(readByte())\n            }\n\n            // size_t u8len = decodeLength(&u8str); @ResourceTypes.cpp#723, the exact length\n            hb = readByte()\n            bytes.add(hb)\n            if (hb & 0x80) {\n                short lb = readByte()\n                bytes.add(lb)\n                hb = ((hb & 0x7F) << 8) | (lb & 0xff)\n            }\n\n            def N = bytes.size()\n            def data = new byte[N]\n            for (int i = 0; i < N; i++) {\n                data[i] = (byte)bytes[i]\n            }\n            return [data: data, value: hb]\n        } else {\n            // *u16len = decodeLength(&str); @ResourceTypes.cpp#705\n            def bytes = []\n            def buffer = readBytes(2)\n            bytes.addAll(buffer)\n            int hb = getShort(buffer)\n            if (hb & 0x8000) {\n                buffer = readBytes(2)\n                bytes.addAll(buffer)\n                int lb = getShort(buffer)\n                hb = ((hb & 0x7FFF) << 16) | (lb & 0xFFFF)\n            }\n\n            def N = bytes.size()\n            def data = new byte[N]\n            for (int i = 0; i < N; i++) {\n                data[i] = (byte)bytes[i]\n            }\n            return [data: data, value: (hb << 1)]\n        }\n    }\n    /** Filter ResStringPool with specific string indexes */\n    protected static def filterStringPool(sp, ids) {\n        if (sp.stringsStart == 0) return sp\n\n        // add style strings\n        def newStyleIdsMap = [:]\n        sp.styleStringIds.each {\n            def newId = ids.indexOf(it)\n            if (newId < 0) {\n                ids.add(it)\n                newId = ids.size() - 1\n            }\n            newStyleIdsMap[it] = newId\n        }\n\n        def strings = []\n        def offsets = []\n        def lens = []\n        def stringOffset = 0\n\n        // Filter strings\n        ids.each {\n            def s = sp.strings[it]\n            strings.add(s)\n            offsets.add(stringOffset)\n            def lenData = sp.stringLens[it]\n            lens.add(lenData)\n            def l = s.length\n            stringOffset += l + lenData.length + sp.stringsEOF.length // len for 0x0\n        }\n        def newStringCount = strings.size()\n        def d = (sp.stringCount - newStringCount) * 4\n        sp.strings = strings\n        sp.stringOffsets = offsets\n        sp.stringLens = lens\n        sp.stringCount = strings.size()\n\n\n        // Filter styles\n        def styles = []\n        def styleOffsets = []\n        def styleLens = []\n        def styleOffset = 0\n\n        ids.each {\n            if (it < sp.styleCount) {\n                def style = sp.styles[it]\n                styles.add(style)\n                style.each { span ->\n                    def newRef = newStyleIdsMap[span.name]\n                    if (newRef) {\n                        span.name = newRef\n                    } else {\n                        throw new Exception(\"Required new StringRef of ${span.name}\")\n                    }\n                }\n                styleOffsets.add(styleOffset)\n                styleLens.add(sp.styleLens[it])\n                styleOffset += sp.styleLens[it]\n            }\n        }\n        def newStyleCount = styles.size()\n        d += (sp.styleCount - newStyleCount) * 4\n        sp.styles = styles\n        sp.styleOffsets = styleOffsets\n        sp.styleLens = styleLens\n        sp.styleCount = newStyleCount\n\n\n        // Adjust strings start position\n        sp.stringsStart -= d\n\n        d += sp.stringsSize - stringOffset\n        sp.stringsSize = stringOffset\n\n\n        // Adjust string padding (string size should be a multiple of 4)\n        def newStringPadding = 0\n        def flag = stringOffset & 3\n        if (flag != 0) {\n            newStringPadding = 4 - flag\n        }\n        d += sp.stringPadding - newStringPadding\n        sp.stringPadding = newStringPadding\n\n        // Adjust styles start position\n        if (sp.stylesStart > 0) {\n            sp.stylesStart -= d\n\n            d += sp.styleSize - (styleOffset + 8)\n            sp.styleSize = styleOffset + 8\n        }\n\n        // Adjust entry size\n        def newSize = sp.header.size - d\n        sp.header.size = newSize\n    }\n\n    /** Dump ResStringPool, as `aapt d xmlstrings' command */\n    protected static def dumpStringPool(pool) {\n        def type = pool.flags == 0 ? 'UTF-16' : 'UTF-8'\n        println \"String pool of ${pool.stringCount} unique $type non-sorted strings, \" +\n                \"${pool.stringCount} entries and ${pool.styleCount} styles \" +\n                \"using ${pool.header.size} bytes:\"\n        pool.strings.eachWithIndex { v, i ->\n            if (pool.isUtf8) {\n                println \"String #$i: ${new String(v)}\"\n            } else {\n                println \"String #$i: ${getUtf8String(v)}\"\n            }\n        }\n        pool.styles.eachWithIndex { v, i ->\n            if (pool.isUtf8) {\n                println \"Style #$i: $v\"\n            } else {\n                println \"Style #$i: $v\"\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/groovy/com.didi.virtualapk/aapt/CppHexEditor.groovy",
    "content": "package com.didi.virtualapk.aapt\n\nimport java.nio.ByteBuffer\nimport java.nio.ByteOrder\n\n/**\n * Class of c++ hex file (little endian) editor\n */\npublic class CppHexEditor {\n\n    private File file\n    private File clipFile\n    private RandomAccessFile raf\n    private RandomAccessFile clipRaf\n    private boolean edited\n    private long lengthBeforeClip\n\n    public CppHexEditor(final File file) {\n        this.file = file\n        this.raf = new RandomAccessFile(file, 'rw')\n    }\n\n    protected seek(final long offset) {\n        this.raf.seek(offset)\n    }\n\n    protected skip(final long count) {\n        this.raf.skipBytes((int) count)\n    }\n\n    protected tellp() {\n        return this.raf.getFilePointer()\n    }\n\n    protected length() {\n        return this.raf.length()\n    }\n\n    protected setLength(final long length) {\n        this.raf.setLength(length)\n    }\n\n    protected close() {\n        this.raf.close()\n    }\n\n    /*\n     * Following reader & writer convert endian from c++(aapt) to java\n     *  c++: little endian\n     *  java: big endian\n     */\n    protected byte readByte() {\n        return this.raf.readByte()\n    }\n\n    protected void writeByte(val) {\n        final def buffer = new byte[1]\n        buffer[0] = (byte) (val & 0xFF)\n        writeBytes(buffer)\n    }\n\n    protected short readShort() {\n        final def buffer = readBytes(2)\n        return getShort(buffer)\n    }\n\n    protected short getShort(final byte[] buffer) {\n        final ByteBuffer bb = ByteBuffer.wrap(buffer)\n        bb.order(ByteOrder.LITTLE_ENDIAN)\n        return bb.getShort()\n    }\n\n    protected void writeShort(i) {\n        final def buffer = new byte[2];\n        buffer[1] = (byte) ((i >> 8) & 0xFF);\n        buffer[0] = (byte) (i & 0xFF);\n        writeBytes(buffer)\n    }\n\n    protected int readInt() {\n        def buffer = readBytes(4)\n        ByteBuffer bb = ByteBuffer.wrap(buffer)\n        bb.order(ByteOrder.LITTLE_ENDIAN)\n        return bb.getInt()\n    }\n\n    protected void writeInt(i) {\n        final def buffer = new byte[4];\n        buffer[3] = (byte) ((i >> 24) & 0xFF);\n        buffer[2] = (byte) ((i >> 16) & 0xFF);\n        buffer[1] = (byte) ((i >> 8) & 0xFF);\n        buffer[0] = (byte) (i & 0xFF);\n        writeBytes(buffer)\n    }\n\n    protected byte[] readBytes(n) {\n        final byte[] buffer = new byte[n]\n        this.raf.read(buffer)\n        return buffer\n    }\n\n    protected void writeBytes(final byte[] buffer) {\n        this.raf.write(buffer)\n        if (!this.edited) {\n            this.edited = true\n        }\n    }\n\n    protected void clipLaterData(final long pos) {\n        this.clipFile = new File(this.file.parentFile, \"${file.name}~\")\n        this.clipRaf = new RandomAccessFile(this.clipFile, 'rw')\n\n        this.lengthBeforeClip = this.raf.length()\n        def sc = this.raf.channel\n        def cc = this.clipRaf.channel\n        sc.transferTo(pos, this.lengthBeforeClip - pos, cc)\n        sc.truncate(pos)\n    }\n\n    protected void pasteLaterData(final long pos) {\n        final def newPos = tellp()\n        final def sc = this.raf.channel\n        final def cc = this.clipRaf.channel\n        cc.position(0L)\n        sc.transferFrom(cc, newPos, this.lengthBeforeClip - pos)\n\n        this.clipRaf.close()\n        this.clipFile.delete()\n    }\n\n    /**\n     * Print bytes in length with hex string\n     *\n     * @param length\n     * @return\n     */\n    protected def dumpBytes(final long length) {\n        for (int i = 0; i < length; i++) {\n            def s = String.format('%02X ', readByte())\n            if (i % 16 == 0) {\n                s = '\\t' + s\n            } else if ((i + 17) % 16 == 0) {\n                s += \"\\n\"\n            } else if ((i + 5) % 4 == 0) {\n                s += \" \"\n            }\n\n            print s\n        }\n\n        println \"\"\n    }\n\n    /**\n     * Check if has been written any bytes\n     *\n     * @return true edited\n     */\n    protected boolean isEdited() {\n        return this.edited\n    }\n}\n"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/groovy/com.didi.virtualapk/aapt/ResAttr.groovy",
    "content": "package com.didi.virtualapk.aapt\n\n/**\n * enum from libs/androidfw/Command.cpp\n */\npublic final class ResAttr {\n\n    public static final int LABEL_ATTR = 0x01010001\n\n    public static final int ICON_ATTR = 0x01010002\n\n    public static final int NAME_ATTR = 0x01010003\n\n    public static final int PERMISSION_ATTR = 0x01010006\n\n    public static final int EXPORTED_ATTR = 0x01010010\n\n    public static final int GRANT_URI_PERMISSIONS_ATTR = 0x0101001b\n\n    public static final int RESOURCE_ATTR = 0x01010025\n\n    public static final int DEBUGGABLE_ATTR = 0x0101000f\n\n    public static final int VALUE_ATTR = 0x01010024\n\n    public static final int VERSION_CODE_ATTR = 0x0101021b\n\n    public static final int VERSION_NAME_ATTR = 0x0101021c\n\n    public static final int SCREEN_ORIENTATION_ATTR = 0x0101001e\n\n    public static final int MIN_SDK_VERSION_ATTR = 0x0101020c\n\n    public static final int MAX_SDK_VERSION_ATTR = 0x01010271\n\n    public static final int REQ_TOUCH_SCREEN_ATTR = 0x01010227\n\n    public static final int REQ_KEYBOARD_TYPE_ATTR = 0x01010228\n\n    public static final int REQ_HARD_KEYBOARD_ATTR = 0x01010229\n\n    public static final int REQ_NAVIGATION_ATTR = 0x0101022a\n\n    public static final int REQ_FIVE_WAY_NAV_ATTR = 0x01010232\n\n    public static final int TARGET_SDK_VERSION_ATTR = 0x01010270\n\n    public static final int TEST_ONLY_ATTR = 0x01010272\n\n    public static final int ANY_DENSITY_ATTR = 0x0101026c\n\n    public static final int GL_ES_VERSION_ATTR = 0x01010281\n\n    public static final int SMALL_SCREEN_ATTR = 0x01010284\n\n    public static final int NORMAL_SCREEN_ATTR = 0x01010285\n\n    public static final int LARGE_SCREEN_ATTR = 0x01010286\n\n    public static final int XLARGE_SCREEN_ATTR = 0x010102bf\n\n    public static final int REQUIRED_ATTR = 0x0101028e\n\n    public static final int INSTALL_LOCATION_ATTR = 0x010102b7\n\n    public static final int SCREEN_SIZE_ATTR = 0x010102ca\n\n    public static final int SCREEN_DENSITY_ATTR = 0x010102cb\n\n    public static final int REQUIRES_SMALLEST_WIDTH_DP_ATTR = 0x01010364\n\n    public static final int COMPATIBLE_WIDTH_LIMIT_DP_ATTR = 0x01010365\n\n    public static final int LARGEST_WIDTH_LIMIT_DP_ATTR = 0x01010366\n\n    public static final int PUBLIC_KEY_ATTR = 0x010103a6\n\n    public static final int CATEGORY_ATTR = 0x010103e8\n\n    public static final int BANNER_ATTR = 0x10103f2\n\n    public static final int ISGAME_ATTR = 0x10103f4\n\n}\n"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/groovy/com.didi.virtualapk/aapt/ResStringFlag.groovy",
    "content": "package com.didi.virtualapk.aapt\n\n/**\n * enum from include/androidfw/ResourceTypes.h\n */\npublic final class ResStringFlag {\n\n    public static final int SORTED_FLAG = 1 << 0\n\n    public static final int UTF8_FLAG = 1 << 8\n\n}\n"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/groovy/com.didi.virtualapk/aapt/ResStringPoolSpan.groovy",
    "content": "package com.didi.virtualapk.aapt\n\n/**\n * enum from include/androidfw/ResourceTypes.h\n */\npublic final class ResStringPoolSpan {\n\n    public static final int END = 0xFFFFFFFF\n\n    public static final byte[] END_SPAN = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]\n\n}"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/groovy/com.didi.virtualapk/aapt/ResTableEntry.groovy",
    "content": "package com.didi.virtualapk.aapt\n\n/**\n * enum from include/androidfw/ResourceTypes.h\n */\npublic final class ResTableEntry {\n\n    public static final int FLAG_COMPLEX = 0X0001\n\n    public static final int FLAG_PUBLIC = 0X0002\n\n    public static final int FLAG_WEAK = 0X0004\n\n}\n"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/groovy/com.didi.virtualapk/aapt/ResTableType.groovy",
    "content": "package com.didi.virtualapk.aapt\n\n/**\n * enum from include/androidfw/ResourceTypes.h\n */\npublic final class ResTableType {\n\n    public static final int NO_ENTRY = -1;\n\n}\n"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/groovy/com.didi.virtualapk/aapt/ResType.groovy",
    "content": "package com.didi.virtualapk.aapt\n\n/**\n * enum from include/androidfw/ResourceTypes.h\n */\npublic final class ResType {\n    public static final int RES_NULL_TYPE = 0x0000\n    public static final int RES_STRING_POOL_TYPE = 0x0001\n    public static final int RES_TABLE_TYPE = 0x0002\n    public static final int RES_XML_TYPE = 0x0003\n\n    // Chunk types in RES_XML_TYPE\n    public static final int RES_XML_FIRST_CHUNK_TYPE = 0x0100\n    public static final int RES_XML_START_NAMESPACE_TYPE = 0x0100\n    public static final int RES_XML_END_NAMESPACE_TYPE = 0x0101\n    public static final int RES_XML_START_ELEMENT_TYPE = 0x0102\n    public static final int RES_XML_END_ELEMENT_TYPE = 0x0103\n    public static final int RES_XML_CDATA_TYPE = 0x0104\n    public static final int RES_XML_LAST_CHUNK_TYPE = 0x017\n    // This contains a uint32_t array mapping strings in the string\n    // pool back to resource identifiers.  It is optional.\n    public static final int RES_XML_RESOURCE_MAP_TYPE = 0x0180\n\n    // Chunk types in RES_TABLE_TYPE\n    public static final int RES_TABLE_PACKAGE_TYPE = 0x0200\n    public static final int RES_TABLE_TYPE_TYPE = 0x0201\n    public static final int RES_TABLE_TYPE_SPEC_TYPE = 0x0202\n    public static final int RES_TABLE_LIBRARY_TYPE = 0x0203\n}\n"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/groovy/com.didi.virtualapk/aapt/ResValueDataType.groovy",
    "content": "package com.didi.virtualapk.aapt\n\n/**\n * enum from include/androidfw/ResourceTypes.h\n */\npublic final class ResValueDataType {\n\n    public static final int TYPE_REFERENCE = 0x01\n\n    public static final int TYPE_STRING = 0x03\n\n}\n"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/groovy/com.didi.virtualapk/aapt/SymbolParser.groovy",
    "content": "package com.didi.virtualapk.aapt\n\n/**\n * Class to parse aapt-generated text symbols file (intermediates/symbols/R.txt)\n */\npublic final class SymbolParser {\n\n    public static final class Entry {\n        public final String type\n        public final String name\n\n        public String getKey() {\n            return \"$type/$name\"\n        }\n\n        Entry(final type, final name) {\n            this.type = type\n            this.name = name\n        }\n\n        @Override\n        String toString() {\n            return this.key\n        }\n\n        @Override\n        boolean equals(final Object obj) {\n            if (this.is(obj)) {\n                return true\n            }\n\n            if (obj instanceof Entry) {\n                Entry e = (Entry) obj\n                return e.type.equals(type) && e.name.equals(name)\n            }\n\n            return false\n        }\n\n        @Override\n        int hashCode() {\n            return this.key.hashCode()\n        }\n    }\n\n    /**\n     * Get declare of one line\n     *\n     * @param s\n     *            e.g. 'int anim abc_fade_in 0x7f050000'\n     * @return e.g. 'int anim abc_fade_in'\n     */\n    public static String getResourceDeclare(final String s) {\n        def arr = s.toCharArray()\n        def find = 0\n        def i = 0\n        for (; i < arr.length; i++) {\n            def c = arr[i]\n            if (c == ' ') find++\n            if (find == 3) break // skip 3 spaces\n        }\n\n        return s.substring(0, i)\n    }\n\n    /**\n     * Get entry data of one line\n     *\n     * @param str\n     *            the line text\n     * @param needsId\n     * @return entry map, e.g. [type:string, typeId:6, entryId:21, key:hello, id:0x7f060015]\n     */\n    public static Map<String, ?> getResourceEntry(String str) {\n        if (str == '') return null\n\n        final def tokenizer = new StringTokenizer(str)\n        final def vtype = tokenizer.nextToken()     // value type (int or int[])\n        final def type = tokenizer.nextToken()      // resource type (attr/string/color etc.)\n        final def key = tokenizer.nextToken()\n        final def idStr = tokenizer.nextToken('\\r\\n').trim()\n\n        if (type == 'styleable') {\n            // Styleables won't be compiled to resources.arsc file but saved in `R.java',\n            // it just stores the 'attr/*' ids which are used to access values from AttributeSet.\n            // As example:\n            //   'int[] styleable MyTextView { 0x7f010055, 0x7f010056 }'\n            //   'int styleable MyTextView_label 0'\n            //   'int styleable MyTextView_label2 1'\n            //\n            //   TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MyTextView);\n            //   String label = ta.getString(R.styleable.MyTextView_label);\n            //\n            // The id 0x7f010055 and 0x7f010056 refer to 'attr/label' and 'attr/label2', so the\n            // reading TypedArray contains the values of them.\n            // The id 0 and 1 specify the value location in TypedArray.\n            def e = [vtype: vtype, type: type, key: key, idStr: idStr, isStyleable: true]\n            def idLen = idStr.length()\n            if (idLen > 4) { // hereby, vtype must be int[] and the idStr is not empty as '{ }'\n                def ids = idStr.substring(2, idStr.length() - 2) // bypass '{ ' and ' }'\n                e.idStrs = ids.split(', ') as List<String>\n            }\n            return e\n        }\n\n        int typeId = Integer.parseInt(idStr.substring(4, 6), 16)\n        int entryId = Integer.parseInt(idStr.substring(6), 16)\n        int id = Integer.decode(idStr)\n        return [vtype: vtype, type: type, key: key,\n                typeId: typeId, entryId: entryId, idStr: idStr, id: id, isStyleable: false]\n    }\n\n    /**\n     * Get entries data of each line\n     *\n     * @param file\n     * @return\n     */\n    public static Map<String, Map<String, ?>> getResourceEntries(final File file) {\n        def es = [:]\n        if (!file.exists()) {\n            return es\n        }\n\n        file.eachLine { str ->\n            def entry = getResourceEntry(str)\n            if (entry == null) return\n            es.put(\"${entry.type}/${entry.key}\", entry)\n        }\n        return es\n    }\n\n    public static void collectResourceKeys(File file, String targetType, List excludes,\n                                           List outEntries, List outStyleableKeys) {\n        if (!file.exists()) return\n\n        file.eachLine { str ->\n            if (str == '') return\n\n            def i = str.indexOf(' ')\n            str = str.substring(i + 1)\n            i = str.indexOf(' ')\n            def type = str.substring(0, i)\n            if (targetType != null && type != targetType) return\n\n            str = str.substring(i + 1)\n            i = str.indexOf(' ')\n            def name = str.substring(0, i)\n            if (excludes != null && excludes.contains(name)) return\n\n            if (type == 'styleable') {\n                if (outStyleableKeys != null) {\n                    outStyleableKeys.add(name)\n                }\n            } else {\n                if (outEntries != null) {\n                    outEntries.add(new Entry(type, name))\n                }\n            }\n        }\n    }\n\n    public static void collectAarResourceKeys(File file, Set outEntries, Set outStyleableKeys) {\n        if (!file.exists()) return\n\n        file.eachLine { str ->\n            if (str == '') return\n\n            def i = str.indexOf(' ')\n            str = str.substring(i + 1)\n            i = str.indexOf(' ')\n            def type = str.substring(0, i)\n\n            str = str.substring(i + 1)\n            i = str.indexOf(' ')\n            def name = str.substring(0, i)\n\n            if (type == 'styleable') {\n                if (outStyleableKeys != null) {\n                    outStyleableKeys.add(name)\n                }\n            } else {\n                if (outEntries != null) {\n                    outEntries.add(new Entry(type, name))\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/groovy/com.didi.virtualapk/aapt/packageinfo",
    "content": "Editor of ARSC file, forked from https://github.com/wequick/Small and made some improvements. Thanks to the Small team.\n"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/groovy/com.didi.virtualapk/collector/HostClassAndResCollector.groovy",
    "content": "package com.didi.virtualapk.collector\n\nimport com.didi.virtualapk.collector.dependence.AarDependenceInfo\nimport com.didi.virtualapk.collector.dependence.DependenceInfo\n\nimport java.util.zip.ZipFile\n\n/**\n * Collector of Class and Java Resource(no-class files in jar) in host apk\n *\n * @author zhengtao\n */\n\nclass HostClassAndResCollector {\n\n    private def hostJarFiles = [] as LinkedList<File>\n    private def hostClassesAndResources = [] as LinkedHashSet<String>\n\n    /**\n     * Collect jar entries that already exist in the host apk\n     *\n     * @param stripDependencies DependencyInfos that exists in the host apk, including AAR and JAR\n     * @return set of classes and java resources\n     */\n    public Set<String> collect(Collection<DependenceInfo> stripDependencies) {\n        flatToJarFiles(stripDependencies, hostJarFiles)\n        hostJarFiles.each {\n            hostClassesAndResources.addAll(unzipJar(it))\n        }\n        hostClassesAndResources\n    }\n\n    /**\n     * Collect the jar files that are held by the DependenceInfo， including local jars of the DependenceInfo\n     * @param stripDependencies Collection of DependenceInfo\n     * @param jarFiles Collection used to store jar files\n     */\n    def flatToJarFiles(Collection<DependenceInfo> stripDependencies, Collection<File> jarFiles) {\n        stripDependencies.each {\n            jarFiles.add(it.jarFile)\n            if (it instanceof AarDependenceInfo) {\n                it.localJars.each {\n                    jarFiles.add(it)\n                }\n            }\n        }\n    }\n\n    /**\n     * Unzip the entries of Jar\n     *\n     * @return Set of entries in the JarFile\n     */\n    public static Set<String> unzipJar(File jarFile) {\n\n        def jarEntries = [] as Set<String>\n\n        ZipFile zipFile = new ZipFile(jarFile)\n        try {\n            zipFile.entries().each {\n                jarEntries.add(it.name)\n            }\n        } finally {\n            zipFile.close();\n        }\n\n        return jarEntries\n    }\n\n}"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/groovy/com.didi.virtualapk/collector/HostJniLibsCollector.groovy",
    "content": "package com.didi.virtualapk.collector\n\nimport com.android.SdkConstants\nimport com.android.annotations.NonNull\nimport com.android.build.api.transform.QualifiedContent\nimport com.didi.virtualapk.collector.dependence.AarDependenceInfo\nimport com.didi.virtualapk.collector.dependence.DependenceInfo\nimport com.didi.virtualapk.collector.dependence.JarDependenceInfo\nimport com.didi.virtualapk.utils.PackagingUtils\nimport com.google.common.collect.ArrayListMultimap\nimport com.google.common.collect.ListMultimap\n\nimport java.util.jar.JarFile\nimport java.util.regex.Matcher\nimport java.util.regex.Pattern\nimport java.util.zip.ZipEntry\nimport java.util.zip.ZipFile\n\nimport static com.android.SdkConstants.FD_APK_NATIVE_LIBS\n\n/**\n * Native lib file(.so) collector\n *\n * @author zhengtao\n */\nclass HostJniLibsCollector {\n\n    private final Pattern jarAbiPattern = Pattern.compile(\"lib/([^/]+)/[^/]+\")\n    private final Pattern folderAbiPattern = Pattern.compile(\"([^/]+)/[^/]+\")\n    private final Pattern filenamePattern = Pattern.compile(\".*\\\\.so\")\n\n    /**\n     * Map of .so files need to be stripped, any .so file may exist in multiple folders(aar) or jars\n     */\n    ListMultimap<String, DependenceInfo> jniFileList = ArrayListMultimap.create();\n\n    /**\n     * Collect all the .so files in the excluded dependencies\n     * @param stripDependencies  DependencyInfos that exists in the host apk, including AAR and JAR\n     * @return .so files need to be stripped\n     */\n    def collect(Collection<DependenceInfo> stripDependencies) {\n        stripDependencies.each {\n            if (it instanceof AarDependenceInfo) {\n                gatherListFromFolder(it as AarDependenceInfo, jniFileList)\n            } else {\n                gatherListFromJar(it as JarDependenceInfo, jniFileList)\n            }\n        }\n        jniFileList.keySet()\n    }\n\n\n    private void gatherListFromJar(\n            @NonNull JarDependenceInfo jarDependence,\n            @NonNull ListMultimap<String, DependenceInfo> content) throws IOException {\n\n        ZipFile zipFile = new ZipFile(jarDependence.jarFile);\n        try {\n            Enumeration<? extends ZipEntry> entries = zipFile.entries();\n            while (entries.hasMoreElements()) {\n                ZipEntry entry = entries.nextElement();\n\n                String path = entry.getName();\n                if (skipEntry(entry, path)) {\n                    continue;\n                }\n\n                content.put(path, jarDependence);\n            }\n\n        } finally {\n            zipFile.close();\n        }\n    }\n\n\n    private void gatherListFromFolder(\n            @NonNull AarDependenceInfo aarDependence,\n            @NonNull ListMultimap<String, DependenceInfo> content) {\n        gatherListFromFolder(aarDependence.jniFolder, \"\", aarDependence, content);\n    }\n\n\n\n    private boolean skipEntry(\n            @NonNull ZipEntry entry,\n            @NonNull String path) {\n        if (entry.directory || JarFile.MANIFEST_NAME == path ||\n                !validateJarPath(path)) {\n            return true;\n        }\n\n        // split the path into segments.\n        String[] segments = path.split(\"/\");\n\n        // empty path? skip to next entry.\n        if (segments.length == 0) {\n            return true;\n        }\n\n        // Check each folders to make sure they should be included.\n        // Folders like CVS, .svn, etc.. should already have been excluded from the\n        // jar file, but we need to exclude some other folder (like /META-INF) so\n        // we check anyway.\n        for (int i = 0 ; i < segments.length - 1; i++) {\n            if (!PackagingUtils.checkFolderForPackaging(segments[i])) {\n                return true;\n            }\n        }\n\n        return !PackagingUtils.checkFileForPackaging(segments[segments.length-1],\n                false /*allowClassFiles*/);\n    }\n\n\n\n\n    private void gatherListFromFolder(\n            @NonNull File file,\n            @NonNull String path,\n            @NonNull AarDependenceInfo aarDependenceInfo,\n            @NonNull ListMultimap<String, DependenceInfo> content) {\n        File[] children = file.listFiles(new FilenameFilter() {\n            @Override\n            public boolean accept(File f, String name) {\n\n                return f.isDirectory() || !name.endsWith(SdkConstants.DOT_CLASS);\n            }\n        });\n\n        if (children != null) {\n            for (File child : children) {\n                String newPath = path.isEmpty() ? child.getName() : path + '/' + child.getName();\n                if (child.isDirectory()) {\n                    gatherListFromFolder(\n                            child,\n                            newPath,\n                            aarDependenceInfo,\n                            content);\n                } else if (child.isFile() && validateFolderPath(newPath)) {\n                    content.put(folderPathToKey(newPath), aarDependenceInfo);\n                }\n            }\n        }\n    }\n\n\n    public boolean validateJarPath(@NonNull String path) {\n        // extract abi from path, checking the general path structure (lib/<abi>/<filename>)\n        Matcher m = jarAbiPattern.matcher(path);\n\n        // if the ABI is accepted, check the 3rd segment\n        if (m.matches()) {\n            // remove the beginning of the path (lib/<abi>/)\n            String filename = path.substring(5 + m.group(1).length());\n            // and check the filename\n            return filenamePattern.matcher(filename).matches() ||\n                    SdkConstants.FN_GDBSERVER == filename ||\n                    SdkConstants.FN_GDB_SETUP == filename;\n        }\n\n        return false;\n    }\n\n\n    public boolean validateFolderPath(@NonNull String path) {\n        // extract abi from path, checking the general path structure (<abi>/<filename>)\n        Matcher m = folderAbiPattern.matcher(path);\n\n        // if the ABI is accepted, check the 3rd segment\n        if (m.matches()) {\n            // remove the beginning of the path (<abi>/)\n            String filename = path.substring(1 + m.group(1).length());\n            // and check the filename\n            return filenamePattern.matcher(filename).matches() ||\n                    SdkConstants.FN_GDBSERVER == filename ||\n                    SdkConstants.FN_GDB_SETUP == filename;\n        }\n\n        return false;\n    }\n\n    @NonNull\n    public String folderPathToKey(@NonNull String path) {\n        return FD_APK_NATIVE_LIBS + \"/\" + path;\n    }\n\n    @NonNull\n    public String keyToFolderPath(@NonNull String path) {\n        return path.substring(FD_APK_NATIVE_LIBS.length() + 1);\n    }\n}"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/groovy/com.didi.virtualapk/collector/ResourceCollector.groovy",
    "content": "package com.didi.virtualapk.collector\n\nimport com.android.build.gradle.tasks.ProcessAndroidResources\nimport com.didi.virtualapk.VAExtention\nimport com.didi.virtualapk.collector.dependence.AarDependenceInfo\nimport com.didi.virtualapk.collector.res.ResourceEntry\nimport com.didi.virtualapk.collector.res.StyleableEntry\nimport com.google.common.collect.ArrayListMultimap\nimport com.google.common.collect.ListMultimap\nimport com.google.common.collect.Lists\nimport org.gradle.api.Project\n\n/**\n * Collect all(host+plugin) resources&styleables in the APK and reassign the resource ID\n *\n * @author zhengtao\n */\nclass ResourceCollector {\n\n    private Project project\n    private VAExtention virtualApk\n    private VAExtention.VAContext vaContext\n\n    /**\n     * Gradle task of process resource in Android build system\n     */\n    private ProcessAndroidResources processResTask\n    /**\n     * R symbol File that records all resources, generated by aapt\n     */\n    private File allRSymbolFile\n    /**\n     * Host R symbol File, need saved after the host APK compilation\n     */\n    private File hostRSymbolFile\n\n    /**\n     * Map of all resources, KEY is the resource type, e.g. drawable, VALUE is all entries of this type\n     */\n    ListMultimap<String, ResourceEntry> allResources = ArrayListMultimap.create()\n    /**\n     * List of all styleables\n     */\n    List<StyleableEntry> allStyleables = Lists.newArrayList()\n\n\n    private ListMultimap<String, ResourceEntry> hostResources = ArrayListMultimap.create()\n    private List<StyleableEntry> hostStyleables = Lists.newArrayList()\n\n    /**\n     * pluginResources = allResources - hostResources\n     */\n    ListMultimap<String, ResourceEntry> pluginResources = ArrayListMultimap.create()\n    List<StyleableEntry> pluginStyleables = Lists.newArrayList()\n\n    public ResourceCollector(Project project, ProcessAndroidResources par) {\n\n        this.project = project\n        virtualApk = project.virtualApk\n        vaContext = virtualApk.getVaContext(par.variantName)\n\n        processResTask = par\n\n        allRSymbolFile = par.textSymbolOutputFile\n        hostRSymbolFile = vaContext.hostSymbolFile\n    }\n\n    /**\n     * Perform resource collection and ID redistribution\n     */\n    def collect() {\n\n        //1、First, collect all resources by parsing the R symbol file.\n        parseResEntries(allRSymbolFile, allResources, allStyleables)\n\n        //2、Then, collect host resources by parsing the host apk R symbol file, should be stripped.\n        parseResEntries(hostRSymbolFile, hostResources, hostStyleables)\n\n        //3、Compute the resources that should be retained in the plugin apk.\n        filterPluginResources()\n\n        //4、Reassign the resource ID. If the resource entry exists in host apk, the reassign ID\n        //   should be same with value in host apk; If the resource entry is owned by plugin project,\n        //   then we should recalculate the ID value.\n        reassignPluginResourceId()\n\n        //5、Collect all the resources in the retained AARs, to regenerate the R java file that uses the new resource ID\n        vaContext.retainedAarLibs.each {\n            gatherReservedAarResources(it)\n        }\n    }\n\n    /**\n     * Collect resources and styleables by parsing R symbol file\n     * @param RSymbolFile R symbol file records the resource entries\n     * @param resourcesMap Map used to store resources\n     * @param styleableList List used to store styleables\n     */\n    private void parseResEntries(File RSymbolFile, ListMultimap resourcesMap, List styleableList) {\n        if (!RSymbolFile.exists()) {\n            return\n        }\n        RSymbolFile.eachLine { line ->\n            /**\n             *  Line Content:\n             *  Common Res:  int string abc_action_bar_home_description 0x7f090000\n             *  Styleable:   int[] styleable TagLayout { 0x010100af, 0x7f0102b5, 0x7f0102b6 }\n             *            or int styleable TagLayout_android_gravity 0\n             */\n            if (!line.empty) {\n                def tokenizer = new StringTokenizer(line)\n                def valueType = tokenizer.nextToken()     // value type (int or int[])\n                def resType = tokenizer.nextToken()      // resource type (attr/string/color etc.)\n                def resName = tokenizer.nextToken()\n                def resId = tokenizer.nextToken('\\r\\n').trim()\n\n                if (resType == 'styleable') {\n                    styleableList.add(new StyleableEntry(resName, resId, valueType))\n                } else {\n                    resourcesMap.put(resType, new ResourceEntry(resType, resName, Integer.decode(resId)))\n                }\n            }\n        }\n    }\n\n    /**\n     * Filter out the resources that need to be retained in the plugin apk,\n     * pluginResources = allResources - hostResources\n     */\n    private void filterPluginResources() {\n        allResources.values().each {\n            def index = hostResources.get(it.resourceType).indexOf(it)\n            if(index >= 0){\n                /**\n                 * If the resource entry exists in host apk, assign the host resource ID of this entry\n                 * as the new resource id.\n                 * Then replace the object reference in host resource collection with the object\n                 * in all resource collection, to make both of them point to the same object\n                 */\n                it.newResourceId = hostResources.get(it.resourceType).get(index).resourceId\n                hostResources.get(it.resourceType).set(index, it)\n            } else {\n                pluginResources.put(it.resourceType, it)\n            }\n        }\n\n        allStyleables.each {\n            def index = hostStyleables.indexOf(it)\n            if(index >= 0) {\n                /**\n                 * Do not support the same name but different content styleable entry\n                 */\n                it.value = hostStyleables.get(index).value\n                hostStyleables.set(index, it)\n            } else {\n                pluginStyleables.add(it)\n            }\n        }\n    }\n\n    /**\n     * Reassign the ID for resources need retained in the plugin apk\n     * Set the packageId specified in the build.gradle file, and reassign type&entry ID\n     */\n    private void reassignPluginResourceId() {\n\n        def resourceIdList = []\n        pluginResources.keySet().each { String resType ->\n            List<ResourceEntry> entryList = pluginResources.get(resType)\n            resourceIdList.add([resType: resType, typeId: entryList.empty ? -100 : parseTypeIdFromResId(entryList.first().resourceId)])\n        }\n\n\n        resourceIdList.sort { t1, t2 ->\n            t1.typeId - t2.typeId\n        }\n\n        int lastType = 1\n        resourceIdList.each {\n            if (it.typeId < 0) {\n                return\n            }\n            def typeId = 0\n            def entryId = 0\n            typeId = lastType++\n            pluginResources.get(it.resType).each {\n                it.setNewResourceId(virtualApk.packageId, typeId, entryId++)\n            }\n        }\n\n        List<ResourceEntry> attrEntries = allResources.get('attr')\n\n        pluginStyleables.findAll { it.valueType == 'int[]'}.each { StyleableEntry styleableEntry->\n            List<String> values = styleableEntry.valueAsList\n            values.eachWithIndex { hexResId, idx ->\n                ResourceEntry resEntry = attrEntries.find { it.hexResourceId == hexResId }\n                if (resEntry != null) {\n                    values[idx] = resEntry.hexNewResourceId\n                }\n            }\n            styleableEntry.value = values\n        }\n    }\n\n    /**\n     * Parse the type part of a android resource id\n     */\n    def parseTypeIdFromResId(int resourceId) {\n        resourceId >> 16 & 0xFF\n    }\n\n    /**\n     * Collect all resources the aar project can access\n     * @param aarDependenceInfo aar dependence info\n     */\n    def gatherReservedAarResources(AarDependenceInfo aarDependenceInfo) {\n        def aarResKeys = aarDependenceInfo.resourceKeys\n        if (aarResKeys.empty) return\n\n        allResources.keySet().each { resType ->\n            allResources.get(resType).each { resEntry ->\n                if (aarResKeys.contains(\"${resType}:${resEntry.resourceName}\")) {\n                    aarDependenceInfo.aarResources.put(resType, resEntry)\n                }\n            }\n        }\n\n        aarDependenceInfo.aarStyleables = allStyleables.findAll { styleableEntry ->\n            aarResKeys.contains(\"styleable:${styleableEntry.name}\")\n        }\n    }\n\n    /**\n     * Returns the mapping from the original ID to the redistributed ID\n     */\n    def getResIdMap() {\n        def idMap = [:] as Map<Integer, Integer>\n        allResources.values().each { resEntry ->\n            idMap.put(resEntry.resourceId, resEntry.newResourceId)\n        }\n        return idMap\n    }\n\n\n    private void dump() {\n        final def resSplitDir = new File(project.buildDir, 'generated')\n\n        final def retainTypeFile = new File(resSplitDir, 'retainType.txt')\n        if (!retainTypeFile.exists()) {\n            retainTypeFile.createNewFile()\n        }\n        retainTypeFile.withPrintWriter { pw ->\n            pluginResources.values().each {\n                pw.println \"${it.resourceType} ${it.resourceName} 0x${Integer.toHexString(it.resourceId)} 0x${Integer.toHexString(it.newResourceId)}\"\n            }\n            pw.println \"****************Styleables*****************\"\n            pluginStyleables.each {\n                pw.println \"${it.name} ${it.valueType} ${it.value}\"\n            }\n        }\n\n\n        final def allTypeFile = new File(resSplitDir, \"allType.txt\")\n        if (!allTypeFile.exists()) {\n            allTypeFile.createNewFile()\n        }\n        allTypeFile.withPrintWriter { pw ->\n            allResources.values().each {\n                pw.println \"${it.resourceType} ${it.resourceName} 0x${Integer.toHexString(it.resourceId)} 0x${Integer.toHexString(it.newResourceId)}\"\n            }\n            pw.println \"****************Styleables*****************\"\n            allStyleables.each {\n                pw.println \"${it.name} ${it.valueType} ${it.value}\"\n            }\n        }\n\n        final def vendorTypeFile = new File(resSplitDir, 'vendorType.txt')\n        if (!vendorTypeFile.exists()) {\n            vendorTypeFile.createNewFile()\n        }\n\n        vendorTypeFile.withPrintWriter { pw ->\n            vaContext.retainedAarLibs.each { aarLib ->\n                pw.println \"${aarLib.name}\"\n\n                aarLib.aarResources.values().each {\n                    pw.println \"${it.resourceType} ${it.resourceName} 0x${Integer.toHexString(it.resourceId)} 0x${Integer.toHexString(it.newResourceId)}\"\n                }\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/groovy/com.didi.virtualapk/collector/dependence/AarDependenceInfo.groovy",
    "content": "package com.didi.virtualapk.collector.dependence\n\nimport com.android.SdkConstants\nimport com.android.build.gradle.internal.TaskManager\nimport com.android.builder.model.AndroidLibrary\nimport com.android.utils.FileUtils\nimport com.didi.virtualapk.collector.res.ResourceEntry\nimport com.didi.virtualapk.collector.res.StyleableEntry\nimport com.didi.virtualapk.utils.Log\nimport com.google.common.collect.ArrayListMultimap\nimport com.google.common.collect.ListMultimap\nimport com.google.common.collect.Lists\n\n/**\n * Represents a AAR dependence from Maven repository or Android library module\n *\n * @author zhengtao\n */\nclass AarDependenceInfo extends DependenceInfo {\n\n    /**\n     * Android library dependence in android build system, delegate of AarDependenceInfo\n     */\n    AndroidLibrary library\n\n    File intermediatesFile\n\n    /**\n     * All resources(e.g. drawable, layout...) this library can access\n     * include resources of self-project and dependence(direct&transitive) project\n     */\n    ListMultimap<String, ResourceEntry> aarResources = ArrayListMultimap.create()\n    /**\n     * All styleables this library can access, like \"aarResources\"\n     */\n    List<StyleableEntry> aarStyleables = Lists.newArrayList()\n\n    AarDependenceInfo(String group, String artifact, String version, AndroidLibrary library) {\n        super(group, artifact, version)\n        this.library = library\n    }\n\n    @Override\n    File getJarFile() {\n        Log.i 'AarDependenceInfo', \"Found [${library.resolvedCoordinates}]'s jar file: ${library.jarFile}\"\n        return library.jarFile\n    }\n\n    @Override\n    DependenceType getDependenceType() {\n        return DependenceType.AAR\n    }\n    \n    File getAssetsFolder() {\n        Log.i 'AarDependenceInfo', \"Found [${library.resolvedCoordinates}]'s assets folder: ${library.assetsFolder}\"\n        return library.assetsFolder\n    }\n\n    File getJniFolder() {\n        Log.i 'AarDependenceInfo', \"Found [${library.resolvedCoordinates}]'s jni folder: ${library.jniFolder}\"\n        return library.jniFolder\n    }\n\n    Collection<File> getLocalJars() {\n        Log.i 'AarDependenceInfo', \"Found [${library.resolvedCoordinates}]'s local jars: ${library.localJars}\"\n        return library.localJars\n    }\n\n    /**\n     * Return collection of \"resourceType:resourceName\", parse from R symbol file\n     * @return set of a combination of resource type and name\n     */\n    public Set<String> getResourceKeys() {\n\n        def resKeys = [] as Set<String>\n\n        def rSymbol = getFile(library.symbolFile, TaskManager.DIR_BUNDLES, library.projectVariant, SdkConstants.FN_RESOURCE_TEXT)\n        if (rSymbol.exists()) {\n            Log.i 'AarDependenceInfo', \"Found [${library.resolvedCoordinates}]'s symbol file: ${rSymbol}\"\n            rSymbol.eachLine { line ->\n                if (!line.empty) {\n                    def tokenizer = new StringTokenizer(line)\n                    def valueType = tokenizer.nextToken()\n                    def resType = tokenizer.nextToken()       // resource type (attr/string/color etc.)\n                    def resName = tokenizer.nextToken()       // resource name\n\n                    resKeys.add(\"${resType}:${resName}\")\n                }\n            }\n        }\n\n        return resKeys\n    }\n\n    /**\n     * Return the package name of this library, parse from manifest file\n     * manifest file are obtained by delegating to \"library\"\n     * @return package name of this library\n     */\n    public String getPackage() {\n        File manifest = getFile(library.manifest, 'manifests', 'full', library.projectVariant, SdkConstants.ANDROID_MANIFEST_XML)\n        Log.i 'AarDependenceInfo', \"Found [${library.resolvedCoordinates}]'s manifest file: ${manifest}\"\n        def xmlManifest = new XmlParser().parse(manifest)\n        return xmlManifest.@package\n    }\n\n    File getIntermediatesDir() {\n        if (intermediatesFile == null) {\n            String path = library.folder.path\n            try {\n                intermediatesFile = new File(path.substring(0, path.indexOf(\"${File.separator}intermediates${File.separator}\")), 'intermediates')\n\n            } catch (Exception e) {\n                Log.e('AarDependenceInfo', \"Can not find [${library.resolvedCoordinates}]'s intermediates dir from the path: ${path}\")\n                intermediatesFile = library.folder\n            }\n        }\n        return intermediatesFile\n    }\n\n    File getFile(File defaultFile, String... paths) {\n        if (library.projectVariant == null) {\n            return defaultFile\n        }\n\n        if (defaultFile.exists()) {\n            return defaultFile\n        }\n\n        // module library\n        return FileUtils.join(intermediatesDir, paths)\n     }\n\n    @Override\n    String toString() {\n        return \"${super.toString()} -> ${library}\"\n    }\n}"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/groovy/com.didi.virtualapk/collector/dependence/DependenceInfo.groovy",
    "content": "package com.didi.virtualapk.collector.dependence\n\n/**\n * Represents a library in Android Project\n *\n * @author zhengtao\n */\npublic abstract class DependenceInfo {\n\n    /**\n     * The type of of the DependenceInfo.\n     */\n    public enum DependenceType{\n\n        /**\n         * Type of Android library\n         */\n        AAR(0x01),\n\n        /**\n         * Type of Java library\n         */\n        JAR(0x02)\n\n        private final int value;\n\n        DependenceType(int value) {\n            this.value = value;\n        }\n\n        public int getValue() {\n            return value;\n        }\n    }\n\n    /**\n     * Group name of dependence in a Maven repository\n     */\n    private String group\n    /**\n     * Module name of dependence in a Maven repository\n     */\n    private String artifact\n    /**\n     * Version of dependence in a Maven repository\n     */\n    private String version\n\n\n    DependenceInfo(String group, String artifact, String version) {\n        this.group = group\n        this.artifact = artifact\n        this.version = version\n    }\n\n\n    String getGroup() {\n        return group\n    }\n\n    String getArtifact() {\n        return artifact\n    }\n\n    String getVersion() {\n        return version\n    }\n\n    abstract File getJarFile()\n    abstract DependenceType getDependenceType()\n\n    @Override\n    String toString() {\n        return \"${group}:${artifact}:${version} -> ${jarFile} -> ${super.toString()}\"\n    }\n}"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/groovy/com.didi.virtualapk/collector/dependence/JarDependenceInfo.groovy",
    "content": "package com.didi.virtualapk.collector.dependence\n\nimport com.android.builder.model.JavaLibrary\nimport com.didi.virtualapk.utils.Log\n\n/**\n * Represents a Jar library. This could be the output of a Java project.\n *\n * @author zhengtao\n */\nclass JarDependenceInfo extends DependenceInfo {\n\n    JavaLibrary library\n\n    JarDependenceInfo(String group, String artifact, String version, JavaLibrary library) {\n        super(group, artifact, version)\n        this.library = library\n    }\n\n    @Override\n    File getJarFile() {\n        Log.i 'JarDependenceInfo', \"Found [${library.resolvedCoordinates}]'s jar file: ${library.jarFile}\"\n        return library.jarFile\n    }\n\n    @Override\n    DependenceType getDependenceType() {\n        return DependenceType.JAR\n    }\n}"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/groovy/com.didi.virtualapk/collector/res/ResourceEntry.groovy",
    "content": "package com.didi.virtualapk.collector.res\n/**\n * Represent a resource entry(e.g. drawable, anim, attr, layout...) in android project,\n * the information in this class will be recorded in the arsc file.\n *\n * @author zhengtao\n */\nclass ResourceEntry {\n\n    /**\n     * Type of a resource entry, e.g. drawable\n     */\n    String resourceType\n    /**\n     * Name of a resource entry, e.g. abc_btn_check_material is name of a drawable\n     */\n    String resourceName\n    /**\n     * Original id value of a resource entry, generated by aapt\n     */\n    int resourceId\n\n    /**\n     * New assigned id value of resource entry, pid is modified\n     */\n    int newResourceId\n\n    public ResourceEntry(resType, resName, resId) {\n        resourceType = resType\n        resourceName = resName\n        resourceId = resId\n    }\n\n    public void setNewResourceId(id) {\n        this.newResourceId = id\n    }\n\n    /**\n     * Generate a new resource id from packageId, typeId, entryId.\n     *\n     * For example: 0x7f02000e is a id of resource entry\n     *\n     * @param packageId  7f\n     * @param typeId   02\n     * @param entryId  0000e\n     */\n    public void setNewResourceId(packageId, typeId, entryId) {\n        newResourceId = packageId << 24 | typeId << 16 | entryId\n    }\n\n    /**\n     * @return the hexadecimal resource id\n     */\n    public String getHexResourceId() {\n        return \"0x${Integer.toHexString(resourceId)}\"\n    }\n\n    /**\n     * @return the hexadecimal new resource id\n     */\n    public String getHexNewResourceId() {\n        return \"0x${Integer.toHexString(newResourceId)}\"\n    }\n\n\n    boolean equals(o) {\n        if (this.is(o)) return true\n        if (getClass() != o.class) return false\n\n        ResourceEntry that = (ResourceEntry) o\n\n        if (resourceName != that.resourceName) return false\n        if (resourceType != that.resourceType) return false\n\n        return true\n    }\n\n\n    int hashCode() {\n        int result\n        result = resourceType.hashCode()\n        result = 31 * result + resourceName.hashCode()\n        return result\n    }\n}"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/groovy/com.didi.virtualapk/collector/res/StyleableEntry.groovy",
    "content": "package com.didi.virtualapk.collector.res\n\n/**\n * Represent styleable item in R symbol file\n * e.g.\n * int[] styleable TagLayout { 0x010100af, 0x7f0102b5, 0x7f0102b6 }\n * int styleable TagLayout_android_gravity 0\n *\n *\n * Styleable will not be recorded to the arsc file, and the representation\n * in the R file is different from other resource types, so separate representation\n *\n * @author zhengtao\n */\nclass StyleableEntry {\n\n    /**\n     * Name of a styleable entry, e.g. TagLayout or TagLayout_android_gravity\n     */\n    String name\n    /**\n     * Value of a styleable entry represent in R file, e.g. { 0x010100af, 0x7f0102b5, 0x7f0102b6 } or 0\n     */\n    String value\n\n    /**\n     * Type of a styleable entry value , int or int[]\n     */\n    String valueType\n\n    public StyleableEntry(name, value, valueType) {\n        this.name = name\n        this.value = value\n        this.valueType = valueType\n    }\n\n    /**\n     * e.g.\n     * when value of the entry is: { 0x010100af, 0x7f0102b5, 0x7f0102b6 }\n     * this method return [0x010100af, 0x7f0102b5, 0x7f0102b6]\n     *\n     * @return value of styleable entry as list\n     */\n    def getValueAsList() {\n        value.trim()[1..-2].split(',')*.trim()\n    }\n\n    /**\n     * Generate the value of int[] type styleable entry by list of value element\n     * @param values list of\n     */\n    def setValue(List<String> values) {\n        value = \"{ ${values.join(', ')} }\"\n    }\n\n    def setValue(String value) {\n        this.value = value\n    }\n\n\n    boolean equals(o) {\n        if (this.is(o)) return true\n        if (getClass() != o.class) return false\n\n        StyleableEntry that = (StyleableEntry) o\n\n        if (name != that.name) return false\n        if (valueType != that.valueType) return false\n\n        return true\n    }\n\n    int hashCode() {\n        int result\n        result = name.hashCode()\n        result = 31 * result + valueType.hashCode()\n        return result\n    }\n}"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/groovy/com.didi.virtualapk/hooker/DxTaskHooker.groovy",
    "content": "package com.didi.virtualapk.hooker\n\nimport com.android.build.gradle.api.ApkVariant\nimport com.android.build.gradle.internal.pipeline.TransformTask\nimport com.didi.virtualapk.utils.Log\nimport org.apache.commons.io.FilenameUtils\nimport org.gradle.api.Project\n\n/**\n * Minify R class file under the applicationId namespace before dx task\n *\n * @author zhengtao\n */\nclass DxTaskHooker extends GradleTaskHooker<TransformTask> {\n\n\n    public DxTaskHooker(Project project, ApkVariant apkVariant) {\n        super(project, apkVariant)\n    }\n\n    @Override\n    String getTransformName() {\n        return \"dex\"\n    }\n\n    /**\n     * Replace the R class files record all resources with the stripped R only record plugin resources.\n     * Input file may be a directory or jar file\n     *\n     * @param task Gradle transform task for dex\n     */\n    @Override\n    void beforeTaskExecute(TransformTask task) {\n        task.inputs.files.each { input ->\n//            Log.i 'DxTaskHooker', \"${task.name}: ${input.absoluteFile}\"\n            if(input.directory) {\n                input.eachFileRecurse { file ->\n                    handleFile(file)\n                }\n            } else {\n                handleFile(input)\n            }\n        }\n    }\n\n    void handleFile(File file) {\n        if (file.directory && file.path.endsWith(vaContext.packagePath)) {\n\n            if (recompileSplitR(file)) {\n                Log.i 'DxTaskHooker', \"Recompiled R.java in dir: ${file.absoluteFile}\"\n            }\n\n        } else if (file.file && file.name.endsWith('.jar')) {\n            // Decompress jar file\n            File unzipJarDir = new File(file.parentFile, FilenameUtils.getBaseName(file.name))\n            project.copy {\n                from project.zipTree(file)\n                into unzipJarDir\n            }\n\n            // VirtualApk Package Dir\n            File pkgDir = new File(unzipJarDir, vaContext.packagePath)\n            if (pkgDir.exists()) {\n                if (recompileSplitR(pkgDir)) {\n                    Log.i 'DxTaskHooker', \"Recompiled R.java in jar: ${file.absoluteFile}\"\n                    File backupDir = new File(vaContext.getBuildDir(scope), 'origin/classes')\n                    backupDir.deleteDir()\n                    project.copy {\n                        from file\n                        into backupDir\n                    }\n\n                    project.ant.zip(baseDir: unzipJarDir, destFile: file)\n                }\n            }\n        }\n    }\n\n    /**\n     * Delete the large R class file under the applicationId namespace, then\n     * compile the splitRJavaFile to generate the R class file only records\n     * plugin resources\n     *\n     * @param pkgDir The path to storing the R class file\n     * @return true if the search&delete&compile actions succeed\n     */\n    boolean recompileSplitR(File pkgDir) {\n\n        File[] RClassFiles = pkgDir.listFiles(new FilenameFilter() {\n            @Override\n            boolean accept(File dir, String name) {\n                return name.startsWith('R$') && name.endsWith('.class')\n            }\n        })\n\n        if(RClassFiles?.length) {\n            RClassFiles.each {\n                it.delete()\n            }\n\n            String baseDir = pkgDir.path - \"${File.separator}${vaContext.packagePath}\"\n\n            project.ant.javac(\n                srcdir: vaContext.splitRJavaFile.parentFile,\n                source: apkVariant.javaCompiler.sourceCompatibility,\n                target: apkVariant.javaCompiler.targetCompatibility,\n                destdir: new File(baseDir))\n\n            mark()\n            return true\n        }\n\n        return false\n    }\n\n\n    @Override\n    void afterTaskExecute(TransformTask task) { }\n}"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/groovy/com.didi.virtualapk/hooker/GradleTaskHooker.groovy",
    "content": "package com.didi.virtualapk.hooker\n\nimport com.android.build.gradle.api.ApkVariant\nimport com.android.build.gradle.internal.api.ApplicationVariantImpl\nimport com.android.build.gradle.internal.scope.VariantScope\nimport com.android.build.gradle.internal.variant.BaseVariantData\nimport com.didi.virtualapk.VAExtention\nimport com.didi.virtualapk.VAExtention.VAContext\nimport org.gradle.api.Project\nimport org.gradle.api.Task\n\n/**\n * Base class of gradle task hooker， provides some common field used by hookers\n * @param <T> Type of hooked task\n *\n * @author zhengtao\n */\npublic abstract class GradleTaskHooker<T extends Task> {\n\n    private Project project\n\n    /**\n     * A Build variant when build a apk and all its public data.\n     */\n    private ApkVariant apkVariant\n\n    private VAExtention virtualApk\n\n    private TaskHookerManager taskHookerManager\n\n    public GradleTaskHooker(Project project, ApkVariant apkVariant) {\n        this.project = project\n        this.apkVariant = apkVariant\n        this.virtualApk = project.virtualApk\n\n        vaContext.checkList.addCheckPoint(taskName)\n    }\n\n    public Project getProject() {\n        return this.project\n    }\n\n    public ApkVariant getApkVariant() {\n        return this.apkVariant\n    }\n\n    public BaseVariantData getVariantData() {\n        return ((ApplicationVariantImpl) this.apkVariant).variantData\n    }\n\n    public VariantScope getScope() {\n        return variantData.scope\n    }\n\n    public VAExtention getVirtualApk() {\n        return this.virtualApk\n    }\n\n    public VAContext getVaContext() {\n        return this.virtualApk.getVaContext(apkVariant.name)\n    }\n\n    public void mark() {\n        vaContext.checkList.mark(taskName)\n    }\n\n    public void setTaskHookerManager(TaskHookerManager taskHookerManager) {\n        this.taskHookerManager = taskHookerManager\n    }\n\n    public TaskHookerManager getTaskHookerManager() {\n        return this.taskHookerManager\n    }\n\n    public T getTask() {\n\n    }\n\n    /**\n     * Return the transform name of the hooked task(transform task)\n     */\n    public String getTransformName() {\n        return \"\"\n    }\n\n    /**\n     * Return the task name(exclude transform task)\n     */\n    public String getTaskName() {\n        return \"${transformName}For${apkVariant.name.capitalize()}\"\n    }\n\n    /**\n     * Callback function before the hooked task executes\n     * @param task Hooked task\n     */\n    public abstract void beforeTaskExecute(T task)\n    /**\n     * Callback function after the hooked task executes\n     * @param task Hooked task\n     */\n    public abstract void afterTaskExecute(T task)\n}"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/groovy/com.didi.virtualapk/hooker/MergeAssetsHooker.groovy",
    "content": "package com.didi.virtualapk.hooker\n\nimport com.android.build.gradle.api.ApkVariant\nimport com.android.build.gradle.tasks.MergeSourceSetFolders\nimport com.android.ide.common.res2.AssetSet\nimport com.didi.virtualapk.collector.dependence.AarDependenceInfo\nimport com.didi.virtualapk.utils.Log\nimport com.didi.virtualapk.utils.Reflect\nimport org.gradle.api.Project\n\nimport java.util.function.Predicate\nimport java.util.function.Supplier\n\n/**\n * Remove the asset directory included in the excluded library before mergeAssets task\n *\n * @author zhengtao\n */\nclass MergeAssetsHooker extends GradleTaskHooker<MergeSourceSetFolders> {\n\n    public MergeAssetsHooker(Project project, ApkVariant apkVariant) {\n        super(project, apkVariant)\n    }\n\n    @Override\n    String getTaskName() {\n        return scope.getTaskName('merge', 'Assets')\n    }\n\n    /**\n     * Remove the element(AssetSet) generated by the stripped dependenceInfos in the task inputDirectorySets\n     * @param task Gradle task of mergeAssets\n     */\n    @Override\n    void beforeTaskExecute(MergeSourceSetFolders task) {\n\n        Set<String> strippedAssetPaths = vaContext.stripDependencies.collect {\n            if (it instanceof AarDependenceInfo) {\n                return it.assetsFolder.path\n            }\n            return ''\n        }\n\n        Reflect reflect = Reflect.on(task)\n        reflect.set('assetSetSupplier', new FixedSupplier(this, reflect.get('assetSetSupplier'), strippedAssetPaths))\n    }\n\n    @Override\n    void afterTaskExecute(MergeSourceSetFolders task) {\n    }\n    \n    static class FixedSupplier implements Supplier<List<AssetSet>> {\n\n        MergeAssetsHooker hooker\n        Supplier<List<AssetSet>> origin\n        Set<String> strippedAssetPaths\n        \n        FixedSupplier(MergeAssetsHooker hooker, Supplier<List<AssetSet>> origin, Set<String> strippedAssetPaths) {\n            this.hooker = hooker\n            this.origin = origin\n            this.strippedAssetPaths = strippedAssetPaths\n        }\n        \n        @Override\n        List<AssetSet> get() {\n            List<AssetSet> assetSets = origin.get()\n            assetSets.removeIf(new Predicate<AssetSet>() {\n                @Override\n                boolean test(AssetSet assetSet) {\n                    boolean ret = strippedAssetPaths.contains(assetSet.sourceFiles.get(0).path)\n                    if (ret) {\n                        Log.i 'MergeAssetsHooker', \"Stripped asset of artifact: ${assetSet} -> ${assetSet.sourceFiles.get(0).path}\"\n                    }\n                    return ret\n                }\n            })\n            hooker.mark()\n            return assetSets\n        }\n    }\n}"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/groovy/com.didi.virtualapk/hooker/MergeJniLibsHooker.groovy",
    "content": "package com.didi.virtualapk.hooker\n\nimport com.android.build.gradle.AndroidConfig\nimport com.android.build.gradle.AppExtension\nimport com.android.build.gradle.api.ApkVariant\nimport com.android.build.gradle.internal.pipeline.TransformTask\nimport com.didi.virtualapk.collector.HostJniLibsCollector\nimport com.didi.virtualapk.utils.Log\nimport org.gradle.api.Project\n\n/**\n * Remove the Native libs(.so) in stripped dependencies before mergeJniLibs task\n *\n * @author zhengtao\n */\nclass MergeJniLibsHooker extends GradleTaskHooker<TransformTask> {\n\n    HostJniLibsCollector jniLibsCollector\n    AndroidConfig androidConfig\n\n    public MergeJniLibsHooker(Project project, ApkVariant apkVariant) {\n        super(project, apkVariant)\n        jniLibsCollector = new HostJniLibsCollector()\n        androidConfig = project.extensions.findByType(AppExtension)\n    }\n\n    @Override\n    String getTransformName() {\n        return \"mergeJniLibs\"\n    }\n\n    /**\n     * Prevent .so files from packaging into apk via the PackagingOptions exclude configuration\n     * @param task Gradle task of mergeJniLibs\n     */\n    @Override\n    void beforeTaskExecute(TransformTask task) {\n\n        def excludeJniFiles = jniLibsCollector.collect(vaContext.stripDependencies)\n\n        excludeJniFiles.each {\n            androidConfig.packagingOptions.exclude(\"/${it}\")\n            Log.i 'MergeJniLibsHooker', \"Stripped jni file: ${it}\"\n        }\n\n        mark()\n//        Reflect.on(task.transform)\n//                .set('packagingOptions', new ParsedPackagingOptions(androidConfig.packagingOptions))\n    }\n\n    @Override\n    void afterTaskExecute(TransformTask task) {}\n}"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/groovy/com.didi.virtualapk/hooker/MergeManifestsHooker.groovy",
    "content": "package com.didi.virtualapk.hooker\n\nimport com.android.build.gradle.api.ApkVariant\nimport com.android.build.gradle.internal.scope.TaskOutputHolder\nimport com.android.build.gradle.tasks.MergeManifests\nimport com.didi.virtualapk.Constants\nimport com.didi.virtualapk.collector.dependence.DependenceInfo\nimport com.didi.virtualapk.utils.Log\nimport com.didi.virtualapk.utils.Reflect\nimport groovy.xml.QName\nimport groovy.xml.XmlUtil\nimport org.gradle.api.Project\nimport org.gradle.api.artifacts.ArtifactCollection\nimport org.gradle.api.artifacts.result.ResolvedArtifactResult\nimport org.gradle.api.file.FileCollection\nimport org.gradle.api.internal.file.AbstractFileCollection\nimport org.gradle.api.tasks.TaskDependency\n\nimport java.util.function.Consumer\nimport java.util.function.Predicate\n\n/**\n * Filter the stripped ManifestDependency in the ManifestDependency list of MergeManifests task\n *\n * @author zhengtao\n */\nclass MergeManifestsHooker extends GradleTaskHooker<MergeManifests> {\n\n    public static final String ANDROID_NAMESPACE = 'http://schemas.android.com/apk/res/android'\n\n    public MergeManifestsHooker(Project project, ApkVariant apkVariant) {\n        super(project, apkVariant)\n    }\n\n    @Override\n    String getTaskName() {\n        return scope.getTaskName('process', 'Manifest')\n    }\n\n    @Override\n    void beforeTaskExecute(MergeManifests task) {\n\n        def stripAarNames = vaContext.stripDependencies.\n                findAll {\n                    it.dependenceType == DependenceInfo.DependenceType.AAR\n                }.\n                collect { DependenceInfo dep ->\n                    \"${dep.group}:${dep.artifact}:${dep.version}\"\n                } as Set<String>\n\n        Reflect reflect = Reflect.on(task)\n        ArtifactCollection manifests = new FixedArtifactCollection(this, reflect.get('manifests'), stripAarNames)\n        reflect.set('manifests', manifests)\n    }\n\n    /**\n     * Filter specific attributes from <application /> element after MergeManifests task executed\n     */\n    @Override\n    void afterTaskExecute(MergeManifests task) {\n        if (project.extensions.extraProperties.get(Constants.GRADLE_3_1_0)) {\n            File outputFile = Reflect.on('com.android.build.gradle.internal.scope.ExistingBuildElements')\n                    .call('from', TaskOutputHolder.TaskOutputType.MERGED_MANIFESTS, scope.getOutput(TaskOutputHolder.TaskOutputType.MERGED_MANIFESTS))\n                    .call('element', variantData.outputScope.mainSplit)\n                    .call('getOutputFile')\n                    .get()\n            rewrite(outputFile)\n        } else {\n            variantData.outputScope.getOutputs(TaskOutputHolder.TaskOutputType.MERGED_MANIFESTS).each {\n                rewrite(it.outputFile)\n            }\n        }\n    }\n    \n    void rewrite(File xml) {\n        if (xml?.exists()) {\n            final Node manifest = new XmlParser().parse(xml)\n\n\n            manifest.application.each { application ->\n                [ 'icon', 'label', 'allowBackup', 'supportsRtl' ].each {\n                    application.attributes().remove(new QName(MergeManifestsHooker.ANDROID_NAMESPACE, it))\n                }\n            }\n\n            xml.withPrintWriter('utf-8', { pw ->\n                XmlUtil.serialize(manifest, pw)\n            })\n        }\n    }\n    \n    private static class FixedArtifactCollection implements ArtifactCollection {\n\n        private MergeManifestsHooker hooker\n        private ArtifactCollection origin\n        def stripAarNames\n        \n        FixedArtifactCollection(MergeManifestsHooker hooker, ArtifactCollection origin, stripAarNames) {\n            this.hooker = hooker\n            this.origin = origin\n            this.stripAarNames = stripAarNames\n        }\n\n        @Override\n        FileCollection getArtifactFiles() {\n            Set<File> set = getArtifacts().collect { ResolvedArtifactResult result ->\n                result.file\n            } as Set<File>\n            FileCollection fileCollection = origin.getArtifactFiles()\n\n            return new AbstractFileCollection() {\n                @Override\n                String getDisplayName() {\n                    return fileCollection.getDisplayName()\n                }\n\n                @Override\n                TaskDependency getBuildDependencies() {\n                    return fileCollection.getBuildDependencies()\n                }\n\n                @Override\n                Set<File> getFiles() {\n                    Set<File> files = new LinkedHashSet(fileCollection.getFiles())\n                    files.retainAll(set)\n                    return files\n                }\n            }\n        }\n\n        @Override\n        Set<ResolvedArtifactResult> getArtifacts() {\n            Set<ResolvedArtifactResult> set = origin.getArtifacts()\n            set.removeIf(new Predicate<ResolvedArtifactResult>() {\n                @Override\n                boolean test(ResolvedArtifactResult result) {\n                    boolean ret = stripAarNames.contains(\"${result.id.componentIdentifier.displayName}\")\n                    if (ret) {\n                        Log.i 'MergeManifestsHooker', \"Stripped manifest of artifact: ${result} -> ${result.file}\"\n                    }\n                    return ret\n                }\n            })\n\n            hooker.mark()\n            return set\n        }\n\n        @Override\n        Collection<Throwable> getFailures() {\n            return origin.getFailures()\n        }\n\n        @Override\n        Iterator<ResolvedArtifactResult> iterator() {\n            return getArtifacts().iterator()\n        }\n\n        @Override\n        void forEach(Consumer<? super ResolvedArtifactResult> action) {\n            getArtifacts().forEach(action)\n        }\n\n        @Override\n        Spliterator<ResolvedArtifactResult> spliterator() {\n            return getArtifacts().spliterator()\n        }\n    }\n}"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/groovy/com.didi.virtualapk/hooker/PrepareDependenciesHooker.groovy",
    "content": "package com.didi.virtualapk.hooker\n\nimport com.android.build.gradle.api.ApkVariant\nimport com.android.build.gradle.internal.ide.ArtifactDependencyGraph\nimport com.android.build.gradle.internal.tasks.AppPreBuildTask\nimport com.android.builder.model.Dependencies\nimport com.android.builder.model.SyncIssue\nimport com.didi.virtualapk.Constants\nimport com.didi.virtualapk.collector.dependence.AarDependenceInfo\nimport com.didi.virtualapk.collector.dependence.DependenceInfo\nimport com.didi.virtualapk.collector.dependence.JarDependenceInfo\nimport com.didi.virtualapk.utils.FileUtil\nimport com.didi.virtualapk.utils.Log\nimport com.didi.virtualapk.utils.Reflect\nimport com.google.common.collect.ImmutableMap\nimport org.gradle.api.Project\n\nimport java.util.function.Consumer\n\n/**\n * Gather list of dependencies(aar&jar) need to be stripped&retained after the PrepareDependenciesTask finished.\n * The entire stripped operation throughout the build lifecycle is based on the result of this hooker。\n *\n * @author zhengtao\n */\nclass PrepareDependenciesHooker extends GradleTaskHooker<AppPreBuildTask> {\n\n    //group:artifact:version\n    def hostDependencies = [] as Set\n\n    def retainedAarLibs = [] as Set<AarDependenceInfo>\n    def retainedJarLib = [] as Set<JarDependenceInfo>\n    def stripDependencies = [] as Collection<DependenceInfo>\n\n    public PrepareDependenciesHooker(Project project, ApkVariant apkVariant) {\n        super(project, apkVariant)\n    }\n\n    @Override\n    String getTaskName() {\n        return scope.getTaskName('pre', 'Build')\n    }\n\n    /**\n     * Collect host dependencies via hostDependenceFile or exclude configuration before PrepareDependenciesTask execute,\n     * @param task Gradle Task fo PrepareDependenciesTask\n     */\n    @Override\n    void beforeTaskExecute(AppPreBuildTask task) {\n\n        hostDependencies.addAll(virtualApk.hostDependencies.keySet())\n\n        virtualApk.excludes.each { String artifact ->\n            final def module = artifact.split(':')\n            hostDependencies.add(\"${module[0]}:${module[1]}\")\n        }\n    }\n\n    /**\n     * Classify all dependencies into retainedAarLibs & retainedJarLib & stripDependencies\n     *\n     * @param task Gradle Task fo PrepareDependenciesTask\n     */\n    @Override\n    void afterTaskExecute(AppPreBuildTask task) {\n        Consumer consumer = new Consumer<SyncIssue>() {\n            @Override\n            void accept(SyncIssue syncIssue) {\n                Log.i 'PrepareDependenciesHooker', \"Error: ${syncIssue}\"\n            }\n        }\n        Dependencies dependencies\n        if (project.extensions.extraProperties.get(Constants.GRADLE_3_1_0)) {\n            ImmutableMap<String, String> buildMapping = Reflect.on('com.android.build.gradle.internal.ide.ModelBuilder')\n                    .call('computeBuildMapping', project.gradle)\n                    .get()\n            dependencies = new ArtifactDependencyGraph().createDependencies(scope, false, buildMapping, consumer)\n        } else {\n            dependencies = new ArtifactDependencyGraph().createDependencies(scope, false, consumer)\n        }\n\n        dependencies.libraries.each {\n            def mavenCoordinates = it.resolvedCoordinates\n            if (hostDependencies.contains(\"${mavenCoordinates.groupId}:${mavenCoordinates.artifactId}\")) {\n                Log.i 'PrepareDependenciesHooker', \"Need strip aar: ${mavenCoordinates.groupId}:${mavenCoordinates.artifactId}:${mavenCoordinates.version}\"\n                stripDependencies.add(\n                        new AarDependenceInfo(\n                                mavenCoordinates.groupId,\n                                mavenCoordinates.artifactId,\n                                mavenCoordinates.version,\n                                it))\n\n            } else {\n                Log.i 'PrepareDependenciesHooker', \"Need retain aar: ${mavenCoordinates.groupId}:${mavenCoordinates.artifactId}:${mavenCoordinates.version}\"\n                retainedAarLibs.add(\n                        new AarDependenceInfo(\n                                mavenCoordinates.groupId,\n                                mavenCoordinates.artifactId,\n                                mavenCoordinates.version,\n                                it))\n            }\n\n        }\n        dependencies.javaLibraries.each {\n            def mavenCoordinates = it.resolvedCoordinates\n            if (hostDependencies.contains(\"${mavenCoordinates.groupId}:${mavenCoordinates.artifactId}\")) {\n                Log.i 'PrepareDependenciesHooker', \"Need strip jar: ${mavenCoordinates.groupId}:${mavenCoordinates.artifactId}:${mavenCoordinates.version}\"\n                stripDependencies.add(\n                        new JarDependenceInfo(\n                                mavenCoordinates.groupId,\n                                mavenCoordinates.artifactId,\n                                mavenCoordinates.version,\n                                it))\n            } else {\n                Log.i 'PrepareDependenciesHooker', \"Need retain jar: ${mavenCoordinates.groupId}:${mavenCoordinates.artifactId}:${mavenCoordinates.version}\"\n                retainedJarLib.add(\n                        new JarDependenceInfo(\n                                mavenCoordinates.groupId,\n                                mavenCoordinates.artifactId,\n                                mavenCoordinates.version,\n                                it))\n            }\n\n        }\n\n        File hostDir = vaContext.getBuildDir(scope)\n        FileUtil.saveFile(hostDir, \"${taskName}-stripDependencies\", stripDependencies)\n        FileUtil.saveFile(hostDir, \"${taskName}-retainedAarLibs\", retainedAarLibs)\n        FileUtil.saveFile(hostDir, \"${taskName}-retainedJarLib\", retainedJarLib)\n\n        checkDependencies()\n\n        Log.i 'PrepareDependenciesHooker', \"Analyzed all dependencis. Get more infomation in dir: ${hostDir.absoluteFile}\"\n\n        vaContext.stripDependencies = stripDependencies\n        vaContext.retainedAarLibs = retainedAarLibs\n        mark()\n    }\n\n    void checkDependencies() {\n        ArrayList<DependenceInfo> allRetainedDependencies = new ArrayList<>()\n        allRetainedDependencies.addAll(retainedAarLibs)\n        allRetainedDependencies.addAll(retainedJarLib)\n\n        ArrayList<String> checked = new ArrayList<>()\n\n        allRetainedDependencies.each {\n            String group = it.group\n            String artifact = it.artifact\n            String version = it.version\n\n            // com.didi.virtualapk:core\n            if (group == 'com.didi.virtualapk' && artifact == 'core') {\n                checked.add(\"${group}:${artifact}:${version}\")\n            }\n\n            // com.android.support:all\n            if (group == 'com.android.support' || group.startsWith('com.android.support.')) {\n                checked.add(\"${group}:${artifact}:${version}\")\n            }\n\n            // com.android.databinding:all\n            if (group == 'com.android.databinding' || group.startsWith('com.android.databinding.')) {\n                checked.add(\"${group}:${artifact}:${version}\")\n            }\n        }\n\n        if (!checked.empty) {\n            throw new Exception(\"The dependencies [${String.join(', ', checked)}] that will be used in the current plugin must be included in the host app first. Please add it in the host app as well.\")\n        }\n    }\n}"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/groovy/com.didi.virtualapk/hooker/ProcessResourcesHooker.groovy",
    "content": "package com.didi.virtualapk.hooker\n\nimport com.android.build.gradle.AndroidConfig\nimport com.android.build.gradle.AppExtension\nimport com.android.build.gradle.api.ApkVariant\nimport com.android.build.gradle.internal.scope.TaskOutputHolder\nimport com.android.build.gradle.tasks.ProcessAndroidResources\nimport com.android.sdklib.BuildToolInfo\nimport com.didi.virtualapk.Constants\nimport com.didi.virtualapk.aapt.Aapt\nimport com.didi.virtualapk.collector.ResourceCollector\nimport com.didi.virtualapk.collector.res.ResourceEntry\nimport com.didi.virtualapk.collector.res.StyleableEntry\nimport com.didi.virtualapk.utils.FileUtil\nimport com.didi.virtualapk.utils.Log\nimport com.didi.virtualapk.utils.Reflect\nimport com.google.common.collect.ListMultimap\nimport com.google.common.io.Files\nimport org.gradle.api.Project\n\n/**\n * Filter the host resources out of the plugin apk.\n * Modify the .arsc file to delete host element,\n * rearrange plugin element, hold the new resource IDs\n *\n * @author zhengtao\n */\nclass ProcessResourcesHooker extends GradleTaskHooker<ProcessAndroidResources> {\n\n    /**\n     * Collector to gather the sources and styleables\n     */\n    ResourceCollector resourceCollector\n    /**\n     * Android config information specified in build.gradle\n     */\n    AndroidConfig androidConfig\n\n    ProcessResourcesHooker(Project project, ApkVariant apkVariant) {\n        super(project, apkVariant)\n        androidConfig = project.extensions.findByType(AppExtension)\n    }\n\n    @Override\n    String getTaskName() {\n        return scope.getTaskName('process', 'Resources')\n    }\n\n    @Override\n    void beforeTaskExecute(ProcessAndroidResources aaptTask) {\n\n    }\n\n    /**\n     * Since we need to remove the host resources and modify the resource ID,\n     * we will reedit the AP_ file and repackage it after the task execute\n     *\n     * @param par Gradle task of process android resources\n     */\n    @Override\n    void afterTaskExecute(ProcessAndroidResources par) {\n        if (project.extensions.extraProperties.get(Constants.GRADLE_3_1_0)) {\n            File outputFile = Reflect.on('com.android.build.gradle.internal.scope.ExistingBuildElements')\n                    .call('from', TaskOutputHolder.TaskOutputType.PROCESSED_RES, scope.getOutput(TaskOutputHolder.TaskOutputType.PROCESSED_RES))\n                    .call('element', variantData.outputScope.mainSplit)\n                    .call('getOutputFile')\n                    .get()\n            repackage(par, outputFile)\n        } else {\n            variantData.outputScope.getOutputs(TaskOutputHolder.TaskOutputType.PROCESSED_RES).each {\n                repackage(par, it.outputFile)\n            }\n        }\n    }\n\n    void repackage(ProcessAndroidResources par, File apFile) {\n        def resourcesDir = new File(apFile.parentFile, Files.getNameWithoutExtension(apFile.name))\n\n        /*\n         * Clean up resources merge directory\n         */\n        resourcesDir.deleteDir()\n\n        File backupFile = new File(apFile.getParentFile(), \"${Files.getNameWithoutExtension(apFile.name)}-original.${Files.getFileExtension(apFile.name)}\")\n        backupFile.delete()\n        project.copy {\n            from apFile\n            into apFile.getParentFile()\n            rename { backupFile.name }\n        }\n\n        /*\n         * Unzip resources-${variant.name}.ap_\n         */\n        project.copy {\n            from project.zipTree(apFile)\n            into resourcesDir\n\n            include 'AndroidManifest.xml'\n            include 'resources.arsc'\n            include 'res/**/*'\n        }\n\n//        File backupDir = new File(resourcesDir.parentFile, resourcesDir.name + '-original')\n//        backupDir.deleteDir()\n//        project.copy {\n//            from project.fileTree(resourcesDir)\n//            into backupDir\n//        }\n\n        resourceCollector = new ResourceCollector(project, par)\n        resourceCollector.collect()\n\n        def retainedTypes = convertResourcesForAapt(resourceCollector.pluginResources)\n        def retainedStylealbes = convertStyleablesForAapt(resourceCollector.pluginStyleables)\n        def resIdMap = resourceCollector.resIdMap\n\n        def rSymbolFile = par.textSymbolOutputFile\n        def libRefTable = [\"${virtualApk.packageId}\": par.applicationId]\n        def filteredResources = [] as HashSet<String>\n        def updatedResources = [] as HashSet<String>\n\n        def aapt = new Aapt(resourcesDir, rSymbolFile, androidConfig.buildToolsRevision)\n\n        //Delete host resources, must do it before filterPackage\n        aapt.filterResources(retainedTypes, filteredResources)\n        //Modify the arsc file, and replace ids of related xml files\n        aapt.filterPackage(retainedTypes, retainedStylealbes, virtualApk.packageId, resIdMap, libRefTable, updatedResources)\n\n        File hostDir = vaContext.getBuildDir(scope)\n        FileUtil.saveFile(hostDir, \"${taskName}-retainedTypes\", retainedTypes)\n        FileUtil.saveFile(hostDir, \"${taskName}-retainedStylealbes\", retainedStylealbes)\n        FileUtil.saveFile(hostDir, \"${taskName}-filteredResources\", true, filteredResources)\n        FileUtil.saveFile(hostDir, \"${taskName}-updatedResources\", true, updatedResources)\n\n        /*\n         * Delete filtered entries and then add updated resources into resources-${variant.name}.ap_\n         */\n        com.didi.virtualapk.utils.ZipUtil.with(apFile).deleteAll(filteredResources + updatedResources)\n\n        project.exec {\n            executable par.buildTools.getPath(BuildToolInfo.PathId.AAPT)\n            workingDir resourcesDir\n            args 'add', apFile.path\n            args updatedResources\n            standardOutput = System.out\n            errorOutput = System.err\n        }\n\n        updateRJava(aapt, par.sourceOutputDir)\n        mark()\n    }\n\n    /**\n     * Because the resource ID has changed, we need to regenerate the R.java file,\n     * include the all resources R, plugin resources R, and R files of retained aars\n     *\n     * @param aapt Class to expand aapt function\n     * @param sourceOutputDir Directory of R.java files generated by aapt\n     *\n     */\n    def updateRJava(Aapt aapt, File sourceOutputDir) {\n        File vaBuildDir = vaContext.getBuildDir(scope)\n        File backupDir = new File(vaBuildDir, \"origin/r\")\n\n        project.ant.move(todir: backupDir) {\n            fileset(dir: sourceOutputDir) {\n                include(name: '**/R.java')\n            }\n        }\n\n        FileUtil.deleteEmptySubfolders(sourceOutputDir)\n\n        def rSourceFile = new File(sourceOutputDir, \"${vaContext.packagePath}${File.separator}R.java\")\n        aapt.generateRJava(rSourceFile, apkVariant.applicationId, resourceCollector.allResources, resourceCollector.allStyleables)\n        Log.i 'ProcessResourcesHooker', \"Updated R.java: ${rSourceFile.absoluteFile}\"\n\n        def splitRSourceFile = new File(vaBuildDir, \"source${File.separator}r${File.separator}${vaContext.packagePath}${File.separator}R.java\")\n        aapt.generateRJava(splitRSourceFile, apkVariant.applicationId, resourceCollector.pluginResources, resourceCollector.pluginStyleables)\n        Log.i 'ProcessResourcesHooker', \"Updated R.java: ${splitRSourceFile.absoluteFile}\"\n        vaContext.splitRJavaFile = splitRSourceFile\n\n        vaContext.retainedAarLibs.each {\n            def aarPackage = it.package\n            def rJavaFile = new File(sourceOutputDir, \"${aarPackage.replace('.'.charAt(0), File.separatorChar)}${File.separator}R.java\")\n            aapt.generateRJava(rJavaFile, aarPackage, it.aarResources, it.aarStyleables)\n            Log.i 'ProcessResourcesHooker', \"Updated R.java: ${rJavaFile.absoluteFile}\"\n        }\n    }\n\n    /**\n     * We use the third party library to modify the ASRC file,\n     * this method used to transform resource data into the structure of the library\n     * @param pluginResources Map of plugin resources\n     */\n    def convertResourcesForAapt(ListMultimap<String, ResourceEntry> pluginResources) {\n        def retainedTypes = []\n\n        pluginResources.keySet().each { resType ->\n            def firstEntry = pluginResources.get(resType).get(0)\n            def typeEntry = [ type: \"int\", name: resType,\n                              id: parseTypeIdFromResId(firstEntry.resourceId),\n                              _id: parseTypeIdFromResId(firstEntry.newResourceId),\n                              entries: []]\n\n            pluginResources.get(resType).each { resEntry ->\n                typeEntry.entries.add([\n                        name : resEntry.resourceName,\n                        id : parseEntryIdFromResId(resEntry.resourceId),\n                        _id: parseEntryIdFromResId(resEntry.newResourceId),\n                        v : resEntry.resourceId, _v : resEntry.newResourceId,\n                        vs: resEntry.hexResourceId, _vs : resEntry.hexNewResourceId])\n            }\n\n            retainedTypes.add(typeEntry)\n        }\n\n        retainedTypes.sort { t1, t2 ->\n            t1._id - t2._id\n        }\n        \n        return retainedTypes\n    }\n\n    /**\n     * Transform styleable data into the structure of the aapt library\n     * @param pluginStyleables Map of plugin styleables\n     */\n    def convertStyleablesForAapt(List<StyleableEntry> pluginStyleables) {\n        def retainedStyleables = []\n        pluginStyleables.each { styleableEntry ->\n            retainedStyleables.add([vtype : styleableEntry.valueType,\n                                    type : 'styleable',\n                                    key : styleableEntry.name,\n                                    idStr : styleableEntry.value])\n        }\n        return retainedStyleables\n    }\n\n    /**\n     * Parse the type part of a android resource id\n     */\n    def parseTypeIdFromResId(int resourceId) {\n        resourceId >> 16 & 0xFF\n    }\n\n    /**\n     * Parse the entry part of a android resource id\n     */\n    def parseEntryIdFromResId(int resourceId) {\n        resourceId & 0xFFFF\n    }\n\n}"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/groovy/com.didi.virtualapk/hooker/ProguardHooker.groovy",
    "content": "package com.didi.virtualapk.hooker\n\nimport com.android.build.gradle.api.ApkVariant\nimport com.android.build.gradle.internal.pipeline.TransformTask\nimport com.android.build.gradle.internal.transforms.ProGuardTransform\nimport com.didi.virtualapk.collector.dependence.AarDependenceInfo\nimport org.gradle.api.InvalidUserDataException\nimport org.gradle.api.Project\n\n/**\n * Apply host mapping file to maintain compatibility between the plugin and host apk.\n * And input the stripped jar files as library, to prevent proguard errors\n *\n * @author zhengtao\n */\nclass ProguardHooker extends GradleTaskHooker<TransformTask> {\n\n    public static final String MAPPING_KEY = \"applyMapping\"\n\n    ProguardHooker(Project project, ApkVariant apkVariant) {\n        super(project, apkVariant)\n    }\n\n    @Override\n    String getTransformName() {\n        return \"proguard\"\n    }\n\n    /**\n     * Before the run of proguard, specifies the mapping file to reuse when obfuscating,\n     * if classes in host apk is obfuscated, we need to ensure compatibility by specifying\n     * the mapping file.\n     *\n     * In addition, we need to input the stripped jars into the Proguard program in the\n     * form of libarary, via the libraryJar(protected) method\n     *\n     * @param task Gradle task of proguard\n     */\n    @Override\n    void beforeTaskExecute(TransformTask task) {\n\n        def proguardTransform = task.transform as ProGuardTransform\n\n        File applyMappingFile;\n\n        //Specifies the proguard mapping file through ${ MAPPING_KEY }\n        if (project.hasProperty(MAPPING_KEY)) {\n            applyMappingFile = new File(project.properties[MAPPING_KEY])\n            if (!applyMappingFile.exists()) {\n                throw new InvalidUserDataException(\"${project.properties[MAPPING_KEY]} does not exist\")\n            }\n            if (!applyMappingFile.isFile()) {\n                throw new InvalidUserDataException(\"${project.properties[MAPPING_KEY]} is not a file\")\n            }\n        }\n\n        //Default to use the mapping file generated by host apk\n        if (virtualApk.applyHostMapping && applyMappingFile == null) {\n            applyMappingFile = new File(project.rootProject.projectDir, \"host/mapping.txt\")\n        }\n\n        if (applyMappingFile?.exists()) {\n            proguardTransform.applyTestedMapping(applyMappingFile)\n        }\n\n        vaContext.stripDependencies.each {\n            proguardTransform.libraryJar(it.jarFile)\n            if (it instanceof AarDependenceInfo) {\n                it.localJars.each {\n                    proguardTransform.libraryJar(it)\n                }\n            }\n        }\n        mark()\n    }\n\n    @Override\n    void afterTaskExecute(TransformTask task) { }\n}"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/groovy/com.didi.virtualapk/hooker/ShrinkResourcesHooker.groovy",
    "content": "package com.didi.virtualapk.hooker\n\nimport com.android.build.api.transform.TransformException\nimport com.android.build.api.transform.TransformInvocation\nimport com.android.build.gradle.api.ApkVariant\nimport com.android.build.gradle.internal.pipeline.TransformTask\nimport com.android.build.gradle.internal.transforms.ShrinkResourcesTransform\nimport com.didi.virtualapk.transform.TransformWrapper\nimport com.didi.virtualapk.utils.Log\nimport com.didi.virtualapk.utils.Reflect\nimport org.gradle.api.Project\n\nclass ShrinkResourcesHooker extends GradleTaskHooker<TransformTask> {\n    \n    ShrinkResourcesHooker(Project project, ApkVariant apkVariant) {\n        super(project, apkVariant)\n    }\n    \n    @Override\n    String getTransformName() {\n        return \"shrinkRes\"\n    }\n    \n    @Override\n    void beforeTaskExecute(TransformTask task) {\n        def shrinkResourcesTransform = task.transform as ShrinkResourcesTransform\n        Reflect.on(task).set('transform', new TransformWrapper(shrinkResourcesTransform) {\n            @Override\n            void transform(TransformInvocation invocation) throws TransformException, InterruptedException, IOException {\n                Log.i 'ShrinkResourcesHooker', \"sourceDir: ${Reflect.on(origin).get('sourceDir')}\"\n                super.transform(invocation)\n                mark()\n            }\n        })\n    }\n\n    @Override\n    void afterTaskExecute(TransformTask task) {\n\n    }\n}"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/groovy/com.didi.virtualapk/hooker/TaskHookerManager.groovy",
    "content": "package com.didi.virtualapk.hooker\n\nimport com.android.build.gradle.AppExtension\nimport com.android.build.gradle.internal.api.ApplicationVariantImpl\nimport com.android.build.gradle.internal.pipeline.TransformTask\nimport com.didi.virtualapk.utils.Log\nimport org.gradle.api.Project\nimport org.gradle.api.Task\nimport org.gradle.api.execution.TaskExecutionListener\nimport org.gradle.api.tasks.TaskState\nimport org.gradle.internal.reflect.Instantiator\n\n/**\n * Manager of hookers, responsible for registration and scheduling execution\n *\n * @author zhengtao\n */\npublic abstract class TaskHookerManager {\n\n    protected Map<String, GradleTaskHooker> taskHookerMap = new HashMap<>()\n\n    protected Project project\n    protected AppExtension android\n    protected Instantiator instantiator\n\n    public TaskHookerManager(Project project, Instantiator instantiator) {\n        this.project = project\n        this.instantiator = instantiator\n        android = project.extensions.findByType(AppExtension)\n        project.gradle.addListener(new VirtualApkTaskListener())\n    }\n\n    public abstract void registerTaskHookers()\n\n    protected void registerTaskHooker(GradleTaskHooker taskHooker) {\n        taskHooker.setTaskHookerManager(this)\n        taskHookerMap.put(taskHooker.taskName, taskHooker)\n    }\n\n\n    public <T> T findHookerByName(String taskName) {\n        return taskHookerMap[taskName] as T\n    }\n\n    private class VirtualApkTaskListener implements TaskExecutionListener {\n\n        @Override\n        void beforeExecute(Task task) {\n//            Log.i 'TaskHookerManager', \"beforeExecute ${task.name} tid: ${Thread.currentThread().id} t: ${Thread.currentThread().name}\"\n            if (task.project == project) {\n                if (task in TransformTask) {\n                    taskHookerMap[\"${task.transform.name}For${task.variantName.capitalize()}\".toString()]?.beforeTaskExecute(task)\n                } else {\n                    taskHookerMap[task.name]?.beforeTaskExecute(task)\n                }\n            }\n        }\n\n        @Override\n        void afterExecute(Task task, TaskState taskState) {\n//            Log.i 'TaskHookerManager', \"afterExecute ${task.name} tid: ${Thread.currentThread().id} t: ${Thread.currentThread().name}\"\n            if (task.project == project) {\n                if (task in TransformTask) {\n                    taskHookerMap[\"${task.transform.name}For${task.variantName.capitalize()}\".toString()]?.afterTaskExecute(task)\n                } else {\n                    taskHookerMap[task.name]?.afterTaskExecute(task)\n                }\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/groovy/com.didi.virtualapk/tasks/AssemblePlugin.groovy",
    "content": "package com.didi.virtualapk.tasks\n\nimport com.android.build.gradle.api.ApkVariant\nimport com.android.build.gradle.internal.api.ApplicationVariantImpl\nimport com.didi.virtualapk.VAExtention\nimport com.didi.virtualapk.utils.Log\nimport com.sun.istack.internal.NotNull\nimport org.gradle.api.Action\nimport org.gradle.api.DefaultTask\nimport org.gradle.api.Project\nimport org.gradle.api.tasks.Input\nimport org.gradle.api.tasks.OutputDirectory\nimport org.gradle.api.tasks.TaskAction\n\n/**\n * Gradle task for assemble plugin apk\n * @author zhengtao\n */\npublic class AssemblePlugin extends DefaultTask {\n\n    @OutputDirectory\n    File pluginApkDir\n\n    @Input\n    String appPackageName\n\n    @Input\n    String apkTimestamp\n\n    @Input\n    File originApkFile\n\n    String variantName\n\n    String buildDir\n\n    /**\n     * Copy the plugin apk to out/plugin directory and rename to\n     * the format required for the backend system\n     */\n    @TaskAction\n    public void outputPluginApk() {\n        VAExtention virtualApk = project.virtualApk\n        virtualApk.getVaContext(variantName).checkList.check()\n        virtualApk.printWarning(name)\n\n        if (virtualApk.getFlag('tip.forceUseHostDependences')) {\n            def tip = new StringBuilder('To avoid configuration WARNINGs, you could set the forceUseHostDependences to be true in build.gradle,\\n ')\n            tip.append('please declare it in application project build.gradle:\\n')\n            tip.append('    virtualApk {\\n')\n            tip.append('        forceUseHostDependences = true \\n')\n            tip.append('    }\\n')\n            Log.i name, tip.toString()\n        }\n\n        Log.i name, \"More building infomation could be found in the dir: ${buildDir}.\"\n\n        getProject().copy {\n            from originApkFile\n            into pluginApkDir\n            rename { \"${appPackageName}_${apkTimestamp}.apk\" }\n        }\n    }\n\n\n    public static class ConfigAction implements Action<AssemblePlugin> {\n\n        @NotNull\n        Project project\n        @NotNull\n        ApplicationVariantImpl variant\n\n        ConfigAction(@NotNull Project project, @NotNull ApkVariant variant) {\n            this.project = project\n            this.variant = variant\n        }\n\n        @Override\n        void execute(AssemblePlugin assemblePluginTask) {\n            VAExtention virtualApk = project.virtualApk\n\n            assemblePluginTask.appPackageName = variant.applicationId\n            assemblePluginTask.apkTimestamp = new Date().format(\"yyyyMMddHHmmss\")\n            assemblePluginTask.originApkFile = variant.outputs[0].outputFile\n            assemblePluginTask.pluginApkDir = new File(project.buildDir, \"/outputs/plugin/${variant.name}\")\n            assemblePluginTask.variantName = variant.name\n            assemblePluginTask.buildDir = virtualApk.getVaContext(variant.name).getBuildDir(variant.variantData.scope).canonicalPath\n\n            assemblePluginTask.setGroup(\"build\")\n            assemblePluginTask.setDescription(\"Build ${variant.name.capitalize()} plugin apk\")\n            assemblePluginTask.dependsOn(variant.assemble.name)\n        }\n    }\n\n}\n"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/groovy/com.didi.virtualapk/transform/StripClassAndResTransform.groovy",
    "content": "package com.didi.virtualapk.transform\n\nimport com.android.build.api.transform.*\nimport com.android.build.gradle.api.ApplicationVariant\nimport com.android.build.gradle.internal.pipeline.TransformManager\nimport com.didi.virtualapk.VAExtention\nimport com.didi.virtualapk.collector.HostClassAndResCollector\nimport com.didi.virtualapk.utils.Log\nimport groovy.io.FileType\nimport org.apache.commons.io.FileUtils\nimport org.gradle.api.Project\n/**\n * Strip Host classes and java resources from project, it's an equivalent of provided compile\n * @author zhengtao\n */\nclass StripClassAndResTransform extends Transform {\n\n    private Project project\n    private VAExtention virtualApk\n    private HostClassAndResCollector classAndResCollector\n\n    StripClassAndResTransform(Project project) {\n        this.project = project\n        this.virtualApk = project.virtualApk\n        classAndResCollector = new HostClassAndResCollector()\n    }\n\n    void onProjectAfterEvaluate() {\n        project.android.applicationVariants.each { ApplicationVariant variant ->\n            virtualApk.getVaContext(variant.name).checkList.addCheckPoint(name)\n        }\n    }\n\n    @Override\n    String getName() {\n        return 'stripClassAndRes'\n    }\n\n    @Override\n    Set<QualifiedContent.ContentType> getInputTypes() {\n        return TransformManager.CONTENT_JARS\n    }\n\n    @Override\n    Set<QualifiedContent.Scope> getScopes() {\n        return TransformManager.SCOPE_FULL_PROJECT\n    }\n\n    @Override\n    boolean isIncremental() {\n        return false\n    }\n\n    /**\n     * Only copy the jars or classes and java resources of retained aar into output directory\n     */\n    @Override\n    void transform(final TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {\n\n        VAExtention.VAContext vaContext = virtualApk.getVaContext(transformInvocation.context.variantName)\n        def stripEntries = classAndResCollector.collect(vaContext.stripDependencies)\n\n        if (!isIncremental()) {\n            transformInvocation.outputProvider.deleteAll()\n        }\n\n        transformInvocation.inputs.each {\n            it.directoryInputs.each { directoryInput ->\n                Log.i 'StripClassAndResTransform', \"input dir: ${directoryInput.file.absoluteFile}\"\n                def destDir = transformInvocation.outputProvider.getContentLocation(\n                        directoryInput.name, directoryInput.contentTypes, directoryInput.scopes, Format.DIRECTORY)\n                Log.i 'StripClassAndResTransform', \"output dir: ${destDir.absoluteFile}\"\n                directoryInput.file.traverse(type: FileType.FILES) {\n                    def entryName = it.path.substring(directoryInput.file.path.length() + 1)\n//                    Log.i 'StripClassAndResTransform', \"found file: ${it.absoluteFile}\"\n//                    Log.i 'StripClassAndResTransform', \"entryName: ${entryName}\"\n                    if (!stripEntries.contains(entryName)) {\n                        def dest = new File(destDir, entryName)\n                        FileUtils.copyFile(it, dest)\n//                        Log.i 'StripClassAndResTransform', \"Copied to file: ${dest.absoluteFile}\"\n                    } else {\n                        Log.i 'StripClassAndResTransform', \"Stripped file: ${it.absoluteFile}\"\n                    }\n                }\n            }\n\n            it.jarInputs.each { jarInput ->\n                Log.i 'StripClassAndResTransform', \"input jar: ${jarInput.file.absoluteFile}\"\n                Set<String> jarEntries = HostClassAndResCollector.unzipJar(jarInput.file)\n                if (!stripEntries.containsAll(jarEntries)){\n                    def dest = transformInvocation.outputProvider.getContentLocation(jarInput.name,\n                            jarInput.contentTypes, jarInput.scopes, Format.JAR)\n                    Log.i 'StripClassAndResTransform', \"output jar: ${dest.absoluteFile}\"\n                    FileUtils.copyFile(jarInput.file, dest)\n//                    Log.i 'StripClassAndResTransform', \"Copied to jar: ${dest.absoluteFile}\"\n                } else {\n                    Log.i 'StripClassAndResTransform', \"Stripped jar: ${jarInput.file.absoluteFile}\"\n                }\n            }\n        }\n\n        vaContext.checkList.mark(name)\n    }\n}"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/groovy/com.didi.virtualapk/transform/TransformWrapper.groovy",
    "content": "package com.didi.virtualapk.transform\n\nimport com.android.build.api.transform.*\nimport com.didi.virtualapk.utils.Log\n\npublic class TransformWrapper extends Transform {\n\n    Transform origin\n\n    TransformWrapper(Transform transform) {\n        origin = transform\n    }\n\n    @Override\n    String getName() {\n        return origin.getName()\n    }\n\n    @Override\n    Set<QualifiedContent.ContentType> getInputTypes() {\n        return origin.getInputTypes()\n    }\n\n    @Override\n    Set<QualifiedContent.ContentType> getOutputTypes() {\n        return origin.getOutputTypes()\n    }\n\n    @Override\n    Set<? super QualifiedContent.Scope> getScopes() {\n        return origin.getScopes()\n    }\n\n    @Override\n    Set<? super QualifiedContent.Scope> getReferencedScopes() {\n        return origin.getReferencedScopes()\n    }\n\n    @Override\n    Collection<File> getSecondaryFileInputs() {\n        return origin.getSecondaryFileInputs()\n    }\n\n    @Override\n    Collection<SecondaryFile> getSecondaryFiles() {\n        return origin.getSecondaryFiles()\n    }\n\n    @Override\n    Collection<File> getSecondaryFileOutputs() {\n        return origin.getSecondaryFileOutputs()\n    }\n\n    @Override\n    Collection<File> getSecondaryDirectoryOutputs() {\n        return origin.getSecondaryDirectoryOutputs()\n    }\n\n    @Override\n    Map<String, Object> getParameterInputs() {\n        return origin.getParameterInputs()\n    }\n\n    @Override\n    boolean isIncremental() {\n        return origin.isIncremental()\n    }\n\n    @Override\n    void transform(Context context, Collection<TransformInput> inputs, Collection<TransformInput> referencedInputs, TransformOutputProvider outputProvider, boolean isIncremental) throws IOException, TransformException, InterruptedException {\n        origin.transform(context, inputs, referencedInputs, outputProvider, isIncremental)\n    }\n\n    @Override\n    void transform(TransformInvocation invocation) throws TransformException, InterruptedException, IOException {\n        Collection<TransformInput> inputs = invocation.getInputs()\n        for (TransformInput input : inputs) {\n            for (DirectoryInput directoryInput : input.getDirectoryInputs()) {\n                Log.i \"${name}\", \"input dir: ${directoryInput.getFile()}\"\n            }\n            for (JarInput jarInput : input.getJarInputs()) {\n                Log.i \"${name}\", \"input jar: ${jarInput.getFile()}\"\n            }\n        }\n\n        Collection<TransformInput> referencedInputs = invocation.getReferencedInputs();\n        for (TransformInput transformInput : referencedInputs) {\n            for (DirectoryInput directoryInput : transformInput.getDirectoryInputs()) {\n                Log.i \"${name}\", \"referenced input dir: ${directoryInput.getFile()}\"\n            }\n            for (JarInput jarInput : transformInput.getJarInputs()) {\n                Log.i \"${name}\", \"referenced input jar: ${jarInput.getFile()}\"\n            }\n        }\n\n        origin.transform(invocation)\n    }\n\n    @Override\n    boolean isCacheable() {\n        return origin.isCacheable()\n    }\n\n    @Override\n    String toString() {\n        return origin.toString()\n    }\n}"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/groovy/com.didi.virtualapk/utils/CheckList.groovy",
    "content": "package com.didi.virtualapk.utils\n\nclass CheckList {\n    Map<String, Boolean> mMap = new LinkedHashMap<>()\n    String variantName\n\n    CheckList(String variantName) {\n        this.variantName = variantName\n    }\n\n    void addCheckPoint(String key) {\n        if (mMap.containsKey(key)) {\n            throw new RuntimeException(\"[${key}] has already exists.\")\n        }\n        mMap.put(key, false)\n//        Log.i('test', \"addCheckPoint: ${key}\")\n    }\n\n    void mark(String key) {\n        mMap.put(key, true)\n//        Log.i('test', \"mark: ${key}\")\n    }\n\n    void check() {\n        boolean check = true\n        Map matched = mMap\n\n        matched.each {\n            check &= it.value\n        }\n\n        if (check) {\n            Log.i 'CheckList', \"All checked ok.\"\n            return\n        }\n\n        Log.i 'CheckList', \"Checked WARNING:\"\n        matched.each {\n            Log.i 'CheckList', \"${it.key}: ${it.value}\"\n        }\n    }\n}"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/groovy/com.didi.virtualapk/utils/FileBinaryCategory.groovy",
    "content": "package com.didi.virtualapk.utils\n\n/**\n * Extend << operator to copy file\n * @author zhengtao\n */\nclass FileBinaryCategory {\n\n\n    //Write content from a url to a file\n    def static leftShift(File file, URL url) {\n        def conn = url.openConnection()\n        conn.with {\n            def is = conn.getInputStream()\n            file.withOutputStream { os->\n                def bs = new BufferedOutputStream(os)\n                bs << is\n            }\n        }\n    }\n\n\n    //Write content from a src file to a dst file\n    def static leftShift(File dst, File src) {\n        src.withInputStream { is->\n            dst.withOutputStream { os->\n                def bs = new BufferedOutputStream(os)\n                bs << is\n            }\n        }\n    }\n}\n\n"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/groovy/com.didi.virtualapk/utils/FileUtil.groovy",
    "content": "package com.didi.virtualapk.utils\n/**\n * Created by qiaopu on 2017/9/4.\n */\npublic class FileUtil {\n    \n    public static void saveFile(File dir, String fileName, Closure<List<?>> action) {\n        List<?> list = action.call();\n        saveFile(dir, fileName, list)\n    }\n    \n    public static void saveFile(File dir, String fileName, Collection<?> collection) {\n        saveFile(dir, fileName, false, collection)\n    }\n\n    public static void saveFile(File dir, String fileName, boolean sort, Collection<?> collection) {\n        dir.mkdirs()\n        def file = new File(dir, \"${fileName}.txt\")\n        ArrayList<?> list = new ArrayList<>(collection)\n        if (sort) {\n            Collections.sort(list)\n        }\n        list.add('')\n        file.write(list.join('\\r\\n'))\n    }\n    \n    public static boolean deleteEmptySubfolders(File dir) {\n        if(dir == null || !dir.exists()) {\n            return true;\n        }\n        if(!dir.isDirectory()) {\n            return false;\n        }\n        File[] files = dir.listFiles();\n\n        if (files == null || files.length == 0) {\n            return dir.delete();\n        }\n\n        boolean result = true;\n        int len = files.length;\n\n        for(int i = 0; i < len; ++i) {\n            File file = files[i];\n            if(file.isDirectory()) {\n                if(!deleteEmptySubfolders(file)) {\n                    result = false;\n                }\n            }\n        }\n\n        File[] updatedFiles = dir.listFiles();\n        if (updatedFiles == null || updatedFiles.length == 0) {\n            if (!dir.delete()) {\n                result = false;\n            }\n        }\n\n        return result;\n    }\n}\n"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/groovy/com.didi.virtualapk/utils/PackagingUtils.java",
    "content": "/*\n * Copyright (C) 2013 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.didi.virtualapk.utils;\n\nimport com.android.SdkConstants;\nimport com.android.annotations.NonNull;\n\nimport org.gradle.internal.impldep.com.google.common.base.Charsets;\nimport org.gradle.internal.impldep.com.google.common.collect.ImmutableList;\nimport org.gradle.internal.impldep.com.google.common.hash.HashFunction;\nimport org.gradle.internal.impldep.com.google.common.hash.Hasher;\nimport org.gradle.internal.impldep.com.google.common.hash.Hashing;\n\nimport java.io.File;\nimport java.io.IOException;\n\n/**\n * Utility class for packaging.\n */\npublic class PackagingUtils {\n    \n    /**\n     * Checks whether a folder and its content is valid for packaging into the .apk as\n     * standard Java resource.\n     * @param folderName the name of the folder.\n     *\n     * @return true if the folder is valid for packaging.\n     */\n    public static boolean checkFolderForPackaging(@NonNull String folderName) {\n        return !folderName.equalsIgnoreCase(\"CVS\") &&\n            !folderName.equalsIgnoreCase(\".svn\") &&\n            !folderName.equalsIgnoreCase(\"SCCS\") &&\n            !folderName.startsWith(\"_\");\n    }\n    \n    /**\n     * Checks a file to make sure it should be packaged as standard resources.\n     * @param fileName the name of the file (including extension)\n     * @param allowClassFiles whether to allow java class files\n     * @return true if the file should be packaged as standard java resources.\n     */\n    public static boolean checkFileForPackaging(@NonNull String fileName, boolean allowClassFiles) {\n        String[] fileSegments = fileName.split(\"\\\\.\");\n        String fileExt = \"\";\n        if (fileSegments.length > 1) {\n            fileExt = fileSegments[fileSegments.length-1];\n        }\n        \n        return checkFileForPackaging(fileName, fileExt, allowClassFiles);\n    }\n    \n    /**\n     * Checks a file to make sure it should be packaged as standard resources.\n     * @param fileName the name of the file (including extension)\n     * @return true if the file should be packaged as standard java resources.\n     */\n    public static boolean checkFileForPackaging(@NonNull String fileName) {\n        return checkFileForPackaging(fileName, false);\n    }\n    \n    /**\n     * Checks a file to make sure it should be packaged as standard resources.\n     * @param fileName the name of the file (including extension)\n     * @param extension the extension of the file (excluding '.')\n     * @param allowClassFiles whether to allow java class files\n     * @return true if the file should be packaged as standard java resources.\n     */\n    public static boolean checkFileForPackaging(\n        @NonNull String fileName,\n        @NonNull String extension,\n        boolean allowClassFiles) {\n        // ignore hidden files and backup files\n        return !(fileName.charAt(0) == '.' || fileName.charAt(fileName.length() - 1) == '~') &&\n            !isOfNonResourcesExtensions(extension, allowClassFiles) &&\n            !isNotAResourceFile(fileName);\n    }\n    \n    /**\n     * Checks a file to make sure it should be packaged as standard resources.\n     * @param fileName the name of the file (including extension)\n     * @param extension the extension of the file (excluding '.')\n     * @return true if the file should be packaged as standard java resources.\n     */\n    public static boolean checkFileForPackaging(\n        @NonNull String fileName,\n        @NonNull String extension) {\n        // ignore hidden files and backup files\n        return !(fileName.charAt(0) == '.' || fileName.charAt(fileName.length() - 1) == '~') &&\n            !isOfNonResourcesExtensions(extension, false) &&\n            !isNotAResourceFile(fileName);\n    }\n    \n    private static boolean isOfNonResourcesExtensions(\n        @NonNull String extension,\n        boolean allowClassFiles) {\n        for (String ext : NON_RESOURCES_EXTENSIONS) {\n            if (ext.equalsIgnoreCase(extension)) {\n                return true;\n            }\n        }\n        \n        return !allowClassFiles && SdkConstants.EXT_CLASS.equals(extension);\n    }\n    \n    private static boolean isNotAResourceFile(@NonNull String fileName) {\n        for (String name : NON_RESOURCES_FILENAMES) {\n            if (name.equalsIgnoreCase(fileName)) {\n                return true;\n            }\n        }\n        return false;\n    }\n    \n    /**\n     * Returns the list of file extensions that represents non resources files.\n     */\n    public static final ImmutableList<String> NON_RESOURCES_EXTENSIONS =\n        ImmutableList.<String>builder()\n            .add(\"aidl\")            // Aidl files\n            .add(\"rs\")              // RenderScript files\n            .add(\"fs\")              // FilterScript files\n            .add(\"rsh\")             // RenderScript header files\n            .add(\"d\")               // Dependency files\n            .add(\"java\")            // Java files\n            .add(\"scala\")           // Scala files\n            .add(\"scc\")             // VisualSourceSafe\n            .add(\"swp\")             // vi swap file\n            .build();\n    \n    /**\n     * Return file names that are not resource files.\n     */\n    public static final ImmutableList<String> NON_RESOURCES_FILENAMES =\n        ImmutableList.<String>builder()\n            .add(\"thumbs.db\")       // image index file\n            .add(\"picasa.ini\")      // image index file\n            .add(\"about.html\")      // Javadoc\n            .add(\"package.html\")    // Javadoc\n            .add(\"overview.html\")   // Javadoc\n            .build();\n    \n    /**\n     * Computes an \"application hash\", a reasonably unique identifier for an app.\n     * <p>\n     * This is currently used by Instant Run to prevent apps on a device from guessing\n     * the authentication token associated with an instant run developed app on the same\n     * device.\n     * <p>\n     * This method creates the \"secret\", the field in the AppInfo class which is used as a simple\n     * authentication token between the IDE and the app server.\n     * <p>\n     * This is not a cryptographically strong unique id; we could attempt to make a truly random\n     * number here, but we'd need to store that id somewhere such that subsequent builds\n     * will use the same secret, to avoid having the IDE and the app getting out of sync,\n     * and there isn't really a natural place for us to store the key to make it survive across\n     * a clean build. (One possibility is putting it into local.properties).\n     * <p>\n     * However, we have much simpler needs: we just need a key that can't be guessed from\n     * a hostile app on the developer's device, and it only has a few guesses (after providing\n     * the wrong secret to the server a few times, the server will shut down.) We can't\n     * rely on the package name along, since the port number is known, and the package name is\n     * discoverable by the hostile app (by querying the contents of /data/data/*). Therefore\n     * we also include facts that the hostile app can't know, such as as the path on the\n     * developer's machine to the app project and the name of the developer's machine, etc.\n     * The goal is for this secret to be reasonably stable (e.g. the same from build to build)\n     * yet not something an app could guess if it only has a couple of tries.\n     */\n    public static long computeApplicationHash(@NonNull File projectDir) {\n        HashFunction hashFunction = Hashing.md5();\n        Hasher hasher = hashFunction.newHasher();\n        try {\n            projectDir = projectDir.getCanonicalFile();\n        } catch (IOException ignore) {\n            // If this throws an exception the assignment won't\n            // be done and we'll stick with the regular project dir\n        }\n        String path = projectDir.getPath();\n        hasher.putBytes(path.getBytes(Charsets.UTF_8));\n        String user = System.getProperty(\"user.name\");\n        if (user != null) {\n            hasher.putBytes(user.getBytes(Charsets.UTF_8));\n        }\n        return hasher.hash().asLong();\n    }\n}\n"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/groovy/com.didi.virtualapk/utils/Reflect.java",
    "content": "/*\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.didi.virtualapk.utils;\n\n// ...\nimport java.lang.reflect.AccessibleObject;\nimport java.lang.reflect.Constructor;\nimport java.lang.reflect.Field;\nimport java.lang.reflect.InvocationHandler;\nimport java.lang.reflect.Member;\nimport java.lang.reflect.Method;\nimport java.lang.reflect.Modifier;\nimport java.lang.reflect.Proxy;\nimport java.util.Arrays;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\n\n/**\n * A wrapper for an {@link Object} or {@link Class} upon which reflective calls\n * can be made.\n * <p>\n * An example of using <code>Reflect</code> is <code><pre>\n * // Static import all reflection methods to decrease verbosity\n * import static org.joor.Reflect.*;\n *\n * // Wrap an Object / Class / class name with the on() method:\n * on(\"java.lang.String\")\n * // Invoke constructors using the create() method:\n * .create(\"Hello World\")\n * // Invoke methods using the call() method:\n * .call(\"toString\")\n * // Retrieve the wrapped object\n *\n * @author Lukas Eder\n * @author Irek Matysiewicz\n * @author Thomas Darimont\n */\npublic class Reflect {\n    \n    // ---------------------------------------------------------------------\n    // Static API used as entrance points to the fluent API\n    // ---------------------------------------------------------------------\n    \n    /**\n     * Wrap a class name.\n     * <p>\n     * This is the same as calling <code>on(Class.forName(name))</code>\n     *\n     * @param name A fully qualified class name\n     * @return A wrapped class object, to be used for further reflection.\n     * @throws ReflectException If any reflection exception occurred.\n     * @see #on(Class)\n     */\n    public static Reflect on(String name) throws ReflectException {\n        return on(forName(name));\n    }\n    \n    /**\n     * Wrap a class name, loading it via a given class loader.\n     * <p>\n     * This is the same as calling\n     * <code>on(Class.forName(name, classLoader))</code>\n     *\n     * @param name A fully qualified class name.\n     * @param classLoader The class loader in whose context the class should be\n     *            loaded.\n     * @return A wrapped class object, to be used for further reflection.\n     * @throws ReflectException If any reflection exception occurred.\n     * @see #on(Class)\n     */\n    public static Reflect on(String name, ClassLoader classLoader) throws ReflectException {\n        return on(forName(name, classLoader));\n    }\n    \n    /**\n     * Wrap a class.\n     * <p>\n     * Use this when you want to access static fields and methods on a\n     * {@link Class} object, or as a basis for constructing objects of that\n     * class using {@link #create(Object...)}\n     *\n     * @param clazz The class to be wrapped\n     * @return A wrapped class object, to be used for further reflection.\n     */\n    public static Reflect on(Class<?> clazz) {\n        return new Reflect(clazz);\n    }\n    \n    /**\n     * Wrap an object.\n     * <p>\n     * Use this when you want to access instance fields and methods on any\n     * {@link Object}\n     *\n     * @param object The object to be wrapped\n     * @return A wrapped object, to be used for further reflection.\n     */\n    public static Reflect on(Object object) {\n        return new Reflect(object == null ? Object.class : object.getClass(), object);\n    }\n    \n    private static Reflect on(Class<?> type, Object object) {\n        return new Reflect(type, object);\n    }\n    \n    /**\n     * Conveniently render an {@link AccessibleObject} accessible.\n     * <p>\n     * To prevent {@link SecurityException}, this is only done if the argument\n     * object and its declaring class are non-public.\n     *\n     * @param accessible The object to render accessible\n     * @return The argument object rendered accessible\n     */\n    public static <T extends AccessibleObject> T accessible(T accessible) {\n        if (accessible == null) {\n            return null;\n        }\n        \n        if (accessible instanceof Member) {\n            Member member = (Member) accessible;\n            \n            if (Modifier.isPublic(member.getModifiers()) &&\n                Modifier.isPublic(member.getDeclaringClass().getModifiers())) {\n                \n                return accessible;\n            }\n        }\n        \n        // [jOOQ #3392] The accessible flag is set to false by default, also for public members.\n        if (!accessible.isAccessible()) {\n            accessible.setAccessible(true);\n        }\n        \n        return accessible;\n    }\n    \n    // ---------------------------------------------------------------------\n    // Members\n    // ---------------------------------------------------------------------\n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    /**\n     * The type of the wrapped object.\n     */\n    private final Class<?> type;\n    \n    /**\n     * The wrapped object.\n     */\n    private final Object   object;\n    \n    // ---------------------------------------------------------------------\n    // Constructors\n    // ---------------------------------------------------------------------\n    \n    private Reflect(Class<?> type) {\n        this(type, type);\n    }\n    \n    private Reflect(Class<?> type, Object object) {\n        this.type = type;\n        this.object = object;\n    }\n    \n    // ---------------------------------------------------------------------\n    // Fluent Reflection API\n    // ---------------------------------------------------------------------\n    \n    /**\n     * Get the wrapped object\n     *\n     * @param <T> A convenience generic parameter for automatic unsafe casting\n     */\n    @SuppressWarnings(\"unchecked\")\n    public <T> T get() {\n        return (T) object;\n    }\n    \n    /**\n     * Set a field value.\n     * <p>\n     * This is roughly equivalent to {@link Field#set(Object, Object)}. If the\n     * wrapped object is a {@link Class}, then this will set a value to a static\n     * member field. If the wrapped object is any other {@link Object}, then\n     * this will set a value to an instance member field.\n     * <p>\n     * This method is also capable of setting the value of (static) final\n     * fields. This may be convenient in situations where no\n     * {@link SecurityManager} is expected to prevent this, but do note that\n     * (especially static) final fields may already have been inlined by the\n     * javac and/or JIT and relevant code deleted from the runtime verison of\n     * your program, so setting these fields might not have any effect on your\n     * execution.\n     * <p>\n     * For restrictions of usage regarding setting values on final fields check:\n     * <a href=\n     * \"http://stackoverflow.com/questions/3301635/change-private-static-final-field-using-java-reflection\">http://stackoverflow.com/questions/3301635/change-private-static-final-field-using-java-reflection</a>\n     * ... and <a href=\n     * \"http://pveentjer.blogspot.co.at/2017/01/final-static-boolean-jit.html\">http://pveentjer.blogspot.co.at/2017/01/final-static-boolean-jit.html</a>\n     *\n     * @param name The field name\n     * @param value The new field value\n     * @return The same wrapped object, to be used for further reflection.\n     * @throws ReflectException If any reflection exception occurred.\n     */\n    public Reflect set(String name, Object value) throws ReflectException {\n        try {\n            Field field = field0(name);\n            if ((field.getModifiers() & Modifier.FINAL) == Modifier.FINAL) {\n                Field modifiersField = Field.class.getDeclaredField(\"modifiers\");\n                modifiersField.setAccessible(true);\n                modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);\n            }\n            field.set(object, unwrap(value));\n            return this;\n        }\n        catch (Exception e) {\n            throw new ReflectException(e);\n        }\n    }\n    \n    /**\n     * Get a field value.\n     * <p>\n     * This is roughly equivalent to {@link Field#get(Object)}. If the wrapped\n     * object is a {@link Class}, then this will get a value from a static\n     * member field. If the wrapped object is any other {@link Object}, then\n     * this will get a value from an instance member field.\n     * <p>\n     * If you want to \"navigate\" to a wrapped version of the field, use\n     * {@link #field(String)} instead.\n     *\n     * @param name The field name\n     * @return The field value\n     * @throws ReflectException If any reflection exception occurred.\n     * @see #field(String)\n     */\n    public <T> T get(String name) throws ReflectException {\n        return field(name).<T>get();\n    }\n    \n    /**\n     * Get a wrapped field.\n     * <p>\n     * This is roughly equivalent to {@link Field#get(Object)}. If the wrapped\n     * object is a {@link Class}, then this will wrap a static member field. If\n     * the wrapped object is any other {@link Object}, then this wrap an\n     * instance member field.\n     *\n     * @param name The field name\n     * @return The wrapped field\n     * @throws ReflectException If any reflection exception occurred.\n     */\n    public Reflect field(String name) throws ReflectException {\n        try {\n            Field field = field0(name);\n            return on(field.getType(), field.get(object));\n        }\n        catch (Exception e) {\n            throw new ReflectException(e);\n        }\n    }\n    \n    private Field field0(String name) throws ReflectException {\n        Class<?> t = type();\n        \n        // Try getting a public field\n        try {\n            return accessible(t.getField(name));\n        }\n        \n        // Try again, getting a non-public field\n        catch (NoSuchFieldException e) {\n            do {\n                try {\n                    return accessible(t.getDeclaredField(name));\n                }\n                catch (NoSuchFieldException ignore) {}\n                \n                t = t.getSuperclass();\n            }\n            while (t != null);\n            \n            throw new ReflectException(e);\n        }\n    }\n    \n    /**\n     * Get a Map containing field names and wrapped values for the fields'\n     * values.\n     * <p>\n     * If the wrapped object is a {@link Class}, then this will return static\n     * fields. If the wrapped object is any other {@link Object}, then this will\n     * return instance fields.\n     * <p>\n     * These two calls are equivalent <code><pre>\n     * on(object).field(\"myField\");\n     * on(object).fields().get(\"myField\");\n     * </pre></code>\n     *\n     * @return A map containing field names and wrapped values.\n     */\n    public Map<String, Reflect> fields() {\n        Map<String, Reflect> result = new LinkedHashMap<String, Reflect>();\n        Class<?> t = type();\n        \n        do {\n            for (Field field : t.getDeclaredFields()) {\n                if (type != object ^ Modifier.isStatic(field.getModifiers())) {\n                    String name = field.getName();\n                    \n                    if (!result.containsKey(name))\n                        result.put(name, field(name));\n                }\n            }\n            \n            t = t.getSuperclass();\n        }\n        while (t != null);\n        \n        return result;\n    }\n    \n    /**\n     * Call a method by its name.\n     * <p>\n     * This is a convenience method for calling\n     * <code>call(name, new Object[0])</code>\n     *\n     * @param name The method name\n     * @return The wrapped method result or the same wrapped object if the\n     *         method returns <code>void</code>, to be used for further\n     *         reflection.\n     * @throws ReflectException If any reflection exception occurred.\n     * @see #call(String, Object...)\n     */\n    public Reflect call(String name) throws ReflectException {\n        return call(name, new Object[0]);\n    }\n    \n    /**\n     * Call a method by its name.\n     * <p>\n     * This is roughly equivalent to {@link Method#invoke(Object, Object...)}.\n     * If the wrapped object is a {@link Class}, then this will invoke a static\n     * method. If the wrapped object is any other {@link Object}, then this will\n     * invoke an instance method.\n     * <p>\n     * Just like {@link Method#invoke(Object, Object...)}, this will try to wrap\n     * primitive types or unwrap primitive type wrappers if applicable. If\n     * several methods are applicable, by that rule, the first one encountered\n     * is called. i.e. when calling <code><pre>\n     * on(...).call(\"method\", 1, 1);\n     * </pre></code> The first of the following methods will be called:\n     * <code><pre>\n     * public void method(int param1, Integer param2);\n     * public void method(Integer param1, int param2);\n     * public void method(Number param1, Number param2);\n     * public void method(Number param1, Object param2);\n     * public void method(int param1, Object param2);\n     * </pre></code>\n     * <p>\n     * The best matching method is searched for with the following strategy:\n     * <ol>\n     * <li>public method with exact signature match in class hierarchy</li>\n     * <li>non-public method with exact signature match on declaring class</li>\n     * <li>public method with similar signature in class hierarchy</li>\n     * <li>non-public method with similar signature on declaring class</li>\n     * </ol>\n     *\n     * @param name The method name\n     * @param args The method arguments\n     * @return The wrapped method result or the same wrapped object if the\n     *         method returns <code>void</code>, to be used for further\n     *         reflection.\n     * @throws ReflectException If any reflection exception occurred.\n     */\n    public Reflect call(String name, Object... args) throws ReflectException {\n        Class<?>[] types = types(args);\n        \n        // Try invoking the \"canonical\" method, i.e. the one with exact\n        // matching argument types\n        try {\n            Method method = exactMethod(name, types);\n            return on(method, object, args);\n        }\n        \n        // If there is no exact match, try to find a method that has a \"similar\"\n        // signature if primitive argument types are converted to their wrappers\n        catch (NoSuchMethodException e) {\n            try {\n                Method method = similarMethod(name, types);\n                return on(method, object, args);\n            } catch (NoSuchMethodException e1) {\n                throw new ReflectException(e1);\n            }\n        }\n    }\n    \n    /**\n     * Searches a method with the exact same signature as desired.\n     * <p>\n     * If a public method is found in the class hierarchy, this method is returned.\n     * Otherwise a private method with the exact same signature is returned.\n     * If no exact match could be found, we let the {@code NoSuchMethodException} pass through.\n     */\n    private Method exactMethod(String name, Class<?>[] types) throws NoSuchMethodException {\n        Class<?> t = type();\n        \n        // first priority: find a public method with exact signature match in class hierarchy\n        try {\n            return t.getMethod(name, types);\n        }\n        \n        // second priority: find a private method with exact signature match on declaring class\n        catch (NoSuchMethodException e) {\n            do {\n                try {\n                    return t.getDeclaredMethod(name, types);\n                }\n                catch (NoSuchMethodException ignore) {}\n                \n                t = t.getSuperclass();\n            }\n            while (t != null);\n            \n            throw new NoSuchMethodException();\n        }\n    }\n    \n    /**\n     * Searches a method with a similar signature as desired using\n     * {@link #isSimilarSignature(java.lang.reflect.Method, String, Class[])}.\n     * <p>\n     * First public methods are searched in the class hierarchy, then private\n     * methods on the declaring class. If a method could be found, it is\n     * returned, otherwise a {@code NoSuchMethodException} is thrown.\n     */\n    private Method similarMethod(String name, Class<?>[] types) throws NoSuchMethodException {\n        Class<?> t = type();\n        \n        // first priority: find a public method with a \"similar\" signature in class hierarchy\n        // similar interpreted in when primitive argument types are converted to their wrappers\n        for (Method method : t.getMethods()) {\n            if (isSimilarSignature(method, name, types)) {\n                return method;\n            }\n        }\n        \n        // second priority: find a non-public method with a \"similar\" signature on declaring class\n        do {\n            for (Method method : t.getDeclaredMethods()) {\n                if (isSimilarSignature(method, name, types)) {\n                    return method;\n                }\n            }\n            \n            t = t.getSuperclass();\n        }\n        while (t != null);\n        \n        throw new NoSuchMethodException(\"No similar method \" + name + \" with params \" + Arrays.toString(types) + \" could be found on type \" + type() + \".\");\n    }\n    \n    /**\n     * Determines if a method has a \"similar\" signature, especially if wrapping\n     * primitive argument types would result in an exactly matching signature.\n     */\n    private boolean isSimilarSignature(Method possiblyMatchingMethod, String desiredMethodName, Class<?>[] desiredParamTypes) {\n        return possiblyMatchingMethod.getName().equals(desiredMethodName) && match(possiblyMatchingMethod.getParameterTypes(), desiredParamTypes);\n    }\n    \n    /**\n     * Call a constructor.\n     * <p>\n     * This is a convenience method for calling\n     * <code>create(new Object[0])</code>\n     *\n     * @return The wrapped new object, to be used for further reflection.\n     * @throws ReflectException If any reflection exception occurred.\n     * @see #create(Object...)\n     */\n    public Reflect create() throws ReflectException {\n        return create(new Object[0]);\n    }\n    \n    /**\n     * Call a constructor.\n     * <p>\n     * This is roughly equivalent to {@link Constructor#newInstance(Object...)}.\n     * If the wrapped object is a {@link Class}, then this will create a new\n     * object of that class. If the wrapped object is any other {@link Object},\n     * then this will create a new object of the same type.\n     * <p>\n     * Just like {@link Constructor#newInstance(Object...)}, this will try to\n     * wrap primitive types or unwrap primitive type wrappers if applicable. If\n     * several constructors are applicable, by that rule, the first one\n     * encountered is called. i.e. when calling <code><pre>\n     * on(C.class).create(1, 1);\n     * </pre></code> The first of the following constructors will be applied:\n     * <code><pre>\n     * public C(int param1, Integer param2);\n     * public C(Integer param1, int param2);\n     * public C(Number param1, Number param2);\n     * public C(Number param1, Object param2);\n     * public C(int param1, Object param2);\n     * </pre></code>\n     *\n     * @param args The constructor arguments\n     * @return The wrapped new object, to be used for further reflection.\n     * @throws ReflectException If any reflection exception occurred.\n     */\n    public Reflect create(Object... args) throws ReflectException {\n        Class<?>[] types = types(args);\n        \n        // Try invoking the \"canonical\" constructor, i.e. the one with exact\n        // matching argument types\n        try {\n            Constructor<?> constructor = type().getDeclaredConstructor(types);\n            return on(constructor, args);\n        }\n        \n        // If there is no exact match, try to find one that has a \"similar\"\n        // signature if primitive argument types are converted to their wrappers\n        catch (NoSuchMethodException e) {\n            for (Constructor<?> constructor : type().getDeclaredConstructors()) {\n                if (match(constructor.getParameterTypes(), types)) {\n                    return on(constructor, args);\n                }\n            }\n            \n            throw new ReflectException(e);\n        }\n    }\n    \n    /**\n     * Create a proxy for the wrapped object allowing to typesafely invoke\n     * methods on it using a custom interface\n     *\n     * @param proxyType The interface type that is implemented by the proxy\n     * @return A proxy for the wrapped object\n     */\n    @SuppressWarnings(\"unchecked\")\n    public <P> P as(final Class<P> proxyType) {\n        final boolean isMap = (object instanceof Map);\n        final InvocationHandler handler = new InvocationHandler() {\n            @SuppressWarnings(\"null\")\n            @Override\n            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {\n                String name = method.getName();\n                \n                // Actual method name matches always come first\n                try {\n                    return on(type, object).call(name, args).get();\n                }\n                \n                // [#14] Emulate POJO behaviour on wrapped map objects\n                catch (ReflectException e) {\n                    if (isMap) {\n                        Map<String, Object> map = (Map<String, Object>) object;\n                        int length = (args == null ? 0 : args.length);\n                        \n                        if (length == 0 && name.startsWith(\"get\")) {\n                            return map.get(property(name.substring(3)));\n                        }\n                        else if (length == 0 && name.startsWith(\"is\")) {\n                            return map.get(property(name.substring(2)));\n                        }\n                        else if (length == 1 && name.startsWith(\"set\")) {\n                            map.put(property(name.substring(3)), args[0]);\n                            return null;\n                        }\n                    }\n                    \n                    \n                    \n                    \n                    \n                    \n                    \n                    \n                    \n                    \n                    \n                    throw e;\n                }\n            }\n        };\n        \n        return (P) Proxy.newProxyInstance(proxyType.getClassLoader(), new Class[] { proxyType }, handler);\n    }\n    \n    /**\n     * Get the POJO property name of an getter/setter\n     */\n    private static String property(String string) {\n        int length = string.length();\n        \n        if (length == 0) {\n            return \"\";\n        }\n        else if (length == 1) {\n            return string.toLowerCase();\n        }\n        else {\n            return string.substring(0, 1).toLowerCase() + string.substring(1);\n        }\n    }\n    \n    // ---------------------------------------------------------------------\n    // Object API\n    // ---------------------------------------------------------------------\n    \n    /**\n     * Check whether two arrays of types match, converting primitive types to\n     * their corresponding wrappers.\n     */\n    private boolean match(Class<?>[] declaredTypes, Class<?>[] actualTypes) {\n        if (declaredTypes.length == actualTypes.length) {\n            for (int i = 0; i < actualTypes.length; i++) {\n                if (actualTypes[i] == NULL.class)\n                    continue;\n                \n                if (wrapper(declaredTypes[i]).isAssignableFrom(wrapper(actualTypes[i])))\n                    continue;\n                \n                return false;\n            }\n            \n            return true;\n        }\n        else {\n            return false;\n        }\n    }\n    \n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public int hashCode() {\n        return object.hashCode();\n    }\n    \n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public boolean equals(Object obj) {\n        if (obj instanceof Reflect) {\n            return object.equals(((Reflect) obj).get());\n        }\n        \n        return false;\n    }\n    \n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public String toString() {\n        return object.toString();\n    }\n    \n    // ---------------------------------------------------------------------\n    // Utility methods\n    // ---------------------------------------------------------------------\n    \n    /**\n     * Wrap an object created from a constructor\n     */\n    private static Reflect on(Constructor<?> constructor, Object... args) throws ReflectException {\n        try {\n            return on(constructor.getDeclaringClass(), accessible(constructor).newInstance(args));\n        }\n        catch (Exception e) {\n            throw new ReflectException(e);\n        }\n    }\n    \n    /**\n     * Wrap an object returned from a method\n     */\n    private static Reflect on(Method method, Object object, Object... args) throws ReflectException {\n        try {\n            accessible(method);\n            \n            if (method.getReturnType() == void.class) {\n                method.invoke(object, args);\n                return on(object);\n            }\n            else {\n                return on(method.invoke(object, args));\n            }\n        }\n        catch (Exception e) {\n            throw new ReflectException(e);\n        }\n    }\n    \n    /**\n     * Unwrap an object\n     */\n    private static Object unwrap(Object object) {\n        if (object instanceof Reflect) {\n            return ((Reflect) object).get();\n        }\n        \n        return object;\n    }\n    \n    /**\n     * Get an array of types for an array of objects\n     *\n     * @see Object#getClass()\n     */\n    private static Class<?>[] types(Object... values) {\n        if (values == null) {\n            return new Class[0];\n        }\n        \n        Class<?>[] result = new Class[values.length];\n        \n        for (int i = 0; i < values.length; i++) {\n            Object value = values[i];\n            result[i] = value == null ? NULL.class : value.getClass();\n        }\n        \n        return result;\n    }\n    \n    /**\n     * Load a class\n     *\n     * @see Class#forName(String)\n     */\n    private static Class<?> forName(String name) throws ReflectException {\n        try {\n            return Class.forName(name);\n        }\n        catch (Exception e) {\n            throw new ReflectException(e);\n        }\n    }\n    \n    private static Class<?> forName(String name, ClassLoader classLoader) throws ReflectException {\n        try {\n            return Class.forName(name, true, classLoader);\n        }\n        catch (Exception e) {\n            throw new ReflectException(e);\n        }\n    }\n    \n    /**\n     * Get the type of the wrapped object.\n     *\n     * @see Object#getClass()\n     */\n    public Class<?> type() {\n        return type;\n    }\n    \n    /**\n     * Get a wrapper type for a primitive type, or the argument type itself, if\n     * it is not a primitive type.\n     */\n    public static Class<?> wrapper(Class<?> type) {\n        if (type == null) {\n            return null;\n        }\n        else if (type.isPrimitive()) {\n            if (boolean.class == type) {\n                return Boolean.class;\n            }\n            else if (int.class == type) {\n                return Integer.class;\n            }\n            else if (long.class == type) {\n                return Long.class;\n            }\n            else if (short.class == type) {\n                return Short.class;\n            }\n            else if (byte.class == type) {\n                return Byte.class;\n            }\n            else if (double.class == type) {\n                return Double.class;\n            }\n            else if (float.class == type) {\n                return Float.class;\n            }\n            else if (char.class == type) {\n                return Character.class;\n            }\n            else if (void.class == type) {\n                return Void.class;\n            }\n        }\n        \n        return type;\n    }\n    \n    private static class NULL {}\n    \n    /**\n     * A unchecked wrapper for any of Java's checked reflection exceptions:\n     * <p>\n     * These exceptions are\n     * <ul>\n     * <li> {@link ClassNotFoundException}</li>\n     * <li> {@link IllegalAccessException}</li>\n     * <li> {@link IllegalArgumentException}</li>\n     * <li> {@link InstantiationException}</li>\n     * <li> {@link InvocationTargetException}</li>\n     * <li> {@link NoSuchMethodException}</li>\n     * <li> {@link NoSuchFieldException}</li>\n     * <li> {@link SecurityException}</li>\n     * </ul>\n     *\n     * @author Lukas Eder\n     */\n    public static class ReflectException extends RuntimeException {\n        \n        /**\n         * Generated UID\n         */\n        private static final long serialVersionUID = -6213149635297151442L;\n        \n        public ReflectException(String message) {\n            super(message);\n        }\n        \n        public ReflectException(String message, Throwable cause) {\n            super(message, cause);\n        }\n        \n        public ReflectException() {\n            super();\n        }\n        \n        public ReflectException(Throwable cause) {\n            super(cause);\n        }\n    }\n}"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/groovy/com.didi.virtualapk/utils/ZipUtil.groovy",
    "content": "package com.didi.virtualapk.utils\n\nimport java.util.jar.JarFile\nimport java.util.zip.ZipEntry\nimport java.util.zip.ZipFile\nimport java.util.zip.ZipOutputStream\n\n\n/**\n * operate on jar/zip file\n */\npublic class ZipUtil {\n\n    private byte[] buffer = new byte[1024]\n\n    private final File file\n\n    public static ZipUtil with(final File file) {\n        return new ZipUtil(file)\n    }\n\n    /**\n     * unzip jar to class names\n     * @param jarFile\n     * @return class name set\n     */\n    public static Set<String> jarFileToClasses(final File jarFile) {\n        if (jarFile == null || !jarFile.exists() || !jarFile.name.endsWith(\"jar\")) {\n            return Collections.EMPTY_SET;\n        }\n\n        Set<String> entryNames = new LinkedHashSet<>();\n        JarFile jar = new JarFile(jarFile)\n        jar.entries().each { entry ->\n            entryNames.add(entry.name)\n        }\n\n        return entryNames\n    }\n\n    private ZipUtil(final File file) {\n        this.file = file\n    }\n\n    public ZipUtil deleteAll(final Set<String> deletes) {\n//        deletes.each {\n//            println \"Deleting [${it}]...\"\n//        }\n        ZipFile zf = new ZipFile(this.file)\n        File temp = new File(this.file.parentFile, \"${this.file.name}~\")\n        ZipOutputStream os = new ZipOutputStream(new FileOutputStream(temp))\n\n        def entries = zf.entries()\n        while (entries.hasMoreElements()) {\n            ZipEntry ze = entries.nextElement()\n            //print \"#### [${ze.name}] \"\n            if (!deletes.find { it == ze.name }) {\n                writeEntry(zf, os, ze)\n                //println \" +\"\n            } else {\n                //println \" -\"\n            }\n        }\n\n        zf.close()\n        os.flush()\n        os.close()\n\n        this.file.delete() // delete first to avoid `renameTo' failed on Windows\n        temp.renameTo(file)\n        return this\n    }\n\n    private void writeEntry(ZipFile zf, ZipOutputStream os, ZipEntry ze) throws IOException {\n        ZipEntry ze2 = new ZipEntry(ze.getName());\n        ze2.setMethod(ze.getMethod());\n        ze2.setTime(ze.getTime());\n        ze2.setComment(ze.getComment());\n        ze2.setExtra(ze.getExtra());\n        if (ze.getMethod() == ZipEntry.STORED) {\n            ze2.setSize(ze.getSize());\n            ze2.setCrc(ze.getCrc());\n        }\n        os.putNextEntry(ze2);\n        writeBytes(zf, ze, os);\n    }\n\n    private synchronized void writeBytes(ZipFile zf, ZipEntry ze, ZipOutputStream os) throws IOException {\n        int n;\n\n        InputStream is = null;\n        try {\n            is = zf.getInputStream(ze);\n            long left = ze.getSize();\n\n            while((left > 0) && (n = is.read(buffer, 0, buffer.length)) != -1) {\n                os.write(buffer, 0, n);\n                left -= n;\n            }\n        } finally {\n            if (is != null) {\n                is.close();\n            }\n        }\n    }\n}"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/java/com/didi/virtualapk/databinding/annotationprocessor/ProcessDataBinding.java",
    "content": "/*\n * Copyright (C) 2015 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.didi.virtualapk.databinding.annotationprocessor;\n\nimport com.didi.virtualapk.utils.Log;\n\nimport java.io.BufferedReader;\nimport java.io.BufferedWriter;\nimport java.io.Closeable;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.Reader;\nimport java.io.Writer;\nimport java.util.Set;\nimport java.util.regex.Pattern;\n\nimport javax.annotation.processing.AbstractProcessor;\nimport javax.annotation.processing.ProcessingEnvironment;\nimport javax.annotation.processing.RoundEnvironment;\nimport javax.annotation.processing.SupportedAnnotationTypes;\nimport javax.lang.model.SourceVersion;\nimport javax.lang.model.element.Element;\nimport javax.lang.model.element.TypeElement;\nimport javax.tools.FileObject;\nimport javax.tools.JavaFileObject;\nimport javax.tools.StandardLocation;\n\n@SupportedAnnotationTypes({\n    \"android.databinding.BindingAdapter\",\n    \"android.databinding.Untaggable\",\n    \"android.databinding.BindingMethods\",\n    \"android.databinding.BindingConversion\",\n    \"android.databinding.BindingBuildInfo\"}\n)\n/**\n * Parent annotation processor that dispatches sub steps to ensure execution order.\n * Use initProcessingSteps to add a new step.\n */\npublic class ProcessDataBinding extends AbstractProcessor {\n    \n    private static final String PARAM_MODULE_PKG = \"android.databinding.modulePackage\";\n    private static final String DATA_BINDER_MAPPER_PACKAGE = \"android.databinding\";\n    private static final String DATA_BINDER_MAPPER_CLASS_NAME = \"DataBinderMapper\";\n    private static final String DATA_BINDER_MAPPER_FULL_NAME = DATA_BINDER_MAPPER_PACKAGE + \".\" + DATA_BINDER_MAPPER_CLASS_NAME;\n    \n    private String modulePackage;\n    private FileObject fileObject;\n    \n    @Override\n    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {\n        for (Element element : roundEnv.getRootElements()) {\n            if (element instanceof TypeElement) {\n                TypeElement typeElement = (TypeElement) element;\n                \n                if (DATA_BINDER_MAPPER_FULL_NAME.contentEquals(typeElement.getQualifiedName())) {\n                    readDataBinderMapper();\n                }\n            }\n        }\n    \n        return false;\n    }\n    \n    private void readDataBinderMapper() {\n        BufferedReader reader = null;\n        BufferedWriter writer = null;\n        try {\n            reader = new BufferedReader(fileObject.openReader(false));\n    \n            String className = DATA_BINDER_MAPPER_CLASS_NAME + \"_\" + modulePackage.replace('.', '_');\n            JavaFileObject javaFileObject = processingEnv.getFiler().createSourceFile(DATA_BINDER_MAPPER_PACKAGE + \".\" + className);\n            writer = new BufferedWriter(javaFileObject.openWriter());\n            writer.write(\"// Generated by VirtualApk gradle plugin.\");\n            writer.newLine();\n            \n            String line;\n            while ((line = reader.readLine()) != null) {\n                if (line.contains(DATA_BINDER_MAPPER_CLASS_NAME)) {\n                    if (line.contains(\"class\")) {\n                        line = line.replace(DATA_BINDER_MAPPER_CLASS_NAME, className + \" extends \" + DATA_BINDER_MAPPER_CLASS_NAME);\n    \n                    } else {\n                        line = line.replace(DATA_BINDER_MAPPER_CLASS_NAME, className);\n                    }\n                }\n                \n                writer.write(line);\n                writer.newLine();\n            }\n            \n            Log.i(\"ProcessDataBinding\", \"Generated java source file: \" + DATA_BINDER_MAPPER_PACKAGE + \".\" + className);\n    \n        } catch (Exception e) {\n            e.printStackTrace();\n        \n        } finally {\n            closeSafely(reader);\n            closeSafely(writer);\n        }\n    }\n    \n    private static void closeSafely(Closeable closeable) {\n        if (closeable != null) {\n            try {\n                closeable.close();\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n    \n    @Override\n    public SourceVersion getSupportedSourceVersion() {\n        return SourceVersion.latest();\n    }\n    \n    @Override\n    public synchronized void init(ProcessingEnvironment processingEnv) {\n        super.init(processingEnv);\n    \n        modulePackage = processingEnv.getOptions().getOrDefault(PARAM_MODULE_PKG, \"unknown\");\n        try {\n            fileObject = processingEnv.getFiler().getResource(StandardLocation.SOURCE_OUTPUT, DATA_BINDER_MAPPER_PACKAGE, DATA_BINDER_MAPPER_CLASS_NAME + \".java\");\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n    }\n    \n}\n"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/java/com/didi/virtualapk/utils/Log.java",
    "content": "package com.didi.virtualapk.utils;\n\npublic final class Log {\n\n    private Log() {\n\n    }\n\n    public static int i(String tag, String msg) {\n        System.out.println(\"[INFO][\" + tag + \"] \" + msg);\n        return 0;\n    }\n\n    public static int e(String tag, String msg) {\n        System.err.println(\"[ERROR][\" + tag + \"] \" + msg);\n        return 0;\n    }\n}\n"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/resources/META-INF/gradle-plugins/com.didi.virtualapk.host.properties",
    "content": "implementation-class=com.didi.virtualapk.VAHostPlugin\n"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/resources/META-INF/gradle-plugins/com.didi.virtualapk.plugin.properties",
    "content": "implementation-class=com.didi.virtualapk.VAPlugin\n"
  },
  {
    "path": "virtualapk-gradle-plugin/src/main/resources/META-INF/services/javax.annotation.processing.Processor",
    "content": "com.didi.virtualapk.databinding.annotationprocessor.ProcessDataBinding"
  },
  {
    "path": "virtualapk-gradle-plugin/upload.gradle",
    "content": "apply plugin: 'com.github.dcendents.android-maven'\napply plugin: 'com.jfrog.bintray'\n\n\ndef siteUrl = 'https://github.com/didi/VirtualAPK' // 项目的主页\ndef gitUrl = 'https://github.com/didi/VirtualAPK' // Git仓库的url\n\ngroup = GROUP_ID\narchivesBaseName = 'gradle'\nversion = VERSION\n\ninstall {\n    repositories.mavenInstaller {\n        // This generates POM.xml with proper parameters\n        pom {\n            artifactId = ARTIFACT_ID\n\n            project {\n                packaging 'aar'\n                // Add your description here\n                name 'A powerful but lightweight plugin framework for Android' //项目描述\n                url siteUrl\n                // Set your license\n                licenses {\n                    license {\n                        name 'Apache License 2.0'\n                        url 'http://www.apache.org/licenses/LICENSE-2.0'\n                    }\n                }\n                developers {\n                    developer {\n                        id 'zhengtao'    //填写的一些基本信息\n                        name 'DiDi'\n                        email 'zhengtao620@gmail.com'\n                    }\n                }\n                scm {\n                    connection gitUrl\n                    developerConnection gitUrl\n                    url siteUrl\n                }\n            }\n        }\n    }\n}\n\ntask sourcesJar(type: Jar) {\n    from sourceSets.main.allJava\n    classifier = 'sources'\n}\ntask javadocJar(type: Jar, dependsOn: javadoc) {\n    classifier = 'javadoc'\n    from groovydoc.destinationDir\n}\nartifacts {\n    //archives javadocJar\n    archives sourcesJar\n}\n\nProperties properties = new Properties()\nproperties.load(project.rootProject.file('local.properties').newDataInputStream())\nbintray {\n    user = properties.getProperty(\"bintray.user\")\n    key = properties.getProperty(\"bintray.apikey\")\n    configurations = ['archives']\n    pkg {\n        repo = \"maven\"\n        name = \"${GROUP_ID}:${ARTIFACT_ID}\"    //发布到JCenter上的项目名字\n        websiteUrl = siteUrl\n        vcsUrl = gitUrl\n        licenses = [\"Apache-2.0\"]\n        publish = true\n    }\n\n}"
  }
]