Repository: DroidPluginTeam/DroidPlugin Branch: master Commit: c6ebf652e0f7 Files: 270 Total size: 1.6 MB Directory structure: gitextract_kra60iuy/ ├── .gitignore ├── DOC/ │ ├── .gitignore │ ├── hejunlin/ │ │ ├── LICENSE │ │ ├── 插件占坑,四大组件动态注册前奏(一) 系统Activity的启动流程.md │ │ ├── 插件占坑,四大组件动态注册前奏(三) 系统BroadCast的注册发送流程.md │ │ ├── 插件占坑,四大组件动态注册前奏(二) 系统Service的启动流程.md │ │ ├── 插件开发之360 DroidPlugin源码分析(一)初识.md │ │ ├── 插件开发之360 DroidPlugin源码分析(三)Binder代理.md │ │ ├── 插件开发之360 DroidPlugin源码分析(二)Hook机制.md │ │ ├── 插件开发之360 DroidPlugin源码分析(五)Service预注册占坑.md │ │ └── 插件开发之360 DroidPlugin源码分析(四)Activity预注册占坑.md │ ├── tianweishu/ │ │ ├── .gitignore │ │ ├── Activity生命周期管理.md │ │ ├── BroadcastReceiver插件化.md │ │ ├── ClassLoader管理.md │ │ ├── ContentProvider插件化.md │ │ ├── Hook机制之AMS&PMS.md │ │ ├── Hook机制之Binder-Hook.md │ │ ├── Hook机制之代理Hook.md │ │ ├── Service插件化.md │ │ └── 概述.md │ └── 插件机制介绍.pptx ├── LICENSE ├── project/ │ ├── .gitignore │ ├── Libraries/ │ │ └── DroidPlugin/ │ │ ├── LICENSE.txt │ │ ├── build.gradle │ │ ├── lib/ │ │ │ └── layoutlib.jar │ │ ├── proguard-project.txt │ │ ├── project.properties │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ ├── aidl/ │ │ │ ├── android/ │ │ │ │ └── app/ │ │ │ │ └── IServiceConnection.aidl │ │ │ └── com/ │ │ │ └── morgoo/ │ │ │ └── droidplugin/ │ │ │ └── pm/ │ │ │ ├── IApplicationCallback.aidl │ │ │ ├── IPackageDataObserver.aidl │ │ │ └── IPluginManager.aidl │ │ ├── java/ │ │ │ └── com/ │ │ │ └── morgoo/ │ │ │ ├── droidplugin/ │ │ │ │ ├── MyCrashHandler.java │ │ │ │ ├── PluginApplication.java │ │ │ │ ├── PluginHelper.java │ │ │ │ ├── PluginManagerService.java │ │ │ │ ├── PluginPatchManager.java │ │ │ │ ├── PluginServiceProvider.java │ │ │ │ ├── am/ │ │ │ │ │ ├── BaseActivityManagerService.java │ │ │ │ │ ├── MyActivityManagerService.java │ │ │ │ │ ├── RunningActivities.java │ │ │ │ │ ├── RunningProcessList.java │ │ │ │ │ ├── ServiceStubMap.java │ │ │ │ │ └── StaticProcessList.java │ │ │ │ ├── core/ │ │ │ │ │ ├── Env.java │ │ │ │ │ ├── PluginClassLoader.java │ │ │ │ │ ├── PluginDirHelper.java │ │ │ │ │ └── PluginProcessManager.java │ │ │ │ ├── hook/ │ │ │ │ │ ├── BaseHookHandle.java │ │ │ │ │ ├── Hook.java │ │ │ │ │ ├── HookFactory.java │ │ │ │ │ ├── HookedMethodHandler.java │ │ │ │ │ ├── binder/ │ │ │ │ │ │ ├── BinderHook.java │ │ │ │ │ │ ├── IAppOpsServiceBinderHook.java │ │ │ │ │ │ ├── IAudioServiceBinderHook.java │ │ │ │ │ │ ├── IClipboardBinderHook.java │ │ │ │ │ │ ├── IContentServiceBinderHook.java │ │ │ │ │ │ ├── IDisplayManagerBinderHook.java │ │ │ │ │ │ ├── IGraphicsStatsBinderHook.java │ │ │ │ │ │ ├── IInputMethodManagerBinderHook.java │ │ │ │ │ │ ├── ILocationManagerBinderHook.java │ │ │ │ │ │ ├── IMediaRouterServiceBinderHook.java │ │ │ │ │ │ ├── IMmsBinderHook.java │ │ │ │ │ │ ├── IMountServiceBinderHook.java │ │ │ │ │ │ ├── INotificationManagerBinderHook.java │ │ │ │ │ │ ├── IPhoneSubInfoBinderHook.java │ │ │ │ │ │ ├── ISearchManagerBinderHook.java │ │ │ │ │ │ ├── ISessionManagerBinderHook.java │ │ │ │ │ │ ├── ISmsBinderHook.java │ │ │ │ │ │ ├── ISubBinderHook.java │ │ │ │ │ │ ├── ITelephonyBinderHook.java │ │ │ │ │ │ ├── ITelephonyRegistryBinderHook.java │ │ │ │ │ │ ├── IWifiManagerBinderHook.java │ │ │ │ │ │ ├── IWindowManagerBinderHook.java │ │ │ │ │ │ ├── MyServiceManager.java │ │ │ │ │ │ ├── ServiceManagerBinderHook.java │ │ │ │ │ │ └── ServiceManagerCacheBinderHook.java │ │ │ │ │ ├── handle/ │ │ │ │ │ │ ├── IActivityManagerHookHandle.java │ │ │ │ │ │ ├── IAppOpsServiceHookHandle.java │ │ │ │ │ │ ├── IAudioServiceHookHandle.java │ │ │ │ │ │ ├── IClipboardHookHandle.java │ │ │ │ │ │ ├── IContentProviderInvokeHandle.java │ │ │ │ │ │ ├── IContentServiceHandle.java │ │ │ │ │ │ ├── IDisplayManagerHookHandle.java │ │ │ │ │ │ ├── IGraphicsStatsHookHandle.java │ │ │ │ │ │ ├── IInputMethodManagerHookHandle.java │ │ │ │ │ │ ├── ILocationManagerHookHandle.java │ │ │ │ │ │ ├── IMediaRouterServiceHookHandle.java │ │ │ │ │ │ ├── IMmsHookHandle.java │ │ │ │ │ │ ├── IMountServiceHookHandle.java │ │ │ │ │ │ ├── INotificationManagerHookHandle.java │ │ │ │ │ │ ├── IPackageManagerHookHandle.java │ │ │ │ │ │ ├── IPhoneSubInfoHookHandle.java │ │ │ │ │ │ ├── ISearchManagerHookHandle.java │ │ │ │ │ │ ├── ISessionManagerHookHandle.java │ │ │ │ │ │ ├── ISmsHookHandle.java │ │ │ │ │ │ ├── ISubBinderHookHandle.java │ │ │ │ │ │ ├── ITelephonyHookHandle.java │ │ │ │ │ │ ├── ITelephonyRegistryHookHandle.java │ │ │ │ │ │ ├── IWifiManagerHookHandle.java │ │ │ │ │ │ ├── IWindowManagerHookHandle.java │ │ │ │ │ │ ├── IWindowSessionInvokeHandle.java │ │ │ │ │ │ ├── LibCoreHookHandle.java │ │ │ │ │ │ ├── PluginCallback.java │ │ │ │ │ │ ├── PluginInstrumentation.java │ │ │ │ │ │ ├── ReplaceCallingPackageHookedMethodHandler.java │ │ │ │ │ │ └── WebViewFactoryProviderHookHandle.java │ │ │ │ │ ├── proxy/ │ │ │ │ │ │ ├── IActivityManagerHook.java │ │ │ │ │ │ ├── IContentProviderHook.java │ │ │ │ │ │ ├── IPackageManagerHook.java │ │ │ │ │ │ ├── IWindowSessionHook.java │ │ │ │ │ │ ├── InstrumentationHook.java │ │ │ │ │ │ ├── LibCoreHook.java │ │ │ │ │ │ ├── PluginCallbackHook.java │ │ │ │ │ │ ├── ProxyHook.java │ │ │ │ │ │ └── WebViewFactoryProviderHook.java │ │ │ │ │ └── xhook/ │ │ │ │ │ └── SQLiteDatabaseHook.java │ │ │ │ ├── pm/ │ │ │ │ │ ├── IPluginManagerImpl.java │ │ │ │ │ ├── PluginManager.java │ │ │ │ │ └── parser/ │ │ │ │ │ ├── IntentMatcher.java │ │ │ │ │ ├── PackageParser.java │ │ │ │ │ ├── PackageParserApi15.java │ │ │ │ │ ├── PackageParserApi16.java │ │ │ │ │ ├── PackageParserApi20.java │ │ │ │ │ ├── PackageParserApi21.java │ │ │ │ │ ├── PackageParserApi22.java │ │ │ │ │ ├── PackageParserApi22Preview1.java │ │ │ │ │ └── PluginPackageParser.java │ │ │ │ ├── reflect/ │ │ │ │ │ ├── FieldUtils.java │ │ │ │ │ ├── MemberUtils.java │ │ │ │ │ ├── MethodUtils.java │ │ │ │ │ ├── Utils.java │ │ │ │ │ └── Validate.java │ │ │ │ └── stub/ │ │ │ │ ├── AbstractContentProviderStub.java │ │ │ │ ├── AbstractServiceStub.java │ │ │ │ ├── ActivityStub.java │ │ │ │ ├── ContentProviderStub.java │ │ │ │ ├── MyFakeIBinder.java │ │ │ │ ├── ServcesManager.java │ │ │ │ ├── ServiceStub.java │ │ │ │ └── ShortcutProxyActivity.java │ │ │ └── helper/ │ │ │ ├── AttributeCache.java │ │ │ ├── ComponentNameComparator.java │ │ │ ├── Log.java │ │ │ ├── MyProxy.java │ │ │ ├── Utils.java │ │ │ ├── compat/ │ │ │ │ ├── ActivityManagerCompat.java │ │ │ │ ├── ActivityManagerNativeCompat.java │ │ │ │ ├── ActivityThreadCompat.java │ │ │ │ ├── BuildCompat.java │ │ │ │ ├── BundleCompat.java │ │ │ │ ├── CompatibilityInfoCompat.java │ │ │ │ ├── ContentProviderCompat.java │ │ │ │ ├── ContentProviderHolderCompat.java │ │ │ │ ├── IActivityManagerCompat.java │ │ │ │ ├── IAppOpsServiceCompat.java │ │ │ │ ├── IAudioServiceCompat.java │ │ │ │ ├── IClipboardCompat.java │ │ │ │ ├── IContentServiceCompat.java │ │ │ │ ├── IDisplayManagerCompat.java │ │ │ │ ├── IGraphicsStatsCompat.java │ │ │ │ ├── IInputMethodManagerCompat.java │ │ │ │ ├── ILocationManagerCompat.java │ │ │ │ ├── IMediaRouterServiceCompat.java │ │ │ │ ├── IMmsCompat.java │ │ │ │ ├── IMountServiceCompat.java │ │ │ │ ├── INotificationManagerCompat.java │ │ │ │ ├── IPackageDataObserverCompat.java │ │ │ │ ├── IPhoneSubInfoCompat.java │ │ │ │ ├── ISearchManagerCompat.java │ │ │ │ ├── ISessionManagerCompat.java │ │ │ │ ├── ISmsCompat.java │ │ │ │ ├── ISubCompat.java │ │ │ │ ├── ITelephonyCompat.java │ │ │ │ ├── ITelephonyRegistryCompat.java │ │ │ │ ├── IWifiManagerCompat.java │ │ │ │ ├── IWindowManagerCompat.java │ │ │ │ ├── NativeLibraryHelperCompat.java │ │ │ │ ├── PackageManagerCompat.java │ │ │ │ ├── ParceledListSliceCompat.java │ │ │ │ ├── ProcessCompat.java │ │ │ │ ├── QueuedWorkCompat.java │ │ │ │ ├── ServiceManagerCompat.java │ │ │ │ ├── SingletonCompat.java │ │ │ │ ├── SystemPropertiesCompat.java │ │ │ │ ├── UserHandleCompat.java │ │ │ │ ├── VMRuntimeCompat.java │ │ │ │ └── WebViewFactoryCompat.java │ │ │ └── utils/ │ │ │ └── ProcessUtils.java │ │ └── res/ │ │ ├── drawable-xxhdpi/ │ │ │ └── plugin_activity_loading.xml │ │ ├── values/ │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ ├── values-v11/ │ │ │ └── styles.xml │ │ └── values-v14/ │ │ └── styles.xml │ ├── Test/ │ │ └── ApiTest/ │ │ ├── build.gradle │ │ ├── lib/ │ │ │ └── dalviksystem.jar │ │ ├── proguard-project.txt │ │ ├── project.properties │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ ├── cpp/ │ │ │ ├── Core.cpp │ │ │ ├── HelperJni/ │ │ │ │ ├── HelperJni.cpp │ │ │ │ └── HelperJni.h │ │ │ └── help/ │ │ │ ├── log.h │ │ │ └── nativehelper/ │ │ │ ├── JNIHelp.cpp │ │ │ └── JNIHelp.h │ │ ├── java/ │ │ │ └── com/ │ │ │ ├── example/ │ │ │ │ └── ApiTest/ │ │ │ │ ├── ActivityTestActivity.java │ │ │ │ ├── BaseService.java │ │ │ │ ├── Binder1.aidl │ │ │ │ ├── Binder2.aidl │ │ │ │ ├── BroadcastReceiverTest.java │ │ │ │ ├── ContentProviderTest.java │ │ │ │ ├── ContentProviderTest2.java │ │ │ │ ├── LaunchModeTestActivity.java │ │ │ │ ├── MyActivity.java │ │ │ │ ├── MyContentProvider1.java │ │ │ │ ├── MyContentProvider2.java │ │ │ │ ├── NativeTestActivity.java │ │ │ │ ├── NotificationTest.java │ │ │ │ ├── Service1.java │ │ │ │ ├── Service2.java │ │ │ │ ├── Service3.java │ │ │ │ ├── Service4.java │ │ │ │ ├── ServiceTest1.java │ │ │ │ ├── ServiceTest2.java │ │ │ │ ├── SingleInstanceActivity.java │ │ │ │ ├── SingleTaskActivity.java │ │ │ │ ├── SingleTopActivity.java │ │ │ │ ├── StandardActivity.java │ │ │ │ ├── StaticBroadcastReceiver.java │ │ │ │ └── WebViewTestActivity.java │ │ │ └── morgoo/ │ │ │ └── nativec/ │ │ │ └── NativeCHelper.java │ │ └── res/ │ │ ├── layout/ │ │ │ ├── activity_activity_test.xml │ │ │ ├── activity_launchmode.xml │ │ │ ├── activity_native_test.xml │ │ │ ├── activity_web_view_test.xml │ │ │ ├── broadcast_receiver.xml │ │ │ ├── content_provider.xml │ │ │ ├── main.xml │ │ │ ├── notification_test.xml │ │ │ └── service.xml │ │ ├── values/ │ │ │ ├── dimens.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ ├── values-v11/ │ │ │ └── styles.xml │ │ ├── values-v14/ │ │ │ └── styles.xml │ │ └── values-w820dp/ │ │ └── dimens.xml │ ├── TestPlugin/ │ │ ├── build.gradle │ │ ├── proguard-project.txt │ │ ├── project.properties │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── com/ │ │ │ └── example/ │ │ │ └── TestPlugin/ │ │ │ ├── ApkFragment.java │ │ │ ├── ApkItem.java │ │ │ ├── InstalledFragment.java │ │ │ ├── MainActivity.java │ │ │ └── MyActivity.java │ │ └── res/ │ │ ├── layout/ │ │ │ ├── apk_item.xml │ │ │ └── main.xml │ │ ├── values/ │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ ├── values-v11/ │ │ │ └── styles.xml │ │ └── values-v14/ │ │ └── styles.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradlew │ ├── gradlew.bat │ └── settings.gradle ├── readme.md └── readme_cn.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ /project/Libraries/DroidPlugin/gen /project/Libraries/DroidPlugin/bin /project/Test/ApiTest/gen /project/Test/ApiTest/bin /project/TestPlugin/gen /project/TestPlugin/bin /project/gradle /project/.gradle /project/.idea /project/build /project/local.properties /happy_chinese_new_year_of_monkey *.iml ================================================ FILE: DOC/.gitignore ================================================ .DS_Store ================================================ FILE: DOC/hejunlin/LICENSE ================================================ GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. ================================================ FILE: DOC/hejunlin/插件占坑,四大组件动态注册前奏(一) 系统Activity的启动流程.md ================================================ 前言:为什么要了解系统Activity,Service,,BroadCastReceiver,ContentProvider的启动流程,这是一个对于即将理解插件中的四大组件动态注册,占坑的前提,如果不了解的话,那么很难了解插件hook哪些东西,又是如何骗过AMS来启动Activity,Service,BroadCastReceiver,ContentProvider? 本节主要记录系统Activity的启动流程: 先看下时序图: ![这里写图片描述](http://img.blog.csdn.net/20160812104759495) ![这里写图片描述](http://img.blog.csdn.net/20160812104813573) //接上面两张图: ![这里写图片描述](http://img.blog.csdn.net/20160812104827618) 和插件相关主要在第三张图, 先看H类:这是ActivityThread中的内部类,此类继承自Handler,重写handleMeaage方法, 将msg转换成一个ActivityClientRecord对象,调用ActivityThread类成员函数getPackageInfoNoCheck来获得一个LoadApk对象,并且保存在ActivityClientRecord对象r的成员变量packageInfo中。 ActivityThread有一个该类型的成员变量mH。scheduleLaunchActivity()里会发送一个LAUNCH_ACTIVITY类型的消息,该消息被mH处理并调用handleLaunchActivity()。 handleLaunchActivity(r,main)来启动ActivityClientRecord对象t所描述的一个Activity组件。即MyActivity ``` private class H extends Handler { public static final int LAUNCH_ACTIVITY = 100; public static final int PAUSE_ACTIVITY = 101; public static final int PAUSE_ACTIVITY_FINISHING= 102; public static final int STOP_ACTIVITY_SHOW = 103; public static final int STOP_ACTIVITY_HIDE = 104; public static final int SHOW_WINDOW = 105; public static final int HIDE_WINDOW = 106; public static final int RESUME_ACTIVITY = 107; public static final int SEND_RESULT = 108; public static final int DESTROY_ACTIVITY = 109; public static final int BIND_APPLICATION = 110; public static final int EXIT_APPLICATION = 111; public static final int NEW_INTENT = 112; public static final int RECEIVER = 113; public static final int CREATE_SERVICE = 114; public static final int SERVICE_ARGS = 115; public static final int STOP_SERVICE = 116; public static final int REQUEST_THUMBNAIL = 117; public static final int CONFIGURATION_CHANGED = 118; public static final int CLEAN_UP_CONTEXT = 119; public static final int GC_WHEN_IDLE = 120; public static final int BIND_SERVICE = 121; public static final int UNBIND_SERVICE = 122; public static final int DUMP_SERVICE = 123; public static final int LOW_MEMORY = 124; public static final int ACTIVITY_CONFIGURATION_CHANGED = 125; public static final int RELAUNCH_ACTIVITY = 126; public static final int PROFILER_CONTROL = 127; public static final int CREATE_BACKUP_AGENT = 128; public static final int DESTROY_BACKUP_AGENT = 129; public static final int SUICIDE = 130; public static final int REMOVE_PROVIDER = 131; public static final int ENABLE_JIT = 132; public static final int DISPATCH_PACKAGE_BROADCAST = 133; public static final int SCHEDULE_CRASH = 134; public static final int DUMP_HEAP = 135; public static final int DUMP_ACTIVITY = 136; public static final int SLEEPING = 137; public static final int SET_CORE_SETTINGS = 138; public static final int UPDATE_PACKAGE_COMPATIBILITY_INFO = 139; public static final int TRIM_MEMORY = 140; public static final int DUMP_PROVIDER = 141; public static final int UNSTABLE_PROVIDER_DIED = 142; public static final int REQUEST_ASSIST_CONTEXT_EXTRAS = 143; public static final int TRANSLUCENT_CONVERSION_COMPLETE = 144; public static final int INSTALL_PROVIDER = 145; String codeToString(int code) { if (DEBUG_MESSAGES) { switch (code) { case LAUNCH_ACTIVITY: return "LAUNCH_ACTIVITY"; case PAUSE_ACTIVITY: return "PAUSE_ACTIVITY"; case PAUSE_ACTIVITY_FINISHING: return "PAUSE_ACTIVITY_FINISHING"; case STOP_ACTIVITY_SHOW: return "STOP_ACTIVITY_SHOW"; case STOP_ACTIVITY_HIDE: return "STOP_ACTIVITY_HIDE"; case SHOW_WINDOW: return "SHOW_WINDOW"; case HIDE_WINDOW: return "HIDE_WINDOW"; case RESUME_ACTIVITY: return "RESUME_ACTIVITY"; case SEND_RESULT: return "SEND_RESULT"; case DESTROY_ACTIVITY: return "DESTROY_ACTIVITY"; case BIND_APPLICATION: return "BIND_APPLICATION"; case EXIT_APPLICATION: return "EXIT_APPLICATION"; case NEW_INTENT: return "NEW_INTENT"; case RECEIVER: return "RECEIVER"; case CREATE_SERVICE: return "CREATE_SERVICE"; case SERVICE_ARGS: return "SERVICE_ARGS"; case STOP_SERVICE: return "STOP_SERVICE"; case REQUEST_THUMBNAIL: return "REQUEST_THUMBNAIL"; case CONFIGURATION_CHANGED: return "CONFIGURATION_CHANGED"; case CLEAN_UP_CONTEXT: return "CLEAN_UP_CONTEXT"; case GC_WHEN_IDLE: return "GC_WHEN_IDLE"; case BIND_SERVICE: return "BIND_SERVICE"; case UNBIND_SERVICE: return "UNBIND_SERVICE"; case DUMP_SERVICE: return "DUMP_SERVICE"; case LOW_MEMORY: return "LOW_MEMORY"; case ACTIVITY_CONFIGURATION_CHANGED: return "ACTIVITY_CONFIGURATION_CHANGED"; case RELAUNCH_ACTIVITY: return "RELAUNCH_ACTIVITY"; case PROFILER_CONTROL: return "PROFILER_CONTROL"; case CREATE_BACKUP_AGENT: return "CREATE_BACKUP_AGENT"; case DESTROY_BACKUP_AGENT: return "DESTROY_BACKUP_AGENT"; case SUICIDE: return "SUICIDE"; case REMOVE_PROVIDER: return "REMOVE_PROVIDER"; case ENABLE_JIT: return "ENABLE_JIT"; case DISPATCH_PACKAGE_BROADCAST: return "DISPATCH_PACKAGE_BROADCAST"; case SCHEDULE_CRASH: return "SCHEDULE_CRASH"; case DUMP_HEAP: return "DUMP_HEAP"; case DUMP_ACTIVITY: return "DUMP_ACTIVITY"; case SLEEPING: return "SLEEPING"; case SET_CORE_SETTINGS: return "SET_CORE_SETTINGS"; case UPDATE_PACKAGE_COMPATIBILITY_INFO: return "UPDATE_PACKAGE_COMPATIBILITY_INFO"; case TRIM_MEMORY: return "TRIM_MEMORY"; case DUMP_PROVIDER: return "DUMP_PROVIDER"; case UNSTABLE_PROVIDER_DIED: return "UNSTABLE_PROVIDER_DIED"; case REQUEST_ASSIST_CONTEXT_EXTRAS: return "REQUEST_ASSIST_CONTEXT_EXTRAS"; case TRANSLUCENT_CONVERSION_COMPLETE: return "TRANSLUCENT_CONVERSION_COMPLETE"; case INSTALL_PROVIDER: return "INSTALL_PROVIDER"; } } return Integer.toString(code); } public void handleMessage(Message msg) { if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what)); switch (msg.what) { case LAUNCH_ACTIVITY: { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart"); ActivityClientRecord r = (ActivityClientRecord)msg.obj; r.packageInfo = getPackageInfoNoCheck( r.activityInfo.applicationInfo, r.compatInfo); handleLaunchActivity(r, null); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } break; case RELAUNCH_ACTIVITY: { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart"); ActivityClientRecord r = (ActivityClientRecord)msg.obj; handleRelaunchActivity(r); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } break; case PAUSE_ACTIVITY: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause"); handlePauseActivity((IBinder)msg.obj, false, msg.arg1 != 0, msg.arg2); maybeSnapshot(); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; case PAUSE_ACTIVITY_FINISHING: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause"); handlePauseActivity((IBinder)msg.obj, true, msg.arg1 != 0, msg.arg2); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; case STOP_ACTIVITY_SHOW: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStop"); handleStopActivity((IBinder)msg.obj, true, msg.arg2); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; case STOP_ACTIVITY_HIDE: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStop"); handleStopActivity((IBinder)msg.obj, false, msg.arg2); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; case SHOW_WINDOW: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityShowWindow"); handleWindowVisibility((IBinder)msg.obj, true); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; case HIDE_WINDOW: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityHideWindow"); handleWindowVisibility((IBinder)msg.obj, false); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; case RESUME_ACTIVITY: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityResume"); handleResumeActivity((IBinder)msg.obj, true, msg.arg1 != 0, true); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; case SEND_RESULT: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityDeliverResult"); handleSendResult((ResultData)msg.obj); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; case DESTROY_ACTIVITY: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityDestroy"); handleDestroyActivity((IBinder)msg.obj, msg.arg1 != 0, msg.arg2, false); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; case BIND_APPLICATION: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication"); AppBindData data = (AppBindData)msg.obj; handleBindApplication(data); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; case EXIT_APPLICATION: if (mInitialApplication != null) { mInitialApplication.onTerminate(); } Looper.myLooper().quit(); break; case NEW_INTENT: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityNewIntent"); handleNewIntent((NewIntentData)msg.obj); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; case RECEIVER: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastReceiveComp"); handleReceiver((ReceiverData)msg.obj); maybeSnapshot(); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; case CREATE_SERVICE: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceCreate"); handleCreateService((CreateServiceData)msg.obj); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; case BIND_SERVICE: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceBind"); handleBindService((BindServiceData)msg.obj); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; case UNBIND_SERVICE: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceUnbind"); handleUnbindService((BindServiceData)msg.obj); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; case SERVICE_ARGS: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceStart"); handleServiceArgs((ServiceArgsData)msg.obj); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; case STOP_SERVICE: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceStop"); handleStopService((IBinder)msg.obj); maybeSnapshot(); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; case REQUEST_THUMBNAIL: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "requestThumbnail"); handleRequestThumbnail((IBinder)msg.obj); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; case CONFIGURATION_CHANGED: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "configChanged"); mCurDefaultDisplayDpi = ((Configuration)msg.obj).densityDpi; handleConfigurationChanged((Configuration)msg.obj, null); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; case CLEAN_UP_CONTEXT: ContextCleanupInfo cci = (ContextCleanupInfo)msg.obj; cci.context.performFinalCleanup(cci.who, cci.what); break; case GC_WHEN_IDLE: scheduleGcIdler(); break; case DUMP_SERVICE: handleDumpService((DumpComponentInfo)msg.obj); break; case LOW_MEMORY: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "lowMemory"); handleLowMemory(); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; case ACTIVITY_CONFIGURATION_CHANGED: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityConfigChanged"); handleActivityConfigurationChanged((IBinder)msg.obj); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; case PROFILER_CONTROL: handleProfilerControl(msg.arg1 != 0, (ProfilerControlData)msg.obj, msg.arg2); break; case CREATE_BACKUP_AGENT: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backupCreateAgent"); handleCreateBackupAgent((CreateBackupAgentData)msg.obj); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; case DESTROY_BACKUP_AGENT: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backupDestroyAgent"); handleDestroyBackupAgent((CreateBackupAgentData)msg.obj); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; case SUICIDE: Process.killProcess(Process.myPid()); break; case REMOVE_PROVIDER: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "providerRemove"); completeRemoveProvider((ProviderRefCount)msg.obj); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; case ENABLE_JIT: ensureJitEnabled(); break; case DISPATCH_PACKAGE_BROADCAST: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastPackage"); handleDispatchPackageBroadcast(msg.arg1, (String[])msg.obj); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; case SCHEDULE_CRASH: throw new RemoteServiceException((String)msg.obj); case DUMP_HEAP: handleDumpHeap(msg.arg1 != 0, (DumpHeapData)msg.obj); break; case DUMP_ACTIVITY: handleDumpActivity((DumpComponentInfo)msg.obj); break; case DUMP_PROVIDER: handleDumpProvider((DumpComponentInfo)msg.obj); break; case SLEEPING: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "sleeping"); handleSleeping((IBinder)msg.obj, msg.arg1 != 0); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; case SET_CORE_SETTINGS: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "setCoreSettings"); handleSetCoreSettings((Bundle) msg.obj); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; case UPDATE_PACKAGE_COMPATIBILITY_INFO: handleUpdatePackageCompatibilityInfo((UpdateCompatibilityData)msg.obj); break; case TRIM_MEMORY: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "trimMemory"); handleTrimMemory(msg.arg1); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; case UNSTABLE_PROVIDER_DIED: handleUnstableProviderDied((IBinder)msg.obj, false); break; case REQUEST_ASSIST_CONTEXT_EXTRAS: handleRequestAssistContextExtras((RequestAssistContextExtras)msg.obj); break; case TRANSLUCENT_CONVERSION_COMPLETE: handleTranslucentConversionComplete((IBinder)msg.obj, msg.arg1 == 1); break; case INSTALL_PROVIDER: handleInstallProvider((ProviderInfo) msg.obj); break; } if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what)); } ``` 再看handlePeformLanchActivity方法, 发送一个已中止状态的进程间通信请求给ActivityThread. ![这里写图片描述](http://img.blog.csdn.net/20160812111503608) 以MyActivity继承为例(假定是我们app的第一个Activity),MyActivity被启动,共要经历5个大的步骤: MyActivity组件是由Launcher组件来启动的,而Launcher组件又是Activity管理服务ActivityManagerService来启动的。 MyActivity组件、Launcher组件和ActivityManagerService组件分别运行在不同的进程中,MyActivity启动过程就涉及了 三个进程,这三个进程通过Binder进程间通信来完成。 Launcher组件启动MyActivity的过程如下: - **Lanuncher组件向ActivityManagerService发送一个启动MyActivity组件的进程间通信请求** - **ActivityManagerService首先将要启动的MyActivity组件的信息保存下来,然后再向Launcher组件发送一个中止状态的进程间通信请求** - **Launcher组件进入到中止状态后,就会向ActivityManagerService发送一个已中止状态的进程间通信请求,以便ActivityManagerService可以继续执行MyActivity组件的操作** - **ActivityManagerService发现用来运行MyActivity组件的应用程序不存在,因此,它就会启动一个新的应用程序进程** - **新的应用程序进程启动完成后,就会向ActivityManagerService发送一个启动完成的进程间通信请求,以便ActivityManagerService可以继续执行启动MyActivity组件的操作。** - **ActivityManagerService将第2步保存下来的MyActivity组件的信息发送给第4步创建的应用程序进程,以便它可以将yActivity组件启动起来。** ================================================ FILE: DOC/hejunlin/插件占坑,四大组件动态注册前奏(三) 系统BroadCast的注册发送流程.md ================================================ 前言:为什么要了解系统Activity,Service,BroadCastReceiver,ContentProvider的启动流程,这是一个对于即将理解插件中的四大组件动态注册,占坑的前提,如果不了解的话,那么很难了解插件hook哪些东西,又是如何骗过AMS来启动Activity,Service,BroadCastReceiver,ContentProvider? 本节主要记录系统BroadCastReceiver的注册,发送流程: 在了解注册,发送之前,先想一个问题:为什么有广播? - **1、广播是一种组件之间传递的方式,这些组件可以运行在同一进程中,也可以运行在不同的进程中。** - **2、广播的机制是建立在Binder进程间通信基础上的。在Binder进程间通信,Client组件在和Service组件进行通信之前,必须要先获取它的一个代理对象,即Client组件事先要知道Service组件的存在。然而,在广播机制中,广播发送者事先不需要知道广播接收者是存在的,这样就可以降低广播接收者和发送者之间的耦合度,得到模块分离。** - **3、广播机制是一种基于消息发布和订阅的事件驱动模型,广播发送者负责发布消息,而广播接收者需要先订阅消息,然后才能收到消息。** - **4、广播机制存在一个注册中心,它是由ActivityManagerService来担当的,广播接收者订阅消息的表现形式就是将自己注册到ActivieyManagerService中,并且指定要接收的广播的类型,当广播发送者向广播接收者发送一个广播时,这个广播首先发送到ActivityManagerService,然后ActivityManagerService可根据这个类型找到相应的广播接收者,最后将这个广播发送给它们处理。** - **5、广播接收者的注册分为静态注册和动态注册,广播的发送方式分为有序和无序两种** - **6、广播的生命周期,从对象调用它开始,到onReceiver方法执行完成之后结束。另外,每次广播被接收后会重新创建BroadcastReceiver对象,并在onReceiver方法中执行完就销毁,如果BroadcastReceiver的onReceiver方法中不能在10秒内执行完成,Android会出现ANR异常。所以不要在BroadcastReceiver的onReceiver方法中执行耗时的操作。** - **7、如果需要在BroadcastReceiver中执行耗时的操作,可以通过Intent启动Service来完成。但不能绑定Service。特别是,您可能无法从一个BroadcastReceiver中显示一个对话框,或绑定到服务。对于前者,则应该使用NotificationManager的API。对于后者,你可以使用Context.startService()来启动一个Service。** ##BroadCastReceiver注册流程 先看一张时序图: ![这里写图片描述](http://img.blog.csdn.net/20160814131305362) 首先得有一个广播类,以下叫MyBroadCastReceiver: ![这里写图片描述](http://img.blog.csdn.net/20160814131656986) 有一个TestBroadCastActivity的类: ![这里写图片描述](http://img.blog.csdn.net/20160814131516594) 通过动态注册的方式注册了一个MyBroadCastReceiver广播类,以上就是一个广播的注册过程。 ##BroadCastReceiver发送流程 先看下面两张时序图(ps:太长,只能分开截图):发送过程远比注册过程复杂的多 ![这里写图片描述](http://img.blog.csdn.net/20160814132904100) ![这里写图片描述](http://img.blog.csdn.net/20160814132931584) 广播的发送过程: - **1、广播发送者,即一个Activity组件或者一个service组件,将一个特定类型的广播发送给ActivityManagerService;** - 2 、ActivityManagerService接收到一个广播之后,首先找到与这个广播对应的广播接收者,然后将他们添加一个广播调度队列中,最后向 ActivityManagerService发送一个类型为BROADCAST_INTENT_MSG的值,这时广播对发送者来说,一个广播发送过程就完成了; - **3、当发送到ActivityManagerService所运行的线程的消息队列中BROADCAST_INTENT_MSG消息被处理时,ActivityManagerService就会从广播调度队列中找到需要的广播的接收者,并且将对应的广播发送给它们所运行的应用程序进程。** - **4、广播接收者所运行在的应用程序进程接收到ActivityManagerService发送过来的广播之后,并不是直接将接受到的广播分发给广播接收者来处理,而是将接收到的广播封装成一个消息,并且发送到主线程的消息队列中,当这个消息被处理时,应用程序进程才会将它所描述的广播发送给相应的广播接收者处理。** - **5、ActivityManagerService向一个应用程序发送一个广播时,采用的是异步进程间通信,Binder驱动结构体中binder_node时提到,发送给一一个Binder实体对象的所有异步事务都是保存在一个异步事务队列中的,由于保存在一个异步事务队列中的异步事务在同一时刻只有一个会得到处理,即只有位于队列头部的异步事务才会得到处理,因为,ActivityManagerService就可以保证它发送给同一个应用程序的所有都可以按照发送顺序来串行地接收和处理。** 以上就是广播的注册发送过程,ContentProvider不再分析,下篇将正式进入插件占坑,四大组件动态化注册分析。 ================================================ FILE: DOC/hejunlin/插件占坑,四大组件动态注册前奏(二) 系统Service的启动流程.md ================================================ 前言:为什么要了解系统Activity,Service,BroadCastReceiver,ContentProvider的启动流程,这是一个对于即将理解插件中的四大组件动态注册,占坑的前提,如果不了解的话,那么很难了解插件hook哪些东西,又是如何骗过AMS来启动Activity,Service,BroadCastReceiver,ContentProvider? 本节主要记录系统Service的启动流程: 先看时序图: ![这里写图片描述](http://img.blog.csdn.net/20160814112136868) 与Activity组件的启动方式很像,Service启动分为隐式和显式两种,对于隐式启动Service组件来说,我们只需要知道它的组件名称,而对于显示的Service组件来说,需要知道它的类名称。 以一个后台播放音乐场景来说明: 通过实现一个MyService来实现一个异步任务来播放后台音乐 MyActivity.java ![这里写图片描述](http://img.blog.csdn.net/20160814124249549) ![这里写图片描述](http://img.blog.csdn.net/20160814121252748) MyService.java ![这里写图片描述](http://img.blog.csdn.net/20160814121329312) MyActivity组件绑定MyService的过程: - **1.MyActivity向ActivityManagerService发送一个绑定CounterService组件的进程间通信请求。** - **2.ActivityManagerService发现用来运行MyService组件的应用程序进程即为MyActivity组件所运行的应用程序进程,因此,它就直接通知应用程序进程将MyService启动起来。** - **3.MyService组件启动起来后,ActivityManagerService就请求它返回一个Binder本地对象,以便MyActivity可以通过这个Binder本地对象来和MyService组件建立连接。** - **4.ActivityManagerService将前面从MyService组件中获得的一个Binder本地对象发送给MyActivity组件。** - **5.MyActivity组件获得了ActivityManagerService给它发送的Binder本地对象之后,就可以通过它来获得MyService组件的一个访问接口,MyActivity组件之后就可以通过这个访问接口来使用MyService组件所提供的服务,这就相当于将MyService绑定在了MyActivity中了。** 那service在系统中绑定是如何的呢? 同样看下时序图: ![这里写图片描述](http://img.blog.csdn.net/20160814121642240) ![这里写图片描述](http://img.blog.csdn.net/20160814121710224) 客户端组件启动Server组件的过程: - **1.Client组件启动ActivityManagerService发送一个启动Server组件的进程间通信请求。** - 2.ActivityManagerService发现用来运行Server组件的应用程序进程不存在,因此,它就会首先将Server组件的信息保存下来,接着再 创建一个新的应用程序进程 - **3.新的应用程序启动完成之后,就会向ActivityManagerService发送一个启动完成进程间通信请求,以便ActivityManagerServices可以继续执行启动Service组件的操作。** - **4.ActitivtyManagerService将2中保存下来的Service组件信息发送级第2步创建的应用程序进程,以便它可以将Server组件启动起来。** ================================================ FILE: DOC/hejunlin/插件开发之360 DroidPlugin源码分析(一)初识.md ================================================ 插件开发之360 DroidPlugin源码分析(一)初识 ###DroidPlugin的是什么? 一种新的插件机制,一种免安装的运行机制,是一个沙箱(但是不完全的沙箱。就是对于使用者来说,并不知道他会把apk怎么样), 是模块化的基础。 ###DroidPlugin的缺点是什么? - a.通知栏限制(无法在插件中发送具有自定义资源的Notification,例如: 1. 带自定义RemoteLayout的Notification 2. 图标通过R.drawable.XXX指定的通知(插件系统会自动将其转化为Bitmap) - b.安全性担忧(可以修改,hook一些重要信息) - c.机型适配(不是所有机器上都能行,因为大量用反射相关,如果rom厂商深度定制了framework层,反射的方法或者类不在,容易插件运用失败) - d. 需要预先注册权限(在Library中申请了原生系统所有的权限) - e. 无法在插件中注册一些具有特殊Intent Filter的Service、Activity、BroadcastReceiver、ContentProvider等组件以供Android系统、已经安装的其他APP调用。 - f. 缺乏对Native层的Hook,对某些带native代码的apk支持不好,可能无法运行。比如一部分游戏无法当作插件运行。 ###DroidPlugin的特点是什么? - a.免安装(就是如果只要从网上下载一个apk,不用安装apk,在插件机制下,就能运行) - b.无需修改源码(因为大量反射,代理,Binder相关,这些足以骗过framework层) - c.二进制级别隔离 - d.插件之间可以相互调用 - e.解除耦合 - f.静默安装,就是前面说的不用安装,就可在插件机制中运行apk - g.崩溃隔离,插件崩溃,对主应用来说,不会有明显影响 - h.还原插件自己的多进程机制,适配性 - i.模块隔离,如可以把UI和控制逻辑进行隔离,控制逻辑可用插件化的方式 ###官方说明: - 支持Androd 2.3以上系统 - 插件APK完全不需做任何修改,可以独立安装运行、也可以做插件运行。要以插件模式运行某个APK,你无需重新编译、无需知道其源码。 - 插件的四大组件完全不需要在Host程序中注册,支持Service、Activity、BroadcastReceiver、ContentProvider四大组件 - 插件之间、Host程序与插件之间会互相认为对方已经"安装"在系统上了。 - API低侵入性:极少的API。HOST程序只是需要一行代码即可集成Droid Plugin - 超强隔离:插件之间、插件与Host之间完全的代码级别的隔离:不能互相调用对方的代码。通讯只能使用Android系统级别的通讯方法。 - 支持所有系统API - 资源完全隔离:插件之间、与Host之间实现了资源完全隔离,不会出现资源窜用的情况。 - 实现了进程管理,插件的空进程会被及时回收,占用内存低。 - 插件的静态广播会被当作动态处理,如果插件没有运行(即没有插件进程运行),其静态广播也永远不会被触发。 ###DroidPlugin的的基本原理是什么? - a.共享进程:为android提供一个进程运行多个apk的机制,通过API欺骗机制瞒过系统 - b.占坑:通过预先占坑的方式实现不用在manifest注册,通过一带多的方式实现服务管理 - c.Hook机制:动态代理实现函数hook,Binder代理绕过部分系统服务限制,IO重定向(先获取原始Object-->Read,然后动态代理Hook Object后-->Write回去,达到瞒天过海的目的) 插件Host的程序架构: ![这里写图片描述](http://img.blog.csdn.net/20160804222350965?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 下一篇开始分析基本原理中的Hook机制 --- ================================================ FILE: DOC/hejunlin/插件开发之360 DroidPlugin源码分析(三)Binder代理.md ================================================ - **Hook机制中Binder代理类关系图** - **Hook机制中Binder代理时序图** - **MyServiceManager** - **ServiceManagerCacheBinderHook** - **ServiceManagerBinderHook** - **BinderHook** ##Hook机制中Binder代理类关系图 ![这里写图片描述](http://img.blog.csdn.net/20160806093432608) ##Hook机制中Binder代理时序图 ![这里写图片描述](http://img.blog.csdn.net/20160806195618775) ![这里写图片描述](http://img.blog.csdn.net/20160806195645164) ##MyServiceManager ![这里写图片描述](http://img.blog.csdn.net/20160806093657469) - **mOriginServiceCache:这里存储的是原始的service cache。每个ActivityThread在bindApplication()的时候,会从ServiceManager那边获得一个service cache(可以减少和Binder代理之间通信,系统Binder是一个专门的BinderProxy和把上层的service和Binder driver进行IPC),每次要和某个service通信时,会先检查这个cache里有没有代理对象,如果有的话就直接用,不需要再和ServiceManager进行一次binder交互了。** - **mProxiedServiceCache:这里存储的就是service cache的代理对象了,因为我们要hook这些binder和上层(serviceConnection时会转成IBinder接口)调用,所以必须把service cache也替换成我们的代理对象,每次调用都会走进ServiceManagerCacheBinderHook对象的invoke()方法。** - **mProxiedObjCache:这里存储的是所有的proxyservice Object,那原始的service对象放在哪里呢?其实是在BinderHook的mOldObj里。** ##ServiceManagerCacheBinderHook ![这里写图片描述](http://img.blog.csdn.net/20160806201458002) ![这里写图片描述](http://img.blog.csdn.net/20160806201643611) ![这里写图片描述](http://img.blog.csdn.net/20160806201701892) 前面把service cache存起来,下次如果要真正和service进行通信,通过getOriginService()把原始的service cache拿出来用就行了。 ##ServiceManagerBinderHook ![这里写图片描述](http://img.blog.csdn.net/20160806202125416) 这个类继承自ProxyHook,主要是用来hook住getService()和checkService()这两个API。如果这两个API被调用,并且在mProxiedObjCache发现有对应的代理对象,则直接返回这个代理对象。 ##BinderHook ![这里写图片描述](http://img.blog.csdn.net/20160806204805563) ![这里写图片描述](http://img.blog.csdn.net/20160806204917738) 先调用ServiceManagerCacheBinderHook的onInstall()方法更新一下service cache,然后生成一个新的代理对象放到mProxiedObjCache里。这样下次不管是从cache里取,还是直接通过binder调用,就都会返回我们的代理对象。 Binder代理其实在android 系统中也是一个十分重要的角色。 这部分可以从网上搜下相关资料。360 plugin 的Binder代理就是借鉴了系统的Binder相关。 ================================================ FILE: DOC/hejunlin/插件开发之360 DroidPlugin源码分析(二)Hook机制.md ================================================ 前言:新插件的开发,可以说是为插件开发者带来了福音,虽然还很多坑要填补,对于这款牛逼的插件机制,一直想找个时间分析和总结下它的code,话不多说,直接入正题,本文是分析../hook/handle及../hook/proxy下代码,../hook/binder单独分析 - **Hook机制的包结构关系** - **Hook机制的类图关系** - **Hook机制的时序图关系** - **Manifest权制申请** - **基类Hook做了什么?** - **HookedMethodHandler** - **基类BaseHookHandle和Hook有什么关系?** - **ProxyHook能干什么?** - **实例-如何hook IPackageManager** ##Hook机制的包结构关系 ![这里写图片描述](http://img.blog.csdn.net/20160804225345196) ##Hook机制类图关系 ![这里写图片描述](http://img.blog.csdn.net/20160804223440825) 首先定义了一个基类Hook,抽象类,外部可以通过setEnable()方法来使能否hook。声明了onInstall和onUnInstall及相关的方法,子类可以覆盖这些方法完成相应的车间机床,这里相当于提供一个车间,机床上的具体操作什么由子类去自己实现。 ##Hook机制的时序图关系 ![这里写图片描述](http://img.blog.csdn.net/20160804231948003) ##Manifest权限申请 插件管理服务类声明: ![这里写图片描述](http://img.blog.csdn.net/20160804223958687) 权限申请: ![这里写图片描述](http://img.blog.csdn.net/20160804224026000) ##基类Hook做了什么? ![这里写图片描述](http://img.blog.csdn.net/20160804224240240) ##ProxyHook ![这里写图片描述](http://img.blog.csdn.net/20160804224830919) ProxyHook继承自Hook,实现了InvocationHandler接口。它有一个setOldObj()方法,用来保存原始对象。新的代理对象可以看到在代码中是如何实现的(动态代理) ##BaseHookHandle ![这里写图片描述](http://img.blog.csdn.net/20160804224623385) ![这里写图片描述](http://img.blog.csdn.net/20160804224710183) 接上面ProxyHook中的invoke()方法,mHookHandles是一个BaseHookHandle对象,内部包含了一个Map,可以根据API名映射到对应对应的HookedMethodHandler对象。这个Map由其子类IXXXHookHandle在初始化的时候进行填充。 紧接着调用HookedMethodHandler的doHookInner()方法: ##HookedMethodHandler ![这里写图片描述](http://img.blog.csdn.net/20160804224440227) ##ReplaceCallingPackageHookedMethodHandler ![这里写图片描述](http://img.blog.csdn.net/20160804232212379) ##IO重定向 ![这里写图片描述](http://img.blog.csdn.net/20160804225003968) ##递归遍历 ![这里写图片描述](http://img.blog.csdn.net/20160804225111866) ##以IPackageManager为例 IPackageManagerHook:Hook所有IPackageManager的方法 IActivityManagerHookHandle:安装所有被Hook的方法的处理对象,加入到Map中 IPackageManagerHandle.checkSignatures:这是一个内部类,继承HookedMethodHandler, 专门校验签名的。以此,还有各种各样的PackageManger原生中的方法,在这都变成了一个内部类继承了HookedMethodHandler.上图: IPackageManagerHookHandle: ![这里写图片描述](http://img.blog.csdn.net/20160804232822115) ================================================ FILE: DOC/hejunlin/插件开发之360 DroidPlugin源码分析(五)Service预注册占坑.md ================================================ 在了解系统的activity,service,broadcastReceiver的启动过程后,今天将分析下360 DroidPlugin是如何预注册占坑的?本篇文章主要分析Service预注册占坑,Service占了坑后又是什么时候开始瞒天过海欺骗AMS的?先看下Agenda: - **AndroidManifest.xml中概览** - **Service中关键方法被hook时机** - **startService被hook** - **瞒天过海流程图** - **认识ServiceManager** ##AndroidMainfest.xml中概览 ![这里写图片描述](http://img.blog.csdn.net/20160821023438578) android:process=":PluginP02" 表示自定义一个名为PluginP02的进程,该属性一旦设置, 那么App启动之后, Service肯定会以远程服务方式启动, ---- 可通过adb shell ps 就可以看到有一个独立的进程启动了. 项目 Package为 com.morgoo.droidplugin , 并设置 android:process=":PluginP02", 那么App运行时, 通过 adb shell ps | grep com.morgoo.droidplugin , 可以看到有两个进程: ![这里写图片描述](http://img.blog.csdn.net/20160821025123414) ##startService被hook IActivityManagerHookHandle.startService方法 ![这里写图片描述](http://img.blog.csdn.net/20160821025156117) replaceFirstServiceIntentOfArgs() ![这里写图片描述](http://img.blog.csdn.net/20160821025222023) 接下来我们继续回到StartService类中,看下afterInvoke方法 ![这里写图片描述](http://img.blog.csdn.net/20160821025241211) stopService,bindService,unbindService,setServiceForeground都是类似逻辑,可从源码中证实。 在前一篇[《插件开发之360 DroidPlugin源码分析(四)Activity预注册占坑》](http://blog.csdn.net/hejjunlin/article/details/52258434),我们曾了解到,8个进程中的service在预注册占坑,在AndroidManifest.xml中只有一个,那如果插件要启动多个service怎么办?这是第一个问题,而我们知道所有和服务相关的都要在系统的systemServer进程中进行注册,难道DroidPlugin要逆天的本事?这是第二个问题 我们从代码中发现有一个ServceManager,这和android系统中大管家ServiceManager相差一个i,难道要改大管家的职责?又多了一个疑惑 仔细看了下ServceManager中的代码: ![这里写图片描述](http://img.blog.csdn.net/20160821025303415) ![这里写图片描述](http://img.blog.csdn.net/20160821025323853) ![这里写图片描述](http://img.blog.csdn.net/20160821025349024) 以上代码可总结为:(以虚线分上下两部分) 1.上部分主要是ServceManager单例及有一个handleXXX相关的方法,下部分主要是Service的一些生命周期方法。 2.我们基本可以确定之前问题2和问题3的答案了,没有那么逆天的本事来操作ServiceManager,也不能修改大管家ServiceManager的职责 3.那问题又来了,这个类到底是做啥的?还起一个叫ServceManager的名字, 以hanldeOnTaskRemoveOne方法为例看看: ![这里写图片描述](http://img.blog.csdn.net/20160821025410931) 接着再看下ServceManager中的onBind方法: ![这里写图片描述](http://img.blog.csdn.net/20160821025431244) 然后调到handleOnBindOne方法中,我们前面分析了一个handleOnTaskRemovedOne方法,下面1-7步骤套路都是一样,主要在return时,直接调用service自身的onBind了,这和我们平时在Activity中new Intent,然后把这个intent传到onBind中. ![这里写图片描述](http://img.blog.csdn.net/20160821025448183) 那么问题来了,还要这么一个类倒腾来干啥呢?还记得上面有一处逻辑service为null时,就调用了handleCreateServiceOne,好戏在这里: ![这里写图片描述](http://img.blog.csdn.net/20160821025514512) ![这里写图片描述](http://img.blog.csdn.net/20160821025554495) 看了上面的分析后,我们再去回想第一个问题,那如果插件要启动多个service怎么办?不知道不没有注意到mTokenServices,mNameService,mServiceTaskIds这些成员变量,它们都是Map,key不一样,value全是Service,如个有多个service在插件中启动了,mTokenServices,mNameService,mServiceTaskIds这些成员变量分别掌握了Service的Token,name,及任务id(通过token拿到的)。那岂不是已经在管理这些多个service了。至于附属于这个类起的名字,叫什么都无所谓。 以上可用如下流程图表示: ![这里写图片描述](http://img.blog.csdn.net/20160821033205794) 前面一直有个问题解释的不彻底,就是问题3,ServceManager是否担当了修改系统大管家ServiceManager的职责?接下来,我们就稍微了解下系统大管家ServiceManager是做什么的?(PS: ServiceManager和Binder机制一样,不是一天两个就能研究的清楚的) ##认识ServiceManager Android系统Binder机制的总管是ServiceManager,所有的Server(System Server)都需要向它注册,应用程序需要向其查询相应的服务。平时,我们在studio上的DDMS中调试程序时,下图这个就是ServiceManager 这里以Java层加入ServiceManager及getService为数据流分析一下。 复习一下典型的Binder模式,有利于后面的理解: 1、客户端通过某种方式得到服务器端的代理对象。从客户端角度看来代理对象和他的本地对象没有什么差别。它可以像其他本地对象一样调用其方法,访问其变量。 2、客户端通过调用服务器代理对象的方法向服务器端发送请求。 3、代理对象把用户请求通过Android内核(Linux内核)的Binder驱动发送到服务器进程。 4、服务器进程处理用户请求,并通过Android内核(Linux内核)的Binder驱动返回处理结果给客户端的服务器代理对象。 5、客户端收到服务器端的返回结果。 JAVA层代码分析: ServiceManager.java (frameworks\base\core\java\android\os) 对于xxxManager获取服务端service基本如此用法: 举例: 利用ContextImpl.java中的 public Object getSystemService(String name) ![这里写图片描述](http://img.blog.csdn.net/20160821025616886) 然后再调用:ServiceManager.getService(ServiceName);获取相应的服务端 ![这里写图片描述](http://img.blog.csdn.net/20160821025639136) 知道了客户端获取一个Service的方法之后,我们回到ServiceManager的服务端: ![这里写图片描述](http://img.blog.csdn.net/20160821025700683) BinderInternal.getContextObject() @ BinderInternal.java 是一个native 函数: android_os_BinderInternal_getContextObject @ android_util_Binder.cpp 返回一个 BinderProxy对象保存到类成员mRemote(ServiceManagerProxy类成员) public abstract class ServiceManagerNative extends Binder implements IServiceManager ServiceManagerNative 继承自 Binder 并实现了 IServiceManager 接口,利用 asInterface()则提供一个 ServiceManagerProxy 代理对象使用 class ServiceManagerProxy implements IServiceManager 定义了类ServiceManagerProxy(代理),ServiceManagerProxy继承自IServiceManager,并实现了其声明的操作函数,只会被ServiceManagerNative创建,它实现了IServiceManager的接口,IServiceManager提供了getService和addService两个成员函数来管理系统中的Service。 ![这里写图片描述](http://img.blog.csdn.net/20160821025721230) 上面代码总结如下:Java层先利用Parcel对象将数据进行序列化,然后利用transact将数据传给binder驱动,就是上面mRemote.transact,mRemote在JNI层是一个叫BpBinder的对象: JNI层代码分析: android_util_Binder.cpp ![这里写图片描述](http://img.blog.csdn.net/20160821025737074) 每当我们利用BpBinder的transact()函数发起一次跨进程事务时,其内部其实是调用IPCThreadState对象的transact()。BpBinder的transact()代码如下: ![这里写图片描述](http://img.blog.csdn.net/20160821025803184) 到此,不再向下深究,具体想了解可以看Binder机制及IPC相关内容。 所以,到这把第2个问题彻底弄明白了,ServiceManager和ServiceManager根本不是一回事。到次,Service的瞒天过海得以实现,接着,就是介绍时候说的那样无需修改源码,多进程,多service等功能。另外ContentProvider及BroadCast原理是类似,不再进行分析。 下篇将开始分析包管理(宿主插件和插件包名有什么规则),APK解析(为什么可以免安装),进程管理(能确保隐藏起来,不会在process轻易被kill) ================================================ FILE: DOC/hejunlin/插件开发之360 DroidPlugin源码分析(四)Activity预注册占坑.md ================================================ 在了解系统的activity,service,broadcastReceiver的启动过程后,今天将分析下360 DroidPlugin是如何预注册占坑的?本篇文章主要分析Activity预注册占坑,Activity占了坑后又是什么时候开始瞒天过海欺骗AMS的?先看下Agenda: - **AndroidMainfest.xml中概览** - **Activity中关键方法被hook时机** - **startActivity被hook** - **handelPerformActivity被hook** - **Activity预注册占坑整体流程图** - **瞒天过海,冒充真实身份,欺骗AMS** ##AndroidMainfest.xml中概览 我们知道所有能用的四大组件都要在Manifest中注册声明,第一步,先看AndroidManifest.xml,虽然在说hook机制时,也有提及过,但是毕竟没细致分析,话不多说,看代码上图: ![这里写图片描述](http://img.blog.csdn.net/20160820022434578) 然后看下各个属性的意思 ![这里写图片描述](http://img.blog.csdn.net/20160820022530891) 表示一个activity1原来属于task1,但是如果task2启动起来的话,activity1可能不再属于task1了,转而投奔task2去了。 ![这里写图片描述](http://img.blog.csdn.net/20160820022615141) 功能:启动硬件加速 缺点:占用内存 特点:可以在Application、Activity、Window、View四个级别进行硬件加速控制 从Android3.0(API Level 11)开始,Android 2D渲染管道能够更好的支持硬件加速。硬件加速执行的所有的绘图操作都是使用GPU在View对象的画布上来进行的。因为启用硬件加速会增加资源的需求,因此这样的应用会占用更多的内存。 启用硬件加速的最容易的方法是给整个应用程序都打开全局硬件加速功能。如果应用程序只使用标准的View和Drawable,那么打开全局硬件加速不会导致任何的不良的绘制效果。但是,因为硬件加速并不支持所有的2D图形绘制操作,所以对于那些使用定制的View和绘制调用的应用程序来说,打开全局硬件加速,可以会影响绘制效果。问题通常会出现在对那些不可见的元素进行了异常或错误的像素渲染。为了避免这种问题,Android提供以下级别,以便可选择性的启用或禁止硬件加速: 控制硬件加速,能够用以下级别来控制硬件加速: 1、Application级别 在应用的Android清单文件中,把下列属性添加到元素中,来开启整个应用程序的硬件加速。 2、Activity级别 如果应用程序不能够正确的使用被打开的全局硬件加速,那么也可以对Activity分别进行控制。在元素中使用android:hardwareAccelerated属性,能够启用或禁止Activity级别的硬件加速。以下示例启用全局的硬件加速,但却禁止了一个Activity的硬件加速。 3、Window级别 如果需要更细粒度的控制,就可以使用下列代码来针对给定的窗口来启用硬件加速: 注意:当前不能在Window级别禁止硬件加速。 ![这里写图片描述](http://img.blog.csdn.net/20160820022635297) 4、View级别 能够使用下列代码在运行时针对一个独立的View对象来禁止硬件加速: ![这里写图片描述](http://img.blog.csdn.net/20160820022653567) 注意:当前不能在View级别开启硬件加速。View层除了禁止硬件加速以外,还有其他的功能,更多的相关信息请看本文的“View层”。 ![这里写图片描述](http://img.blog.csdn.net/20160820022712099) ![这里写图片描述](http://img.blog.csdn.net/20160820022731756) 以上就是声明属性的含义说明,作为背景了解即可。重点看下面的分析 ![这里写图片描述](http://img.blog.csdn.net/20160820023211675) Manifest中注册了8个进程,加上主进程共9个 然后第每个进程下面又有26个Activity注册,一个service,一个contentprovider,那么问题来了,搞这么多注册在manifest做什么用?仔细分类:就两类一类是Activity,一类是Dialog,我们知道Dialog是建立在Activity之上的,如果Activity被finish或destory后,就会报出异常:android.view.WindowManager$BadTokenException: Unable to add window — token android.os.BinderProxy@438e7108 is not valid; is your activity running? 附[《插件占坑,四大组件动态注册前奏(一) 系统Activity的启动流程》](http://blog.csdn.net/hejjunlin/article/details/52190050)(也可点击链接过去看详细过程)的activity的启动时序列图: ![这里写图片描述](http://img.blog.csdn.net/20160820023850754) ![这里写图片描述](http://img.blog.csdn.net/20160820023720565) ##Activity中关键方法被hook时机 其中startActivity()和handleLanchActivity()是被DroidPlugin 要欺骗系统的两个主要方法: 第一个方法是最被经常使用的startActivity(),hook机制见[《插件开发之360 DroidPlugin源码分析(二)Hook机制》](http://blog.csdn.net/hejjunlin/article/details/52124397)中分析,主要是通过Java的反射机制替换掉IActivityManager全局对象,具体IActivityManagerHookHandle的onInstall()方法如下: ![这里写图片描述](http://img.blog.csdn.net/20160820024836386) ![这里写图片描述](http://img.blog.csdn.net/20160820024906121) 第二个方法是handleLaunchActivity(),这个方法属于ActivityThread的一个叫做H的内部类,前面讲Activity启动时,已埋下伏笔,可以参考[《插件占坑,四大组件动态注册前奏(一) 系统Activity的启动流程》](http://blog.csdn.net/hejjunlin/article/details/52190050),如果学过中间人攻击协议的话,我们知道,现个通信双方发出消息后,进行消息验证,如果中间人拦截相关协议内容,通过一个代理进行转发出去,从而达到欺骗的目的,这里暂且理解handleLanchActivity被中间人攻击了,当启动handleLanchActivity时,被DroidPlugin hook后,那多人可能会想,你怎么hook住hanleLanchActivity呢?别忘了,我们可是在Manifest占了一堆坑的。要是不好理解,可以参看《插件前奏-android黑科技 hook介绍 :http://blog.csdn.net/hejjunlin/article/details/52091833》,可能更直观些,哪在DroidPlugin中,如何在代码中瞒天过海的呢? 我们可以看下:PluginCallbackHook.java,其中有一个onInstall()方法: ![这里写图片描述](http://img.blog.csdn.net/20160820025146498) 如上代码也有注释,可总结为: 1.DroidPlugin不是完全攻击mH这个内部类,而是把mH的mCallback成员变量攻击了,然后替换成了一个PluginCallback对象,进行消息分发,那么就可以在在PluginCallback的handleMessage()里任意拦截想要分发的消息,来欺骗AMS。经过中间人这么一闹腾,那就能达到控制这个区域的目的了。 最狠的看下面: 上面代码中,有一个mH的mCallback,是被拦截攻击了吧,既然被拦截后又要冒充一个担当mH的mCallback职责相关,(这里暂且夸张的说)那肯定得扒了它身上的某些特性放到冒充的mCallback吧,如身份验证相关之类,所以那原来的mCallback丢了它的身份相关,不就是废了,暂且理解为mCallback为null,实际上有mCallback在未赋值之前,初始化时,本身也是null。然后看下PluginCallback的handleMessage()方法:(ps: PluginCallback简直就是仿照ActivityThread中的H内部类写的,几乎逻辑一样,具体可看系统源码证实) ![这里写图片描述](http://img.blog.csdn.net/20160820025336718) 这里只拦截了一个消息类型为LAUNCH_ACTIVITY(ActivityThread中的H内部类中会有很多消息类型,也包含LAUNCH_ACTIVITY)。mCallback不为null,就是发一些伪装的消息(因为我们刚拦截了mCallback),如果为null,返回false,这样就会会再通过mH调用回真正ActivityThread的handleLaunchActivity()。(ps:系统发的消息没什么卵用时,我们就不去拦截,暂且理解为这样) 以上就是两个方法怎么被hook相关分析,也可参考在Hook机制举例为IPackageManager的验证签名被hook的过程,接下来就是要看hook中做了什么事?附一张Activity预注册占坑整体流程图: ![这里写图片描述](http://img.blog.csdn.net/20160820041934362) 前面我们都是在说DroidPlugin怎么用中间人攻击方式,拦截了startActivity()方法和hanleLanchActivity()方法,接下来要看下拦截了后,怎么把这种身份角色搞变化了(就是一个怎么扮演冒充的角色过程): 1.首先得找一个和真实的人像的角色,这时启用的我们备用人员,也就是事先占的坑stub.ActivityStubxxx。 2.真实的角色身上有某些特定的特性,如爱抽烟,头发乱糟糟的,像Activity中Intent作为一个extra传到另一个Activity时,还有一些自己的lanchmode(启动方式),theme(主题),这些都是Activity的一些特性,所以stub.ActivityStubxxx也得有这些,否则怎么能达到冒充呢,这些我们在代码中早就写好了,所有的那些注册的26个备用人员都是继承ActivityStub,而ActivityStub是直接继承Activity,那还说啥呢,不就是天然的冒充么?说这么多了,直接看代码IActivityManagerHookHandle$startActvity: 这是一个静态内部类,继承ReplaceCallingPackageHookedMethodHandler,重写了beforeInvoke方法,暂且理解为在冒充之前的一些准备工作,如下: ![这里写图片描述](http://img.blog.csdn.net/20160820030006720) 接着再看beforeStartActivity方法: ![这里写图片描述](http://img.blog.csdn.net/20160820030135351) 就是判断了下Activity的启动模式,接着都调入doFinshIt(mRunningXXXActivityList),继续看此方法: ![这里写图片描述](http://img.blog.csdn.net/20160820030246447) STUB_NO_ACTIVITY_MAX_NUM为4,上面方法总结为:runningActivityList(备用activity的list的总数)大于等于3时,就把最早进入activityRecord栈中的那个finish掉,因为坑就那么多,要是都占着不干活,那要它干嘛? ##瞒天过海,冒充真实身份,欺骗AMS 前面这些,还是小打小闹,接下来看一个核心方法,就是beforeInvoke中的doReplaceIntentForStartActivityAPILow方法: ![这里写图片描述](http://img.blog.csdn.net/20160820030850599) 前面我们说了,欺骗了两个方法,上面说的都是startActivity(),接下来看另个一个方法handleLanchActivity(), 这个方法在哪呢?我们前面说过有一个仿照ActivityThread中H内部类的class叫PluginCallBack,对,handleLaunchActivity就是在接到handleMessage中消息类型为LANCH_ACTIVITY时调用的: ![这里写图片描述](http://img.blog.csdn.net/20160820031140005)![这里写图片描述](http://img.blog.csdn.net/20160820031214928)![这里写图片描述](http://img.blog.csdn.net/20160820031234975) 以上就是Activity预注册占坑,并欺骗AMS的过程,下篇分析Service预注册占坑。 如果你觉得好,随手点赞,也是对笔者的肯定,也可以分享此公众号给你更多的人,原创不易 ================================================ FILE: DOC/tianweishu/.gitignore ================================================ .DS_Store ================================================ FILE: DOC/tianweishu/Activity生命周期管理.md ================================================ # Activity生命周期管理 之前的 [Android插件化原理解析][1] 系列文章揭开了Hook机制的神秘面纱,现在我们手握倚天屠龙,那么如何通过这种技术完成插件化方案呢?具体来说,插件中的Activity,Service等组件如何在Android系统上运行起来? 在Java平台要做到动态运行模块、热插拔可以使用`ClassLoader`技术进行动态类加载,比如广泛使用的`OSGi`技术。在Android上当然也可以使用动态加载技术,但是仅仅把类加载进来就足够了吗?`Activity`,`Service`等组件是有生命周期的,它们统一由系统服务`AMS`管理;使用`ClassLoader`可以从插件中创建Activity对象,但是,一个没有生命周期的Activity对象有什么用?所以在Android系统上,仅仅完成动态类加载是不够的;我们需要想办法把我们加载进来的Activity等组件交给系统管理,让`AMS`赋予组件生命周期;这样才算是一个有血有肉的完善的插件化方案。 接下来的系列文章会讲述 DroidPlugin对于Android四大组件的处理方式,我们且看它如何采用Hook技术坑蒙拐骗把系统玩弄于股掌之中,最终赋予Activity,Service等组件生命周期,完成借尸还魂的。 首先,我们来看看DroidPlugin对于`Activity`组件的处理方式。 阅读本文之前,可以先clone一份 [understand-plugin-framework][2],参考此项目的intercept-activity模块。另外,如果对于Hook技术不甚了解,请先查阅我之前的文章: 1. [Hook机制之动态代理][3] 2. [Hook机制之Binder Hook][4] 3. [Hook机制之AMS&PMS][5] ## AndroidManifest.xml的限制 读到这里,或许有部分读者觉得疑惑了,启动Activity不就是一个`startActivity`的事吗,有这么神秘兮兮的? 启动Activity确实非常简单,但是Android却有一个限制:**必须在AndroidManifest.xml中显示声明使用的Activity**;我相信读者肯定会遇到下面这种异常: ```java 03-18 15:29:56.074 20709-20709/com.weishu.intercept_activity.app E/AndroidRuntime﹕ FATAL EXCEPTION: main Process: com.weishu.intercept_activity.app, PID: 20709 android.content.ActivityNotFoundException: Unable to find explicit activity class {com.weishu.intercept_activity.app/com.weishu.intercept_activity.app.TargetActivity}; have you declared this activity in your AndroidManifest.xml? ``` 『必须在AndroidManifest.xml中显示声明使用的Activity』这个硬性要求很大程度上限制了插件系统的发挥:假设我们需要启动一个插件的Activity,插件使用的Activity是无法预知的,这样肯定也不会在Manifest文件中声明;如果插件新添加一个Activity,主程序的AndroidManifest.xml就需要更新;既然双方都需要修改升级,何必要使用插件呢?这已经违背了动态加载的初衷:不修改插件框架而动态扩展功能。 能不能想办法绕过这个限制呢? 束手无策啊,怎么办?借刀杀人偷梁换柱无中生有以逸待劳乘火打劫瞒天过海...等等!偷梁换柱瞒天过海?貌似可以一试。 我们可以耍个障眼法:既然AndroidManifest文件中必须声明,那么我就声明一个(或者有限个)替身Activity好了,当需要启动插件的某个Activity的时候,先让系统以为启动的是AndroidManifest中声明的那个替身,暂时骗过系统;然后到合适的时候又替换回我们需要启动的真正的Activity;所谓瞒天过海,莫过如此! 现在有了方案了,但是该如何做呢?兵书又说,知己知彼百战不殆!如果连Activity的启动过程都不熟悉,怎么完成这个瞒天过海的过程? ## Activity启动过程 启动Activity非常简单,一个`startActivity`就完事了;那么在这个简单调用的背后发生了什么呢?Look the fucking source code! 关于Activity 的启动过程,也不是三言两语能解释清楚的,如果按照源码一步一步走下来,插件化系列文章就不用写了;所以这里我就给出一个大致流程,只列出关键的调用点(以Android 6.0源码为例);如果读者希望更详细的讲解,可以参考老罗的 [ Android应用程序的Activity启动过程简要介绍和学习计划][6] 首先是Activity类的`startActivity`方法: ```java public void startActivity(Intent intent) { startActivity(intent, null); } ``` 跟着这个方法一步一步跟踪,会发现它最后在`startActivityForResult`里面调用了Instrument对象的`execStartActivity`方法;接着在这个函数里面调用了ActivityManagerNative类的`startActivity`方法;这个过程在前文已经反复举例讲解了,我们知道接下来会通过Binder IPC到`AMS`所在进程调用`AMS`的`startActivity`方法;Android系统的组件生命周期管理就是在`AMS`里面完成的,那么在`AMS`里面到底做了什么呢? ActivityManagerService的`startActivity`方法如下: ``` public final int startActivity(IApplicationThread caller, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, String profileFile, ParcelFileDescriptor profileFd, Bundle options) { return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo, resultWho, requestCode, startFlags, profileFile, profileFd, options, UserHandle.getCallingUserId()); } ``` 很简单,直接调用了`startActivityAsUser`这个方法;接着是`ActivityStackSupervisor`类的`startActivityMayWait`方法。这个ActivityStackSupervisor类到底是个啥?如果仔细查阅,低版本的Android源码上是没有这个类的;后来AMS的代码进行了部分重构,关于Activity栈管理的部分单独提取出来成为了`ActivityStackSupervisor`类;好了,继续看代码。 startActivityMayWait这个方法前面对参数进行了一系列处理,我们需要知道的是,在这个方法内部对传进来的Intent进行了解析,并尝试从中取出关于启动Activity的信息。 然后这个方法调用了startActivityLocked方法;在startActivityLocked方法内部进行了一系列重要的检查:比如权限检查,Activity的exported属性检查等等;我们上文所述的,启动没有在Manifestfest中显示声明的Activity抛异常也是这里发生的: ``` if (err == ActivityManager.START_SUCCESS && aInfo == null) { // We couldn't find the specific class specified in the Intent. // Also the end of the line. err = ActivityManager.START_CLASS_NOT_FOUND; } ``` 这里返回ActivityManager.START_CLASS_NOT_FOUND之后,在Instrument的execStartActivity返回之后会检查这个值,然后跑出异常: ``` case ActivityManager.START_CLASS_NOT_FOUND: if (intent instanceof Intent && ((Intent)intent).getComponent() != null) throw new ActivityNotFoundException( "Unable to find explicit activity class " + ((Intent)intent).getComponent().toShortString() + "; have you declared this activity in your AndroidManifest.xml?"); ``` 源码看到这里,我们已经确认了『必须在AndroidManifest.xml中显示声明使用的Activity』的原因;然而这个校检过程发生在`AMS`所在的进程`system_server`,我们没有办法篡改,只能另寻他路。 OK,我们继续跟踪源码;在startActivityLocked之后处理的都是Activity任务栈相关内容,这一系列ActivityStack和ActivityStackSupervisor纠缠不清的调用看下图就明白了;不明白也没关系: D 目前用处不大。 ![调用流程图](http://7xp3xc.com1.z0.glb.clouddn.com/201601/1458296458099.png) 这一系列调用最终到达了ActivityStackSupervisor的realStartActivityLocked方法;人如其名,这个方法开始了真正的“启动Activity”:它调用了ApplicationThread的scheduleLaunchActivity方法,开始了真正的Activity对象创建以及启动过程。 这个ApplicationThread是什么,是一个线程吗?与ActivityThread有什么区别和联系? 不要被名字迷惑了,这个ApplicationThread实际上是一个Binder对象,是App所在的进程与AMS所在进程system_server通信的桥梁;在Activity启动的过程中,App进程会频繁地与AMS进程进行通信: 1. App进程会委托AMS进程完成Activity生命周期的管理以及任务栈的管理;这个通信过程AMS是Server端,App进程通过持有AMS的client代理ActivityManagerNative完成通信过程; 2. AMS进程完成生命周期管理以及任务栈管理后,会把控制权交给App进程,让App进程完成Activity类对象的创建,以及生命周期回调;这个通信过程也是通过Binder完成的,App所在server端的Binder对象存在于ActivityThread的内部类ApplicationThread;AMS所在client通过持有IApplicationThread的代理对象完成对于App进程的通信。 App进程与AMS进程的通信过程如图所示: App进程内部的ApplicationThread server端内部有自己的Binder线程池,它与App主线程的通信通过Handler完成,这个Handler存在于ActivityThread类,它的名字很简单就叫`H`,这一点我们接下来就会讲到。 现在我们明白了这个ApplicationThread到底是个什么东西,接上文继续跟踪Activity的启动过程;我们查看ApplicationThread的`scheduleLaunchActivity`方法,这个方法很简单,就是包装了参数最终使用Handler发了一个消息。 正如刚刚所说,ApplicationThread所在的Binder服务端使用Handler与主线程进行通信,这里的scheduleLaunchActivity方法直接把启动Activity的任务通过一个消息转发给了主线程;我们查看Handler类对于这个消息的处理: ``` Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart"); ActivityClientRecord r = (ActivityClientRecord)msg.obj; r.packageInfo = getPackageInfoNoCheck( r.activityInfo.applicationInfo, r.compatInfo); handleLaunchActivity(r, null); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); ``` 可以看到,这里直接调用了ActivityThread的handleLaunchActivity方法,在这个方法内部有一句非常重要: ``` Activity a = performLaunchActivity(r, customIntent); ``` 绕了这么多弯,我们的Activity终于被创建出来了!继续跟踪这个performLaunchActivity方法看看发生了什么;由于这个方法较长,我就不贴代码了,读者可以自行查阅;要指出的是,这个方法做了两件很重要的事情: 1. 使用ClassLoader加载并通过反射创建Activity对象 ``` java.lang.ClassLoader cl = r.packageInfo.getClassLoader(); activity = mInstrumentation.newActivity( cl, component.getClassName(), r.intent); StrictMode.incrementExpectedActivityCount(activity.getClass()); r.intent.setExtrasClassLoader(cl); ``` 2. 如果Application还没有创建,那么创建Application对象并回调相应的生命周期方法; ```java Application app = r.packageInfo.makeApplication(false, mInstrumentation); // ... 省略 if (r.isPersistable()) { mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState); } else { mInstrumentation.callActivityOnCreate(activity, r.state); } ``` Activity的启动过程到这里就结束了,可能读者还是觉得迷惑:不就是调用了一系列方法吗?具体做了什么还是不太清楚,而且为什么Android要这么设计? 方法调用链再长也木有关系,有两点需要明白: 1. 平时我们所说的Application被创建了,onCreate方法被调用了,我们或许并没有意识到我们所说的`Application, Activity`除了代表Android应用层通常所代表的“组件”之外,它们其实都是普通的Java对象,也是需要被构造函数构造出来的对象的;在这个过程中,我们明白了这些对象到底是如何被创建的。 2. 为什么需要一直与AMS进行通信?哪些操作是在AMS中进行的?其实`AMS`正如名字所说,管理所有的“活动”,整个系统的Activity堆栈,Activity生命周期回调都是由AMS所在的系统进程system_server帮开发者完成的;Android的Framework层帮忙完成了诸如生命周期管理等繁琐复杂的过程,简化了应用层的开发。 ## 瞒天过海——启动不在AndroidManifest.xml中声明的Activity ### 简要分析 通过上文的分析,我们已经对Activity的启动过程了如指掌了;就让我们干点坏事吧 :D 对与『必须在AndroidManifest.xml中显示声明使用的Activity』这个问题,上文给出了思路——瞒天过海;我们可以在AndroidManifest.xml里面声明一个替身Activity,然后**在合适的时候**把这个假的替换成我们真正需要启动的Activity就OK了。 那么问题来了,『合适的时候』到底是什么时候?在前文[Hook机制之动态代理][3]中我们提到过Hook过程最重要的一步是**寻找Hook点**;如果是在同一个进程,`startActivity`到Activity真正启动起来这么长的调用链,我们随便找个地方Hook掉就完事儿了;但是问题木有这么简单。 Activity启动过程中很多重要的操作(正如上文分析的『必须在AndroidManifest.xml中显式声明要启动的Activity』)都不是在App进程里面执行的,而是在AMS所在的系统进程system_server完成,由于**进程隔离**的存在,我们对别的进程无能为力;所以这个Hook点就需要花点心思了。 这时候Activity启动过程的知识就派上用场了;虽然整个启动过程非常复杂,但其实一张图就能总结: ![简要启动过程](http://7xp3xc.com1.z0.glb.clouddn.com/201601/1458532084072.png) 先从App进程调用`startActivity`;然后通过IPC调用进入系统进程system_server,完成Activity管理以及一些校检工作,最后又回到了APP进程完成真正的Activioty对象创建。 由于这个检验过程是在AMS进程完成的,我们对system_server进程里面的操作无能为力,只有在我们APP进程里面执行的过程才是有可能被Hook掉的,也就是第一步和第三步;具体应该怎么办呢? 既然需要一个显式声明的Activity,那就声明一个!**可以在第一步假装启动一个已经在AndroidManifest.xml里面声明过的替身Activity,让这个Activity进入AMS进程接受检验;最后在第三步的时候换成我们真正需要启动的Activity**;这样就成功欺骗了AMS进程,瞒天过海! 说到这里,是不是有点小激动呢?我们写个demo验证一下:『启动一个并没有在AndroidManifest.xml中显示声明的Activity』 ### 实战过程 具体来说,我们打算实现如下功能:在MainActivity中启动一个并没有在AndroidManifest.xml中声明的TargetActivity;按照上文分析,我们需要声明一个替身Activity,我们叫它StubActivity; 那么,我们的AndroidManifest.xml如下: ```xml ``` OK,那么我们启动TargetActivity很简单,就是个`startActivity`调用的事: ```java startActivity(new Intent(MainActivity.this, TargetActivity.class)); ``` 如果你直接这么运行,肯定会直接抛出ActivityNotFoundException然后直接退出;我们接下来要做的就是让这个调用成功启动TargetActivity。 #### 狸猫换太子——使用替身Activity绕过AMS 由于`AMS`进程会对Activity做显式声明验证,因此在 启动Activity的控制权转移到`AMS`进程之前,我们需要想办法**临时**把TargetActivity替换成替身StubActivity;在这之间有很长的一段调用链,我们可以轻松Hook掉;选择什么地方Hook是一个很自由的事情,但是Hook的步骤越后越可靠——Hook得越早,后面的调用就越复杂,越容易出错。 我们可以选择在进入`AMS`进程的入口进行Hook,具体来说也就是Hook `AMS`在本进程的代理对象ActivityManagerNative。如果你不知道如何Hook掉这个AMS的代理对象,请查阅我之前的文章 [Hook机制之AMS&PMS][5] 我们Hook掉ActivityManagerNative对于startActivity方法的调用,替换掉交给AMS的intent对象,将里面的TargetActivity的暂时替换成已经声明好的替身StubActivity;这种Hook方式 [前文][5] 讲述的很详细,不赘述;替换的关键代码如下: ```java if ("startActivity".equals(method.getName())) { // 只拦截这个方法 // 替换参数, 任你所为;甚至替换原始Activity启动别的Activity偷梁换柱 // API 23: // public final Activity startActivityNow(Activity parent, String id, // Intent intent, ActivityInfo activityInfo, IBinder token, Bundle state, // Activity.NonConfigurationInstances lastNonConfigurationInstances) { // 找到参数里面的第一个Intent 对象 Intent raw; int index = 0; for (int i = 0; i < args.length; i++) { if (args[i] instanceof Intent) { index = i; break; } } raw = (Intent) args[index]; Intent newIntent = new Intent(); // 这里包名直接写死,如果再插件里,不同的插件有不同的包 传递插件的包名即可 String targetPackage = "com.weishu.intercept_activity.app"; // 这里我们把启动的Activity临时替换为 StubActivity ComponentName componentName = new ComponentName(targetPackage, StubActivity.class.getCanonicalName()); newIntent.setComponent(componentName); // 把我们原始要启动的TargetActivity先存起来 newIntent.putExtra(HookHelper.EXTRA_TARGET_INTENT, raw); // 替换掉Intent, 达到欺骗AMS的目的 args[index] = newIntent; Log.d(TAG, "hook success"); return method.invoke(mBase, args); } return method.invoke(mBase, args); ``` 通过这个替换过程,在ActivityManagerNative的startActivity调用之后,system_server端收到Binder驱动的消息,开始执行ActivityManagerService里面真正的`startActivity`方法;这时候AMS看到的`intent`参数里面的组件已经是`StubActivity`了,因此可以成功绕过检查,这时候如果不做后面的Hook,直接调用 ```java startActivity(new Intent(MainActivity.this, TargetActivity.class)); ``` 也不会出现上文的ActivityNotFoundException #### 借尸还魂——拦截Callback从恢复真身 行百里者半九十。现在我们的`startActivity`启动一个没有显式声明的Activity已经不会抛异常了,但是要真正正确地把TargetActivity启动起来,还有一些事情要做。其中最重要的一点是,我们用替身StubActivity临时换了TargetActivity,肯定需要在『合适的』时候替换回来;接下来我们就完成这个过程。 在AMS进程里面我们是没有办法换回来的,因此我们要等AMS把控制权交给App所在进程,也就是上面那个『Activity启动过程简图』的第三步。AMS进程转移到App进程也是通过Binder调用完成的,承载这个功能的Binder对象是IApplicationThread;在App进程它是Server端,在Server端接受Binder远程调用的是Binder线程池,Binder线程池通过Handler将消息转发给App的主线程;(我这里不厌其烦地叙述Binder调用过程,希望读者不要反感,其一加深印象,其二懂Binder真的很重要)我们可以在这个**Handler里面将替身恢复成真身**。 这里不打算讲述Handler 的原理,我们简单看一下Handler是如何处理接收到的Message的,如果我们能拦截这个Message的接收过程,就有可能完成替身恢复工作;Handler类的`dispathMesage`如下: ```java public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } } ``` 从这个方法可以看出来,Handler类消息分发的过程如下: 1. 如果传递的Message本身就有callback,那么直接使用Message对象的callback方法; 2. 如果Handler类的成员变量`mCallback`存在,那么首先执行这个`mCallback`回调; 3. 如果`mCallback`的回调返回`true`,那么表示消息已经成功处理;直接结束。 4. 如果`mCallback`的回调返回`false`,那么表示消息没有处理完毕,会继续使用Handler类的`handleMessage`方法处理消息。 那么,ActivityThread中的Handler类`H`是如何实现的呢?`H`的部分源码如下: ```java public void handleMessage(Message msg) { if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what)); switch (msg.what) { case LAUNCH_ACTIVITY: { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart"); ActivityClientRecord r = (ActivityClientRecord)msg.obj; r.packageInfo = getPackageInfoNoCheck( r.activityInfo.applicationInfo, r.compatInfo); handleLaunchActivity(r, null); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } break; case RELAUNCH_ACTIVITY: { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart"); ActivityClientRecord r = (ActivityClientRecord)msg.obj; handleRelaunchActivity(r); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); // 以下略 } } ``` 可以看到`H`类仅仅重载了`handleMessage`方法;通过dispathMessage的消息分发过程得知,我们可以拦截这一过程:**把这个`H`类的`mCallback`替换为我们的自定义实现**,这样`dispathMessage`就会首先使用这个自定义的`mCallback`,然后看情况使用`H`重载的`handleMessage`。 这个`Handler.Callback`是一个接口,我们可以使用动态代理或者普通代理完成Hook,这里我们使用普通的静态代理方式;创建一个自定义的Callback类: ```java /* package */ class ActivityThreadHandlerCallback implements Handler.Callback { Handler mBase; public ActivityThreadHandlerCallback(Handler base) { mBase = base; } @Override public boolean handleMessage(Message msg) { switch (msg.what) { // ActivityThread里面 "LAUNCH_ACTIVITY" 这个字段的值是100 // 本来使用反射的方式获取最好, 这里为了简便直接使用硬编码 case 100: handleLaunchActivity(msg); break; } mBase.handleMessage(msg); return true; } private void handleLaunchActivity(Message msg) { // 这里简单起见,直接取出TargetActivity; Object obj = msg.obj; // 根据源码: // 这个对象是 ActivityClientRecord 类型 // 我们修改它的intent字段为我们原来保存的即可. /* switch (msg.what) { / case LAUNCH_ACTIVITY: { / Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart"); / final ActivityClientRecord r = (ActivityClientRecord) msg.obj; / / r.packageInfo = getPackageInfoNoCheck( / r.activityInfo.applicationInfo, r.compatInfo); / handleLaunchActivity(r, null); */ try { // 把替身恢复成真身 Field intent = obj.getClass().getDeclaredField("intent"); intent.setAccessible(true); Intent raw = (Intent) intent.get(obj); Intent target = raw.getParcelableExtra(HookHelper.EXTRA_TARGET_INTENT); raw.setComponent(target.getComponent()); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } } ``` 这个Callback类的使命很简单:**把替身StubActivity恢复成真身TargetActivity**;有了这个自定义的Callback之后我们需要把ActivityThread里面处理消息的Handler类`H`的的`mCallback`修改为自定义callback类的对象: ```java // 先获取到当前的ActivityThread对象 Class activityThreadClass = Class.forName("android.app.ActivityThread"); Field currentActivityThreadField = activityThreadClass.getDeclaredField("sCurrentActivityThread"); currentActivityThreadField.setAccessible(true); Object currentActivityThread = currentActivityThreadField.get(null); // 由于ActivityThread一个进程只有一个,我们获取这个对象的mH Field mHField = activityThreadClass.getDeclaredField("mH"); mHField.setAccessible(true); Handler mH = (Handler) mHField.get(currentActivityThread); // 设置它的回调, 根据源码: // 我们自己给他设置一个回调,就会替代之前的回调; // public void dispatchMessage(Message msg) { // if (msg.callback != null) { // handleCallback(msg); // } else { // if (mCallback != null) { // if (mCallback.handleMessage(msg)) { // return; // } // } // handleMessage(msg); // } // } Field mCallBackField = Handler.class.getDeclaredField("mCallback"); mCallBackField.setAccessible(true); mCallBackField.set(mH, new ActivityThreadHandlerCallback(mH)); ``` 到这里,我们已经成功地绕过`AMS`,完成了『启动没有在AndroidManifest.xml中显式声明的Activity』的过程;瞒天过海,这种玩弄系统与股掌之中的快感你们能体会到吗? #### 僵尸or活人?——能正确收到生命周期回调吗 虽然我们完成了『启动没有在AndroidManifest.xml中显式声明的Activity 』,但是启动的TargetActivity是否有自己的生命周期呢,我们还需要额外的处理过程吗? 实际上TargetActivity已经是一个有血有肉的Activity了:它具有自己正常的生命周期;可以运行[Demo代码][2]验证一下。 这个过程是如何完成的呢?我们以`onDestroy`为例简要分析一下: > 从Activity的`finish`方法开始跟踪,最终会通过ActivityManagerNative到`AMS`然后接着通过ApplicationThread到ActivityThread,然后通过`H`转发消息到ActivityThread的handleDestroyActivity,接着这个方法把任务交给performDestroyActivity完成。 在真正分析这个方法之前,需要说明一点的是:不知读者是否感受得到,App进程与`AMS`交互几乎都是这么一种模式,几个角色 ActivityManagerNative, ApplicationThread, ActivityThread以及Handler类`H`分工明确,读者可以按照这几个角色的功能分析`AMS`的任何调用过程,屡试不爽;这也是我的初衷——希望分析插件框架的过程中能帮助深入理解Android Framework。 好了继续分析performDestroyActivity,关键代码如下: ```java ActivityClientRecord r = mActivities.get(token); // ...略 mInstrumentation.callActivityOnDestroy(r.activity); ``` 这里通过`mActivities`拿到了一个ActivityClientRecord,然后直接把这个record里面的Activity交给Instrument类完成了onDestroy的调用。 在我们这个demo的场景下,r.activity是TargetActivity还是StubActivity?按理说,由于我们欺骗了`AMS`,`AMS`应该只知道`StubActivity`的存在,它压根儿就不知道TargetActivity是什么,为什么它能正确完成对TargetActivity生命周期的回调呢? 一切的秘密在`token`里面。`AMS`与`ActivityThread`之间对于Activity的生命周期的交互,并没有直接使用Activity对象进行交互,而是使用一个token来标识,这个token是binder对象,因此可以方便地跨进程传递。Activity里面有一个成员变量`mToken`代表的就是它,token可以唯一地标识一个Activity对象,它在Activity的`attach`方法里面初始化; 在`AMS`处理Activity的任务栈的时候,使用这个token标记Activity,因此在我们的demo里面,`AMS`进程里面的token对应的是StubActivity,也就是`AMS`还在傻乎乎地操作StubActivity(关于这一点,你可以dump出任务栈的信息,可以观察到dump出的确实是StubActivity)。但是在我们App进程里面,token对应的却是TargetActivity!因此,在ActivityThread执行回调的时候,能正确地回调到TargetActivity相应的方法。 为什么App进程里面,token对应的是TargetActivity呢? 回到代码,ActivityClientRecord是在`mActivities`里面取出来的,确实是根据token取;那么这个token是什么时候添加进去的呢?我们看performLaunchActivity就完成明白了:它通过classloader加载了TargetActivity,然后完成一切操作之后把这个activity添加进了`mActivities`!另外,在这个方法里面我们还能看到对Ativity`attach`方法的调用,它传递给了新创建的Activity一个token对象,而这个token是在ActivityClientRecord构造函数里面初始化的。 至此我们已经可以确认,通过这种方式启动的Activity有它自己完整而独立的生命周期! ## 小节 本文讲述了『启动一个并没有在AndroidManifest.xml中显示声明的Activity』的解决办法,我们成功地绕过了Android的这个限制,这个是插件Activity管理技术的基础;但是要做到启动一个插件Activity问题远没有这么简单。 首先,在Android中,Activity有不同的启动模式;我们声明了一个替身StubActivity,肯定没有满足所有的要求;因此,我们需要在AndroidManifest.xml中声明一系列的有不同launchMode的Activity,还需要完成替身与真正Activity launchMode的匹配过程;这样才能完成启动各种类型Activity的需求,关于这一点,在 DroidPlugin 的com.morgoo.droidplugin.stub包下面可以找到。 另外,每启动一个插件的Activity都需要一个StubActivity,但是AndroidManifest.xml中肯定只能声明有限个,如果一直`startActivity`而不finish的话,那么理论上就需要无限个StubActivity;这个问题该如何解决呢?事实上,这个问题在技术上没有好的解决办法。但是,如果你的App startActivity了十几次,而没有finish任何一个Activity,这样在Activity的回退栈里面有十几个Activity,用户难道按back十几次回到主页吗?有这种需求说明你的产品设计有问题;一个App一级页面,二级页面..到五六级的页面已经影响体验了,所以,每种LauchMode声明十个StubActivity绝对能满足需求了。 最后,在本文所述例子中,TargetActivity与StubActivity存在于同一个Apk,因此系统的ClassLoader能够成功加载并创建TargetActivity的实例。但是在实际的插件系统中,要启动的目标Activity肯定存在于一个单独的文件中,系统默认的ClassLoader无法加载插件中的Activity类——系统压根儿就不知道要加载的插件在哪,谈何加载?因此还有一个很重要的问题需要处理: **我们要完成插件系统中类的加载**,这可以通过自定义ClassLoader实现。解决了『启动没有在AndroidManifest.xml中显式声明的,并且存在于外部文件中的Activity』的问题,插件系统对于Activity的管理才算得上是一个完全体。篇幅所限,欲知后事如何,请听下回分解! [1]: 概述.md [2]: https://github.com/tiann/understand-plugin-framework [3]: Hook机制之代理Hook.md [4]: Hook机制之Binder-Hook.md [5]: Hook机制之AMS&PMS.md [6]: http://blog.csdn.net/luoshengyang/article/details/6685853 ================================================ FILE: DOC/tianweishu/BroadcastReceiver插件化.md ================================================ # BroadcastReceiver插件化 在[Activity生命周期管理][1] 以及 [插件加载机制][2] 中我们详细讲述了插件化过程中对于Activity组件的处理方式,为了实现Activity的插件化我们付出了相当多的努力;那么Android系统的其他组件,比如BroadcastReceiver,Service还有ContentProvider,它们又该如何处理呢? 相比Activity,BroadcastReceiver要简单很多——广播的生命周期相当简单;如果希望插件能够支持广播,这意味着什么? 回想一下我们日常开发的时候是如何使用BroadcastReceiver的:**注册**, **发送**和**接收**;因此,要实现BroadcastReceiver的插件化就这三种操作提供支持;接下来我们将一步步完成这个过程。 阅读本文之前,可以先clone一份 [understand-plugin-framework][5],参考此项目的`receiver-management` 模块。另外,插件框架原理解析系列文章见[索引][6]。 如果连BroadcastReceiver的工作原理都不清楚,又怎么能让插件支持它?老规矩,知己知彼。 ## 源码分析 我们可以注册一个BroadcastReceiver然后接收我们感兴趣的广播,也可以给某有缘人发出某个广播;因此,我们对源码的分析按照两条路线展开: ### 注册过程 不论是静态广播还是动态广播,在使用之前都是需要注册的;动态广播的注册需要借助Context类的registerReceiver方法,而静态广播的注册直接在AndroidManifest.xml中声明即可;我们首先分析一下动态广播的注册过程。 Context类的registerReceiver的真正实现在ContextImpl里面,而这个方法间接调用了registerReceiverInternal,源码如下: ```java private Intent registerReceiverInternal(BroadcastReceiver receiver, int userId, IntentFilter filter, String broadcastPermission, Handler scheduler, Context context) { IIntentReceiver rd = null; // Important !!!!! if (receiver != null) { if (mPackageInfo != null && context != null) { if (scheduler == null) { scheduler = mMainThread.getHandler(); } rd = mPackageInfo.getReceiverDispatcher( receiver, context, scheduler, mMainThread.getInstrumentation(), true); } else { if (scheduler == null) { scheduler = mMainThread.getHandler(); } rd = new LoadedApk.ReceiverDispatcher( receiver, context, scheduler, null, true).getIIntentReceiver(); } } try { return ActivityManagerNative.getDefault().registerReceiver( mMainThread.getApplicationThread(), mBasePackageName, rd, filter, broadcastPermission, userId); } catch (RemoteException e) { return null; } } ``` 可以看到,BroadcastReceiver的注册也是通过`AMS`完成的;在进入`AMS`跟踪它的registerReceiver方法之前,我们先弄清楚这个`IIntentReceiver`类型的变量`rd`是什么。首先查阅API文档,很遗憾SDK里面没有导出这个类,我们直接去 [grepcode][3] 上看,文档如下: > System private API for dispatching intent broadcasts. This is given to the activity manager as part of registering for an intent broadcasts, and is called when it receives intents. 这个类是通过AIDL工具生成的,它是一个Binder对象,因此可以用来跨进程传输;文档说的很清楚,它是用来进行广播分发的。什么意思呢? 由于广播的分发过程是在AMS中进行的,而AMS所在的进程和BroadcastReceiver所在的进程不一样,因此要把广播分发到BroadcastReceiver具体的进程需要进行跨进程通信,这个**通信的载体**就是IIntentReceiver类。其实这个类的作用跟 [Activity生命周期管理][4] 中提到的 `IApplicationThread`相同,都是App进程给AMS进程用来进行通信的对象。另外,`IIntentReceiver`是一个接口,从上述代码中可以看出,它的实现类为LoadedApk.ReceiverDispatcher。 OK,我们继续跟踪源码,AMS类的registerReceiver方法代码有点多,这里不一一解释了,感兴趣的话可以自行查阅;这个方法主要做了以下两件事: 1. 对发送者的身份和权限做出一定的校检 2. 把这个BroadcastReceiver以BroadcastFilter的形式存储在AMS的`mReceiverResolver`变量中,供后续使用。 就这样,被传递过来的BroadcastReceiver已经成功地注册在系统之中,能够接收特定类型的广播了;那么注册在AndroidManifest.xml中的静态广播是如何被系统感知的呢? 在 [插件加载机制][2] 中我们知道系统会通过PackageParser解析Apk中的AndroidManifest.xml文件,因此我们有理由认为,系统会在解析AndroidMafest.xml的<receiver>标签(也即静态注册的广播)的时候保存相应的信息;而Apk的解析过程是在PMS中进行的,因此**静态注册广播的信息存储在PMS中**。接下来的分析会证实这一结论。 ### 发送和接收过程 #### 发送过程 发送广播很简单,就是一句context.sendBroadcast(),我们顺藤摸瓜,跟踪这个方法。前文也提到过,Context中方法的调用都会委托到ContextImpl这个类,我们直接看ContextImpl对这个方法的实现: ```java public void sendBroadcast(Intent intent) { warnIfCallingFromSystemProcess(); String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); try { intent.prepareToLeaveProcess(); ActivityManagerNative.getDefault().broadcastIntent( mMainThread.getApplicationThread(), intent, resolvedType, null, Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, null, false, false, getUserId()); } catch (RemoteException e) { throw new RuntimeException("Failure from system", e); } } ``` 嗯,发送广播也是通过AMS进行的,我们直接查看ActivityManagerService类的broadcastIntent方法,这个方法仅仅是调用了broadcastIntentLocked方法,我们继续跟踪;broadcastIntentLocked这个方法相当长,处理了诸如粘性广播,顺序广播,各种Flag以及动态广播静态广播的接收过程,这些我们暂时不关心;值得注意的是,在这个方法中我们发现,其实**广播的发送和接收是融为一体的**。某个广播被发送之后,AMS会找出所有注册过的BroadcastReceiver中与这个广播匹配的接收者,然后将这个广播分发给相应的接收者处理。 #### 匹配过程 某一条广播被发出之后,并不是阿猫阿狗都能接收它并处理的;BroadcastReceiver可能只对某些类型的广播感兴趣,因此它也只能接收和处理这种特定类型的广播;在broadcastIntentLocked方法内部有如下代码: ```java // Figure out who all will receive this broadcast. List receivers = null; List registeredReceivers = null; // Need to resolve the intent to interested receivers... if ((intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) { receivers = collectReceiverComponents(intent, resolvedType, callingUid, users); } if (intent.getComponent() == null) { if (userId == UserHandle.USER_ALL && callingUid == Process.SHELL_UID) { // Query one target user at a time, excluding shell-restricted users // 略 } else { registeredReceivers = mReceiverResolver.queryIntent(intent, resolvedType, false, userId); } } ``` 这里有两个列表`receivers`和`registeredReceivers`,看名字好像是广播接收者的列表;下面是它们的赋值过程: ```java receivers = collectReceiverComponents(intent, resolvedType, callingUid, users); registeredReceivers = mReceiverResolver.queryIntent(intent, resolvedType, false, userId); ``` 读者可以自行跟踪这两个方法的代码,过程比较简单,我这里直接给出结论: 1. `receivers`是对这个广播感兴趣的**静态BroadcastReceiver**列表;collectReceiverComponents 通过PackageManager获取了与这个广播匹配的静态BroadcastReceiver信息;这里也证实了我们在分析BroadcasrReceiver注册过程中的推论——静态BroadcastReceiver的注册过程的确实在PMS中进行的。 2. `mReceiverResolver`存储了**动态注册**的BroadcastReceiver的信息;还记得这个`mReceiverResolver`吗?我们在分析动态广播的注册过程中发现,动态注册的BroadcastReceiver的相关信息最终存储在此对象之中;在这里,通过mReceiverResolver对象匹配出了对应的BroadcastReceiver供进一步使用。 现在系统通过PMS拿到了所有符合要求的静态BroadcastReceiver,然后从AMS中获取了符合要求的动态BroadcastReceiver;因此接下来的工作非常简单:唤醒这些广播接受者。简单来说就是回调它们的`onReceive`方法。 #### 接收过程 通过上文的分析过程我们知道,在AMS的broadcastIntentLocked方法中找出了符合要求的所有BroadcastReceiver;接下来就需要把这个广播分发到这些接收者之中。在broadcastIntentLocked方法的后半部分有如下代码: ```java BroadcastQueue queue = broadcastQueueForIntent(intent); BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage, callingPid, callingUid, resolvedType, requiredPermissions, appOp, brOptions, receivers, resultTo, resultCode, resultData, resultExtras, ordered, sticky, false, userId); boolean replaced = replacePending && queue.replaceOrderedBroadcastLocked(r); if (!replaced) { queue.enqueueOrderedBroadcastLocked(r); queue.scheduleBroadcastsLocked(); } ``` 首先创建了一个BroadcastRecord代表此次发送的这条广播,然后把它丢进一个队列,最后通过scheduleBroadcastsLocked通知队列对广播进行处理。 在BroadcastQueue中通过Handle调度了对于广播处理的消息,调度过程由processNextBroadcast方法完成,而这个方法通过performReceiveLocked最终调用了IIntentReceiver的performReceive方法。 这个`IIntentReceiver`正是在广播注册过程中由App进程提供给AMS进程的Binder对象,现在AMS通过这个Binder对象进行IPC调用通知广播接受者所在进程完成余下操作。在上文我们分析广播的注册过程中提到过,这个IItentReceiver的实现是LoadedApk.ReceiverDispatcher;我们查看这个对象的performReceive方法,源码如下: ```java public void performReceive(Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser) { Args args = new Args(intent, resultCode, data, extras, ordered, sticky, sendingUser); if (!mActivityThread.post(args)) { if (mRegistered && ordered) { IActivityManager mgr = ActivityManagerNative.getDefault(); args.sendFinished(mgr); } } } ``` 这个方法创建了一个`Args`对象,然后把它post到了mActivityThread这个Handler中;我们查看`Args`类的`run`方法:(坚持一下,马上就分析完了 ^ ^) ```java public void run() { final BroadcastReceiver receiver = mReceiver; final boolean ordered = mOrdered; final IActivityManager mgr = ActivityManagerNative.getDefault(); final Intent intent = mCurIntent; mCurIntent = null; if (receiver == null || mForgotten) { if (mRegistered && ordered) { sendFinished(mgr); } return; } try { ClassLoader cl = mReceiver.getClass().getClassLoader(); // Important!! load class intent.setExtrasClassLoader(cl); setExtrasClassLoader(cl); receiver.setPendingResult(this); receiver.onReceive(mContext, intent); // callback } catch (Exception e) { if (mRegistered && ordered) { sendFinished(mgr); } if (mInstrumentation == null || !mInstrumentation.onException(mReceiver, e)) { throw new RuntimeException( "Error receiving broadcast " + intent + " in " + mReceiver, e); } } if (receiver.getPendingResult() != null) { finish(); } } ``` 这里,我们看到了相应BroadcastReceiver的`onReceive`回调;因此,广播的工作原理到这里就水落石出了;我们接下来将探讨如何实现对于广播的插件化。 ## 思路分析 上文中我们分析了BroadcastReceiver的工作原理,那么怎么才能实现对BroadcastReceiver的插件化呢? 从分析过程中我们发现,Framework对于静态广播和动态广播的处理是不同的;不过,这个不同之处仅仅体现在**注册过程**——静态广播需要在AndroidManifest.xml中注册,并且注册的信息存储在PMS中;动态广播不需要预注册,注册的信息存储在AMS中。 从实现Activity的插件化过程中我们知道,需要在AndroidManifest.xml中预先注册是一个相当麻烦的事情——我们需要使用『替身』并在合适的时候进行『偷梁换柱』;因此看起来动态广播的处理要容易那么一点,我们先讨论一下如何实现动态注册BroadcastReceiver的插件化。 首先,广播并没有复杂的生命周期,它的整个存活过程其实就是一个`onReceive`回调;而动态广播又不需要在AndroidManifest.xml中预先注册,所以动态注册的BroadcastReceiver其实可以当作一个普通的Java对象;我们完全可以用纯ClassLoader技术实现它——不就是把插件中的Receiver加载进来,然后想办法让它能接受`onReceive`回调嘛。 静态BroadcastReceiver看起来要复杂一些,但是我们连Activity都搞定了,还有什么难得到我们呢?对于实现静态BroadcastReceiver插件化的问题,有的童鞋或许会想,我们可以借鉴Activity的工作方式——用替身和Hook解决。但是很遗憾,这样是行不通的。为什么呢? BroadcastReceiver有一个IntentFilter的概念,也就是说,每一个BroadcastReceiver只对特定的Broadcast感兴趣;而且,AMS在进行广播分发的时候,也会对这些BroadcastReceiver与发出的广播进行匹配,只有Intent匹配的Receiver才能收到广播;在分析源码的时候也提到了这个匹配过程。如果我们尝试用替身Receiver解决静态注册的问题,那么它的IntentFilter该写什么?我们无法预料插件中静态注册的Receiver会使用什么类型的IntentFilter,就算我们在AndroidManifest.xml中声明替身也没有用——我们压根儿收不到与我们的IntentFilter不匹配的广播。其实,我们对于Activity的处理方式也有这个问题;如果你尝试用IntentFilter的方式启动Activity,这并不能成功;这算得上是DroidPlugin的缺陷之一。 那么,我们就真的对静态BroadcastReceiver无能为力吗?想一想这里的难点是什么? 没错,主要是在静态BroadcastReceiver里面这个IntentFilter我们事先无法确定,它是动态变化的;但是,动态BroadcastReceiver不是可以动态添加IntentFilter吗!!! **可以把静态广播当作动态广播处理** 既然都是广播,它们的功能都是订阅一个特定的消息然后执行某个特定的操作,我们完全可以把插件中的静态广播全部注册为动态广播,这样就解决了静态广播的问题。当然,这样也是有缺陷的,静态BroadcastReceiver与动态BroadcastReceiver一个非常大的不同之处在于:动态BroadcastReceiver在进程死亡之后是无法接收广播的,而静态BroadcastReceiver则可以——系统会唤醒Receiver所在进程;这算得上缺陷之二,当然,瑕不掩瑜。 ## 静态广播非静态的实现 上文我们提到,可以把静态BroadcastReceiver当作动态BroadcastReceiver处理;我们接下来实现这个过程。 ### 解析 要把插件中的静态BroadcastReceiver当作动态BroadcastReceiver处理,我们首先得知道插件中到底注册了哪些广播;这个过程归根结底就是获取AndroidManifest.xml中的<receiver>标签下面的内容,我们可以选择手动解析xml文件;这里我们选择使用系统的 PackageParser 帮助解析,这种方式在之前的 [插件加载过程][] 中也用到过,如果忘记了可以温习一下。 PackageParser中有一系列方法用来提取Apk中的信息,可是翻遍了这个类也没有找到与「Receiver」名字相关的方法;最终我们发现BroadcastReceiver信息是用与Activity相同的类存储的!这一点可以在PackageParser的内部类Package中发现端倪——成员变量`receivers`和`activities`的范型类型相同。所以,我们要解析apk的<receiver>的信息,可以使用PackageParser的`generateActivityInfo`方法。 知道这一点之后,代码就比较简单了;使用反射调用相应的隐藏接口,并且在必要的时候构造相应参数的方式我们在插件化系列文章中已经讲述过很多,相信读者已经熟练,这里就不赘述,直接贴代码: ```java private static void parserReceivers(File apkFile) throws Exception { Class packageParserClass = Class.forName("android.content.pm.PackageParser"); Method parsePackageMethod = packageParserClass.getDeclaredMethod("parsePackage", File.class, int.class); Object packageParser = packageParserClass.newInstance(); // 首先调用parsePackage获取到apk对象对应的Package对象 Object packageObj = parsePackageMethod.invoke(packageParser, apkFile, PackageManager.GET_RECEIVERS); // 读取Package对象里面的receivers字段,注意这是一个 List (没错,底层把当作处理) // 接下来要做的就是根据这个List 获取到Receiver对应的 ActivityInfo (依然是把receiver信息用activity处理了) Field receiversField = packageObj.getClass().getDeclaredField("receivers"); List receivers = (List) receiversField.get(packageObj); // 调用generateActivityInfo 方法, 把PackageParser.Activity 转换成 Class packageParser$ActivityClass = Class.forName("android.content.pm.PackageParser$Activity"); Class packageUserStateClass = Class.forName("android.content.pm.PackageUserState"); Class userHandler = Class.forName("android.os.UserHandle"); Method getCallingUserIdMethod = userHandler.getDeclaredMethod("getCallingUserId"); int userId = (Integer) getCallingUserIdMethod.invoke(null); Object defaultUserState = packageUserStateClass.newInstance(); Class componentClass = Class.forName("android.content.pm.PackageParser$Component"); Field intentsField = componentClass.getDeclaredField("intents"); // 需要调用 android.content.pm.PackageParser#generateActivityInfo(android.content.pm.ActivityInfo, int, android.content.pm.PackageUserState, int) Method generateReceiverInfo = packageParserClass.getDeclaredMethod("generateActivityInfo", packageParser$ActivityClass, int.class, packageUserStateClass, int.class); // 解析出 receiver以及对应的 intentFilter for (Object receiver : receivers) { ActivityInfo info = (ActivityInfo) generateReceiverInfo.invoke(packageParser, receiver, 0, defaultUserState, userId); List filters = (List) intentsField.get(receiver); sCache.put(info, filters); } } ``` ### 注册 我们已经解析得到了插件中静态注册的BroadcastReceiver的信息,现在我们只需要把这些静态广播动态注册一遍就可以了;但是,由于BroadcastReceiver的实现类存在于插件之后,我们需要手动用ClassLoader来加载它;这一点在 [插件加载机制][2] 已有讲述,不啰嗦了。 ```java ClassLoader cl = null; for (ActivityInfo activityInfo : ReceiverHelper.sCache.keySet()) { Log.i(TAG, "preload receiver:" + activityInfo.name); List intentFilters = ReceiverHelper.sCache.get(activityInfo); if (cl == null) { cl = CustomClassLoader.getPluginClassLoader(apk, activityInfo.packageName); } // 把解析出来的每一个静态Receiver都注册为动态的 for (IntentFilter intentFilter : intentFilters) { BroadcastReceiver receiver = (BroadcastReceiver) cl.loadClass(activityInfo.name).newInstance(); context.registerReceiver(receiver, intentFilter); } } ``` 就这样,我们对插件静态BroadcastReceiver的支持已经完成了,是不是相当简单?至于插件中的动态广播如何实现插件化,这一点**交给读者自行完成**,希望你在解决这个问题的过程中能够加深对于插件方案的理解 ^ ^ ## 小节 本文我们介绍了BroadcastReceiver组件的插件化方式,可以看到,插件方案对于BroadcastReceiver的处理相对简单;同时「静态广播非静态」的特性以及BroadcastReceiver先天的一些特点导致插件方案没有办法做到尽善尽美,不过这都是大醇小疵——在绝大多数情况下,这样的处理方式是可以满足需求的。 虽然对于BroadcastReceiver的处理方式相对简单,但是文章的内容却并不短——我们花了大量的篇幅讲述BroadcastReceiver的原理,这也是我的初衷:借助DroidPlugin更深入地了解Android Framework。 [1]: Activity生命周期管理.md [2]: ClassLoader管理.md [3]: http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.0.1_r1/android/content/IIntentReceiver.java?av=f [4]: Activity生命周期管理.md [5]: https://github.com/tiann/understand-plugin-framework [6]: 概述.md ================================================ FILE: DOC/tianweishu/ClassLoader管理.md ================================================ # 插件加载机制 上文 [Activity生命周期管理][3] 中我们地完成了『启动没有在AndroidManifest.xml中显式声明的Activity』的任务;通过Hook `AMS`和拦截ActivityThread中`H`类对于组件调度我们成功地绕过了AndroidMAnifest.xml的限制。 但是我们启动的『没有在AndroidManifet.xml中显式声明』的Activity和宿主程序存在于同一个Apk中;通常情况下,插件均以独立的文件存在甚至通过网络获取,这时候插件中的Activity能否成功启动呢? 要启动Activity组件肯定先要创建对应的Activity类的对象,从上文 [Activity生命周期管理][3] 知道,创建Activity类对象的过程如下: ```java java.lang.ClassLoader cl = r.packageInfo.getClassLoader(); activity = mInstrumentation.newActivity( cl, component.getClassName(), r.intent); StrictMode.incrementExpectedActivityCount(activity.getClass()); r.intent.setExtrasClassLoader(cl); ``` 也就是说,系统通过`ClassLoader`加载了需要的Activity类并通过反射调用构造函数创建出了Activity对象。如果Activity组件存在于独立于宿主程序的文件之中,系统的ClassLoader怎么知道去哪里加载呢?因此,如果不做额外的处理,插件中的Activity对象甚至都没有办法创建出来,谈何启动? 因此,要使存在于独立文件或者网络中的插件被成功启动,首先就需要解决这个**插件类加载**的问题。 下文将围绕此问题展开,完成『启动没有在AndroidManifest.xml中显示声明,并且存在于外部插件中的Activity』的任务。 阅读本文之前,可以先clone一份 [understand-plugin-framework][2],参考此项目的`classloader-hook` 模块。另外,插件框架原理解析系列文章见[索引][1]。 ## ClassLoader机制 或许有的童鞋还不太了解Java的ClassLoader机制,我这里简要介绍一下。 > Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校检、转换解析和初始化的,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。 > 与那些在编译时进行链连接工作的语言不同,在Java语言里面,类型的加载、连接和初始化都是在程序运行期间完成的,这种策略虽然会令类加载时稍微增加一些性能开销,但是会为Java应用程序提供高度的灵活性,Java里天生可以同代拓展的语言特性就是依赖运行期动态加载和动态链接这个特点实现的。例如,如果编写一个面相接口的应用程序,可以等到运行时在制定实际的实现类;用户可以通过Java与定义的和自定义的类加载器,让一个本地的应用程序可以在运行时从网络或其他地方加载一个二进制流作为代码的一部分,这种组装应用程序的方式目前已经广泛应用于Java程序之中。从最基础的Applet,JSP到复杂的OSGi技术,都使用了Java语言运行期类加载的特性。 Java的类加载是一个相对复杂的过程;它包括加载、验证、准备、解析和初始化五个阶段;对于开发者来说,可控性最强的是**加载阶段**;加载阶段主要完成三件事: 1. 根据一个类的全限定名来获取定义此类的二进制字节流 2. 将这个字节流所代表的静态存储结构转化为JVM方法区中的运行时数据结构 3. 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。 『通过一个类的全限定名获取描述此类的二进制字节流』这个过程被抽象出来,就是Java的类加载器模块,也即JDK中ClassLoader API。 Android Framework提供了DexClassLoader这个类,简化了『通过一个类的全限定名获取描述次类的二进制字节流』这个过程;我们只需要告诉DexClassLoader一个dex文件或者apk文件的路径就能完成类的加载。因此本文的内容用一句话就可以概括: **将插件的dex或者apk文件告诉『合适的』DexClassLoader,借助它完成插件类的加载** 关于CLassLoader机制更多的内容,请参阅『深入理解Java虚拟机』这本书。 ## 思路分析 Android系统使用了ClassLoader机制来进行Activity等组件的加载;apk被安装之后,APK文件的代码以及资源会被系统存放在固定的目录(比如/data/app/package_name/base-1.apk )系统在进行类加载的时候,会自动去这一个或者几个特定的路径来寻找这个类;但是系统并不知道存在于插件中的Activity组件的信息(插件可以是任意位置,甚至是网络,系统无法提前预知),因此正常情况下系统无法加载我们插件中的类;因此也没有办法创建Activity的对象,更不用谈启动组件了。 解决这个问题有两个思路,要么全盘接管这个类加载的过程;要么告知系统我们使用的插件存在于哪里,让系统帮忙加载;这两种方式或多或少都需要**干预**这个类加载的过程。老规矩,知己知彼,百战不殆。我们首先分析一下,系统是如果完成这个类加载过程的。 我们再次搬出Activity的创建过程的代码: ```java java.lang.ClassLoader cl = r.packageInfo.getClassLoader(); activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent); StrictMode.incrementExpectedActivityCount(activity.getClass()); r.intent.setExtrasClassLoader(cl); ``` 这里可以很明显地看到,系统通过待启动的Activity的类名`className`,然后使用ClassLoader对象`cl`把这个类加载进虚拟机,最后使用反射创建了这个Activity类的实例对象。要想干预这个ClassLoader(告知它我们的路径或者替换他),我们首先得看看这玩意到底是个什么来头。(从哪里创建的) `cl`这个ClasssLoader对象通过`r.packageInfo`对象的getClassLoader()方法得到,r.packageInfo是一个LoadedApk类的对象;那么,LoadedApk到底是个什么东西?? 我们查阅LoadedApk类的文档,只有一句话,不过说的很明白: > Local state maintained about a currently loaded .apk. **LoadedApk对象是APK文件在内存中的表示。** Apk文件的相关信息,诸如Apk文件的代码和资源,甚至代码里面的Activity,Service等组件的信息我们都可以通过此对象获取。 OK, 我们知道这个LoadedApk是何方神圣了;接下来我们要搞清楚的是:这个 `r.packageInfo` 到底是从哪里获取的? 我们顺着 performLaunchActivity上溯,辗转handleLaunchActivity回到了 `H` 类的LAUNCH_ACTIVITY消息,找到了`r.packageInfo`的来源: ```java final ActivityClientRecord r = (ActivityClientRecord) msg.obj; r.packageInfo = getPackageInfoNoCheck( r.activityInfo.applicationInfo, r.compatInfo); handleLaunchActivity(r, null); ``` getPackageInfoNoCheck方法很简单,直接调用了getPackageInfo方法: ```java public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai, CompatibilityInfo compatInfo) { return getPackageInfo(ai, compatInfo, null, false, true, false); } ``` 在这个getPackageInfo方法里面我们发现了端倪: ```java private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo, ClassLoader baseLoader, boolean securityViolation, boolean includeCode, boolean registerPackage) { // 获取userid信息 final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid)); synchronized (mResourcesManager) { // 尝试获取缓存信息 WeakReference ref; if (differentUser) { // Caching not supported across users ref = null; } else if (includeCode) { ref = mPackages.get(aInfo.packageName); } else { ref = mResourcePackages.get(aInfo.packageName); } LoadedApk packageInfo = ref != null ? ref.get() : null; if (packageInfo == null || (packageInfo.mResources != null && !packageInfo.mResources.getAssets().isUpToDate())) { // 缓存没有命中,直接new packageInfo = new LoadedApk(this, aInfo, compatInfo, baseLoader, securityViolation, includeCode && (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage); // 省略。。更新缓存 return packageInfo; } } ``` 这个方法很重要,我们必须弄清楚每一步; 首先,它判断了调用方和或许App信息的一方是不是同一个userId;如果是同一个user,那么可以共享缓存数据(要么缓存的代码数据,要么缓存的资源数据) 接下来尝试获取缓存数据;如果没有命中缓存数据,才通过LoadedApk的构造函数创建了LoadedApk对象;创建成功之后,如果是同一个uid还放入了缓存。 提到缓存数据,看过[Hook机制之Binder Hook][7]的童鞋可能就知道了,我们之前成功借助ServiceManager的本地代理使用缓存的机制Hook了各种Binder;因此这里完全可以如法炮制——我们拿到这一份缓存数据,修改里面的ClassLoader;自己控制类加载的过程,这样加载插件中的Activity类的问题就解决了。这就引出了我们加载插件类的第一种方案: ## 激进方案:Hook掉ClassLoader,自己操刀 从上述分析中我们得知,在获取LoadedApk的过程中使用了一份缓存数据;这个缓存数据是一个`Map`,从包名到LoadedApk的一个映射。正常情况下,我们的插件肯定不会存在于这个对象里面;但是**如果我们手动把我们插件的信息添加到里面呢?**系统在查找缓存的过程中,会直接命中缓存!进而使用我们添加进去的LoadedApk的ClassLoader来加载这个特定的Activity类!这样我们就能接管我们自己插件类的加载过程了! 这个缓存对象`mPackages`存在于ActivityThread类中;老方法,我们首先获取这个对象: ```java // 先获取到当前的ActivityThread对象 Class activityThreadClass = Class.forName("android.app.ActivityThread"); Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread"); currentActivityThreadMethod.setAccessible(true); Object currentActivityThread = currentActivityThreadMethod.invoke(null); // 获取到 mPackages 这个静态成员变量, 这里缓存了dex包的信息 Field mPackagesField = activityThreadClass.getDeclaredField("mPackages"); mPackagesField.setAccessible(true); Map mPackages = (Map) mPackagesField.get(currentActivityThread); ``` 拿到这个Map之后接下来怎么办呢?**我们需要填充这个map,把插件的信息塞进这个map里面**,以便系统在查找的时候能命中缓存。但是这个填充这个Map我们出了需要包名之外,还需要一个LoadedApk对象;如何创建一个LoadedApk对象呢? 我们当然可以直接反射调用它的构造函数直接创建出需要的对象,但是万一哪里有疏漏,构造参数填错了怎么办?又或者Android的不同版本使用了不同的参数,导致我们创建出来的对象与系统创建出的对象不一致,无法work怎么办? 因此我们需要使用与系统完全相同的方式创建LoadedApk对象;从上文分析得知,系统创建LoadedApk对象是通过`getPackageInfo`来完成的,因此我们可以调用这个函数来创建LoadedApk对象;但是这个函数是`private`的,我们无法使用。 有的童鞋可能会有疑问了,`private`不是也能反射到吗?我们确实能够调用这个函数,但是`private`表明这个函数是内部实现,或许那一天Google高兴,把这个函数改个名字我们就直接GG了;但是public函数不同,public被导出的函数你无法保证是否有别人调用它,因此大部分情况下不会修改;我们最好调用public函数来保证尽可能少的遇到兼容性问题。(当然,如果实在木有路可以考虑调用私有方法,自己处理兼容性问题,这个我们以后也会遇到) 间接调用`getPackageInfo`这个私有函数的public函数有同名的getPackageInfo系列和getPackageInfoNoCheck;简单查看源代码发现,getPackageInfo除了获取包的信息,还检查了包的一些组件;为了绕过这些验证,我们选择使用`getPackageInfoNoCheck`获取LoadedApk信息。 ### 构建插件LoadedApk对象 我们这一步的目的很明确,通过getPackageInfoNoCheck函数创建出我们需要的LoadedApk对象,以供接下来使用。 这个函数的签名如下: ```java public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai, CompatibilityInfo compatInfo) { ``` 因此,为了调用这个函数,我们需要构造两个参数。其一是ApplicationInfo,其二是CompatibilityInfo;第二个参数顾名思义,代表这个App的兼容性信息,比如targetSDK版本等等,这里我们只需要提取出app的信息,因此直接使用默认的兼容性即可;在CompatibilityInfo类里面有一个公有字段DEFAULT_COMPATIBILITY_INFO代表默认兼容性信息;因此,我们的首要目标是获取这个ApplicationInfo信息。 ### 构建插件ApplicationInfo信息 我们首先看看ApplicationInfo代表什么,这个类的文档说的很清楚: > Information you can retrieve about a particular application. This corresponds to information collected from the AndroidManifest.xml's <application> tag. 也就是说,这个类就是AndroidManifest.xml里面的 这个标签下面的信息;这个AndroidManifest.xml无疑是一个标准的xml文件,因此我们完全可以自己使用parse来解析这个信息。 那么,系统是如何获取这个信息的呢?其实Framework就有一个这样的parser,也即PackageParser;理论上,我们也可以借用系统的parser来解析AndroidMAnifest.xml从而得到ApplicationInfo的信息。但遗憾的是,**这个类的兼容性很差**;Google几乎在每一个Android版本都对这个类动刀子,如果坚持使用系统的解析方式,必须写一系列兼容行代码!!DroidPlugin就选择了这种方式,相关类如下: DroidPlugin的PackageParser 看到这里我就问你怕不怕!!!这也是我们之前提到的**私有或者隐藏的API可以使用,但必须处理好兼容性问题**;如果Android 7.0发布,这里估计得添加一个新的类PackageParseApi24。 我这里使用API 23作为演示,**版本不同的可能无法运行**请自行查阅 DroidPlugin 不同版本如何处理。 OK回到正题,我们决定使用PackageParser类来提取ApplicationInfo信息。下图是API 23上,PackageParser的部分类结构图: 看起来有我们需要的方法 generateApplication;确实如此,依靠这个方法我们可以成功地拿到ApplicationInfo。 由于PackageParser是@hide的,因此我们需要通过反射进行调用。我们根据这个generateApplicationInfo方法的签名: ```java public static ApplicationInfo generateApplicationInfo(Package p, int flags, PackageUserState state) ``` 可以写出调用generateApplicationInfo的反射代码: ```java Class packageParserClass = Class.forName("android.content.pm.PackageParser"); // 首先拿到我们得终极目标: generateApplicationInfo方法 // API 23 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // public static ApplicationInfo generateApplicationInfo(Package p, int flags, // PackageUserState state) { // 其他Android版本不保证也是如此. Class packageParser$PackageClass = Class.forName("android.content.pm.PackageParser$Package"); Class packageUserStateClass = Class.forName("android.content.pm.PackageUserState"); Method generateApplicationInfoMethod = packageParserClass.getDeclaredMethod("generateApplicationInfo", packageParser$PackageClass, int.class, packageUserStateClass); ``` 要成功调用这个方法,还需要三个参数;因此接下来我们需要一步一步构建调用此函数的参数信息。 #### 构建PackageParser.Package generateApplicationInfo方法需要的第一个参数是PackageParser.Package;从名字上看这个类代表某个apk包的信息,我们看看文档怎么解释: > Representation of a full package parsed from APK files on disk. A package consists of a single base APK, and zero or more split APKs. 果然,这个类代表从PackageParser中解析得到的某个apk包的信息,是磁盘上apk文件在内存中的数据结构表示;因此,要获取这个类,肯定需要解析整个apk文件。PackageParser中解析apk的核心方法是parsePackage,这个方法返回的就是一个Package类型的实例,因此我们调用这个方法即可;使用反射代码如下: ```java // 首先, 我们得创建出一个Package对象出来供这个方法调用 // 而这个需要得对象可以通过 android.content.pm.PackageParser#parsePackage 这个方法返回得 Package对象得字段获取得到 // 创建出一个PackageParser对象供使用 Object packageParser = packageParserClass.newInstance(); // 调用 PackageParser.parsePackage 解析apk的信息 Method parsePackageMethod = packageParserClass.getDeclaredMethod("parsePackage", File.class, int.class); // 实际上是一个 android.content.pm.PackageParser.Package 对象 Object packageObj = parsePackageMethod.invoke(packageParser, apkFile, 0); ``` 这样,我们就得到了generateApplicationInfo的第一个参数;第二个参数是解析包使用的flag,我们直接选择解析全部信息,也就是0; #### 构建PackageUserState 第三个参数是PackageUserState,代表不同用户中包的信息。由于Android是一个多任务多用户系统,因此不同的用户同一个包可能有不同的状态;这里我们只需要获取包的信息,因此直接使用默认的即可; 至此,generateApplicaionInfo的参数我们已经全部构造完成,直接调用此方法即可得到我们需要的applicationInfo对象;在返回之前我们需要做一点小小的修改:使用系统系统的这个方法解析得到的ApplicationInfo对象中并没有apk文件本身的信息,所以我们把解析的apk文件的路径设置一下(ClassLoader依赖dex文件以及apk的路径): ```java // 第三个参数 mDefaultPackageUserState 我们直接使用默认构造函数构造一个出来即可 Object defaultPackageUserState = packageUserStateClass.newInstance(); // 万事具备!!!!!!!!!!!!!! ApplicationInfo applicationInfo = (ApplicationInfo) generateApplicationInfoMethod.invoke(packageParser, packageObj, 0, defaultPackageUserState); String apkPath = apkFile.getPath(); applicationInfo.sourceDir = apkPath; applicationInfo.publicSourceDir = apkPath; ``` ### 替换ClassLoader #### 获取LoadedApk信息 方才为了获取ApplicationInfo我们费了好大一番精力;回顾一下我们的初衷: 我们最终的目的是调用getPackageInfoNoCheck得到LoadedApk的信息,并替换其中的mClassLoader然后把把添加到ActivityThread的mPackages缓存中;从而达到我们使用自己的ClassLoader加载插件中的类的目的。 现在我们已经拿到了getPackageInfoNoCheck这个方法中至关重要的第一个参数applicationInfo;上文提到第二个参数CompatibilityInfo代表设备兼容性信息,直接使用默认的值即可;因此,两个参数都已经构造出来,我们可以调用getPackageInfoNoCheck获取LoadedApk: ```java // android.content.res.CompatibilityInfo Class compatibilityInfoClass = Class.forName("android.content.res.CompatibilityInfo"); Method getPackageInfoNoCheckMethod = activityThreadClass.getDeclaredMethod("getPackageInfoNoCheck", ApplicationInfo.class, compatibilityInfoClass); Field defaultCompatibilityInfoField = compatibilityInfoClass.getDeclaredField("DEFAULT_COMPATIBILITY_INFO"); defaultCompatibilityInfoField.setAccessible(true); Object defaultCompatibilityInfo = defaultCompatibilityInfoField.get(null); ApplicationInfo applicationInfo = generateApplicationInfo(apkFile); Object loadedApk = getPackageInfoNoCheckMethod.invoke(currentActivityThread, applicationInfo, defaultCompatibilityInfo); ``` 我们成功地构造出了LoadedAPK, 接下来我们需要替换其中的ClassLoader,然后把它添加进ActivityThread的mPackages中: ```java String odexPath = Utils.getPluginOptDexDir(applicationInfo.packageName).getPath(); String libDir = Utils.getPluginLibDir(applicationInfo.packageName).getPath(); ClassLoader classLoader = new CustomClassLoader(apkFile.getPath(), odexPath, libDir, ClassLoader.getSystemClassLoader()); Field mClassLoaderField = loadedApk.getClass().getDeclaredField("mClassLoader"); mClassLoaderField.setAccessible(true); mClassLoaderField.set(loadedApk, classLoader); // 由于是弱引用, 因此我们必须在某个地方存一份, 不然容易被GC; 那么就前功尽弃了. sLoadedApk.put(applicationInfo.packageName, loadedApk); WeakReference weakReference = new WeakReference(loadedApk); mPackages.put(applicationInfo.packageName, weakReference); ``` 我们的这个CustomClassLoader非常简单,直接继承了DexClassLoader,什么都没有做;当然这里可以直接使用DexClassLoader,这里重新创建一个类是为了更有区分度;以后也可以通过修改这个类实现对于类加载的控制: ```java public class CustomClassLoader extends DexClassLoader { public CustomClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) { super(dexPath, optimizedDirectory, libraryPath, parent); } } ``` 到这里,我们已经成功地把把插件的信息放入ActivityThread中,这样我们插件中的类能够成功地被加载;因此插件中的Activity实例能被成功第创建;由于整个流程较为复杂,我们简单梳理一下: 1. 在ActivityThread接收到IApplication的 scheduleLaunchActivity远程调用之后,将消息转发给`H` 2. `H`类在handleMessage的时候,调用了getPackageInfoNoCheck方法来获取待启动的组件信息。在这个方法中会优先查找`mPackages`中的缓存信息,而我们已经手动把插件信息添加进去;因此能够成功命中缓存,获取到独立存在的插件信息。 3. `H`类然后调用handleLaunchActivity最终转发到performLaunchActivity方法;这个方法使用从getPackageInfoNoCheck中拿到LoadedApk中的mClassLoader来加载Activity类,进而使用反射创建Activity实例;接着创建Application,Context等完成Activity组件的启动。 看起来好像已经天衣无缝万事大吉了;但是运行一下会出现一个异常,如下: ```java 04-05 02:49:53.742 11759-11759/com.weishu.upf.hook_classloader E/AndroidRuntime﹕ FATAL EXCEPTION: main Process: com.weishu.upf.hook_classloader, PID: 11759 java.lang.RuntimeException: Unable to start activity ComponentInfo{com.weishu.upf.ams_pms_hook.app/com.weishu.upf.ams_pms_hook.app.MainActivity}: java.lang.RuntimeException: Unable to instantiate application android.app.Application: java.lang.IllegalStateException: Unable to get package info for com.weishu.upf.ams_pms_hook.app; is package not installed? ``` 错误提示说是无法实例化 `Application`,而Application的创建也是在performLaunchActivity中进行的,这里有些蹊跷,我们仔细查看一下。 #### 绕过系统检查 通过ActivityThread的performLaunchActivity方法可以得知,Application通过LoadedApk的makeApplication方法创建,我们查看这个方法,在源码中发现了上文异常抛出的位置: ```java try { java.lang.ClassLoader cl = getClassLoader(); if (!mPackageName.equals("android")) { initializeJavaContextClassLoader(); } ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this); app = mActivityThread.mInstrumentation.newApplication( cl, appClass, appContext); appContext.setOuterContext(app); } catch (Exception e) { if (!mActivityThread.mInstrumentation.onException(app, e)) { throw new RuntimeException( "Unable to instantiate application " + appClass + ": " + e.toString(), e); } } ``` 木有办法,我们只有一行一行地查看到底是哪里抛出这个异常的了;所幸代码不多。(所以说,缩小异常范围是一件多么重要的事情!!!) 第一句 getClassLoader() 没什么可疑的,虽然方法很长,但是它木有抛出任何异常(当然,它调用的代码可能抛出异常,万一找不到只能进一步深搜了;所以我觉得这里应该使用受检异常)。 然后我们看第二句,如果包名不是`android`开头,那么调用了一个叫做initializeJavaContextClassLoader的方法;我们查阅这个方法: ```java private void initializeJavaContextClassLoader() { IPackageManager pm = ActivityThread.getPackageManager(); android.content.pm.PackageInfo pi; try { pi = pm.getPackageInfo(mPackageName, 0, UserHandle.myUserId()); } catch (RemoteException e) { throw new IllegalStateException("Unable to get package info for " + mPackageName + "; is system dying?", e); } if (pi == null) { throw new IllegalStateException("Unable to get package info for " + mPackageName + "; is package not installed?"); } boolean sharedUserIdSet = (pi.sharedUserId != null); boolean processNameNotDefault = (pi.applicationInfo != null && !mPackageName.equals(pi.applicationInfo.processName)); boolean sharable = (sharedUserIdSet || processNameNotDefault); ClassLoader contextClassLoader = (sharable) ? new WarningContextClassLoader() : mClassLoader; Thread.currentThread().setContextClassLoader(contextClassLoader); } ``` 这里,我们找出了这个异常的来源:原来这里调用了`getPackageInfo`方法获取包的信息;而我们的插件**并没有安装在系统上**,因此系统肯定认为插件没有安装,这个方法肯定返回null。所以,我们还要欺骗一下PMS,让系统觉得**插件已经安装在系统上了**;至于如何欺骗 PMS,[Hook机制之AMS&PMS][4] 有详细解释,这里直接给出代码,不赘述了: ```java private static void hookPackageManager() throws Exception { // 这一步是因为 initializeJavaContextClassLoader 这个方法内部无意中检查了这个包是否在系统安装 // 如果没有安装, 直接抛出异常, 这里需要临时Hook掉 PMS, 绕过这个检查. Class activityThreadClass = Class.forName("android.app.ActivityThread"); Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread"); currentActivityThreadMethod.setAccessible(true); Object currentActivityThread = currentActivityThreadMethod.invoke(null); // 获取ActivityThread里面原始的 sPackageManager Field sPackageManagerField = activityThreadClass.getDeclaredField("sPackageManager"); sPackageManagerField.setAccessible(true); Object sPackageManager = sPackageManagerField.get(currentActivityThread); // 准备好代理对象, 用来替换原始的对象 Class iPackageManagerInterface = Class.forName("android.content.pm.IPackageManager"); Object proxy = Proxy.newProxyInstance(iPackageManagerInterface.getClassLoader(), new Class[] { iPackageManagerInterface }, new IPackageManagerHookHandler(sPackageManager)); // 1. 替换掉ActivityThread里面的 sPackageManager 字段 sPackageManagerField.set(currentActivityThread, proxy); } ``` OK到这里,我们已经能够成功地加载**简单的**独立的存在于外部文件系统中的apk了。至此 关于 DroidPlugin 对于Activity生命周期的管理已经完全讲解完毕了;这是一种极其复杂的Activity管理方案,我们仅仅写一个用来理解的demo就Hook了相当多的东西,在Framework层来回牵扯;这其中的来龙去脉要完全把握清楚还请读者亲自翻阅源码。另外,我在此 对DroidPlugin 作者献上我的膝盖~这其中的玄妙让人叹为观止! 上文给出的方案中,我们全盘接管了插件中类的加载过程,这是一种相对暴力的解决方案;能不能更温柔一点呢?通俗来说,我们可以选择改革,而不是革命——告诉系统ClassLoader一些必要信息,让它帮忙完成插件类的加载。 ## 保守方案:委托系统,让系统帮忙加载 我们再次搬出ActivityThread中加载Activity类的代码: ```java java.lang.ClassLoader cl = r.packageInfo.getClassLoader(); activity = mInstrumentation.newActivity( cl, component.getClassName(), r.intent); StrictMode.incrementExpectedActivityCount(activity.getClass()); r.intent.setExtrasClassLoader(cl); ``` 我们知道 这个r.packageInfo中的`r`是通过getPackageInfoNoCheck获取到的;在『激进方案』中我们把插件apk手动添加进缓存,采用自己加载办法解决;如果我们不干预这个过程,导致无法命中mPackages中的缓存,会发生什么? 查阅 getPackageInfo方法如下: ```java private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo, ClassLoader baseLoader, boolean securityViolation, boolean includeCode, boolean registerPackage) { final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid)); synchronized (mResourcesManager) { WeakReference ref; if (differentUser) { // Caching not supported across users ref = null; } else if (includeCode) { ref = mPackages.get(aInfo.packageName); } else { ref = mResourcePackages.get(aInfo.packageName); } LoadedApk packageInfo = ref != null ? ref.get() : null; if (packageInfo == null || (packageInfo.mResources != null && !packageInfo.mResources.getAssets().isUpToDate())) { packageInfo = new LoadedApk(this, aInfo, compatInfo, baseLoader, securityViolation, includeCode && (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage); // 略 } } ``` 可以看到,没有命中缓存的情况下,系统直接new了一个LoadedApk;注意这个构造函数的第二个参数`aInfo`,这是一个ApplicationInfo类型的对象。在『激进方案』中我们为了获取独立插件的ApplicationInfo花了不少心思;那么如果不做任何处理这里传入的这个`aInfo`参数是什么? 追本溯源不难发现,这个aInfo是从我们的替身StubActivity中获取的!而StubActivity存在于宿主程序中,所以,这个`aInfo`对象代表的实际上就是宿主程序的Application信息! 我们知道,接下来会使用new出来的这个LoadedApk的getClassLoader()方法获取到ClassLoader来对插件的类进行加载;而获取到的这个ClassLoader是宿主程序使用的ClassLoader,因此现在还无法加载插件的类;那么,**我们能不能让宿主的ClasLoader获得加载插件类的能力呢?**;如果我们告诉宿主使用的ClassLoader插件使用的类在哪里,就能帮助他完成加载! ### 宿主的ClassLoader在哪里,是唯一的吗? 上面说到,我们可以通过告诉宿主程序的ClassLoader插件使用的类,让宿主的ClasLoader完成对于插件类的加载;那么问题来了,我们如何获取到宿主的ClassLoader?宿主程序使用的ClasLoader默认情况下是全局唯一的吗? 答案是肯定的。 因为在FrameWork中宿主程序也是使用LoadedApk表示的,如同Activity启动是加载Activity类一样,宿主中的类也都是通过LoadedApk的getClassLoader()方法得到的ClassLoader加载的;由类加载机制的『双亲委派』特性,只要有一个应用程序类由某一个ClassLoader加载,那么它引用到的别的类除非父加载器能加载,否则都是由这同一个加载器加载的(不遵循双亲委派模型的除外)。 表示宿主的LoadedApk在Application类中有一个成员变量`mLoadedApk`,而这个变量是从ContextImpl中获取的;ContextImpl重写了getClassLoader方法,因此**我们在Context环境中直接getClassLoader()获取到的就是宿主程序唯一的ClassLoader**。 ### LoadedApk的ClassLoader到底是什么? 现在我们确保了『使用宿主ClassLoader帮助加载插件类』可行性;那么我们应该如何完成这个过程呢? 知己知彼,百战不殆。 不论是宿主程序还是插件程序都是通过LoadedApk的getClassLoader()方法返回的ClassLoader进行类加载的,返回的这个ClassLoader到底是个什么东西??这个方法源码如下: ```java public ClassLoader getClassLoader() { synchronized (this) { if (mClassLoader != null) { return mClassLoader; } if (mIncludeCode && !mPackageName.equals("android")) { // 略... mClassLoader = ApplicationLoaders.getDefault().getClassLoader(zip, lib, mBaseClassLoader); StrictMode.setThreadPolicy(oldPolicy); } else { if (mBaseClassLoader == null) { mClassLoader = ClassLoader.getSystemClassLoader(); } else { mClassLoader = mBaseClassLoader; } } return mClassLoader; } } ``` 可以看到,非`android`开头的包和`android`开头的包分别使用了两种不同的ClassLoader,我们只关心第一种;因此继续跟踪ApplicationLoaders类: ```java public ClassLoader getClassLoader(String zip, String libPath, ClassLoader parent) { ClassLoader baseParent = ClassLoader.getSystemClassLoader().getParent(); synchronized (mLoaders) { if (parent == null) { parent = baseParent; } if (parent == baseParent) { ClassLoader loader = mLoaders.get(zip); if (loader != null) { return loader; } Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, zip); PathClassLoader pathClassloader = new PathClassLoader(zip, libPath, parent); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); mLoaders.put(zip, pathClassloader); return pathClassloader; } Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, zip); PathClassLoader pathClassloader = new PathClassLoader(zip, parent); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); return pathClassloader; } } ``` 可以看到,应用程序使用的ClassLoader都是PathClassLoader类的实例。那么,这个PathClassLoader是什么呢?从Android SDK给出的源码只能看出这么多: ```java public class PathClassLoader extends BaseDexClassLoader { public PathClassLoader(String dexPath, ClassLoader parent) { super((String)null, (File)null, (String)null, (ClassLoader)null); throw new RuntimeException("Stub!"); } public PathClassLoader(String dexPath, String libraryPath, ClassLoader parent) { super((String)null, (File)null, (String)null, (ClassLoader)null); throw new RuntimeException("Stub!"); } } ``` SDK没有导出这个类的源码,我们去[androidxref][5]上面看;发现其实这个类真的就这么多内容;我们继续查看它的父类[BaseDexClassLoader][6];ClassLoader嘛,我们查看findClass或者defineClass方法,BaseDexClassLoader的findClass方法如下: ```java protected Class findClass(String name) throws ClassNotFoundException { List suppressedExceptions = new ArrayList(); Class c = pathList.findClass(name, suppressedExceptions); if (c == null) { ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList); for (Throwable t : suppressedExceptions) { cnfe.addSuppressed(t); } throw cnfe; } return c; } ``` 可以看到,查找Class的任务通过`pathList`完成;这个`pathList`是一个`DexPathList`类的对象,它的`findClass`方法如下: ```java public Class findClass(String name, List suppressed) { for (Element element : dexElements) { DexFile dex = element.dexFile; if (dex != null) { Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed); if (clazz != null) { return clazz; } } } if (dexElementsSuppressedExceptions != null) { suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions)); } return null; } ``` 这个DexPathList内部有一个叫做dexElements的数组,然后findClass的时候会遍历这个数组来查找Class;**如果我们把插件的信息塞进这个数组里面,那么不就能够完成类的加载过程吗?!!** ### 给默认ClassLoader打补丁 通过上述分析,我们知道,可以把插件的相关信息放入BaseDexClassLoader的表示dex文件的数组里面,这样宿主程序的ClassLoader在进行类加载,遍历这个数组的时候,会自动遍历到我们添加进去的插件信息,从而完成插件类的加载! 接下来,我们实现这个过程;我们会用到一些较为复杂的反射技术哦~不过代码非常短: ```java public static void patchClassLoader(ClassLoader cl, File apkFile, File optDexFile) throws IllegalAccessException, NoSuchMethodException, IOException, InvocationTargetException, InstantiationException, NoSuchFieldException { // 获取 BaseDexClassLoader : pathList Field pathListField = DexClassLoader.class.getSuperclass().getDeclaredField("pathList"); pathListField.setAccessible(true); Object pathListObj = pathListField.get(cl); // 获取 PathList: Element[] dexElements Field dexElementArray = pathListObj.getClass().getDeclaredField("dexElements"); dexElementArray.setAccessible(true); Object[] dexElements = (Object[]) dexElementArray.get(pathListObj); // Element 类型 Class elementClass = dexElements.getClass().getComponentType(); // 创建一个数组, 用来替换原始的数组 Object[] newElements = (Object[]) Array.newInstance(elementClass, dexElements.length + 1); // 构造插件Element(File file, boolean isDirectory, File zip, DexFile dexFile) 这个构造函数 Constructor constructor = elementClass.getConstructor(File.class, boolean.class, File.class, DexFile.class); Object o = constructor.newInstance(apkFile, false, apkFile, DexFile.loadDex(apkFile.getCanonicalPath(), optDexFile.getAbsolutePath(), 0)); Object[] toAddElementArray = new Object[] { o }; // 把原始的elements复制进去 System.arraycopy(dexElements, 0, newElements, 0, dexElements.length); // 插件的那个element复制进去 System.arraycopy(toAddElementArray, 0, newElements, dexElements.length, toAddElementArray.length); // 替换 dexElementArray.set(pathListObj, newElements); } ``` 短短的二十几行代码,我们就完成了『委托宿主ClassLoader加载插件类』的任务;因此第二种方案也宣告完成!我们简要总结一下这种方式的原理: 1. 默认情况下performLacunchActivity会使用替身StubActivity的ApplicationInfo也就是宿主程序的CLassLoader加载所有的类;我们的思路是告诉宿主ClassLoader我们在哪,让其帮助完成类加载的过程。 2. 宿主程序的ClassLoader最终继承自BaseDexClassLoader,BaseDexClassLoader通过DexPathList进行类的查找过程;而这个查找通过遍历一个dexElements的数组完成;**我们通过把插件dex添加进这个数组**就让宿主ClasLoader获取了加载插件类的能力。 ## 小结 本文中我们采用两种方案成功完成了『启动没有在AndroidManifest.xml中显示声明,并且存在于外部插件中的Activity』的任务。 『激进方案』中我们自定义了插件的ClassLoader,并且绕开了Framework的检测;利用ActivityThread对于LoadedApk的缓存机制,我们把携带这个自定义的ClassLoader的插件信息添加进`mPackages`中,进而完成了类的加载过程。 『保守方案』中我们深入探究了系统使用ClassLoader findClass的过程,发现应用程序使用的非系统类都是通过同一个PathClassLoader加载的;而这个类的最终父类BaseDexClassLoader通过DexPathList完成类的查找过程;我们hack了这个查找过程,从而完成了插件类的加载。 这两种方案孰优孰劣呢? 很显然,『激进方案』比较麻烦,从代码量和分析过程就可以看出来,这种机制异常复杂;而且在解析apk的时候我们使用的PackageParser的兼容性非常差,我们不得不手动处理每一个版本的apk解析api;另外,它Hook的地方也有点多:不仅需要Hook AMS和`H`,还需要Hook ActivityThread的`mPackages`和PackageManager! 『保守方案』则简单得多(虽然原理也不简单),不仅代码很少,而且Hook的地方也不多;有一点正本清源的意思,从最最上层Hook住了整个类的加载过程。 但是,我们不能简单地说『保守方案』比『激进方案』好。从根本上说,这两种方案的差异在哪呢? 『激进方案』是**多ClassLoader构架**,每一个插件都有一个自己的ClassLoader,因此类的隔离性非常好——如果不同的插件使用了同一个库的不同版本,它们相安无事!『保守方案』是**单ClassLoader方案**,插件和宿主程序的类全部都通过宿主的ClasLoader加载,虽然代码简单,但是鲁棒性很差;一旦插件之间甚至插件与宿主之间使用的类库有冲突,那么直接GG。 多ClassLoader还有一个优点:可以真正完成代码的热加载!如果插件需要升级,直接重新创建一个自定的ClassLoader加载新的插件,然后替换掉原来的版本即可(Java中,不同ClassLoader加载的同一个类被认为是不同的类);单ClassLoader的话实现非常麻烦,有可能需要重启进程。 在J2EE领域中广泛使用ClasLoader的地方均采用多ClassLoader架构,比如Tomcat服务器,Java模块化事实标准的OSGi技术;所以,我们有足够的理由认为**选择多ClassLoader架构在大多数情况下是明智之举**。 目前开源的插件方案中,DroidPlugin采用的『激进方案』,Small采用的『保守方案』那么,有没有两种优点兼顾的方案呢?? 答案自然是有的。 DroidPlugin和Small的共同点是**两者都是非侵入式的插件框架**;什么是『非侵入式』呢?打个比方,你启动一个插件Activity,直接使用`startActivity`即可,就跟开发普通的apk一样,开发插件和普通的程序对于开发者来说没有什么区别。 如果我们一定程度上放弃这种『侵入性』,那么我们就能实现一个两者优点兼而有之的插件框架!这里我先卖个关子~ OK,本文的内容就到这里了;关于『插件机制对于Activity的处理方式』也就此完结。要说明的是,在本文的『保守方案』其实只处理了代码的加载过程,它并不能加载有资源的apk!所以目前我这个实现基本没什么暖用;当然我这里只是就『代码加载』进行举例;至于资源,那牵扯到另外一个问题——**插件系统的资源管理机制**这个在后续文章的合适机会我会单独讲解。 [1]: 概述.md [2]: https://github.com/tiann/understand-plugin-framework [3]: Activity生命周期管理.md [4]: Hook机制之AMS&PMS.md [5]: http://androidxref.com/6.0.1_r10/xref/libcore/dalvik/src/main/java/dalvik/system/PathClassLoader.java [6]: http://androidxref.com/6.0.1_r10/xref/libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java [7]: Hook机制之Binder-Hook.md ================================================ FILE: DOC/tianweishu/ContentProvider插件化.md ================================================ # ContentProvider插件化 目前为止我们已经完成了Android四大组件中Activity,Service以及BroadcastReceiver的插件化,这几个组件各不相同,我们根据它们的特点定制了不同的插件化方案;那么对于ContentProvider,它又有什么特点?应该如何实现它的插件化? 与Activity,BroadcastReceiver等频繁被使用的组件不同,我们接触和使用ContentProvider的机会要少得多;但是,ContentProvider这个组件对于Android系统有着特别重要的作用——作为一种极其方便的**数据共享**的手段,ContentProvider使得广大第三方App能够在壁垒森严的系统中自由呼吸。 在Android系统中,每一个应用程序都有自己的用户ID,而每一个应用程序所创建的文件的读写权限都是只赋予给自己所属的用户,这就限制了应用程序之间相互读写数据的操作。应用程序之间如果希望能够进行交互,只能采取跨进程通信的方式;Binder机制能够满足一般的IPC需求,但是如果应用程序之间需要共享大量数据,单纯使用Binder是很难办到的——我相信大家对于Binder 1M缓冲区以及TransactionTooLargeException一定不陌生;ContentProvider使用了匿名共享内存(Ashmem)机制完成数据共享,因此它可以很方便地完成大量数据的传输。Android系统的短信,联系人,相册,媒体库等等一系列的基础功能都依赖与ContentProvider,它的重要性可见一斑。 既然ContentProvider的核心特性是数据共享,那么要实现它的插件化,必须能让插件能够把它的ContentProvider共享给系统——如果不能「**provide content**」那还叫什么ContentProvider? 但是,如果回想一下Activity等组件的插件化方式,在涉及到「共享」这个问题上,一直没有较好的解决方案: 1. 系统中的第三方App无法启动插件中带有特定IntentFilter的Activity,因为系统压根儿感受不到插件中这个真正的Activity的存在。 2. 插件中的静态注册的广播并不真正是静态的,而是使用动态注册广播模拟实现的;这就导致如果宿主程序进程死亡,这个静态广播不会起作用;这个问题的根本原因在由于BroadcastReceiver的IntentFilter的不可预知性,使得我们没有办法把静态广播真正“共享”给系统。 3. 我们没有办法在第三方App中启动或者绑定插件中的Service组件;因为插件的Service并不是真正的Service组件,系统能感知到的只是那个代理Service;因此如果插件如果带有远程Service组件,它根本不能给第三方App提供远程服务。 虽然在插件系统中一派生机勃勃的景象,Activity,Service等插件组件百花齐放,插件与宿主、插件与插件争奇斗艳;但是一旦脱离了插件系统的温室,这一片和谐景象不复存在:插件组件不过是傀儡而已;活着的,只有宿主——整个插件系统就是一座死寂的鬼城,各个插件组件借尸还魂般地依附在宿主身上,了无生机。 既然希望把插件的ContentProvider共享给整个系统,让第三方的App都能获取到我们插件共享的数据,我们必须解决这个问题;下文将会围绕这个目标展开,完成ContentProvider的插件化,并且顺带给出上述问题的解决方案。阅读本文之前,可以先clone一份 [understand-plugin-framework][1],参考此项目的 contentprovider-management 模块。另外,插件框架原理解析系列文章见 [索引][2]。 ## ContentProvider工作原理 首先我们还是得分析一下ContentProvider的工作原理,很多插件化的思路,以及一些Hook点的发现都严重依赖于对于系统工作原理的理解;对于ContentProvider的插件化,这一点特别重要。 ### 铺垫工作 如同我们通过`startActivity`来启动Activity一样,与ContentProvider打交道的过程也是从Context类的一个方法开始的,这个方法叫做`getContentResolver`,使用ContentProvider的典型代码如下: ```java ContentResolver resolver = content.getContentResolver(); resolver.query(Uri.parse("content://authority/test"), null, null, null, null); ``` 直接去ContextImpl类里面查找的`getContentResolver`实现,发现这个方法返回的类型是android.app.ContextImpl.ApplicationContentResolver,这个类是抽象类android.content.ContentResolver的子类,`resolver.query`实际上是调用父类ContentResolver的`query`实现: ```java public final @Nullable Cursor query(final @NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder, @Nullable CancellationSignal cancellationSignal) { Preconditions.checkNotNull(uri, "uri"); IContentProvider unstableProvider = acquireUnstableProvider(uri); if (unstableProvider == null) { return null; } IContentProvider stableProvider = null; Cursor qCursor = null; try { long startTime = SystemClock.uptimeMillis(); ICancellationSignal remoteCancellationSignal = null; if (cancellationSignal != null) { cancellationSignal.throwIfCanceled(); remoteCancellationSignal = unstableProvider.createCancellationSignal(); cancellationSignal.setRemote(remoteCancellationSignal); } try { qCursor = unstableProvider.query(mPackageName, uri, projection, selection, selectionArgs, sortOrder, remoteCancellationSignal); } catch (DeadObjectException e) { // The remote process has died... but we only hold an unstable // reference though, so we might recover!!! Let's try!!!! // This is exciting!!1!!1!!!!1 unstableProviderDied(unstableProvider); stableProvider = acquireProvider(uri); if (stableProvider == null) { return null; } qCursor = stableProvider.query(mPackageName, uri, projection, selection, selectionArgs, sortOrder, remoteCancellationSignal); } // 略... } ``` 注意这里面的那个`try..catch`语句,`query`方法首先尝试调用抽象方法acquireUnstableProvider拿到一个IContentProvider对象,并尝试调用这个"unstable"对象的`query`方法,万一调用失败(抛出DeadObjectExceptopn,熟悉Binder的应该了解这个异常)说明ContentProvider所在的进程已经死亡,这时候会尝试调用`acquireProvider`这个抽象方法来获取一个可用的IContentProvider(代码里面那个萌萌的注释说明了一切^_^);由于这两个`acquire*`都是抽象方法,我们可以直接看子类`ApplicationContentResolver`的实现: ```java @Override protected IContentProvider acquireUnstableProvider(Context c, String auth) { return mMainThread.acquireProvider(c, ContentProvider.getAuthorityWithoutUserId(auth), resolveUserIdFromAuthority(auth), false); } @Override protected IContentProvider acquireProvider(Context context, String auth) { return mMainThread.acquireProvider(context, ContentProvider.getAuthorityWithoutUserId(auth), resolveUserIdFromAuthority(auth), true); } ``` 可以看到这两个抽象方法最终都通过调用`ActivityThread`类的`acquireProvider`获取到IContentProvider,接下来我们看看到底是如何获取到ContentProvider的。 ### ContentProvider获取过程 ActivityThread类的`acquireProvider`方法如下,我们需要知道的是,方法的最后一个参数`stable`代表着ContentProvider所在的进程是否存活,如果进程已死,可能需要在必要的时候唤起这个进程; ```java public final IContentProvider acquireProvider( Context c, String auth, int userId, boolean stable) { final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable); if (provider != null) { return provider; } IActivityManager.ContentProviderHolder holder = null; try { holder = ActivityManagerNative.getDefault().getContentProvider( getApplicationThread(), auth, userId, stable); } catch (RemoteException ex) { } if (holder == null) { Slog.e(TAG, "Failed to find provider info for " + auth); return null; } holder = installProvider(c, holder, holder.info, true /*noisy*/, holder.noReleaseNeeded, stable); return holder.provider; } ``` 这个方法首先通过`acquireExistingProvider`尝试从本进程中获取ContentProvider,如果获取不到,那么再请求`AMS`获取对应ContentProvider;想象一下,如果你查询的是自己App内部的ContentProvider组件,干嘛要劳烦AMS呢?不论是从哪里获取到的ContentProvider,获取完毕之后会调用`installProvider`来安装ContentProvider。 OK打住,我们思考一下,如果要实现ContentProvider的插件化,我们需要完成一些什么工作?开篇的时候我提到了数据共享,那么具体来说,实现插件的数据共享,需要完成什么?ContentProvider是一个数据共享组件,也就是说它不过是**一个携带数据的载体而已**。为了支持跨进程共享,这个载体是**Binder调用**,为了共享大量数据,使用了匿名共享内存;这么说还是有点抽象,那么想一下,给出一个ContentProvider,你能对它做一些什么操作?如果能让插件支持这些操作,不就支持了插件化么?这就是典型的duck type思想——如果一个东西看起来像ContentProvider,用起来也像ContentProvider,那么它就是ContentProvider。 ContentProvider主要支持`query, insert, update, delete`操作,由于这个组件一般工作在别的进程,因此这些调用都是Binder调用。从上面的代码可以看到,这些调用最终都是委托给一个IContentProvider的Binder对象完成的,如果我们Hook掉这个对象,那么对于ContentProvider的所有操作都会被我们拦截掉,这时候我们可以做进一步的操作来完成对于插件ContentProvider组件的支持。要拦截这个过程,我们可以**假装插件的ContentProvider是自己App的ContentProvider**,也就是说,让`acquireExistingProvider`方法可以直接获取到插件的ContentProvider,这样我们就不需要欺骗AMS就能完成插件化了。当然,你也可以选择Hook掉AMS,让AMS的`getContentProvider`方法返回被我们处理过的对象,这也是可行的;但是,为什么要舍近求远呢? 从上文的分析暂时得出结论:我们可以把插件的ContentProvider信息预先放在App进程内部,使得对于ContentProvider执行CURD操作的时候,可以获取到插件的组件,这样或许就可以实现插件化了。具体来说,我们要做的事情就是让`ActivityThread`的`acquireExistingProvider`方法能够返回插件的ContentProvider信息,我们看看这个方法的实现: ```java public final IContentProvider acquireExistingProvider( Context c, String auth, int userId, boolean stable) { synchronized (mProviderMap) { final ProviderKey key = new ProviderKey(auth, userId); final ProviderClientRecord pr = mProviderMap.get(key); if (pr == null) { return null; } // 略。。 } } ``` 可以看出,App内部自己的ContentProvider信息保存在ActivityThread类的`mProviderMap`中,这个map的类型是ArrayMap;我们当然可以通过反射修改这个成员变量,直接把插件的ContentProvider信息填进去,但是这个ProviderClientRecord对象如何构造?我们姑且看看系统自己是如果填充这个字段的。在ActivityThread类中搜索一遍,发现调用mProviderMap对象的`put`方法的之后`installProviderAuthoritiesLocked`,而这个方法最终被`installProvider`方法调用。在分析ContentProvider的获取过程中我们已经知道,不论是通过本进程的`acquireExistingProvider`还是借助AMS的`getContentProvider`得到ContentProvider,最终都会对这个对象执行`installProvider`操作,也就是「安装」在本进程内部。那么,我们接着看这个`installProvider`做了什么,它是如何「安装」ContentProvider的。 ### 进程内部ContentProvider安装过程 首先,如果之前没有“安装”过,那么holder为null,下面的代码会被执行, ```java final java.lang.ClassLoader cl = c.getClassLoader(); localProvider = (ContentProvider)cl. loadClass(info.name).newInstance(); provider = localProvider.getIContentProvider(); if (provider == null) { Slog.e(TAG, "Failed to instantiate class " + info.name + " from sourceDir " + info.applicationInfo.sourceDir); return null; } if (DEBUG_PROVIDER) Slog.v( TAG, "Instantiating local provider " + info.name); // XXX Need to create the correct context for this provider. localProvider.attachInfo(c, info); ``` 比较直观,直接load这个ContentProvider所在的类,然后用反射创建出这个ContentProvider对象;但是由于查询是需要进行跨进程通信的,在本进程创建出这个对象意义不大,所以我们需要取出ContentProvider承载跨进程通信的Binder对象IContentProvider;创建出对象之后,接下来就是构建合适的信息,保存在ActivityThread内部,也就是`mProviderMap`: ```java if (localProvider != null) { ComponentName cname = new ComponentName(info.packageName, info.name); ProviderClientRecord pr = mLocalProvidersByName.get(cname); if (pr != null) { if (DEBUG_PROVIDER) { Slog.v(TAG, "installProvider: lost the race, " + "using existing local provider"); } provider = pr.mProvider; } else { holder = new IActivityManager.ContentProviderHolder(info); holder.provider = provider; holder.noReleaseNeeded = true; pr = installProviderAuthoritiesLocked(provider, localProvider, holder); mLocalProviders.put(jBinder, pr); mLocalProvidersByName.put(cname, pr); } retHolder = pr.mHolder; } else { ``` 以上就是安装代码,不难理解。 ### 思路尝试——本地安装 那么,了解了「安装」过程再结合上文的分析,我们似乎可以完成ContentProvider的插件化了——直接把插件的ContentProvider安装在进程内部就行了。如果插件系统有多个进程,那么必须在每个进程都「安装」一遍,如果你熟悉Android进程的启动流程那么就会知道,这个安装ContentProvider的过程适合放在Application类中,因为每个Android进程启动的时候,App的Application类是会被启动的。 看起来实现ContentProvider的思路有了,但是这里实际上有一个严重的缺陷! 我们依然没有解决「共享」的问题。我们只是在插件系统启动的进程里面的ActivityThread的`mProviderMap`给修改了,这使得只有通过插件系统启动的进程,才能感知到插件中的ContentProvider(因为我们手动把插件中的信息install到这个进程中去了);如果第三方的App想要使用插件的ContentProvider,那系统只会告诉它查无此人。 那么,我们应该如何解决共享这个问题呢?看来还是逃不过AMS的魔掌,我们继续跟踪源码,看看如果在本进程查询不到ContentProvider,AMS是如何完成这个过程的。在ActivityThread的`acquireProvider`方法中我们提到,如果`acquireExistingProvider`方法返回null,会调用ActivityManagerNative的`getContentProvider`方法通过AMS查询整个系统中是否存在需要的这个ContentProvider。如果第三方App查询插件系统的ContentProvider必然走的是这个流程,我们仔细分析一下这个过程; ### AMS中的ContentProvider 首先我们查阅ActivityManagerService的`getContentProvider`方法,这个方法间接调用了`getContentProviderImpl`方法;`getContentProviderImpl`方法体相当的长,但是实际上只做了两件事件事(我这就不贴代码了,读者可以对着源码看一遍): 1. 使用PackageManagerService的resolveContentProvider根据Uri中提供的auth信息查阅对应的ContentProivoder的信息ProviderInfo。 2. 根据查询到的ContentProvider信息,尝试将这个ContentProvider组件安装到系统上。 #### 查询ContentProvider组件的过程 查询ContentProvider组件的过程看起来很简单,直接调用PackageManager的`resolveContentProvider`就能从URI中获取到对应的`ProviderInfo`信息: ```java @Override public ProviderInfo resolveContentProvider(String name, int flags, int userId) { if (!sUserManager.exists(userId)) return null; // reader synchronized (mPackages) { final PackageParser.Provider provider = mProvidersByAuthority.get(name); PackageSetting ps = provider != null ? mSettings.mPackages.get(provider.owner.packageName) : null; return ps != null && mSettings.isEnabledLPr(provider.info, flags, userId) && (!mSafeMode || (provider.info.applicationInfo.flags &ApplicationInfo.FLAG_SYSTEM) != 0) ? PackageParser.generateProviderInfo(provider, flags, ps.readUserState(userId), userId) : null; } } ``` 但是实际上我们关心的是,这个`mProvidersByAuthority`里面的信息是如何添加进PackageManagerService的,会在什么时候更新?在PackageManagerService这个类中搜索mProvidersByAuthority.put这个调用,会发现在`scanPackageDirtyLI`会更新`mProvidersByAuthority`这个map的信息,接着往前追踪会发现:**这些信息是在Android系统启动的时候收集的**。也就是说,Android系统在启动的时候会扫描一些App的安装目录,典型的比如/data/app/*,获取这个目录里面的apk文件,读取其AndroidManifest.xml中的信息,然后把这些信息保存在PackageManagerService中。合理猜测,在系统启动之后,安装新的App也会触发对新App中AndroidManifest.xml的操作,感兴趣的读者可以自行翻阅源码。 现在我们知道,查询ContentProvider的信息来源在Android系统启动的时候已经初始化好了,这个过程对于我们第三方app来说是鞭长莫及,想要使用类似在进程内部Hack ContentProvider的查找过程是不可能的。 #### 安装ContentProvider组件的过程 获取到URI对应的ContentProvider的信息之后,接下来就是把它安装到系统上了,这样以后有别的查询操作就可以直接拿来使用;但是这个安装过程AMS是没有办法以一己之力完成的。想象一下App DemoA 查询App DemoB 的某个ContentProviderAppB,那么这个ContentProviderAppB必然存在于DemoB这个App中,AMS所在的进程(system_server)连这个ContentProviderAppB的类都没有,因此,AMS必须委托DemoB完成它的ContentProviderAppB的安装;这里就分两种情况:其一,DemoB这个App已经在运行了,那么AMS直接通知DemoB安装ContentProviderAppB(如果B已经安装了那就更好了);其二,DemoB这个app没在运行,那么必须把B进程唤醒,让它干活;这个过程也就是ActivityManagerService的`getContentProviderImpl`方法所做的,如下代码: ```java if (proc != null && proc.thread != null) { if (!proc.pubProviders.containsKey(cpi.name)) { proc.pubProviders.put(cpi.name, cpr); try { proc.thread.scheduleInstallProvider(cpi); } catch (RemoteException e) { } } } else { proc = startProcessLocked(cpi.processName, cpr.appInfo, false, 0, "content provider", new ComponentName(cpi.applicationInfo.packageName, cpi.name), false, false, false); if (proc == null) { return null; } } ``` 如果查询的ContentProvider所在进程处于运行状态,那么AMS会通过这个进程给AMS的ApplicationThread这个Binder对象完成scheduleInstallProvider调用,这个过程比较简单,最终会调用到目标进程的`installProvider`方法,而这个方法我们在上文已经分析过了。我们看一下如果目标进程没有启动,会发生什么情况。 如果ContentProvider所在的进程已经死亡,那么会调用startProcessLocked来启动新的进程,`startProcessLocked`有一系列重载函数,我们一路跟踪,发现最终启动进程的操作交给了`Process`类的`start`方法完成,这个方法通过socket与Zygote进程进行通信,通知Zygote进程fork出一个子进程,然后通过反射调用了之前传递过来的一个入口类的main函数,一般来说这个入口类就是ActivityThread,因此子进程fork出来之后会执行ActivityThread类的main函数。 在我们继续观察子进程ActivityThread的main函数执行之前,我们看看AMS进程这时候会干什么——startProcessLocked之后AMS进程和fork出来的DemoB进程分道扬镳;AMS会继续往下面执行。我们暂时回到AMS的`getContentProviderImpl`方法: ```java // Wait for the provider to be published... synchronized (cpr) { while (cpr.provider == null) { if (cpr.launchingApp == null) { return null; } try { if (conn != null) { conn.waiting = true; } cpr.wait(); } catch (InterruptedException ex) { } finally { if (conn != null) { conn.waiting = false; } } } } ``` 你没看错,一个死循环就是糊在上面:AMS进程会通过一个死循环等到进程B完成ContentProvider的安装,等待完成之后会把ContentProvider的信息返回给进程A。那么,我们现在的疑惑是,**进程B在启动之后,在哪个时间点会完成ContentProvider的安装呢?** 我们接着看ActivityThread的main函数,顺便寻找我们上面那个问题的答案;这个分析实际上就是Android App的启动过程,更详细的过程可以参阅老罗的文章 [Android应用程序启动过程源代码分析][4],这里只给出简要调用流程: ![App启动简要流程](http://7xp3xc.com1.z0.glb.clouddn.com/201605/1468313182087.png) 最终,DemoB进程启动之后会执行ActivityThread类的handleBindApplication方法,这个方法相当之长,基本完成了App进程启动之后所有必要的操作;这里我们只关心ContentProvider相关的初始化操作,代码如下: ```java // If the app is being launched for full backup or restore, bring it up in // a restricted environment with the base application class. Application app = data.info.makeApplication(data.restrictedBackupMode, null); mInitialApplication = app; // don't bring up providers in restricted mode; they may depend on the // app's custom Application class if (!data.restrictedBackupMode) { List providers = data.providers; if (providers != null) { installContentProviders(app, providers); // For process that contains content providers, we want to // ensure that the JIT is enabled "at some point". mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000); } } // Do this after providers, since instrumentation tests generally start their // test thread at this point, and we don't want that racing. try { mInstrumentation.onCreate(data.instrumentationArgs); } catch (Exception e) { } try { mInstrumentation.callApplicationOnCreate(app); } catch (Exception e) { } ``` 仔细观察以上代码,你会发现:**ContentProvider的安装比Application的onCreate回调还要早!!**因此,分析到这里我们已经明白了前面提出的那个问题,**进程启动之后会在Applition类的onCreate 回调之前,在Application对象创建之后完成ContentProvider的安装**。 然后不要忘了,我们的AMS进程还在那傻傻等待DemoB进程完成ContentProviderAppB的安装呢!在DemoB的Application的onCreate回调之前,DemoB的ContentProviderAppB已经安装好了,因此AMS停止等待,把DemoB安装的结果返回给请求这个ContentProvider的DemoA。我们必须对这个时序保持敏感,有时候就是失之毫厘,差之千里!! 到这里,有关ContentProvider的调用过程以及简要的工作原理我们已经分析完毕,关于它如何共享数据,如何使用匿名共享内存这部分不是插件化的重点,感兴趣的可以参考 [Android应用程序组件Content Provider在应用程序之间共享数据的原理分析][5]。 ## 不同之处 在实现ContentProvider的插件化之前,通过分析这个组件的工作原理,我们可以得出它的一些与众不同的特性: 1. ContentProvider本身是用来共享数据的,因此它提供一般的CURD服务;它类似HTTP这种无状态的服务,没有Activity,Service所谓的生命周期的概念,服务要么可用,要么不可用;对应着ContentProvider要么启动,要么随着进程死亡;而通常情况下,死亡之后还会被系统启动。所以,ContentProvider,只要有人需要这个服务,系统可以保证是永生的;这是与其他组件的最大不同;完全不用考虑生命周期的概念。 2. ContentProvider被设计为共享数据,这种数据量一般来说是相当大的;熟悉Binder的人应该知道,Binder进行数据传输有1M限制,因此如果要使用Binder传输大数据,必须使用类似socket的方式一段一段的读,也就是说需要自己在上层架设一层协议;ContentProvider并没有采取这种方式,而是采用了Android系统的匿名共享内存机制,利用Binder来传输这个文件描述符,进而实现文件的共享;这是第二个不同,因为其他的三个组建通信都是基于Binder的,只有ContentProvider使用了Ashmem。 3. 一个App启动过程中,ContentProvider组件的启动是非常早的,甚至比Application的onCreate还要早;我们可以利用这个特性结合它不死的特点,完成一些有意义的事情。 4. ContentProvider存在优先查询本进程的特点,使得它的插件化甚至不需要Hook AMS就能完成。 ## 思路分析 在分析ContentProvider的工作原理的过程中我们提出了一种插件化方案:在进程启动之初,手动把ContentProvider安装到本进程,使得后续对于插件ContentProvider的请求能够顺利完成。我们也指出它的一个严重缺陷,那就是它只能在插件系统内部掩耳盗铃,在插件系统之外,第三方App依然无法感知到插件中的ContentProvider的存在。 如果插件的ContentProvider组件仅仅是为了共享给其他插件或者宿主程序使用,那么这种方案可以解决问题;不需要Hook AMS,非常简单。 但是,如果希望把插件ContenProvider共享给整个系统呢?在分析AMS中获取ContentProvider的过程中我们了解到,ContentProvider信息的注册是在Android系统启动或者新安装App的时候完成的,而AMS把ContentProvider返回给第三方App也是在system_server进程完成;我们无法对其暗箱操作。 在完成Activity,Service组件的插件化之后,这种限制对我们来说已经是小case了:我们在宿主程序里面注册一个货真价实、被系统认可的StubContentProvider组件,把这个组件共享给第三方App;然后通过**代理分发技术**把第三方App对于插件ContentProvider的请求通过这个StubContentProvider分发给对应的插件。 但是这还存在一个问题,由于第三方App查阅的其实是StubContentProvider,因此他们查阅的URI也必然是StubContentProvider的authority,要查询到插件的ContentProvider,必须把要查询的真正的插件ContentProvider信息传递进来。这个问题的解决方案也很容易,我们可以制定一个「插件查询协议」来实现。 举个例子,假设插件系统的宿主程序在AndroidManifest.xml中注册了一个StubContentProvider,它的Authority为`com.test.host_authority`;由于这个组件被注册在AndroidManifest.xml中,是系统认可的ContentProvider组件,整个系统都是可以使用这个共享组件的,使用它的URI一般为`content://com.test.host_authority`;那么,如果插件系统中存在一个插件,这个插件提供了一个PluginContentProvider,它的Authority为`com.test.plugin_authorith`,因为这个插件的PluginContentProvider没有在宿主程序的AndroidMainifest.xml中注册(预先注册就失去插件的意义了),整个系统是无法感知到它的存在的;前面提到代理分发技术,也就是,我们让第三方App请求宿主程序的StubContentProvider,这个StubContentProvider把请求转发给合适的插件的ContentProvider就能完成了(插件内部通过预先installProvider可以查询所有的ContentProvider组件);这个协议可以有很多,比如说:如果第三方App需要请求插件的StubContentProvider,可以以`content://com.test.host_authority/com.test.plugin_authorith`去查询系统;也就是说,我们假装请求StubContentProvider,把真正的需要请求的PluginContentProvider的Authority放在路径参数里面,StubContentProvider收到这个请求之后,拿到这个真正的Authority去请求插件的PluginContentProvider,拿到结果之后再返回给第三方App。 这样,我们通过「代理分发技术」以及「插件查询协议」可以完美解决「共享」的问题,开篇提到了我们之前对于Activity,Service组件插件化方案中对于「共享」功能的缺失,按照这个思路,基本可以解决这一系列问题。比如,对于第三方App无法绑定插件服务的问题,我们可以注册一个StubService,把真正需要bind的插件服务信息放在intent的某个字段中,然后在StubService的onBind中解析出这个插件服务信息,然后去拿到插件Service组件的Binder对象返回给第三方。 ## 实现 上文详细分析了如何实现ContentProvider的插件化,接下来我们就实现这个过程。 ### 预先installProvider 要实现预先installProvider,我们首先需要知道,所谓的「预先」到底是在什么时候? 前文我们提到过App进程安装ContentProvider的时机非常之早,在Application类的onCreate回调执行之前已经完成了;这意味着什么? 现在我们对于ContentProvider插件化的实现方式是通过「代理分发技术」,也就是说在请求插件ContentProvider的时候会先请求宿主程序的StubContentProvider;如果一个第三方App查询插件的ContentProvider,而宿主程序没有启动的话,AMS会启动宿主程序并等待宿主程序的StubContentProvider完成安装,**一旦安装完成就会把得到的IContentProvider返回给这个第三方App**;第三方App拿到IContentProvider这个Binder对象之后就可能发起CURD操作,如果这个时候插件ContentProvider还没有启动,那么肯定就会出异常;要记住,“这个时候”可能宿主程序的onCreate还没有执行完毕呢!! 所以,我们基本可以得出结论,预先安装这个所谓的「预先」必须早于Application的onCreate方法,在Android SDK给我们的回调里面,attachBaseContent这个方法是可以满足要求的,它在Application这个对象被创建之后就会立即调用。 解决了时机问题,那么我们接下来就可以安装ContentProvider了。 安装ContentProvider也就是要调用ActivityThread类的`installProvider`方法,这个方法需要的参数有点多,而且它的第二个参数IActivityManager.ContentProviderHolder是一个隐藏类,我们不知道如何构造,就算通过反射构造由于SDK没有暴露稳定性不易保证,我们看看有什么方法调用了这个installProvider。 installContentProviders这个方法直接调用installProvder看起来可以使用,但是它是一个private的方法,还有public的方法吗?继续往上寻找调用链,发现了installSystemProviders这个方法: ```java public final void installSystemProviders(List providers) { if (providers != null) { installContentProviders(mInitialApplication, providers); } } ``` 但是,我们说过ContentProvider的安装必须相当早,必须在Application类的attachBaseContent方法内,而这个`mInitialApplication`字段是在`onCreate`方法调用之后初始化的,所以,如果直接使用这个`installSystemProviders`势必抛出空指针异常;因此,我们只有退而求其次,选择**通过installContentProviders这个方法完成ContentProvider的安装** 要调用这个方法必须拿到ContentProvider对应的ProviderInfo,这个我们在之前也介绍过,可以通过PackageParser类完成,当然这个类有一些兼容性问题,我们需要手动处理: ```java /** * 解析Apk文件中的 , 并存储起来 * 主要是调用PackageParser类的generateProviderInfo方法 * * @param apkFile 插件对应的apk文件 * @throws Exception 解析出错或者反射调用出错, 均会抛出异常 */ public static List parseProviders(File apkFile) throws Exception { Class packageParserClass = Class.forName("android.content.pm.PackageParser"); Method parsePackageMethod = packageParserClass.getDeclaredMethod("parsePackage", File.class, int.class); Object packageParser = packageParserClass.newInstance(); // 首先调用parsePackage获取到apk对象对应的Package对象 Object packageObj = parsePackageMethod.invoke(packageParser, apkFile, PackageManager.GET_PROVIDERS); // 读取Package对象里面的services字段 // 接下来要做的就是根据这个List 获取到Provider对应的ProviderInfo Field providersField = packageObj.getClass().getDeclaredField("providers"); List providers = (List) providersField.get(packageObj); // 调用generateProviderInfo 方法, 把PackageParser.Provider转换成ProviderInfo Class packageParser$ProviderClass = Class.forName("android.content.pm.PackageParser$Provider"); Class packageUserStateClass = Class.forName("android.content.pm.PackageUserState"); Class userHandler = Class.forName("android.os.UserHandle"); Method getCallingUserIdMethod = userHandler.getDeclaredMethod("getCallingUserId"); int userId = (Integer) getCallingUserIdMethod.invoke(null); Object defaultUserState = packageUserStateClass.newInstance(); // 需要调用 android.content.pm.PackageParser#generateProviderInfo Method generateProviderInfo = packageParserClass.getDeclaredMethod("generateProviderInfo", packageParser$ProviderClass, int.class, packageUserStateClass, int.class); List ret = new ArrayList<>(); // 解析出intent对应的Provider组件 for (Object service : providers) { ProviderInfo info = (ProviderInfo) generateProviderInfo.invoke(packageParser, service, 0, defaultUserState, userId); ret.add(info); } return ret; } ``` 解析出ProviderInfo之后,就可以直接调用installContentProvider了: ```java /** * 在进程内部安装provider, 也就是调用 ActivityThread.installContentProviders方法 * * @param context you know * @param apkFile * @throws Exception */ public static void installProviders(Context context, File apkFile) throws Exception { List providerInfos = parseProviders(apkFile); for (ProviderInfo providerInfo : providerInfos) { providerInfo.applicationInfo.packageName = context.getPackageName(); } Log.d("test", providerInfos.toString()); Class activityThreadClass = Class.forName("android.app.ActivityThread"); Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread"); Object currentActivityThread = currentActivityThreadMethod.invoke(null); Method installProvidersMethod = activityThreadClass.getDeclaredMethod("installContentProviders", Context.class, List.class); installProvidersMethod.setAccessible(true); installProvidersMethod.invoke(currentActivityThread, context, providerInfos); } ``` 整个安装过程**必须在Application类的attachBaseContent里面完成**: ```java /** * 一定需要Application,并且在attachBaseContext里面Hook * 因为provider的初始化非常早,比Application的onCreate还要早 * 在别的地方hook都晚了。 * * @author weishu * @date 16/3/29 */ public class UPFApplication extends Application { @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); try { File apkFile = getFileStreamPath("testcontentprovider-debug.apk"); if (!apkFile.exists()) { Utils.extractAssets(base, "testcontentprovider-debug.apk"); } File odexFile = getFileStreamPath("test.odex"); // Hook ClassLoader, 让插件中的类能够被成功加载 BaseDexClassLoaderHookHelper.patchClassLoader(getClassLoader(), apkFile, odexFile); ProviderHelper.installProviders(base, getFileStreamPath("testcontentprovider-debug.apk")); } catch (Exception e) { throw new RuntimeException("hook failed", e); } } } ``` ### 代理分发以及协议解析 把插件中的ContentProvider安装到插件系统中之后,在插件内部就可以自由使用这些ContentProvider了;要把这些插件共享给整个系统,我们还需要一个货真价实的ContentProvider组件来执行分发: ```xml ``` 第三方App如果要查询到插件的ContentProvider,必须遵循一个「插件查询协议」,这样StubContentProvider才能把对于插件的请求分发到正确的插件组件: ```java /** * 为了使得插件的ContentProvder提供给外部使用,我们需要一个StubProvider做中转; * 如果外部程序需要使用插件系统中插件的ContentProvider,不能直接查询原来的那个uri * 我们对uri做一些手脚,使得插件系统能识别这个uri; * * 这里的处理方式如下: * * 原始查询插件的URI应该为: * content://plugin_auth/path/query * * 如果需要查询插件,需要修改为: * * content://stub_auth/plugin_auth/path/query * * 也就是,我们把插件ContentProvider的信息放在URI的path中保存起来; * 然后在StubProvider中做分发。 * * 当然,也可以使用QueryParamerter,比如: * content://plugin_auth/path/query/ -> content://stub_auth/path/query?plugin=plugin_auth * @param raw 外部查询我们使用的URI * @return 插件真正的URI */ private Uri getRealUri(Uri raw) { String rawAuth = raw.getAuthority(); if (!AUTHORITY.equals(rawAuth)) { Log.w(TAG, "rawAuth:" + rawAuth); } String uriString = raw.toString(); uriString = uriString.replaceAll(rawAuth + '/', ""); Uri newUri = Uri.parse(uriString); Log.i(TAG, "realUri:" + newUri); return newUri; } ``` 通过以上过程我们就实现了ContentProvider的插件化。需要说明的是,DroidPlugind的插件化与上述介绍的方案有一些不同之处: 1. 首先DroidPlugin并没有选择预先安装的方案,而是选择Hook ActivityManagerNative,拦截它的getContentProvider以及publishContentProvider方法实现对于插件组件的控制;从这里可以看出它对ContentProvider与Service的插件化几乎是相同的,Hook才是DroidPlugin Style ^_^. 2. 然后,关于携带插件信息,或者说「插件查询协议」方面;DroidPlugin把插件信息放在查询参数里面,本文呢则是路径参数;这一点完全看个人喜好。 ## 小结 本文我们通过「代理分发技术」以及「插件查询协议」完成了ContentProvider组件的插件化,并且给出了对「插件共享组件」的问题的一般解决方案。值得一提的是,系统的ContentProvider其实是lazy load的,也就是说只有在需要使用的时候才会启动对应的ContentProvider,而我们对于插件的实现则是**预先加载**,这里还有改进的空间,读者可以思考一下解决方案。 由于ContentProvider的使用频度非常低,而很多它使用的场景(比如系统)并不太需要「插件化」,因此在实际的插件方案中,提供ContentProvider插件化的方案非常之少;就算需要实现ContentProvider的插件化,也只是解决插件内部之间共享组件的问题,并没有把插件组件暴露给整个系统。我个人觉得,如果只是希望插件化,那么是否支持ContentProvider无伤大雅,但是,如果希望实现虚拟化或者说容器技术,所有组件是必须支持插件化的。 至此,对于Android系统的四大组件的插件化已经全部介绍完毕;由于是最后一个要介绍的组件,我并没有像之前一样先给出组件的运行原理,然后一通分析最后给出插件方案,而是一边分析代码一边给出自己的思路,把思考——推翻——改进的整个过程完全展现了出来,Android的插件化已经到达了百花齐放的阶段,插件化之路也不只有一条,但是万变不离其宗,希望我的分析和思考对各位读者理解甚至创造插件化方案带来帮助。接下来我会介绍「插件通信机制」,它与本文的ContentProvider以及我反复强调过的一些特性密切相关,敬请期待! [1]: https://github.com/tiann/understand-plugin-framework [2]: 概述.md [3]: http://weishu.me [4]: http://blog.csdn.net/luoshengyang/article/details/6689748 [5]: http://blog.csdn.net/luoshengyang/article/details/6967204 ================================================ FILE: DOC/tianweishu/Hook机制之AMS&PMS.md ================================================ # Hook机制之AMS&PMS 在前面的文章中我们介绍了DroidPlugin的Hook机制,也就是**代理方式**和**Binder Hook**;插件框架通过AOP实现了插件使用和开发的透明性。在讲述DroidPlugin如何实现四大组件的插件化之前,有必要说明一下它对ActivityManagerServiche以及PackageManagerService的Hook方式(以下简称AMS,PMS)。 ActivityManagerService对于FrameWork层的重要性不言而喻,Android的四大组件无一不与它打交道: 1. `startActivity`最终调用了AMS的`startActivity`系列方法,实现了Activity的启动;Activity的生命周期回调,也在AMS中完成; 2. `startService,bindService`最终调用到AMS的startService和bindService方法; 3. 动态广播的注册和接收在`AMS`中完成(静态广播在`PMS`中完成) 4. `getContentResolver`最终从`AMS`的`getContentProvider`获取到ContentProvider 而`PMS`则完成了诸如权限校捡(`checkPermission,checkUidPermission`),Apk meta信息获取(`getApplicationInfo`等),四大组件信息获取(`query`系列方法)等重要功能。 在上文[Android插件化原理解析——Hook机制之Binder Hook][1]中讲述了DroidPlugin的Binder Hook机制;我们知道`AMS`和`PMS`就是以Binder方式提供给应用程序使用的系统服务,理论上我们也可以采用这种方式Hook掉它们。但是由于这两者使用得如此频繁,Framework给他们了一些“特别优待”,这也给了我们相对于Binder Hook更加稳定可靠的hook方式。 阅读本文之前,可以先clone一份 [understand-plugin-framework][2],参考此项目的`ams-pms-hook`模块。另外,插件框架原理解析系列文章见[索引](http://weishu.me/2016/01/28/understand-plugin-framework-overview/)。 ## AMS获取过程 前文提到Android的四大组件无一不与`AMS`相关,也许读者还有些许疑惑;这里我就挑一个例子,依据Android源码来说明,一个简单的`startActivity`是如何调用`AMS`最终通过IPC到system_server的。 不论读者是否知道,我们使用`startActivity`有两种形式: 1. 直接调用`Context`类的`startActivity`方法;这种方式启动的Activity没有Activity栈,因此不能以standard方式启动,必须加上`FLAG_ACTIVITY_NEW_TASK`这个Flag。 2. 调用被`Activity`类重载过的`startActivity`方法,通常在我们的Activity中直接调用这个方法就是这种形式; ### Context.startActivity 我们查看`Context`类的`startActivity`方法,发现这竟然是一个抽象类;查看`Context`的类继承关系图如下: 我们看到诸如`Activity`,`Service`等并没有直接继承`Context`,而是继承了`ContextWrapper`;继续查看`ContextWrapper`的实现: ```java @Override public void startActivity(Intent intent) { mBase.startActivity(intent); } ``` WTF!! 果然人如其名,只是一个wrapper而已;这个`mBase`是什么呢?这里我先直接告诉你,它的真正实现是`ContextImpl`类;至于为什么,有一条思路:*mBase是在ContextWrapper构造的时候传递进来的,那么在ContextWrapper构造的时候可以找到答案* 什么时候会构造ContextWrapper呢?它的子类`Application`,`Service`等被创建的时候。 可以在App的主线程`AcitivityThread`的`performLaunchActivit`方法里面找到答案;更详细的解析可以参考老罗的[ Android应用程序启动过程源代码分析][3] 好了,我们姑且当作已经知道Context.startActivity最终使用了ContextImpl里面的方法,代码如下: ```java public void startActivity(Intent intent, Bundle options) { warnIfCallingFromSystemProcess(); if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) { throw new AndroidRuntimeException( "Calling startActivity() from outside of an Activity " + " context requires the FLAG_ACTIVITY_NEW_TASK flag." + " Is this really what you want?"); } mMainThread.getInstrumentation().execStartActivity( getOuterContext(), mMainThread.getApplicationThread(), null, (Activity)null, intent, -1, options); } ``` 代码相当简单;我们知道了两件事: 1. 其一,我们知道了在Service等非Activity的Context里面启动Activity为什么需要添加`FLAG_ACTIVITY_NEW_TASK`; 2. 其二,真正的`startActivity`使用了`Instrumentation`类的`execStartActivity`方法;继续跟踪: ```java public ActivityResult execStartActivity( Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) { // ... 省略无关代码 try { intent.migrateExtraStreamToClipData(); intent.prepareToLeaveProcess(); // ----------------look here!!!!!!!!!!!!!!!!!!! int result = ActivityManagerNative.getDefault() .startActivity(whoThread, who.getBasePackageName(), intent, intent.resolveTypeIfNeeded(who.getContentResolver()), token, target != null ? target.mEmbeddedID : null, requestCode, 0, null, null, options); checkStartActivityResult(result, intent); } catch (RemoteException e) { } return null; } ``` 到这里我们发现真正调用的是`ActivityManagerNative`的`startActivity`方法;如果你不清楚`ActivityManager`,`ActivityManagerService`以及`ActivityManagerNative`之间的关系;建议先仔细阅读我之前关于Binder的文章 [Binder学习指南][4]。 ### Activity.startActivity Activity类的`startActivity`方法相比Context而言直观了很多;这个`startActivity`通过若干次调用辗转到达`startActivityForResult`这个方法,在这个方法内部有如下代码: ```java Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity( this, mMainThread.getApplicationThread(), mToken, this, intent, requestCode, options); ``` 可以看到,其实通过Activity和ContextImpl类启动Activity并无本质不同,他们都通过`Instrumentation`这个辅助类调用到了`ActivityManagerNative`的方法。 ## Hook AMS OK,我们到现在知道;其实`startActivity`最终通过`ActivityManagerNative`这个方法远程调用了`AMS`的`startActivity`方法。那么这个`ActivityManagerNative`是什么呢? ActivityManagerNative实际上就是`ActivityManagerService`这个远程对象的Binder代理对象;每次需要与AMS打交道的时候,需要借助这个代理对象通过驱动进而完成IPC调用。 我们继续看`ActivityManagerNative`的`getDefault()`方法做了什么: ```java static public IActivityManager getDefault() { return gDefault.get(); } ``` `gDefault`这个静态变量的定义如下: ```java private static final Singleton gDefault = new Singleton() { protected IActivityManager create() { IBinder b = ServiceManager.getService("activity IActivityManager am = asInterface( return am; } }; ``` 由于整个Framework与AMS打交道是如此频繁,framework使用了一个单例把这个`AMS`的代理对象保存了起来;这样只要需要与`AMS`进行IPC调用,获取这个单例即可。这是`AMS`这个系统服务与其他普通服务的不同之处,也是我们不通过Binder Hook的原因——我们只需要简单地Hook掉这个单例即可。 这里还有一点小麻烦:Android不同版本之间对于如何保存这个单例的代理对象是不同的;Android 2.x系统直接使用了一个简单的静态变量存储,Android 4.x以上抽象出了一个Singleton类;具体的差异可以使用`grepcode`进行比较:[差异][5] 我们以4.x以上的代码为例说明如何Hook掉`AMS`;方法使用的动态代理,如果有不理解的,可以参考之前的系列文章[Android插件化原理解析——Hook机制之动态代理][6] ```java Class activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative"); // 获取 gDefault 这个字段, 想办法替换它 Field gDefaultField = activityManagerNativeClass.getDeclaredField("gDefault"); gDefaultField.setAccessible(true); Object gDefault = gDefaultField.get(null); // 4.x以上的gDefault是一个 android.util.Singleton对象; 我们取出这个单例里面的字段 Class singleton = Class.forName("android.util.Singleton"); Field mInstanceField = singleton.getDeclaredField("mInstance"); mInstanceField.setAccessible(true); // ActivityManagerNative 的gDefault对象里面原始的 IActivityManager对象 Object rawIActivityManager = mInstanceField.get(gDefault); // 创建一个这个对象的代理对象, 然后替换这个字段, 让我们的代理对象帮忙干活 Class iActivityManagerInterface = Class.forName("android.app.IActivityManager"); Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[] { iActivityManagerInterface }, new IActivityManagerHandler(rawIActivityManager)); mInstanceField.set(gDefault, proxy); ``` 好了,我们hook成功之后启动Activity看看会发生什么: ``` D/HookHelper﹕ hey, baby; you are hook!! D/HookHelper﹕ method:activityResumed called with args:[android.os.BinderProxy@9bc71b2] D/HookHelper﹕ hey, baby; you are hook!! D/HookHelper﹕ method:activityIdle called with args:[android.os.BinderProxy@9bc71b2, null, false] D/HookHelper﹕ hey, baby; you are hook!! D/HookHelper﹕ method:startActivity called with args:[android.app.ActivityThread$ApplicationThread@17e750c, com.weishu.upf.ams_pms_hook.app, Intent { act=android.intent.action.VIEW dat=http://wwww.baidu.com/... }, null, android.os.BinderProxy@9bc71b2, null, -1, 0, null, null] D/HookHelper﹕ hey, baby; you are hook!! D/HookHelper﹕ method:activityPaused called with args:[android.os.BinderProxy@9bc71b2] ``` 可以看到,简单的几行代码,`AMS`已经被我们完全劫持了!! 至于劫持了能干什么,自己发挥想象吧~ DroidPlugin关于`AMS`的Hook,可以查看`IActivityManagerHook`这个类,它处理了我上述所说的兼容性问题,其他原理相同。另外,也许有童鞋有疑问了,你用`startActivity`为例怎么能确保Hook掉这个静态变量之后就能保证所有使用`AMS`的入口都被Hook了呢? 答曰:无他,唯手熟尔。 Android Framewrok层对于四大组件的处理,调用`AMS`服务的时候,全部都是通过使用这种方式;若有疑问可以自行查看源码。你可以从`Context`类的startActivity, startService,bindService, registerBroadcastReceiver, getContentResolver 等等入口进行跟踪,最终都会发现它们都会使用ActivityManagerNative的这个`AMS`代理对象来完成对远程AMS的访问。 ## PMS获取过程 `PMS`的获取也是通过Context完成的,具体就是`getPackageManager`这个方法;我们姑且当作已经知道了Context的实现在ContextImpl类里面,直奔`ContextImpl`类的`getPackageManager`方法: ```java public PackageManager getPackageManager() { if (mPackageManager != null) { return mPackageManager; } IPackageManager pm = ActivityThread.getPackageManager(); if (pm != null) { // Doesn't matter if we make more than one instance. return (mPackageManager = new ApplicationPackageManager(this, pm)); } return null; } ``` 可以看到,这里干了两件事: 1. 真正的`PMS`的代理对象在`ActivityThread`类里面 2. `ContextImpl`通过`ApplicationPackageManager`对它还进行了一层包装 我们继续查看`ActivityThread`类的`getPackageManager`方法,源码如下: ```java public static IPackageManager getPackageManager() { if (sPackageManager != null) { return sPackageManager; } IBinder b = ServiceManager.getService("package"); sPackageManager = IPackageManager.Stub.asInterface(b); return sPackageManager; } ``` 可以看到,和`AMS`一样,`PMS`的Binder代理对象也是一个全局变量存放在一个静态字段中;我们可以如法炮制,Hook掉PMS。 现在我们的目的很明切,如果需要Hook `PMS`有两个地方需要Hook掉: 1. `ActivityThread`的静态字段`sPackageManager` 2. 通过Context类的`getPackageManager`方法获取到的`ApplicationPackageManager`对象里面的`mPM`字段。 ## Hook PMS 现在使用代理Hook应该是轻车熟路了吧,通过上面的分析,我们Hook两个地方;代码信手拈来: ```java // 获取全局的ActivityThread对象 Class activityThreadClass = Class.forName("android.app.ActivityThread"); Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread"); Object currentActivityThread = currentActivityThreadMethod.invoke(null); // 获取ActivityThread里面原始的 sPackageManager Field sPackageManagerField = activityThreadClass.getDeclaredField("sPackageManager"); sPackageManagerField.setAccessible(true); Object sPackageManager = sPackageManagerField.get(currentActivityThread); // 准备好代理对象, 用来替换原始的对象 Class iPackageManagerInterface = Class.forName("android.content.pm.IPackageManager"); Object proxy = Proxy.newProxyInstance(iPackageManagerInterface.getClassLoader(), new Class[] { iPackageManagerInterface }, new HookHandler(sPackageManager)); // 1. 替换掉ActivityThread里面的 sPackageManager 字段 sPackageManagerField.set(currentActivityThread, proxy); // 2. 替换 ApplicationPackageManager里面的 mPM对象 PackageManager pm = context.getPackageManager(); Field mPmField = pm.getClass().getDeclaredField("mPM"); mPmField.setAccessible(true); mPmField.set(pm, proxy); ``` 好了,Hook完毕我们验证以下结论;调用一下`PMS`的`getInstalledApplications`方法,打印日志如下: ``` 03-07 15:07:27.187 8306-8306/com.weishu.upf.ams_pms_hook.app D/IActivityManagerHandler﹕ hey, baby; you are hook!! 03-07 15:07:27.187 8306-8306/com.weishu.upf.ams_pms_hook.app D/IActivityManagerHandler﹕ method:getInstalledApplications called with args:[0, 0] ``` OK,我们又成功劫持了`PackageManager`!!DroidPlugin 处理PMS的代码可以在`IPackageManagerHook`查看。 在结束讲解PackageManager的Hook之前,我们需要说明一点;那就是`Context`的实现类里面没有使用静态全局变量来保存`PMS`的代理对象,而是每拥有一个`Context`的实例就持有了一个`PMS`代理对象的引用;所以这里有个很蛋疼的事情,那就是我们如果想要完全Hook住`PMS`,需要精确控制整个进程内部创建的`Context`对象;所幸,插件框架中,插件的Activity,Service,ContentProvider,Broadcast等所有使用到Context的地方,都是由框架控制创建的;因此我们要小心翼翼地替换掉所有这些对象持有的`PMS`代理对象。 我前面也提到过,**静态变量和单例**都是良好的Hook点,这里很好地反证了这句话:想要Hook掉一个实例变量该是多么麻烦! ## 小结 写到这里,关于DroidPlugin的Hook技术的讲解已经完结了;我相信读者或多或少地认识到,其实Hook并不是一项神秘的技术;一个干净,透明的框架少不了AOP,而AOP也少不了Hook。 我所讲解的Hook仅仅使用反射和动态代理技术,更加强大的Hook机制可以进行**字节码编织**,比如J2EE广泛使用了cglib和asm进行AOP编程;而Android上现有的插件框架还是加载编译时代码,采用动态生成类的技术理论上也是可行的;之前有一篇文章[Android动态加载黑科技 动态创建Activity模式][7],就讲述了这种方式;现在全球的互联网公司不排除有用这种技术实现插件框架的可能 ;我相信不远的未来,这种技术也会在Android上大放异彩。 了解完Hook技术之后,接下来的系列文章会讲述DroidPlugin对Android四大组件在插件系统上的处理,插件框架对于这一部分的实现是DroidPlugin的精髓,Hook只不过是工具而已。学习这部分内容需要对于Activity,Service,Broadcast以及ContentProvider的工作机制有一定的了解,因此我也会在必要的时候穿插讲解一些Android Framework的知识;我相信这一定会对读者大有裨益。 [1]: Hook机制之Binder-Hook.md [2]: https://github.com/tiann/understand-plugin-framework [3]: http://blog.csdn.net/luoshengyang/article/details/6689748 [4]: http://weishu.me/2016/01/12/binder-index-for-newer/ [5]: http://grepcode.com/file_/repository.grepcode.com/java/ext/com.google.android/android/4.0.1_r1/android/app/ActivityManagerNative.java/?v=diff&id2=2.3.3_r1 [6]: Hook机制之代理Hook.md [7]: https://segmentfault.com/a/1190000004077469 ================================================ FILE: DOC/tianweishu/Hook机制之Binder-Hook.md ================================================ # Hook机制之Binder-Hook Android系统通过Binder机制给应用程序提供了一系列的系统服务,诸如`ActivityManagerService`,`ClipboardManager`, `AudioManager`等;这些广泛存在系统服务给应用程序提供了诸如任务管理,音频,视频等异常强大的功能。 插件框架作为各个插件的管理者,为了使得插件能够**无缝地**使用这些系统服务,自然会对这些系统服务做出一定的改造(Hook),使得插件的开发和使用更加方便,从而大大降低插件的开发和维护成本。比如,Hook住`ActivityManagerService`可以让插件无缝地使用`startActivity`方法而不是使用特定的方式(比如that语法)来启动插件或者主程序的任意界面。 我们把这种Hook系统服务的机制称之为Binder Hook,因为本质上这些服务提供者都是存在于系统各个进程的Binder对象。因此,要理解接下来的内容必须了解Android的Binder机制,可以参考我之前的文章[Binder学习指南][1] 阅读本文之前,可以先clone一份 [understand-plugin-framework][3],参考此项目的`binder-hook` 模块。另外,插件框架原理解析系列文章见[索引][2]。 ## 系统服务的获取过程 我们知道系统的各个远程service对象都是以Binder的形式存在的,而这些Binder有一个管理者,那就是`ServiceManager`;我们要Hook掉这些service,自然要从这个`ServiceManager`下手,不然星罗棋布的Binder广泛存在于系统的各个角落,要一个个找出来还真是大海捞针。 回想一下我们使用系统服务的时候是怎么干的,想必这个大家一定再熟悉不过了:通过`Context`对象的`getSystemService`方法;比如要使用`ActivityManager`: ```java ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); ``` 可是这个貌似跟`ServiceManager`没有什么关系啊?我们再查看`getSystemService`方法;(Context的实现在`ContextImpl`里面): ```java public Object getSystemService(String name) { ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name); return fetcher == null ? null : fetcher.getService(this); } ``` 很简单,所有的service对象都保存在一张`map`里面,我们再看这个map是怎么初始化的: ``` registerService(ACCOUNT_SERVICE, new ServiceFetcher() { public Object createService(ContextImpl ctx) { IBinder b = ServiceManager.getService(ACCOUNT_SERVICE); IAccountManager service = IAccountManager.Stub.asInterface(b); return new AccountManager(ctx, service); }}); ``` 在`ContextImpl`的静态初始化块里面,有的Service是像上面这样初始化的;可以看到,确实使用了`ServiceManager`;当然还有一些service并没有直接使用`ServiceManager`,而是做了一层包装并返回了这个包装对象,比如我们的`ActivityManager`,它返回的是`ActivityManager`这个包装对象: ``` registerService(ACTIVITY_SERVICE, new ServiceFetcher() { public Object createService(ContextImpl ctx) { return new ActivityManager(ctx.getOuterContext(), ctx.mMainThread.getHandler()); }}); ``` 但是在`ActivityManager`这个类内部,也使用了`ServiceManager`;具体来说,因为ActivityManager里面所有的核心操作都是使用`ActivityManagerNative.getDefault()`完成的。那么这个语句干了什么呢? ```java private static final Singleton gDefault = new Singleton() { protected IActivityManager create() { IBinder b = ServiceManager.getService("activity"); IActivityManager am = asInterface(b); return am; } }; ``` 因此,通过分析我们得知,系统Service的使用其实就分为两步: ```java IBinder b = ServiceManager.getService("service_name"); // 获取原始的IBinder对象 IXXInterface in = IXXInterface.Stub.asInterface(b); // 转换为Service接口 ``` ## 寻找Hook点 在[插件框架原理解析——Hook机制之动态代理][4]里面我们说过,Hook分为三步,最关键的一步就是寻找Hook点。我们现在已经搞清楚了系统服务的使用过程,那么就需要找出在这个过程中,在哪个环节是最合适hook的。 由于系统服务的使用者都是对第二步获取到的`IXXInterface`进行操作,因此如果我们要hook掉某个系统服务,**只需要把第二步的`asInterface`方法返回的对象修改为为我们Hook过的对象就可以了。** ### asInterface过程 接下来我们分析`asInterface`方法,然后想办法把这个方法的返回值修改为我们Hook过的系统服务对象。这里我们以系统剪切版服务为例,源码位置为`android.content.IClipboard`,`IClipboard.Stub.asInterface`方法代码如下: ```java public static android.content.IClipboard asInterface(android.os.IBinder obj) { if ((obj == null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); // Hook点 if (((iin != null) && (iin instanceof android.content.IClipboard))) { return ((android.content.IClipboard) iin); } return new android.content.IClipboard.Stub.Proxy(obj); } ``` 这个方法的意思就是:先查看本进程是否存在这个Binder对象,如果有那么直接就是本进程调用了;如果不存在那么创建一个代理对象,让代理对象委托驱动完成跨进程调用。 观察这个方法,前面的那个if语句判空返回肯定动不了手脚;最后一句调用构造函数然后直接返回我们也是无从下手,要修改`asInterface`方法的返回值,我们唯一能做的就是从这一句下手: ``` android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); // Hook点 ``` 我们可以尝试修改这个`obj`对象的`queryLocalInterface`方法的返回值,并保证这个返回值符合接下来的`if`条件检测,那么就达到了修改`asInterface`方法返回值的目的。 而这个`obj`对象刚好是我们第一步返回的`IBinder`对象,接下来我们尝试对这个`IBinder`对象的`queryLocalInterface`方法进行hook。 ### getService过程 上文分析得知,我们想要修改`IBinder`对象的`queryLocalInterface`方法;获取`IBinder`对象的过程如下: ``` IBinder b = ServiceManager.getService("service_name"); ``` 因此,我们希望能修改这个`getService`方法的返回值,让这个方法返回一个我们伪造过的`IBinder`对象;这样,我们可以在自己伪造的`IBinder`对象的`queryLocalInterface`方法作处理,进而使得`asInterface`方法返回在`queryLocalInterface`方法里面处理过的值,最终实现hook系统服务的目的。 在跟踪这个`getService`方法之前我们思考一下,由于系统服务是一系列的远程Service,它们的本体,也就是Binder本地对象一般都存在于某个单独的进程,在这个进程之外的其他进程存在的都是这些Binder本地对象的代理。因此在我们的进程里面,存在的也只是这个Binder代理对象,我们也只能对这些Binder代理对象下手。(如果这一段看不懂,建议不要往下看了,先看[Binder学习指南][1]) 然后,这个`getService`是一个静态方法,如果此方法什么都不做,拿到Binder代理对象之后直接返回;那么我们就无能为力了:我们没有办法拦截一个静态方法,也没有办法获取到这个静态方法里面的局部变量(即我们希望修改的那个Binder代理对象)。 接下来就可以看这个`getService`的代码了: ```java public static IBinder getService(String name) { try { IBinder service = sCache.get(name); if (service != null) { return service; } else { return getIServiceManager().getService(name); } } catch (RemoteException e) { Log.e(TAG, "error in getService", e); } return null; } ``` 天无绝人之路!`ServiceManager`为了避免每次都进行跨进程通信,把这些Binder代理对象缓存在一张`map`里面。 我们可以替换这个map里面的内容为Hook过的`IBinder`对象,由于系统在`getService`的时候每次都会优先查找缓存,因此返回给使用者的都是被我们修改过的对象,从而达到瞒天过海的目的。 总结一下,要达到修改系统服务的目的,我们需要如下两步: 1. 首先肯定需要**伪造一个系统服务对象**,接下来就要想办法让`asInterface`能够返回我们的这个伪造对象而不是原始的系统服务对象。 2. 通过上文分析我们知道,只要让`getService`返回`IBinder`对象的`queryLocalInterface`方法直接返回我们伪造过的系统服务对象就能达到目的。所以,我们需要**伪造一个IBinder对象**,主要是修改它的`queryLocalInterface`方法,让它返回我们伪造的系统服务对象;然后把这个伪造对象放置在`ServiceManager`的缓存`map`里面即可。 我们通过Binder机制的*优先查找本地Binder对象*的这个特性达到了Hook掉系统服务对象的目的。因此`queryLocalInterface`也失去了它原本的意义(只查找本地Binder对象,没有本地对象返回null),这个方法只是一个傀儡,是我们实现hook系统对象的桥梁:我们通过这个“漏洞”让`asInterface`永远都返回我们伪造过的对象。由于我们接管了`asInterface`这个方法的全部,我们伪造过的这个系统服务对象不能是只拥有本地Binder对象(原始`queryLocalInterface`方法返回的对象)的能力,还要有Binder代理对象操纵驱动的能力。 接下来我们就以Hook系统的剪切版服务为例,用实际代码来说明,如何Hook掉系统服务。 ## Hook系统剪切版服务 ### 伪造剪切版服务对象 首先我们用代理的方式伪造一个剪切版服务对象,关于如何使用代理的方式进行hook以及其中的原理,可以查看[插件框架原理解析——Hook机制之动态代理][4]。 具体代码如下,我们用动态代理的方式Hook掉了`hasPrimaryClip()`,`getPrimaryClip()`这两个方法: ```java public class BinderHookHandler implements InvocationHandler { private static final String TAG = "BinderHookHandler"; // 原始的Service对象 (IInterface) Object base; public BinderHookHandler(IBinder base, Class stubClass) { try { Method asInterfaceMethod = stubClass.getDeclaredMethod("asInterface", IBinder.class); // IClipboard.Stub.asInterface(base); this.base = asInterfaceMethod.invoke(null, base); } catch (Exception e) { throw new RuntimeException("hooked failed!"); } } @TargetApi(Build.VERSION_CODES.HONEYCOMB) @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 把剪切版的内容替换为 "you are hooked" if ("getPrimaryClip".equals(method.getName())) { Log.d(TAG, "hook getPrimaryClip"); return ClipData.newPlainText(null, "you are hooked"); } // 欺骗系统,使之认为剪切版上一直有内容 if ("hasPrimaryClip".equals(method.getName())) { return true; } return method.invoke(base, args); } } ``` 注意,我们拿到原始的`IBinder`对象之后,如果我们希望使用被Hook之前的系统服务,并不能直接使用这个`IBinder`对象,而是需要使用`asInterface`方法将它转换为`IClipboard`接口;因为`getService`方法返回的`IBinder`实际上是一个**裸Binder代理对象**,它只有与驱动打交道的能力,但是它并不能独立工作,需要人指挥它;`asInterface`方法返回的`IClipboard.Stub.Proxy`类的对象通过操纵这个裸`BinderProxy`对象从而实现了具体的`IClipboard`接口定义的操作。 ### 伪造`IBinder` 对象 在上一步中,我们已经伪造好了系统服务对象,现在要做的就是想办法让`asInterface`方法返回我们伪造的对象了;我们伪造一个`IBinder`对象: ```java public class BinderProxyHookHandler implements InvocationHandler { private static final String TAG = "BinderProxyHookHandler"; // 绝大部分情况下,这是一个BinderProxy对象 // 只有当Service和我们在同一个进程的时候才是Binder本地对象 // 这个基本不可能 IBinder base; Class stub; Class iinterface; public BinderProxyHookHandler(IBinder base) { this.base = base; try { this.stub = Class.forName("android.content.IClipboard$Stub"); this.iinterface = Class.forName("android.content.IClipboard"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if ("queryLocalInterface".equals(method.getName())) { Log.d(TAG, "hook queryLocalInterface"); // 这里直接返回真正被Hook掉的Service接口 // 这里的 queryLocalInterface 就不是原本的意思了 // 我们肯定不会真的返回一个本地接口, 因为我们接管了 asInterface方法的作用 // 因此必须是一个完整的 asInterface 过的 IInterface对象, 既要处理本地对象,也要处理代理对象 // 这只是一个Hook点而已, 它原始的含义已经被我们重定义了; 因为我们会永远确保这个方法不返回null // 让 IClipboard.Stub.asInterface 永远走到if语句的else分支里面 return Proxy.newProxyInstance(proxy.getClass().getClassLoader(), // asInterface 的时候会检测是否是特定类型的接口然后进行强制转换 // 因此这里的动态代理生成的类型信息的类型必须是正确的 new Class[] { IBinder.class, IInterface.class, this.iinterface }, new BinderHookHandler(base, stub)); } Log.d(TAG, "method:" + method.getName()); return method.invoke(base, args); } } ``` 我们使用动态代理的方式伪造了一个跟原始`IBinder`一模一样的对象,然后在这个伪造的`IBinder`对象的`queryLocalInterface`方法里面返回了我们第一步创建的**伪造过的系统服务对象**;注意看注释,详细解释可以看[代码][3] ### 替换ServiceManager的`IBinder`对象 现在就是万事具备,只欠东风了;我们使用反射的方式修改`ServiceManager`类里面缓存的Binder对象,使得`getService`方法返回我们伪造的`IBinder`对象,进而`asInterface`方法使用伪造`IBinder`对象的`queryLocalInterface`方法返回了我们伪造的系统服务对象。代码较简单,如下: ```java final String CLIPBOARD_SERVICE = "clipboard"; // 下面这一段的意思实际就是: ServiceManager.getService("clipboard"); // 只不过 ServiceManager这个类是@hide的 Class serviceManager = Class.forName("android.os.ServiceManager"); Method getService = serviceManager.getDeclaredMethod("getService", String.class); // ServiceManager里面管理的原始的Clipboard Binder对象 // 一般来说这是一个Binder代理对象 IBinder rawBinder = (IBinder) getService.invoke(null, CLIPBOARD_SERVICE); // Hook 掉这个Binder代理对象的 queryLocalInterface 方法 // 然后在 queryLocalInterface 返回一个IInterface对象, hook掉我们感兴趣的方法即可. IBinder hookedBinder = (IBinder) Proxy.newProxyInstance(serviceManager.getClassLoader(), new Class[] { IBinder.class }, new BinderProxyHookHandler(rawBinder)); // 把这个hook过的Binder代理对象放进ServiceManager的cache里面 // 以后查询的时候 会优先查询缓存里面的Binder, 这样就会使用被我们修改过的Binder了 Field cacheField = serviceManager.getDeclaredField("sCache"); cacheField.setAccessible(true); Map cache = (Map) cacheField.get(null); cache.put(CLIPBOARD_SERVICE, hookedBinder); ``` 接下来,在app里面使用剪切版,比如长按进行粘贴之后,剪切版的内容永远都是`you are hooked`了;这样,我们Hook系统服务的目的宣告完成!详细的代码参见 [github][3]。 也许你会问,插件框架会这么hook吗?如果不是那么插件框架hook这些干什么?插件框架当然不会做替换文本这么无聊的事情,DroidPlugin插件框架管理插件使得插件就像是主程序一样,因此插件需要使用主程序的剪切版,插件之间也会共用剪切版;其他的一些系统服务也类似,这样就可以达到插件和宿主程序之间的天衣服缝,水乳交融!另外,`ActivityManager`以及`PackageManager`这两个系统服务虽然也可以通过这种方式hook,但是由于它们的重要性和特殊性,DroidPlugin使用了另外一种方式,我们会单独讲解。 [1]: http://weishu.me/2016/01/12/binder-index-for-newer/ [2]: 概述.md [3]: https://github.com/tiann/understand-plugin-framework [4]: Hook机制之代理Hook.md [5]: http://weishu.me/ ================================================ FILE: DOC/tianweishu/Hook机制之代理Hook.md ================================================ # Hook机制之动态代理 使用代理机制进行API Hook进而达到方法增强是框架的常用手段,比如J2EE框架Spring通过动态代理优雅地实现了AOP编程,极大地提升了Web开发效率;同样,插件框架也广泛使用了代理机制来增强系统API从而达到插件化的目的。本文将带你了解基于动态代理的Hook机制。 阅读本文之前,可以先clone一份 [understand-plugin-framework][1],参考此项目的`dynamic-proxy-hook`模块。另外,插件框架原理解析系列文章见[索引](http://weishu.me/2016/01/28/understand-plugin-framework-overview/)。 ## 代理是什么 为什么需要代理呢?其实这个代理与日常生活中的“代理”,“中介”差不多;比如你想海淘买东西,总不可能亲自飞到国外去购物吧,这时候我们使用第三方海淘服务比如惠惠购物助手等;同样拿购物为例,有时候第三方购物会有折扣比如当初的米折网,这时候我们可以少花点钱;当然有时候这个“代理”比较坑,坑我们的钱,坑我们的货。 从这个例子可以看出来,代理可以实现**方法增强**,比如常用的*日志*,*缓存*等;也可以实现方法拦截,通过代理方法修改原方法的参数和返回值,从而实现某种不可告人的目的~接下来我们用代码解释一下。 ## 静态代理 静态代理,是最原始的代理方式;假设我们有一个购物的接口,如下: ```java public interface Shopping { Object[] doShopping(long money); } ``` 它有一个原始的实现,我们可以理解为亲自,直接去商店购物: ```java public class ShoppingImpl implements Shopping { @Override public Object[] doShopping(long money) { System.out.println("逛淘宝 ,逛商场,买买买!!"); System.out.println(String.format("花了%s块钱", money)); return new Object[] { "鞋子", "衣服", "零食" }; } } ``` 好了,现在我们自己没时间但是需要买东西,于是我们就找了个代理帮我们买: ```java public class ProxyShopping implements Shopping { Shopping base; ProxyShopping(Shopping base) { this.base = base; } @Override public Object[] doShopping(long money) { // 先黑点钱(修改输入参数) long readCost = (long) (money * 0.5); System.out.println(String.format("花了%s块钱", readCost)); // 帮忙买东西 Object[] things = base.doShopping(readCost); // 偷梁换柱(修改返回值) if (things != null && things.length > 1) { things[0] = "被掉包的东西!!"; } return things; } ``` 很不幸,我们找的这个代理有点坑,坑了我们的钱还坑了我们的货;先忍忍。 ## 动态代理 传统的静态代理模式需要为每一个需要代理的类写一个代理类,如果需要代理的类有几百个那不是要累死?为了更优雅地实现代理模式,JDK提供了动态代理方式,可以简单理解为JVM可以在运行时帮我们动态生成一系列的代理类,这样我们就不需要手写每一个静态的代理类了。依然以购物为例,用动态代理实现如下: ```java public static void main(String[] args) { Shopping women = new ShoppingImpl(); // 正常购物 System.out.println(Arrays.toString(women.doShopping(100))); // 招代理 women = (Shopping) Proxy.newProxyInstance(Shopping.class.getClassLoader(), women.getClass().getInterfaces(), new ShoppingHandler(women)); System.out.println(Arrays.toString(women.doShopping(100))); } ``` 动态代理主要处理`InvocationHandler`和`Proxy`类;完整代码可以见[github][1] ## 代理Hook 我们知道代理有比原始对象更强大的能力,比如飞到国外买东西,比如坑钱坑货;那么很自然,如果我们自己创建代理对象,然后把原始对象替换为我们的代理对象,那么就可以在这个代理对象为所欲为了;修改参数,替换返回值,我们称之为Hook。 下面我们Hook掉`startActivity`这个方法,使得每次调用这个方法之前输出一条日志;(当然,这个输入日志有点点弱,只是为了展示原理;只要你想,你想可以替换参数,拦截这个`startActivity`过程,使得调用它导致启动某个别的Activity,指鹿为马!) 首先我们得找到被Hook的对象,我称之为Hook点;什么样的对象比较好Hook呢?自然是**容易找到的对象**。什么样的对象容易找到?**静态变量和单例**;在一个进程之内,静态变量和单例变量是相对不容易发生变化的,因此非常容易定位,而普通的对象则要么无法标志,要么容易改变。我们根据这个原则找到所谓的Hook点。 然后我们分析一下`startActivity`的调用链,找出合适的Hook点。我们知道对于`Context.startActivity`(Activity.startActivity的调用链与之不同),由于`Context`的实现实际上是`ContextImpl`;我们看`ConetxtImpl`类的`startActivity`方法: ```java @Override public void startActivity(Intent intent, Bundle options) { warnIfCallingFromSystemProcess(); if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) { throw new AndroidRuntimeException( "Calling startActivity() from outside of an Activity " + " context requires the FLAG_ACTIVITY_NEW_TASK flag." + " Is this really what you want?"); } mMainThread.getInstrumentation().execStartActivity( getOuterContext(), mMainThread.getApplicationThread(), null, (Activity)null, intent, -1, options); } ``` 这里,实际上使用了`ActivityThread`类的`mInstrumentation`成员的`execStartActivity`方法;注意到,`ActivityThread` 实际上是主线程,而主线程一个进程只有一个,因此这里是一个良好的Hook点。 接下来就是想要Hook掉我们的主线程对象,也就是把这个主线程对象里面的`mInstrumentation`给替换成我们修改过的代理对象;要替换主线程对象里面的字段,首先我们得拿到主线程对象的引用,如何获取呢?`ActivityThread`类里面有一个静态方法`currentActivityThread`可以帮助我们拿到这个对象类;但是`ActivityThread`是一个隐藏类,我们需要用反射去获取,代码如下: ```java // 先获取到当前的ActivityThread对象 Class activityThreadClass = Class.forName("android.app.ActivityThread"); Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread"); currentActivityThreadMethod.setAccessible(true); Object currentActivityThread = currentActivityThreadMethod.invoke(null); ``` 拿到这个`currentActivityThread`之后,我们需要修改它的`mInstrumentation`这个字段为我们的代理对象,我们先实现这个代理对象,由于JDK动态代理只支持接口,而这个`Instrumentation`是一个类,没办法,我们只有手动写静态代理类,覆盖掉原始的方法即可。(`cglib`可以做到基于类的动态代理,这里先不介绍) ```java public class EvilInstrumentation extends Instrumentation { private static final String TAG = "EvilInstrumentation"; // ActivityThread中原始的对象, 保存起来 Instrumentation mBase; public EvilInstrumentation(Instrumentation base) { mBase = base; } public ActivityResult execStartActivity( Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) { // Hook之前, XXX到此一游! Log.d(TAG, "\n执行了startActivity, 参数如下: \n" + "who = [" + who + "], " + "\ncontextThread = [" + contextThread + "], \ntoken = [" + token + "], " + "\ntarget = [" + target + "], \nintent = [" + intent + "], \nrequestCode = [" + requestCode + "], \noptions = [" + options + "]"); // 开始调用原始的方法, 调不调用随你,但是不调用的话, 所有的startActivity都失效了. // 由于这个方法是隐藏的,因此需要使用反射调用;首先找到这个方法 try { Method execStartActivity = Instrumentation.class.getDeclaredMethod( "execStartActivity", Context.class, IBinder.class, IBinder.class, Activity.class, Intent.class, int.class, Bundle.class); execStartActivity.setAccessible(true); return (ActivityResult) execStartActivity.invoke(mBase, who, contextThread, token, target, intent, requestCode, options); } catch (Exception e) { // 某该死的rom修改了 需要手动适配 throw new RuntimeException("do not support!!! pls adapt it"); } } } ``` Ok,有了代理对象,我们要做的就是偷梁换柱!代码比较简单,采用反射直接修改: ```java public static void attachContext() throws Exception{ // 先获取到当前的ActivityThread对象 Class activityThreadClass = Class.forName("android.app.ActivityThread"); Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread"); currentActivityThreadMethod.setAccessible(true); Object currentActivityThread = currentActivityThreadMethod.invoke(null); // 拿到原始的 mInstrumentation字段 Field mInstrumentationField = activityThreadClass.getDeclaredField("mInstrumentation"); mInstrumentationField.setAccessible(true); Instrumentation mInstrumentation = (Instrumentation) mInstrumentationField.get(currentActivityThread); // 创建代理对象 Instrumentation evilInstrumentation = new EvilInstrumentation(mInstrumentation); // 偷梁换柱 mInstrumentationField.set(currentActivityThread, evilInstrumentation); } ``` 好了,我们启动一个Activity测试一下,结果如下: 可见,Hook确实成功了!这就是使用代理进行Hook的原理——偷梁换柱。整个Hook过程简要总结如下: 1. 寻找Hook点,原则是静态变量或者单例对象,尽量Hook pulic的对象和方法,非public不保证每个版本都一样,需要适配。 2. 选择合适的代理方式,如果是接口可以用动态代理;如果是类可以手动写代理也可以使用cglib。 3. 偷梁换柱——用代理对象替换原始对象 完整代码参照:[understand-plugin-framework][1];里面留有一个作业:我们目前仅Hook了`Context`类的`startActivity`方法,但是`Activity`类却使用了自己的`mInstrumentation`;你可以尝试Hook掉Activity类的`startActivity`方法。 [1]: https://github.com/tiann/understand-plugin-framework ================================================ FILE: DOC/tianweishu/Service插件化.md ================================================ # Service插件化 在 [Activity生命周期管理][1] 以及 [广播的管理][2] 中我们详细探讨了Android系统中的Activity、BroadcastReceiver组件的工作原理以及它们的插件化方案,相信读者已经对Android Framework和插件化技术有了一定的了解;本文将探讨Android四大组件之一——Service组件的插件化方式。 与Activity, BroadcastReceiver相比,Service组件的不同点在哪里呢?我们能否用与之相同的方式实现Service的插件化?如果不行,它们的差别在哪里,应该如何实现Service的插件化? 我们接下来将围绕这几个问题展开,最终给出Service组件的插件化方式;阅读本文之前,可以先clone一份 [understand-plugin-framework][3],参考此项目的 service-management 模块。另外,插件框架原理解析系列文章见[索引][4]。 ## Service工作原理 连Service的工作原理都不了解,谈何插件化?知己知彼。 Service分为两种形式:以startService**启动**的服务和用bindService**绑定**的服务;由于这两个过程大体相似,这里以稍复杂的`bindService`为例分析Service组件的工作原理。 绑定Service的过程是通过`Context`类的`bindService`完成的,这个方法需要三个参数:第一个参数代表想要绑定的Service的Intent,第二个参数是一个ServiceConnetion,我们可以通过这个对象接收到Service绑定成功或者失败的回调;第三个参数则是绑定时候的一些FLAG;关于服务的基本概念,可以参阅 [官方文档][5]。(现在汉化了哦,E文不好童鞋的福音) Context的具体实现在ContextImpl类,ContextImpl中的`bindService`方法直接调用了`bindServiceCommon`方法,此方法源码如下: ```java private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags, UserHandle user) { IServiceConnection sd; if (conn == null) { throw new IllegalArgumentException("connection is null"); } if (mPackageInfo != null) { // important sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(), mMainThread.getHandler(), flags); } else { throw new RuntimeException("Not supported in system context"); } validateServiceIntent(service); try { IBinder token = getActivityToken(); if (token == null && (flags&BIND_AUTO_CREATE) == 0 && mPackageInfo != null && mPackageInfo.getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) { flags |= BIND_WAIVE_PRIORITY; } service.prepareToLeaveProcess(); int res = ActivityManagerNative.getDefault().bindService( mMainThread.getApplicationThread(), getActivityToken(), service, service.resolveTypeIfNeeded(getContentResolver()), sd, flags, getOpPackageName(), user.getIdentifier()); if (res < 0) { throw new SecurityException( "Not allowed to bind to service " + service); } return res != 0; } catch (RemoteException e) { throw new RuntimeException("Failure from system", e); } } ``` 大致观察就能发现这个方法最终通过ActivityManagerNative借助AMS进而完成Service的绑定过程,在跟踪AMS的`bindService`源码之前,我们关注一下这个方法开始处创建的`sd`变量。这个变量的类型是`IServiceConnection`,如果读者还有印象,我们在 [广播的管理][2] 一文中也遇到过类似的处理方式——IIntentReceiver;所以,这个IServiceConnection与IApplicationThread以及IIntentReceiver相同,都是ActivityThread给AMS提供的用来与之进行通信的Binder对象;这个接口的实现类为LoadedApk.ServiceDispatcher。 这个方法最终调用了ActivityManagerNative的bindService,而这个方法的真正实现在AMS里面,源码如下: ```java public int bindService(IApplicationThread caller, IBinder token, Intent service, String resolvedType, IServiceConnection connection, int flags, String callingPackage, int userId) throws TransactionTooLargeException { enforceNotIsolatedCaller("bindService"); // 略去参数校检 synchronized(this) { return mServices.bindServiceLocked(caller, token, service, resolvedType, connection, flags, callingPackage, userId); } } ``` bindService这个方法相当简单,只是做了一些参数校检之后直接调用了ActivityServices类的`bindServiceLocked`方法: ```java int bindServiceLocked(IApplicationThread caller, IBinder token, Intent service, String resolvedType, IServiceConnection connection, int flags, String callingPackage, int userId) throws TransactionTooLargeException { final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller); // 参数校检,略 ServiceLookupResult res = retrieveServiceLocked(service, resolvedType, callingPackage, Binder.getCallingPid(), Binder.getCallingUid(), userId, true, callerFg); // 结果校检, 略 ServiceRecord s = res.record; final long origId = Binder.clearCallingIdentity(); try { // ... 不关心, 略 mAm.startAssociationLocked(callerApp.uid, callerApp.processName, s.appInfo.uid, s.name, s.processName); AppBindRecord b = s.retrieveAppBindingLocked(service, callerApp); ConnectionRecord c = new ConnectionRecord(b, activity, connection, flags, clientLabel, clientIntent); IBinder binder = connection.asBinder(); ArrayList clist = s.connections.get(binder); // 对connection进行处理, 方便存取,略 clist.add(c); if ((flags&Context.BIND_AUTO_CREATE) != 0) { s.lastActivity = SystemClock.uptimeMillis(); if (bringUpServiceLocked(s, service.getFlags(), callerFg, false) != null) { return 0; } } // 与BIND_AUTO_CREATE不同的启动FLAG,原理与后续相同,略 } finally { Binder.restoreCallingIdentity(origId); } return 1; } ``` 这个方法比较长,我这里省去了很多无关代码,只列出关键逻辑;首先它通过`retrieveServiceLocked`方法获取到了intent匹配到的需要bind到的Service组件`res`;然后把ActivityThread传递过来的IServiceConnection使用ConnectionRecord进行了包装,方便接下来使用;最后如果启动的FLAG为BIND_AUTO_CREATE,那么调用`bringUpServiceLocked`开始创建Service,我们跟踪这个方法:(非这种FLAG的代码已经省略,可以自行跟踪) ```java private final String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg, boolean whileRestarting) throws TransactionTooLargeException { // 略。。 final boolean isolated = (r.serviceInfo.flags&ServiceInfo.FLAG_ISOLATED_PROCESS) != 0; final String procName = r.processName; ProcessRecord app; if (!isolated) { app = mAm.getProcessRecordLocked(procName, r.appInfo.uid, false); if (app != null && app.thread != null) { try { app.addPackage(r.appInfo.packageName, r.appInfo.versionCode, mAm.mProcessStats); // 1. important !!! realStartServiceLocked(r, app, execInFg); return null; } catch (TransactionTooLargeException e) { throw e; } catch (RemoteException e) { Slog.w(TAG, "Exception when starting service " + r.shortName, e); } } } else { app = r.isolatedProc; } // Not running -- get it started, and enqueue this service record // to be executed when the app comes up. if (app == null) { // 2. important !!! if ((app=mAm.startProcessLocked(procName, r.appInfo, true, intentFlags, "service", r.name, false, isolated, false)) == null) { bringDownServiceLocked(r); return msg; } if (isolated) { r.isolatedProc = app; } } // 略。。 return null; } ``` 这个方案同样也很长,但是实际上非常简单:注意我注释的两个important的地方,如果Service所在的进程已经启动,那么直接调用`realStartServiceLocked`方法来**真正**启动Service组件;如果Service所在的进程还没有启动,那么先在AMS中记下这个要启动的Service组件,然后通过`startProcessLocked`启动新的进程。 我们先看Service进程已经启动的情况,也即`realStartServiceLocked`分支: ```java private final void realStartServiceLocked(ServiceRecord r, ProcessRecord app, boolean execInFg) throws RemoteException { // 略。。 boolean created = false; try { synchronized (r.stats.getBatteryStats()) { r.stats.startLaunchedLocked(); } mAm.ensurePackageDexOpt(r.serviceInfo.packageName); app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE); app.thread.scheduleCreateService(r, r.serviceInfo, mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo), app.repProcState); r.postNotification(); created = true; } catch (DeadObjectException e) { mAm.appDiedLocked(app); throw e; } finally { // 略。。 } requestServiceBindingsLocked(r, execInFg); // 不关心,略。。 } ``` 这个方法首先调用了app.thread的scheduleCreateService方法,我们知道,这是一个IApplicationThread对象,它是App所在进程提供给AMS的用来与App进程进行通信的Binder对象,这个Binder的Server端在ActivityThread的ApplicationThread类,因此,我们跟踪ActivityThread类,这个方法的实现如下: ```java public final void scheduleCreateService(IBinder token, ServiceInfo info, CompatibilityInfo compatInfo, int processState) { updateProcessState(processState, false); CreateServiceData s = new CreateServiceData(); s.token = token; s.info = info; s.compatInfo = compatInfo; sendMessage(H.CREATE_SERVICE, s); } ``` 它不过是转发了一个消息给ActivityThread的`H`这个Handler,`H`类收到这个消息之后,直接调用了ActivityThread类的`handleCreateService`方法,如下: ```java private void handleCreateService(CreateServiceData data) { unscheduleGcIdler(); LoadedApk packageInfo = getPackageInfoNoCheck( data.info.applicationInfo, data.compatInfo); Service service = null; try { java.lang.ClassLoader cl = packageInfo.getClassLoader(); service = (Service) cl.loadClass(data.info.name).newInstance(); } catch (Exception e) { } try { ContextImpl context = ContextImpl.createAppContext(this, packageInfo); context.setOuterContext(service); Application app = packageInfo.makeApplication(false, mInstrumentation); service.attach(context, this, data.info.name, data.token, app, ActivityManagerNative.getDefault()); service.onCreate(); mServices.put(data.token, service); try { ActivityManagerNative.getDefault().serviceDoneExecuting( data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0); } catch (RemoteException e) { // nothing to do. } } catch (Exception e) { } } ``` 看到这段代码,是不是似曾相识?!没错,这里与Activity组件的创建过程如出一辙!所以这里就不赘述了,可以参阅 [Activity生命周期管理][1]。 需要注意的是,这里Service类的创建过程与Activity是略微有点不同的,虽然都是通过ClassLoader通过反射创建,但是Activity却把创建过程委托给了Instrumentation类,而Service则是直接进行。 OK,现在ActivityThread里面的`handleCreateService`方法成功创建出了Service对象,并且调用了它的`onCreate`方法;到这里我们的Service已经启动成功。`scheduleCreateService`这个Binder调用过程结束,代码又回到了AMS进程的`realStartServiceLocked`方法。这里我们不得不感叹Binder机制的精妙,如此简洁方便高效的跨进程调用,在进程之间来回穿梭,游刃有余。 `realStartServiceLocked`方法的代码如下: ```java private final void realStartServiceLocked(ServiceRecord r, ProcessRecord app, boolean execInFg) throws RemoteException { // 略。。 boolean created = false; try { synchronized (r.stats.getBatteryStats()) { r.stats.startLaunchedLocked(); } mAm.ensurePackageDexOpt(r.serviceInfo.packageName); app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE); app.thread.scheduleCreateService(r, r.serviceInfo, mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo), app.repProcState); r.postNotification(); created = true; } catch (DeadObjectException e) { mAm.appDiedLocked(app); throw e; } finally { // 略。。 } requestServiceBindingsLocked(r, execInFg); // 不关心,略。。 } ``` 这个方法在完成`scheduleCreateService`这个binder调用之后,执行了一个`requestServiceBindingsLocked`方法;看方法名好像于「绑定服务」有关,它简单地执行了一个遍历然后调用了另外一个方法: ```java private final boolean requestServiceBindingLocked(ServiceRecord r, IntentBindRecord i, boolean execInFg, boolean rebind) throws TransactionTooLargeException { if (r.app == null || r.app.thread == null) { return false; } if ((!i.requested || rebind) && i.apps.size() > 0) { try { bumpServiceExecutingLocked(r, execInFg, "bind"); r.app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE); r.app.thread.scheduleBindService(r, i.intent.getIntent(), rebind, r.app.repProcState); // 不关心,略。。 } return true; } ``` 可以看到,这里又通过IApplicationThread这个Binder进行了一次IPC调用,我们跟踪ActivityThread类里面的ApplicationThread的`scheduleBindService`方法,发现这个方法不过通过Handler转发了一次消息,真正的处理代码在`handleBindService`里面: ```java private void handleBindService(BindServiceData data) { Service s = mServices.get(data.token); if (s != null) { try { data.intent.setExtrasClassLoader(s.getClassLoader()); data.intent.prepareToEnterProcess(); try { if (!data.rebind) { IBinder binder = s.onBind(data.intent); ActivityManagerNative.getDefault().publishService( data.token, data.intent, binder); } else { s.onRebind(data.intent); ActivityManagerNative.getDefault().serviceDoneExecuting( data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0); } ensureJitEnabled(); } catch (RemoteException ex) { } } catch (Exception e) { } } } ``` 我们要Bind的Service终于在这里完成了绑定!绑定之后又通过ActivityManagerNative这个Binder进行一次IPC调用,我们查看AMS的`publishService`方法,这个方法简单第调用了`publishServiceLocked`方法,源码如下: ```java void publishServiceLocked(ServiceRecord r, Intent intent, IBinder service) { final long origId = Binder.clearCallingIdentity(); try { if (r != null) { Intent.FilterComparison filter = new Intent.FilterComparison(intent); IntentBindRecord b = r.bindings.get(filter); if (b != null && !b.received) { b.binder = service; b.requested = true; b.received = true; for (int conni=r.connections.size()-1; conni>=0; conni--) { ArrayList clist = r.connections.valueAt(conni); for (int i=0; i ``` ### 拦截startService等调用过程 要手动控制Service组件的生命周期,需要拦截startService,stopService等调用,并且把启动插件Service全部重定向为启动ProxyService(保留原始插件Service信息);这个拦截过程需要Hook ActvityManagerNative,我们对这种技术应该是轻车熟路了;不了解的童鞋可以参考之前的文章 [Hook机制之AMS&PMS][6] 。 ```java public static void hookActivityManagerNative() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, NoSuchFieldException { Class activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative"); Field gDefaultField = activityManagerNativeClass.getDeclaredField("gDefault"); gDefaultField.setAccessible(true); Object gDefault = gDefaultField.get(null); // gDefault是一个 android.util.Singleton对象; 我们取出这个单例里面的字段 Class singleton = Class.forName("android.util.Singleton"); Field mInstanceField = singleton.getDeclaredField("mInstance"); mInstanceField.setAccessible(true); // ActivityManagerNative 的gDefault对象里面原始的 IActivityManager对象 Object rawIActivityManager = mInstanceField.get(gDefault); // 创建一个这个对象的代理对象, 然后替换这个字段, 让我们的代理对象帮忙干活 Class iActivityManagerInterface = Class.forName("android.app.IActivityManager"); Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[] { iActivityManagerInterface }, new IActivityManagerHandler(rawIActivityManager)); mInstanceField.set(gDefault, proxy); } ``` 我们在收到startService,stopService之后可以进行具体的操作,对于startService来说,就是直接替换启动的插件Service为ProxyService等待后续处理,代码如下: ```java if ("startService".equals(method.getName())) { // API 23: // public ComponentName startService(IApplicationThread caller, Intent service, // String resolvedType, int userId) throws RemoteException // 找到参数里面的第一个Intent 对象 Pair integerIntentPair = foundFirstIntentOfArgs(args); Intent newIntent = new Intent(); // 代理Service的包名, 也就是我们自己的包名 String stubPackage = UPFApplication.getContext().getPackageName(); // 这里我们把启动的Service替换为ProxyService, 让ProxyService接收生命周期回调 ComponentName componentName = new ComponentName(stubPackage, ProxyService.class.getName()); newIntent.setComponent(componentName); // 把我们原始要启动的TargetService先存起来 newIntent.putExtra(AMSHookHelper.EXTRA_TARGET_INTENT, integerIntentPair.second); // 替换掉Intent, 达到欺骗AMS的目的 args[integerIntentPair.first] = newIntent; Log.v(TAG, "hook method startService success"); return method.invoke(mBase, args); } ``` 对stopService的处理略有不同但是大同小异,读者可以上 [github][3] 查阅源码。 ### 分发Service Hook ActivityManagerNative之后,所有的插件Service的启动都被重定向了到了我们注册的ProxyService,这样可以保证我们的插件Service有一个真正的Service组件作为宿主;但是要执行特定插件Service的任务,我们必须把这个任务分发到真正要启动的Service上去;以`onStart`为例,在启动ProxyService之后,会收到ProxyService的`onStart`回调,我们可以在这个方法里面把具体的任务交给原始要启动的插件Service组件: ```java public void onStart(Intent intent, int startId) { Log.d(TAG, "onStart() called with " + "intent = [" + intent + "], startId = [" + startId + "]"); // 分发Service ServiceManager.getInstance().onStart(intent, startId); super.onStart(intent, startId); } ``` #### 加载Service 我们可以在ProxyService里面把任务转发给真正要启动的插件Service组件,要完成这个过程肯定需要创建一个对应的插件Service对象,比如PluginService;但是通常情况下插件存在与单独的文件之中,正常的方式是无法创建这个PluginService对象的,宿主程序默认的ClassLoader无法加载插件中对应的这个类;所以,要创建这个对应的PluginService对象,必须先完成插件的加载过程,让这个插件中的所有类都可以被正常访问;这种技术我们在之前专门讨论过,并给出了「激进方案」和「保守方案」,不了解的童鞋可以参考文章 [插件加载机制][7];这里我选择代码较少的「保守方案」为例(Droid Plugin中采用的激进方案): ```java public static void patchClassLoader(ClassLoader cl, File apkFile, File optDexFile) throws IllegalAccessException, NoSuchMethodException, IOException, InvocationTargetException, InstantiationException, NoSuchFieldException { // 获取 BaseDexClassLoader : pathList Field pathListField = DexClassLoader.class.getSuperclass().getDeclaredField("pathList"); pathListField.setAccessible(true); Object pathListObj = pathListField.get(cl); // 获取 PathList: Element[] dexElements Field dexElementArray = pathListObj.getClass().getDeclaredField("dexElements"); dexElementArray.setAccessible(true); Object[] dexElements = (Object[]) dexElementArray.get(pathListObj); // Element 类型 Class elementClass = dexElements.getClass().getComponentType(); // 创建一个数组, 用来替换原始的数组 Object[] newElements = (Object[]) Array.newInstance(elementClass, dexElements.length + 1); // 构造插件Element(File file, boolean isDirectory, File zip, DexFile dexFile) 这个构造函数 Constructor constructor = elementClass.getConstructor(File.class, boolean.class, File.class, DexFile.class); Object o = constructor.newInstance(apkFile, false, apkFile, DexFile.loadDex(apkFile.getCanonicalPath(), optDexFile.getAbsolutePath(), 0)); Object[] toAddElementArray = new Object[] { o }; // 把原始的elements复制进去 System.arraycopy(dexElements, 0, newElements, 0, dexElements.length); // 插件的那个element复制进去 System.arraycopy(toAddElementArray, 0, newElements, dexElements.length, toAddElementArray.length); // 替换 dexElementArray.set(pathListObj, newElements); } ``` #### 匹配过程 上文中我们把启动插件Service重定向为启动ProxyService,现在ProxyService已经启动,因此必须把控制权交回原始的PluginService;在加载插件的时候,我们存储了插件中所有的Service组件的信息,因此,只需要根据Intent里面的Component信息就可以取出对应的PluginService。 ```java private ServiceInfo selectPluginService(Intent pluginIntent) { for (ComponentName componentName : mServiceInfoMap.keySet()) { if (componentName.equals(pluginIntent.getComponent())) { return mServiceInfoMap.get(componentName); } } return null; } ``` #### 创建以及分发 插件被加载之后,我们就需要创建插件Service对应的Java对象了;由于这些类是在运行时动态加载进来的,肯定不能直接使用`new`关键字——我们需要使用反射机制。但是下面的代码创建出插件Service对象能满足要求吗? ```java ClassLoader cl = getClassLoader(); Service service = cl.loadClass("com.plugin.xxx.PluginService1"); ``` Service作为Android系统的组件,最重要的特点是它具有`Context`;所以,直接通过反射创建出来的这个PluginService就是一个壳子——没有Context的Service能干什么?因此我们需要给将要创建的Service类创建出Conetxt;但是Context应该如何创建呢?我们平时压根儿没有这么干过,Context都是系统给我们创建好的。既然这样,我们可以参照一下系统是如何创建Service对象的;在上文的Service源码分析中,在ActivityThread类的handleCreateService完成了这个步骤,摘要如下: ```java try { java.lang.ClassLoader cl = packageInfo.getClassLoader(); service = (Service) cl.loadClass(data.info.name).newInstance(); } catch (Exception e) { } try { ContextImpl context = ContextImpl.createAppContext(this, packageInfo); context.setOuterContext(service); Application app = packageInfo.makeApplication(false, mInstrumentation); service.attach(context, this, data.info.name, data.token, app, ActivityManagerNative.getDefault()); service.onCreate(); ``` 可以看到,系统也是通过反射创建出了对应的Service对象,然后也创建了对应的Context,并给Service注入了活力。如果我们模拟系统创建Context这个过程,势必需要进行一系列反射调用,那么我们何不直接反射handleCreateService方法呢? 当然,handleCreateService这个方法并没有把创建出来的Service对象作为返回值返回,而是存放在ActivityThread的成员变量`mService`之中,这个是小case,我们反射取出来就行;所以,创建Service对象的代码如下: ```java /** * 通过ActivityThread的handleCreateService方法创建出Service对象 * @param serviceInfo 插件的ServiceInfo * @throws Exception */ private void proxyCreateService(ServiceInfo serviceInfo) throws Exception { IBinder token = new Binder(); // 创建CreateServiceData对象, 用来传递给ActivityThread的handleCreateService 当作参数 Class createServiceDataClass = Class.forName("android.app.ActivityThread$CreateServiceData"); Constructor constructor = createServiceDataClass.getDeclaredConstructor(); constructor.setAccessible(true); Object createServiceData = constructor.newInstance(); // 写入我们创建的createServiceData的token字段, ActivityThread的handleCreateService用这个作为key存储Service Field tokenField = createServiceDataClass.getDeclaredField("token"); tokenField.setAccessible(true); tokenField.set(createServiceData, token); // 写入info对象 // 这个修改是为了loadClass的时候, LoadedApk会是主程序的ClassLoader, 我们选择Hook BaseDexClassLoader的方式加载插件 serviceInfo.applicationInfo.packageName = UPFApplication.getContext().getPackageName(); Field infoField = createServiceDataClass.getDeclaredField("info"); infoField.setAccessible(true); infoField.set(createServiceData, serviceInfo); // 写入compatInfo字段 // 获取默认的compatibility配置 Class compatibilityClass = Class.forName("android.content.res.CompatibilityInfo"); Field defaultCompatibilityField = compatibilityClass.getDeclaredField("DEFAULT_COMPATIBILITY_INFO"); Object defaultCompatibility = defaultCompatibilityField.get(null); Field compatInfoField = createServiceDataClass.getDeclaredField("compatInfo"); compatInfoField.setAccessible(true); compatInfoField.set(createServiceData, defaultCompatibility); Class activityThreadClass = Class.forName("android.app.ActivityThread"); Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread"); Object currentActivityThread = currentActivityThreadMethod.invoke(null); // private void handleCreateService(CreateServiceData data) { Method handleCreateServiceMethod = activityThreadClass.getDeclaredMethod("handleCreateService", createServiceDataClass); handleCreateServiceMethod.setAccessible(true); handleCreateServiceMethod.invoke(currentActivityThread, createServiceData); // handleCreateService创建出来的Service对象并没有返回, 而是存储在ActivityThread的mServices字段里面, 这里我们手动把它取出来 Field mServicesField = activityThreadClass.getDeclaredField("mServices"); mServicesField.setAccessible(true); Map mServices = (Map) mServicesField.get(currentActivityThread); Service service = (Service) mServices.get(token); // 获取到之后, 移除这个service, 我们只是借花献佛 mServices.remove(token); // 将此Service存储起来 mServiceMap.put(serviceInfo.name, service); } ``` 现在我们已经创建出了对应的PluginService,并且拥有至关重要的Context对象;接下来就可以把消息分发给原始的PluginService组件了,这个分发的过程很简单,直接执行消息对应的回调(onStart, onDestroy等)即可;因此,完整的startService分发过程如下: ```java public void onStart(Intent proxyIntent, int startId) { Intent targetIntent = proxyIntent.getParcelableExtra(AMSHookHelper.EXTRA_TARGET_INTENT); ServiceInfo serviceInfo = selectPluginService(targetIntent); if (serviceInfo == null) { Log.w(TAG, "can not found service : " + targetIntent.getComponent()); return; } try { if (!mServiceMap.containsKey(serviceInfo.name)) { // service还不存在, 先创建 proxyCreateService(serviceInfo); } Service service = mServiceMap.get(serviceInfo.name); service.onStart(targetIntent, startId); } catch (Exception e) { e.printStackTrace(); } } ``` 至此,我们已经实现了Service组件的插件化;完整的代码见 [github][3],代码以startService, stopService为例进行了说明,bindService以及unbindService的原理是一样的,感兴趣的读者可以自行实现;欢迎PR。 ## 小节 本文中我们以绑定服务为例分析了Service组件的工作原理,并指出用户交导致组件生命周期的变化是Activity与Service的根本差别,这种差别使得插件方案对于它们必须采取不同的处理方式;最后我们通过手动控制Service组件的生命周期结合「代理分发技术」成功地实现了Service组件的插件化;这种插件化方案堪称「完美」,如果非要吹毛求疵,那只能说由于同一个进程的所有Service都挂载在同一个ProxyService上面,如果系统可用内存不够必须回收Service,杀死一个ProxyService会导致一大票的插件Service歇菜。 实际使用过程中,Service组件的更新频度并不高,因此直接把插件Service注册到主程序也是可以接受的;而且如果需要绑定远程Service,完全可以使用一个Service组件根据不同的Intent返回不同的IBinder,所以不实现Service组件的插件化也能满足工程需要。值得一提的是,我们对于Service组件的插件化方案实际上是一种「代理」的方式,用这种方式也能实现Activity组件的插件化,有一些开源的插件方案比如 [DL][8] 就是这么做的。 迄今为止,我们讲述了了Activity、BroadcastReceiver以及Service的插件化方式,不知读者思索过没有,实现插件化的关键点在哪里? Service,Activity等不过就是一些普通的Java类,它们之所称为四大组件,是因为他们有生命周期;这也是简单地采用Java的动态加载技术无法实现插件化的原因——动态加载进来的Service等类如果没有它的生命周期,无异于一个没有灵魂的傀儡。对于Activity组件,由于他的生命周期受用户交互影响,只有系统本身才能对这种交互有全局掌控力,因此它的插件化方式是Hook AMS,但是生命周期依然交由系统管理;而Service以及BroadcastReceiver的生命周期没有额外的因素影响,因此我们选择了手动控制其生命周期的方式。不论是借尸还魂还是女娲造人,对这些组件的插件化终归结底是要赋予组件“生命”。 [1]: Activity生命周期管理.md [2]: BroadcastReceiver插件化.md [3]: https://github.com/tiann/understand-plugin-framework [4]: 概述.md [5]: http://developer.android.com/intl/zh-cn/guide/components/services.html [6]: Hook机制之AMS&PMS.md [7]: ClassLoader管理.md [8]: https://github.com/singwhatiwanna/dynamic-load-apk [9]: http://weishu.me ================================================ FILE: DOC/tianweishu/概述.md ================================================ # 概要 2015年是Android插件化技术突飞猛进的一年,随着业务的发展各大厂商都碰到了Android Native平台的瓶颈: 1. 从技术上讲,业务逻辑的复杂导致代码量急剧膨胀,各大厂商陆续出到65535方法数的天花板;同时,运营为王的时代对于模块热更新提出了更高的要求。 2. 在业务层面上,功能模块的解耦以及维护团队的分离也是大势所趋;各个团队维护着同一个App的不同模块,如果每个模块升级新功能都需要对整个app进行升级,那么发布流程不仅复杂而且效率低下;在讲究小步快跑和持续迭代的移动互联网必将遭到淘汰。 H5和Hybird可以解决这些问题,但是始终比不上native的用户体验;于是,国外的FaceBook推出了`react-native`;而国内各大厂商几乎都选择纯native的插件化技术。可以说,Android的未来必将是`react-native`和插件化的天下。 `react-native`资料很多,但是讲述插件化的却凤毛菱角;插件化技术听起来高深莫测,实际上要解决的就是两个问题: 1. 代码加载 2. 资源加载 ## 代码加载 类的加载可以使用Java的`ClassLoader`机制,但是对于Android来说,并不是说类加载进来就可以用了,很多组件都是有“生命”的;因此对于这些有血有肉的类,必须给它们注入活力,也就是所谓的**组件生命周期管理**; 另外,如何管理加载进来的类也是一个问题。假设多个插件依赖了相同的类,是抽取公共依赖进行管理还是插件单独依赖?这就是**ClassLoader的管理问题**; ## 资源加载 资源加载方案大家使用的原理都差不多,都是用`AssetManager`的隐藏方法`addAssetPath`;但是,不同插件的资源如何管理?是公用一套资源还是插件独立资源?共用资源如何避免资源冲突?对于资源加载,有的方案共用一套资源并采用资源分段机制解决冲突(要么修改`aapt`要么添加编译插件);有的方案选择独立资源,不同插件管理自己的资源。 目前国内开源的较成熟的插件方案有[DL](https://github.com/singwhatiwanna/dynamic-load-apk)和[DroidPlugin](https://github.com/Qihoo360/DroidPlugin);但是DL方案仅仅对Frameworl的表层做了处理,严重依赖`that`语法,编写插件代码和主程序代码需单独区分;而DroidPlugin通过Hook增强了Framework层的很多系统服务,开发插件就跟开发独立app差不多;就拿Activity生命周期的管理来说,DL的代理方式就像是牵线木偶,插件只不过是操纵傀儡而已;而DroidPlugin则是借尸还魂,插件是有血有肉的系统管理的真正组件;DroidPlugin Hook了系统几乎所有的Sevice,欺骗了大部分的系统API;掌握这个Hook过程需要掌握很多系统原理,因此学习DroidPlugin对于整个Android FrameWork层大有裨益。 接下来的一系列文章将以DroidPlugin为例讲解插件框架的原理,揭开插件化的神秘面纱;同时还能帮助深入理解Android Framewrok;主要内容如下: - [Hook机制之动态代理][1] - [Hook机制之Binder Hook][2] - [Hook机制之AMS&PMS][3] - [Activity生命周期管理][4] - [插件加载机制][5] - [广播的管理方式][6] - [Service的插件化][7] - [ContentProvider的插件化][8] - DroidPlugin插件通信机制 - 插件机制之资源管理 - 不同插件框架方案对比 - 插件化的未来 另外,对于每一章内容都会有详细的demo,具体见[understand-plugin-framework](https://github.com/tiann/understand-plugin-framework);喜欢就点个关注吧~ [1]: Hook机制之代理Hook.md [2]: Hook机制之Binder-Hook.md [3]: Hook机制之AMS&PMS.md [4]: Activity生命周期管理.md [5]: ClassLoader管理.md [6]: BroadcastReceiver插件化.md [7]: Service插件化.md [8]: ContentProvider插件化.md ================================================ FILE: LICENSE ================================================ GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. ================================================ FILE: project/.gitignore ================================================ /build /bin ================================================ FILE: project/Libraries/DroidPlugin/LICENSE.txt ================================================ GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. ================================================ FILE: project/Libraries/DroidPlugin/build.gradle ================================================ apply plugin: 'com.android.library' dependencies { compileOnly fileTree(dir: 'lib', include: '*.jar') implementation fileTree(dir: 'libs', include: '*.jar') } android { compileSdkVersion rootProject.ext.compileSdkVersion buildToolsVersion rootProject.ext.buildToolsVersion lintOptions { abortOnError false } defaultConfig{ // 建议改为自己的 packageName + .droidplugin_stub ,防止跟其它本插件使用者冲突 def authorityName = "com.morgoo.droidplugin_stub" minSdkVersion 9 versionCode 1 versionName '1.0' buildConfigField "String", "AUTHORITY_NAME", "\"${authorityName}\"" manifestPlaceholders = [ authorityName:"${authorityName}", ] } } ================================================ FILE: project/Libraries/DroidPlugin/proguard-project.txt ================================================ # To enable ProGuard in your project, edit project.properties # to define the proguard.config property as described in that file. # # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in ${sdk.dir}/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the ProGuard # include property in project.properties. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} ================================================ FILE: project/Libraries/DroidPlugin/project.properties ================================================ # This file is automatically generated by Android Tools. # Do not modify this file -- YOUR CHANGES WILL BE ERASED! # # This file must be checked in Version Control Systems. # # To customize properties used by the Ant build system edit # "ant.properties", and override values to adapt the script to your # project structure. # # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt # Project target. target=android-21 ================================================ FILE: project/Libraries/DroidPlugin/src/main/AndroidManifest.xml ================================================ ================================================ FILE: project/Libraries/DroidPlugin/src/main/aidl/android/app/IServiceConnection.aidl ================================================ /* //device/java/android/android/app/IServiceConnection.aidl ** ** Copyright 2007, The Android Open Source Project ** ** Licensed under the Apache License, Version 2.0 (the "License"); ** you may not use this file except in compliance with the License. ** You may obtain a copy of the License at ** ** http://www.apache.org/licenses/LICENSE-2.0 ** ** Unless required by applicable law or agreed to in writing, software ** distributed under the License is distributed on an "AS IS" BASIS, ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ** See the License for the specific language governing permissions and ** limitations under the License. */ package android.app; import android.content.ComponentName; /** @hide */ oneway interface IServiceConnection { void connected(in ComponentName name, IBinder service); } ================================================ FILE: project/Libraries/DroidPlugin/src/main/aidl/com/morgoo/droidplugin/pm/IApplicationCallback.aidl ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.pm; /** * API for package data change related callbacks from the Package Manager. * Some usage scenarios include deletion of cache directory, generate * statistics related to code, data, cache usage * {@hide} */ interface IApplicationCallback { Bundle onCallback(in Bundle extra); } ================================================ FILE: project/Libraries/DroidPlugin/src/main/aidl/com/morgoo/droidplugin/pm/IPackageDataObserver.aidl ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.pm; /** * API for package data change related callbacks from the Package Manager. * Some usage scenarios include deletion of cache directory, generate * statistics related to code, data, cache usage */ oneway interface IPackageDataObserver { void onRemoveCompleted(in String packageName, boolean succeeded); } ================================================ FILE: project/Libraries/DroidPlugin/src/main/aidl/com/morgoo/droidplugin/pm/IPluginManager.aidl ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.pm; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageInfo; import android.content.pm.ServiceInfo; import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import com.morgoo.droidplugin.pm.IPackageDataObserver; import com.morgoo.droidplugin.pm.IApplicationCallback; import java.util.List; /** * Code by Andy Zhang (zhangyong232@gmail.com) on 2015/2/12. */ interface IPluginManager { //for my api boolean waitForReady(); ////////////////////////////////////// // // THIS API FOR PACKAGE MANAGER // ////////////////////////////////////// PackageInfo getPackageInfo(in String packageName, int flags); boolean isPluginPackage(in String packageName); ActivityInfo getActivityInfo(in ComponentName className, int flags); ActivityInfo getReceiverInfo(in ComponentName className, int flags); ServiceInfo getServiceInfo(in ComponentName className, int flags); ProviderInfo getProviderInfo(in ComponentName className, int flags); ResolveInfo resolveIntent(in Intent intent, in String resolvedType, int flags); List queryIntentActivities(in Intent intent,in String resolvedType, int flags); List queryIntentReceivers(in Intent intent, String resolvedType, int flags); ResolveInfo resolveService(in Intent intent, String resolvedType, int flags); List queryIntentServices(in Intent intent, String resolvedType, int flags); List queryIntentContentProviders(in Intent intent, String resolvedType, int flags); List getInstalledPackages(int flags); List getInstalledApplications(int flags); PermissionInfo getPermissionInfo(in String name, int flags); List queryPermissionsByGroup(in String group, int flags); PermissionGroupInfo getPermissionGroupInfo(in String name, int flags); List getAllPermissionGroups(int flags); ProviderInfo resolveContentProvider(in String name, int flags); void deleteApplicationCacheFiles(in String packageName,in IPackageDataObserver observer); void clearApplicationUserData(in String packageName,in IPackageDataObserver observer); ApplicationInfo getApplicationInfo(in String packageName, int flags); int installPackage(in String filepath,int flags); int deletePackage(in String packageName ,int flags); List getReceivers(in String packageName ,int flags); List getReceiverIntentFilter(in ActivityInfo info); int checkSignatures(in String pkg1, in String pkg2); ////////////////////////////////////// // // THIS API FOR ACTIVITY MANAGER // ////////////////////////////////////// ActivityInfo selectStubActivityInfo(in ActivityInfo targetInfo); ActivityInfo selectStubActivityInfoByIntent(in Intent targetIntent); ServiceInfo selectStubServiceInfo(in ServiceInfo targetInfo); ServiceInfo selectStubServiceInfoByIntent(in Intent targetIntent); ServiceInfo getTargetServiceInfo(in ServiceInfo stubInfo); ProviderInfo selectStubProviderInfo(in String name); List getPackageNameByPid(in int pid); String getProcessNameByPid(in int pid); boolean killBackgroundProcesses(in String packageName); boolean killApplicationProcess(in String pluginPackageName); boolean forceStopPackage(in String pluginPackageName); boolean registerApplicationCallback(in IApplicationCallback callback); boolean unregisterApplicationCallback(in IApplicationCallback callback); void onActivityCreated(in ActivityInfo stubInfo,in ActivityInfo targetInfo); void onActivityDestory(in ActivityInfo stubInfo,in ActivityInfo targetInfo); void onServiceCreated(in ServiceInfo stubInfo,in ServiceInfo targetInfo); void onServiceDestory(in ServiceInfo stubInfo,in ServiceInfo targetInfo); void onProviderCreated(in ProviderInfo stubInfo,in ProviderInfo targetInfo); void reportMyProcessName(in String stubProcessName,in String targetProcessName, String targetPkg); void onActivtyOnNewIntent(in ActivityInfo stubInfo,in ActivityInfo targetInfo, in Intent intent); int getMyPid(); } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/MyCrashHandler.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin; import android.app.ActivityManager; import android.app.ActivityManager.RunningAppProcessInfo; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.os.Environment; import com.morgoo.helper.Log; import com.morgoo.helper.compat.SystemPropertiesCompat; import java.io.File; import java.io.PrintWriter; import java.lang.Thread.UncaughtExceptionHandler; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; /** * @author Andy Zhang(zhangyong232@gmail.com) */ public class MyCrashHandler implements UncaughtExceptionHandler { private static final String TAG = "MyCrashHandler"; private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); private static final SimpleDateFormat SIMPLE_DATE_FORMAT1 = new SimpleDateFormat("yyyyMMddHHmmss"); private static final MyCrashHandler sMyCrashHandler = new MyCrashHandler(); private UncaughtExceptionHandler mOldHandler; private Context mContext; public static MyCrashHandler getInstance() { return sMyCrashHandler; } public void register(Context context) { if (context != null) { mOldHandler = Thread.getDefaultUncaughtExceptionHandler(); if (mOldHandler != this) { Thread.setDefaultUncaughtExceptionHandler(this); } mContext = context; } } @Override public void uncaughtException(Thread thread, Throwable ex) { Log.e(TAG, "uncaughtException", ex); PrintWriter writer = null; try { Date date = new Date(); String dateStr = SIMPLE_DATE_FORMAT1.format(date); File file = new File(Environment.getExternalStorageDirectory(), String.format("PluginLog/CrashLog/CrashLog_%s_%s.log", dateStr, android.os.Process.myPid())); if (!file.getParentFile().exists()) { file.getParentFile().mkdirs(); } if (!file.getParentFile().exists()) { file.getParentFile().mkdirs(); } if (file.exists()) { file.delete(); } writer = new PrintWriter(file); writer.println("Date:" + SIMPLE_DATE_FORMAT.format(date)); writer.println("----------------------------------------System Infomation-----------------------------------"); String packageName = mContext.getPackageName(); writer.println("AppPkgName:" + packageName); try { PackageInfo packageInfo = mContext.getPackageManager().getPackageInfo(packageName, 0); writer.println("VersionCode:" + packageInfo.versionCode); writer.println("VersionName:" + packageInfo.versionName); writer.println("Debug:" + (0 != (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE))); } catch (Exception e) { writer.println("VersionCode:-1"); writer.println("VersionName:null"); writer.println("Debug:Unkown"); } writer.println("PName:" + getProcessName()); try { writer.println("imei:" + getIMEI(mContext)); } catch (Exception e) { } writer.println("Board:" + SystemPropertiesCompat.get("ro.product.board", "unknown")); writer.println("ro.bootloader:" + SystemPropertiesCompat.get("ro.bootloader", "unknown")); writer.println("ro.product.brand:" + SystemPropertiesCompat.get("ro.product.brand", "unknown")); writer.println("ro.product.cpu.abi:" + SystemPropertiesCompat.get("ro.product.cpu.abi", "unknown")); writer.println("ro.product.cpu.abi2:" + SystemPropertiesCompat.get("ro.product.cpu.abi2", "unknown")); writer.println("ro.product.device:" + SystemPropertiesCompat.get("ro.product.device", "unknown")); writer.println("ro.build.display.id:" + SystemPropertiesCompat.get("ro.build.display.id", "unknown")); writer.println("ro.build.fingerprint:" + SystemPropertiesCompat.get("ro.build.fingerprint", "unknown")); writer.println("ro.hardware:" + SystemPropertiesCompat.get("ro.hardware", "unknown")); writer.println("ro.build.host:" + SystemPropertiesCompat.get("ro.build.host", "unknown")); writer.println("ro.build.id:" + SystemPropertiesCompat.get("ro.build.id", "unknown")); writer.println("ro.product.manufacturer:" + SystemPropertiesCompat.get("ro.product.manufacturer", "unknown")); writer.println("ro.product.model:" + SystemPropertiesCompat.get("ro.product.model", "unknown")); writer.println("ro.product.name:" + SystemPropertiesCompat.get("ro.product.name", "unknown")); writer.println("gsm.version.baseband:" + SystemPropertiesCompat.get("gsm.version.baseband", "unknown")); writer.println("ro.build.tags:" + SystemPropertiesCompat.get("ro.build.tags", "unknown")); writer.println("ro.build.type:" + SystemPropertiesCompat.get("ro.build.type", "unknown")); writer.println("ro.build.user:" + SystemPropertiesCompat.get("ro.build.user", "unknown")); writer.println("ro.build.version.codename:" + SystemPropertiesCompat.get("ro.build.version.codename", "unknown")); writer.println("ro.build.version.incremental:" + SystemPropertiesCompat.get("ro.build.version.incremental", "unknown")); writer.println("ro.build.version.release:" + SystemPropertiesCompat.get("ro.build.version.release", "unknown")); writer.println("ro.build.version.sdk:" + SystemPropertiesCompat.get("ro.build.version.sdk", "unknown")); writer.println("\n\n\n----------------------------------Exception---------------------------------------\n\n"); writer.println("----------------------------Exception message:" + ex.getLocalizedMessage() + "\n"); writer.println("----------------------------Exception StackTrace:"); ex.printStackTrace(writer); } catch (Throwable e) { Log.e(TAG, "记录uncaughtException", e); } finally { try { if (writer != null) { writer.flush(); writer.close(); } } catch (Exception e) { } if (mOldHandler != null) { mOldHandler.uncaughtException(thread, ex); } } } private String getIMEI(Context mContext) { return "test"; } public String getProcessName() { ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); List infos = am.getRunningAppProcesses(); for (RunningAppProcessInfo info : infos) { if (info.pid == android.os.Process.myPid()) { return info.processName; } } return null; } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/PluginApplication.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin; import android.app.Application; import android.content.Context; /** * Created by Andy Zhang(zhangyong232@gmail.com) 2014/12/5. */ public class PluginApplication extends Application { private static final String TAG = PluginApplication.class.getSimpleName(); @Override public void onCreate() { super.onCreate(); PluginHelper.getInstance().applicationOnCreate(getBaseContext()); } @Override protected void attachBaseContext(Context base) { PluginHelper.getInstance().applicationAttachBaseContext(base); super.attachBaseContext(base); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/PluginHelper.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin; import android.content.ComponentName; import android.content.Context; import android.content.ServiceConnection; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.MessageQueue; import com.morgoo.droidplugin.core.PluginProcessManager; import com.morgoo.droidplugin.pm.PluginManager; import com.morgoo.droidplugin.reflect.FieldUtils; import com.morgoo.droidplugin.reflect.MethodUtils; import com.morgoo.helper.Log; import com.morgoo.helper.compat.ActivityThreadCompat; import java.lang.reflect.InvocationTargetException; import java.util.HashMap; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/5/18. */ public class PluginHelper implements ServiceConnection { private static final String TAG = PluginHelper.class.getSimpleName(); private static PluginHelper sInstance = null; private PluginHelper() { } public static final PluginHelper getInstance() { if (sInstance == null) { sInstance = new PluginHelper(); } return sInstance; } public void applicationOnCreate(final Context baseContext) { mContext = baseContext; initPlugin(baseContext); } private Context mContext; private void initPlugin(Context baseContext) { long b = System.currentTimeMillis(); try { try { fixMiUiLbeSecurity(); } catch (Throwable e) { Log.e(TAG, "fixMiUiLbeSecurity has error", e); } try { PluginPatchManager.getInstance().init(baseContext); PluginProcessManager.installHook(baseContext); } catch (Throwable e) { Log.e(TAG, "installHook has error", e); } try { if (PluginProcessManager.isPluginProcess(baseContext)) { PluginProcessManager.setHookEnable(true); } else { PluginProcessManager.setHookEnable(false); } } catch (Throwable e) { Log.e(TAG, "setHookEnable has error", e); } try { PluginManager.getInstance().addServiceConnection(PluginHelper.this); PluginManager.getInstance().init(baseContext); } catch (Throwable e) { Log.e(TAG, "installHook has error", e); } } finally { Log.i(TAG, "Init plugin in process cost %s ms", (System.currentTimeMillis() - b)); } } //解决小米JLB22.0 4.1.1系统自带的小米安全中心(lbe.security.miui)广告拦截组件导致的插件白屏问题 private void fixMiUiLbeSecurity() throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { //卸载掉LBE安全的ApplicationLoaders.mLoaders钩子 Class ApplicationLoaders = Class.forName("android.app.ApplicationLoaders"); Object applicationLoaders = MethodUtils.invokeStaticMethod(ApplicationLoaders, "getDefault"); Object mLoaders = FieldUtils.readField(applicationLoaders, "mLoaders", true); if (mLoaders instanceof HashMap) { HashMap oldValue = ((HashMap) mLoaders); if ("com.lbe.security.client.ClientContainer$MonitoredLoaderMap".equals(mLoaders.getClass().getName())) { HashMap value = new HashMap(); value.putAll(oldValue); FieldUtils.writeField(applicationLoaders, "mLoaders", value, true); } } //卸载掉LBE安全的ActivityThread.mPackages钩子 Object currentActivityThread = ActivityThreadCompat.currentActivityThread(); Object mPackages = FieldUtils.readField(currentActivityThread, "mPackages", true); if (mPackages instanceof HashMap) { HashMap oldValue = ((HashMap) mPackages); if ("com.lbe.security.client.ClientContainer$MonitoredPackageMap".equals(mPackages.getClass().getName())) { HashMap value = new HashMap(); value.putAll(oldValue); FieldUtils.writeField(currentActivityThread, "mPackages", value, true); } } //当前已经处在主线程消息队列中的所有消息,找出lbe消息并remove之 if (Looper.getMainLooper() == Looper.myLooper()) { final MessageQueue queue = Looper.myQueue(); try { Object mMessages = FieldUtils.readField(queue, "mMessages", true); if (mMessages instanceof Message) { findLbeMessageAndRemoveIt((Message) mMessages); } Log.e(TAG, "getMainLooper MessageQueue.IdleHandler:" + mMessages); } catch (Exception e) { Log.e(TAG, "fixMiUiLbeSecurity:error on remove lbe message", e); } } } //由于消息队列是一个单向链表,我们递归处理。 //递归当前已经处在主线程消息队列中的所有消息,找出lbe消息并remove之 private void findLbeMessageAndRemoveIt(Message message) { if (message == null) { return; } Runnable callback = message.getCallback(); if (message.what == 0 && callback != null) { if (callback.getClass().getName().indexOf("com.lbe.security.client") >= 0) { message.getTarget().removeCallbacks(callback); } } try { Object nextObj = FieldUtils.readField(message, "next", true); if (nextObj != null) { Message next = (Message) nextObj; findLbeMessageAndRemoveIt(next); } } catch (Exception e) { Log.e(TAG, "findLbeMessageAndRemoveIt:error on remove lbe message", e); } } @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { PluginProcessManager.setHookEnable(true, true); } @Override public void onServiceDisconnected(ComponentName componentName) { } public void applicationAttachBaseContext(Context baseContext) { MyCrashHandler.getInstance().register(baseContext); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/PluginManagerService.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin; import android.app.Notification; import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.IBinder; import com.morgoo.droidplugin.hook.handle.IActivityManagerHookHandle; import com.morgoo.droidplugin.pm.IPluginManagerImpl; import com.morgoo.droidplugin.pm.PluginManager; /** * 插件管理服务。 *

* Code by Andy Zhang (zhangyong232@gmail.com) on 2015/2/11. */ public class PluginManagerService extends Service { private static final String TAG = PluginManagerService.class.getSimpleName(); private static IPluginManagerImpl mPluginPackageManager; public static IPluginManagerImpl getPluginPackageManager(Context context) { if (mPluginPackageManager == null) { synchronized (PluginManager.class) { if (mPluginPackageManager == null) { mPluginPackageManager = new IPluginManagerImpl(context); mPluginPackageManager.onCreate(); } } } return mPluginPackageManager; } @Override public void onCreate() { super.onCreate(); keepAlive(); getPluginPackageManager(this); } private void keepAlive() { try { Notification notification = new Notification(); notification.flags |= Notification.FLAG_NO_CLEAR; notification.flags |= Notification.FLAG_ONGOING_EVENT; startForeground(0, notification); // 设置为前台服务避免kill,Android4.3及以上需要设置id为0时通知栏才不显示该通知; } catch (Throwable e) { e.printStackTrace(); } } @Override public void onDestroy() { try { mPluginPackageManager.onDestroy(); } catch (Exception e) { e.printStackTrace(); } super.onDestroy(); } @Override public IBinder onBind(Intent intent) { return mPluginPackageManager; } @Override public int onStartCommand(Intent intent, int flags, int startId) { //这里要处理IntentService IActivityManagerHookHandle.getIntentSender.handlePendingIntent(this, intent); return super.onStartCommand(intent, flags, startId); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/PluginPatchManager.java ================================================ package com.morgoo.droidplugin; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.os.Handler; import android.os.Looper; import com.morgoo.droidplugin.pm.PluginManager; /** * Created by zhanglong on 2015/12/21. */ // 处理插件异常情况 public class PluginPatchManager { private static final int MAX_WAIT_DAEAMON_TIME = 5000; private static final int CHECK_DAEAMON_INTERVAL = 300; private Context mContext; private Runnable mDelayRunnable; private Intent mDelayIntent; private Handler MainThreadHandler; private long lStartTime = 0; private static PluginPatchManager s_inst = new PluginPatchManager(); public static PluginPatchManager getInstance() { return s_inst; } public void init(Context context){ mContext = context; } // 是否可以启动插件的activity public boolean canStartPluginActivity(Intent intent) { if (intent == null || PluginManager.getInstance().isConnected()) return true; ComponentName name = intent.getComponent(); if (name != null && mContext != null && !name.getPackageName().equals(mContext.getPackageName())) return false; return true; } public boolean startPluginActivity(Intent intent) { if (intent == null) return false; if (PluginManager.getInstance().isConnected()) { mDelayIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); mContext.startActivity(mDelayIntent); return true; } PluginManager.getInstance().connectToService(); initInner(); mDelayIntent = intent; lStartTime = System.currentTimeMillis(); MainThreadHandler.postDelayed(mDelayRunnable, CHECK_DAEAMON_INTERVAL); return true; } private void postDelayImpl() { if (PluginManager.getInstance().isConnected()) { mDelayIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); mContext.startActivity(mDelayIntent); } else { if (System.currentTimeMillis() - lStartTime < MAX_WAIT_DAEAMON_TIME) { MainThreadHandler.postDelayed(mDelayRunnable, CHECK_DAEAMON_INTERVAL); } } } private void initInner() { if (MainThreadHandler == null) MainThreadHandler = new Handler(Looper.getMainLooper()); if (mDelayRunnable == null) { mDelayRunnable = new Runnable() { @Override public void run() { postDelayImpl(); } }; } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/PluginServiceProvider.java ================================================ package com.morgoo.droidplugin; import android.content.ContentProvider; import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import com.morgoo.helper.compat.BundleCompat; public class PluginServiceProvider extends ContentProvider { @Override public boolean onCreate() { return true; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { return null; } @Override public String getType(Uri uri) { return uri.getPath(); } @Override public Uri insert(Uri uri, ContentValues values) { return null; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { return 0; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { return 0; } @Override public Bundle call(String method, String arg, Bundle extras) { if (Method_GetManager.equals(method)) { Uri uri = null; if (extras != null && extras.containsKey(URI_VALUE)) { uri = Uri.parse(extras.getString(URI_VALUE)); } Bundle bundle = new Bundle(); BundleCompat.putBinder(bundle, Arg_Binder, PluginManagerService.getPluginPackageManager(getContext())); return bundle; } return null; } public static final String Method_GetManager = "getPluginManager"; public static final String Arg_Binder = "_binder_"; public static final String URI_KEY = "key"; public static final String URI_VALUE = "uri"; } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/am/BaseActivityManagerService.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.am; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ProviderInfo; import android.content.pm.ServiceInfo; import android.os.Bundle; import android.os.RemoteCallbackList; import android.os.RemoteException; import com.morgoo.droidplugin.pm.IApplicationCallback; import com.morgoo.droidplugin.pm.IPluginManagerImpl; import com.morgoo.droidplugin.pm.parser.PluginPackageParser; import com.morgoo.helper.Log; import java.util.List; import java.util.Map; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/3/10. */ public abstract class BaseActivityManagerService { private static final String TAG = BaseActivityManagerService.class.getSimpleName(); protected Context mHostContext; public BaseActivityManagerService(Context hostContext) { mHostContext = hostContext; } //查询某个进程中运行的插件包名列表 public abstract List getPackageNamesByPid(int pid); public abstract ActivityInfo selectStubActivityInfo(int callingPid, int callingUid, ActivityInfo targetInfo) throws RemoteException; public abstract ServiceInfo selectStubServiceInfo(int callingPid, int callingUid, ServiceInfo targetInfo) throws RemoteException; public abstract ServiceInfo getTargetServiceInfo(int callingPid, int callingUid, ServiceInfo stubInfo) throws RemoteException; public abstract ProviderInfo selectStubProviderInfo(int callingPid, int callingUid, ProviderInfo targetInfo) throws RemoteException; public void onPkgDeleted(Map pluginCache, PluginPackageParser parser, String packageName) throws Exception { } public void onPkgInstalled(Map pluginCache, PluginPackageParser parser, String packageName) throws Exception { } public void onCreate(IPluginManagerImpl pluginManagerImpl) throws Exception { if (mRemoteCallbackList == null) { mRemoteCallbackList = new MyRemoteCallbackList(); } } private RemoteCallbackList mRemoteCallbackList; public String getProcessNameByPid(int pid) { return null; } public void onReportMyProcessName(int callingPid, int callingUid, String stubProcessName, String targetProcessName, String targetPkg) { } public abstract void onActivityOnNewIntent(int callingPid, int callingUid, ActivityInfo stubInfo, ActivityInfo targetInfo, Intent intent); private static class ProcessCookie { private ProcessCookie(int pid, int uid) { this.pid = pid; this.uid = uid; } private final int pid; private final int uid; } private class MyRemoteCallbackList extends RemoteCallbackList { @Override public void onCallbackDied(IApplicationCallback callback, Object cookie) { super.onCallbackDied(callback, cookie); if (cookie != null && cookie instanceof ProcessCookie) { ProcessCookie p = (ProcessCookie) cookie; onProcessDied(p.pid, p.uid); } } } protected void onProcessDied(int pid, int uid) { Log.i(TAG, "onProcessDied,pid=%s,uid=%s", pid, uid); } protected void sendCallBack(Bundle extra) { if (mRemoteCallbackList != null) { int i = mRemoteCallbackList.beginBroadcast(); while (i > 0) { i--; try { mRemoteCallbackList.getBroadcastItem(i).onCallback(extra); } catch (RemoteException e) { // The RemoteCallbackList will take care of removing // the dead object for us. } } mRemoteCallbackList.finishBroadcast(); } } public boolean registerApplicationCallback(int callingPid, int callingUid, IApplicationCallback callback) { return mRemoteCallbackList.register(callback, new ProcessCookie(callingPid, callingUid)); } public boolean unregisterApplicationCallback(int callingPid, int callingUid, IApplicationCallback callback) { return mRemoteCallbackList.unregister(callback); } public void onActivityCreated(int callingPid, int callingUid, ActivityInfo stubInfo, ActivityInfo targetInfo) { } public void onActivityDestroy(int callingPid, int callingUid, ActivityInfo stubInfo, ActivityInfo targetInfo) { } public void onServiceCreated(int callingPid, int callingUid, ServiceInfo stubInfo, ServiceInfo targetInfo) { } public void onServiceDestroy(int callingPid, int callingUid, ServiceInfo stubInfo, ServiceInfo targetInfo) { } public void onProviderCreated(int callingPid, int callingUid, ProviderInfo stubInfo, ProviderInfo targetInfo) { } public void onDestroy() { mRemoteCallbackList.kill(); mRemoteCallbackList = null; } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/am/MyActivityManagerService.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.am; import android.app.ActivityManager; import android.app.ActivityManager.RunningAppProcessInfo; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ProviderInfo; import android.content.pm.ServiceInfo; import android.os.RemoteException; import android.text.TextUtils; import com.morgoo.droidplugin.pm.IApplicationCallback; import com.morgoo.droidplugin.pm.IPluginManagerImpl; import com.morgoo.droidplugin.reflect.FieldUtils; import com.morgoo.droidplugin.stub.AbstractServiceStub; import com.morgoo.helper.AttributeCache; import com.morgoo.helper.Log; import com.morgoo.helper.Utils; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; /** * 这是一个比较复杂的进程管理服务。 * 主要实现的功能为: * 1、系统预定义N个进程。每个进程下有4中launchmod的activity,1个服务,一个ContentProvider。 * 2、每个插件可以在多个进程中运行,这由插件自己的processName属性决定。 * 3、插件系统最多可以同时运行N个进程,M个插件(M <= N or M >= N)。 * 4、多个插件运行在同一个进程中,如果他们的签名相同。(我们可以通过一个开关来决定。) * 5、在运行第M+1个插件时,如果预定义的N个进程被占满,最低优先级的进程会被kill掉。腾出预定义的进程用来运行此个插件。 * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/3/10. */ public class MyActivityManagerService extends BaseActivityManagerService { private static final String TAG = MyActivityManagerService.class.getSimpleName(); private StaticProcessList mStaticProcessList = new StaticProcessList(); private RunningProcessList mRunningProcessList = new RunningProcessList(); public MyActivityManagerService(Context hostContext) { super(hostContext); mRunningProcessList.setContext(mHostContext); } @Override public void onCreate(IPluginManagerImpl pluginManagerImpl) throws Exception { super.onCreate(pluginManagerImpl); AttributeCache.init(mHostContext); mStaticProcessList.onCreate(mHostContext); mRunningProcessList.setContext(mHostContext); } @Override public void onDestroy() { mRunningProcessList.clear(); mStaticProcessList.clear(); runProcessGC(); super.onDestroy(); } @Override protected void onProcessDied(int pid, int uid) { mRunningProcessList.onProcessDied(pid, uid); runProcessGC(); super.onProcessDied(pid, uid); } @Override public boolean registerApplicationCallback(int callingPid, int callingUid, IApplicationCallback callback) { boolean b = super.registerApplicationCallback(callingPid, callingUid, callback); mRunningProcessList.addItem(callingPid, callingUid); if (callingPid == android.os.Process.myPid()) { String stubProcessName = Utils.getProcessName(mHostContext, callingPid); String targetProcessName = Utils.getProcessName(mHostContext, callingPid); String targetPkg = mHostContext.getPackageName(); mRunningProcessList.setProcessName(callingPid, stubProcessName, targetProcessName, targetPkg); } if (TextUtils.equals(mHostContext.getPackageName(), Utils.getProcessName(mHostContext, callingPid))) { String stubProcessName = mHostContext.getPackageName(); String targetProcessName = mHostContext.getPackageName(); String targetPkg = mHostContext.getPackageName(); mRunningProcessList.setProcessName(callingPid, stubProcessName, targetProcessName, targetPkg); } return b; } @Override public ProviderInfo selectStubProviderInfo(int callingPid, int callingUid, ProviderInfo targetInfo) throws RemoteException { runProcessGC(); //先从正在运行的进程中查找看是否有符合条件的进程,如果有则直接使用之 String stubProcessName1 = mRunningProcessList.getStubProcessByTarget(targetInfo); if (stubProcessName1 != null) { List stubInfos = mStaticProcessList.getProviderInfoForProcessName(stubProcessName1); for (ProviderInfo stubInfo : stubInfos) { if (!mRunningProcessList.isStubInfoUsed(stubInfo)) { mRunningProcessList.setTargetProcessName(stubInfo, targetInfo); return stubInfo; } } } List stubProcessNames = mStaticProcessList.getProcessNames(); for (String stubProcessName : stubProcessNames) { List stubInfos = mStaticProcessList.getProviderInfoForProcessName(stubProcessName); if (mRunningProcessList.isProcessRunning(stubProcessName)) { if (mRunningProcessList.isPkgEmpty(stubProcessName)) {//空进程,没有运行任何插件包。 for (ProviderInfo stubInfo : stubInfos) { if (!mRunningProcessList.isStubInfoUsed(stubInfo)) { mRunningProcessList.setTargetProcessName(stubInfo, targetInfo); return stubInfo; } } throw throwException("没有找到合适的StubInfo"); } else if (mRunningProcessList.isPkgCanRunInProcess(targetInfo.packageName, stubProcessName, targetInfo.processName)) { for (ProviderInfo stubInfo : stubInfos) { if (!mRunningProcessList.isStubInfoUsed(stubInfo)) { mRunningProcessList.setTargetProcessName(stubInfo, targetInfo); return stubInfo; } } throw throwException("没有找到合适的StubInfo"); } else { //需要处理签名一样的情况。 } } else { for (ProviderInfo stubInfo : stubInfos) { if (!mRunningProcessList.isStubInfoUsed(stubInfo)) { mRunningProcessList.setTargetProcessName(stubInfo, targetInfo); return stubInfo; } } throw throwException("没有找到合适的StubInfo"); } } throw throwException("没有可用的进程了"); } @Override public ServiceInfo getTargetServiceInfo(int callingPid, int callingUid, ServiceInfo stubInfo) throws RemoteException { //TODO getTargetServiceInfo return null; } @Override public String getProcessNameByPid(int pid) { return mRunningProcessList.getTargetProcessNameByPid(pid); } @Override public ServiceInfo selectStubServiceInfo(int callingPid, int callingUid, ServiceInfo targetInfo) throws RemoteException { runProcessGC(); //先从正在运行的进程中查找看是否有符合条件的进程,如果有则直接使用之 String stubProcessName1 = mRunningProcessList.getStubProcessByTarget(targetInfo); if (stubProcessName1 != null) { List stubInfos = mStaticProcessList.getServiceInfoForProcessName(stubProcessName1); for (ServiceInfo stubInfo : stubInfos) { if (!mRunningProcessList.isStubInfoUsed(stubInfo)) { mRunningProcessList.setTargetProcessName(stubInfo, targetInfo); return stubInfo; } } } List stubProcessNames = mStaticProcessList.getProcessNames(); for (String stubProcessName : stubProcessNames) { List stubInfos = mStaticProcessList.getServiceInfoForProcessName(stubProcessName); if (mRunningProcessList.isProcessRunning(stubProcessName)) {//该预定义的进程正在运行。 if (mRunningProcessList.isPkgEmpty(stubProcessName)) {//空进程,没有运行任何插件包。 for (ServiceInfo stubInfo : stubInfos) { if (!mRunningProcessList.isStubInfoUsed(stubInfo)) { mRunningProcessList.setTargetProcessName(stubInfo, targetInfo); return stubInfo; } } throw throwException("没有找到合适的StubInfo"); } else if (mRunningProcessList.isPkgCanRunInProcess(targetInfo.packageName, stubProcessName, targetInfo.processName)) { for (ServiceInfo stubInfo : stubInfos) { if (!mRunningProcessList.isStubInfoUsed(stubInfo)) { mRunningProcessList.setTargetProcessName(stubInfo, targetInfo); return stubInfo; } } throw throwException("没有找到合适的StubInfo"); } else { //这里需要考虑签名一样的情况,多个插件公用一个进程。 } } else { //该预定义的进程没有。 for (ServiceInfo stubInfo : stubInfos) { if (!mRunningProcessList.isStubInfoUsed(stubInfo)) { mRunningProcessList.setTargetProcessName(stubInfo, targetInfo); return stubInfo; } } throw throwException("没有找到合适的StubInfo"); } } throw throwException("没有可用的进程了"); } private RemoteException throwException(String msg) { RemoteException remoteException = new RemoteException(); remoteException.initCause(new RuntimeException(msg)); return remoteException; } @Override public void onActivityCreated(int callingPid, int callingUid, ActivityInfo stubInfo, ActivityInfo targetInfo) { mRunningProcessList.addActivityInfo(callingPid, callingUid, stubInfo, targetInfo); } @Override public void onActivityDestroy(int callingPid, int callingUid, ActivityInfo stubInfo, ActivityInfo targetInfo) { mRunningProcessList.removeActivityInfo(callingPid, callingUid, stubInfo, targetInfo); runProcessGC(); } @Override public void onActivityOnNewIntent(int callingPid, int callingUid, ActivityInfo stubInfo, ActivityInfo targetInfo, Intent intent) { mRunningProcessList.addActivityInfo(callingPid, callingUid, stubInfo, targetInfo); } @Override public void onServiceCreated(int callingPid, int callingUid, ServiceInfo stubInfo, ServiceInfo targetInfo) { mRunningProcessList.addServiceInfo(callingPid, callingUid, stubInfo, targetInfo); } @Override public void onServiceDestroy(int callingPid, int callingUid, ServiceInfo stubInfo, ServiceInfo targetInfo) { mRunningProcessList.removeServiceInfo(callingPid, callingUid, stubInfo, targetInfo); runProcessGC(); } @Override public void onProviderCreated(int callingPid, int callingUid, ProviderInfo stubInfo, ProviderInfo targetInfo) { mRunningProcessList.addProviderInfo(callingPid, callingUid, stubInfo, targetInfo); } @Override public void onReportMyProcessName(int callingPid, int callingUid, String stubProcessName, String targetProcessName, String targetPkg) { mRunningProcessList.setProcessName(callingPid, stubProcessName, targetProcessName, targetPkg); } @Override public List getPackageNamesByPid(int pid) { return new ArrayList(mRunningProcessList.getPackageNameByPid(pid)); } @Override public ActivityInfo selectStubActivityInfo(int callingPid, int callingUid, ActivityInfo targetInfo) throws RemoteException { runProcessGC(); // if (targetInfo.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) { // targetInfo.launchMode = ActivityInfo.LAUNCH_MULTIPLE; // } // // if (targetInfo.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { // targetInfo.launchMode = ActivityInfo.LAUNCH_MULTIPLE; // } // // if (targetInfo.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP) { // targetInfo.launchMode = ActivityInfo.LAUNCH_MULTIPLE; // } boolean Window_windowIsTranslucent = false; boolean Window_windowIsFloating = false; boolean Window_windowShowWallpaper = false; try { Class R_Styleable_Class = Class.forName("com.android.internal.R$styleable"); int[] R_Styleable_Window = (int[]) FieldUtils.readStaticField(R_Styleable_Class, "Window"); int R_Styleable_Window_windowIsTranslucent = (int) FieldUtils.readStaticField(R_Styleable_Class, "Window_windowIsTranslucent"); int R_Styleable_Window_windowIsFloating = (int) FieldUtils.readStaticField(R_Styleable_Class, "Window_windowIsFloating"); int R_Styleable_Window_windowShowWallpaper = (int) FieldUtils.readStaticField(R_Styleable_Class, "Window_windowShowWallpaper"); AttributeCache.Entry ent = AttributeCache.instance().get(targetInfo.packageName, targetInfo.theme, R_Styleable_Window); if (ent != null && ent.array != null) { Window_windowIsTranslucent = ent.array.getBoolean(R_Styleable_Window_windowIsTranslucent, false); Window_windowIsFloating = ent.array.getBoolean(R_Styleable_Window_windowIsFloating, false); Window_windowShowWallpaper = ent.array.getBoolean(R_Styleable_Window_windowShowWallpaper, false); } } catch (Throwable e) { Log.e(TAG, "error on read com.android.internal.R$styleable", e); } boolean useDialogStyle = Window_windowIsTranslucent || Window_windowIsFloating || Window_windowShowWallpaper; //先从正在运行的进程中查找看是否有符合条件的进程,如果有则直接使用之 String stubProcessName1 = mRunningProcessList.getStubProcessByTarget(targetInfo); if (stubProcessName1 != null) { List stubInfos = mStaticProcessList.getActivityInfoForProcessName(stubProcessName1, useDialogStyle); for (ActivityInfo stubInfo : stubInfos) { if (stubInfo.launchMode == targetInfo.launchMode) { if (stubInfo.launchMode == ActivityInfo.LAUNCH_MULTIPLE) { mRunningProcessList.setTargetProcessName(stubInfo, targetInfo); return stubInfo; } else if (!mRunningProcessList.isStubInfoUsed(stubInfo, targetInfo, stubProcessName1)) { mRunningProcessList.setTargetProcessName(stubInfo, targetInfo); return stubInfo; } } } } List stubProcessNames = mStaticProcessList.getProcessNames(); for (String stubProcessName : stubProcessNames) { List stubInfos = mStaticProcessList.getActivityInfoForProcessName(stubProcessName, useDialogStyle); if (mRunningProcessList.isProcessRunning(stubProcessName)) {//该预定义的进程正在运行。 if (mRunningProcessList.isPkgEmpty(stubProcessName)) {//空进程,没有运行任何插件包。 for (ActivityInfo stubInfo : stubInfos) { if (stubInfo.launchMode == targetInfo.launchMode) { if (stubInfo.launchMode == ActivityInfo.LAUNCH_MULTIPLE) { mRunningProcessList.setTargetProcessName(stubInfo, targetInfo); return stubInfo; } else if (!mRunningProcessList.isStubInfoUsed(stubInfo, targetInfo, stubProcessName1)) { mRunningProcessList.setTargetProcessName(stubInfo, targetInfo); return stubInfo; } } } throw throwException("没有找到合适的StubInfo"); } else if (mRunningProcessList.isPkgCanRunInProcess(targetInfo.packageName, stubProcessName, targetInfo.processName)) { for (ActivityInfo stubInfo : stubInfos) { if (stubInfo.launchMode == targetInfo.launchMode) { if (stubInfo.launchMode == ActivityInfo.LAUNCH_MULTIPLE) { mRunningProcessList.setTargetProcessName(stubInfo, targetInfo); return stubInfo; } else if (!mRunningProcessList.isStubInfoUsed(stubInfo, targetInfo, stubProcessName1)) { mRunningProcessList.setTargetProcessName(stubInfo, targetInfo); return stubInfo; } } } throw throwException("没有找到合适的StubInfo"); } else { //这里需要考虑签名一样的情况,多个插件公用一个进程。 } } else { //该预定义的进程没有。 for (ActivityInfo stubInfo : stubInfos) { if (stubInfo.launchMode == targetInfo.launchMode) { if (stubInfo.launchMode == ActivityInfo.LAUNCH_MULTIPLE) { mRunningProcessList.setTargetProcessName(stubInfo, targetInfo); return stubInfo; } else if (!mRunningProcessList.isStubInfoUsed(stubInfo, targetInfo, stubProcessName1)) { mRunningProcessList.setTargetProcessName(stubInfo, targetInfo); return stubInfo; } } } throw throwException("没有找到合适的StubInfo"); } } throw throwException("没有可用的进程了"); } private static final Comparator sProcessComparator = new Comparator() { @Override public int compare(RunningAppProcessInfo lhs, RunningAppProcessInfo rhs) { if (lhs.importance == rhs.importance) { return 0; } else if (lhs.importance > rhs.importance) { return 1; } else { return -1; } } }; //运行进程GC private void runProcessGC() { if (mHostContext == null) { return; } ActivityManager am = (ActivityManager) mHostContext.getSystemService(Context.ACTIVITY_SERVICE); if (am == null) { return; } List infos = am.getRunningAppProcesses(); List myInfos = new ArrayList(); if (infos == null || infos.size() < 0) { return; } List pns = mStaticProcessList.getOtherProcessNames(); pns.add(mHostContext.getPackageName()); for (RunningAppProcessInfo info : infos) { if (info.uid == android.os.Process.myUid() && info.pid != android.os.Process.myPid() && !pns.contains(info.processName) && mRunningProcessList.isPlugin(info.pid) && !mRunningProcessList.isPersistentApplication(info.pid) /*&& !mRunningProcessList.isPersistentApplication(info.pid)*/) { myInfos.add(info); } } Collections.sort(myInfos, sProcessComparator); for (RunningAppProcessInfo myInfo : myInfos) { if (myInfo.importance == RunningAppProcessInfo.IMPORTANCE_GONE) { doGc(myInfo); } else if (myInfo.importance == RunningAppProcessInfo.IMPORTANCE_EMPTY) { doGc(myInfo); } else if (myInfo.importance == RunningAppProcessInfo.IMPORTANCE_BACKGROUND) { doGc(myInfo); } else if (myInfo.importance == RunningAppProcessInfo.IMPORTANCE_SERVICE) { doGc(myInfo); } /*else if (myInfo.importance == RunningAppProcessInfo.IMPORTANCE_CANT_SAVE_STATE) { //杀死进程,不能保存状态。但是关我什么事? }*/ else if (myInfo.importance == RunningAppProcessInfo.IMPORTANCE_PERCEPTIBLE) { //杀死进程 } else if (myInfo.importance == RunningAppProcessInfo.IMPORTANCE_VISIBLE) { //看得见 } else if (myInfo.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND) { //前台进程。 } } } private void doGc(RunningAppProcessInfo myInfo) { int activityCount = mRunningProcessList.getActivityCountByPid(myInfo.pid); int serviceCount = mRunningProcessList.getServiceCountByPid(myInfo.pid); int providerCount = mRunningProcessList.getProviderCountByPid(myInfo.pid); if (activityCount <= 0 && serviceCount <= 0 && providerCount <= 0) { //杀死空进程。 Log.i(TAG, "doGc kill process(pid=%s,uid=%s processName=%s)", myInfo.pid, myInfo.uid, myInfo.processName); try { android.os.Process.killProcess(myInfo.pid); } catch (Throwable e) { Log.e(TAG, "error on killProcess", e); } } else if (activityCount <= 0 && serviceCount > 0 /*&& !mRunningProcessList.isPersistentApplication(myInfo.pid)*/) { List names = mRunningProcessList.getStubServiceByPid(myInfo.pid); if (names != null && names.size() > 0) { for (String name : names) { Intent service = new Intent(); service.setClassName(mHostContext.getPackageName(), name); AbstractServiceStub.startKillService(mHostContext, service); Log.i(TAG, "doGc kill process(pid=%s,uid=%s processName=%s) service=%s", myInfo.pid, myInfo.uid, myInfo.processName, service); } } } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/am/RunningActivities.java ================================================ package com.morgoo.droidplugin.am; import android.app.Activity; import android.app.LocalActivityManager; import android.content.Intent; import android.content.pm.ActivityInfo; import android.os.Build; import com.morgoo.droidplugin.pm.PluginManager; import com.morgoo.helper.Log; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 正在运行的Activity列表 * Created by zhangyong6 on 2015/10/20. */ public class RunningActivities { private static final String TAG = RunningActivities.class.getSimpleName(); private static Map mRunningActivityList = new HashMap<>(); private static Map mRunningSingleStandardActivityList = new HashMap<>(); private static Map mRunningSingleTopActivityList = new HashMap<>(); private static Map mRunningSingleTaskActivityList = new HashMap<>(); private static Map mRunningSingleInstanceActivityList = new HashMap<>(); public static void onActivtyOnNewIntent(Activity activity, ActivityInfo targetInfo, ActivityInfo stubInfo, Intent intent) { //TODO } private static class RunningActivityRecord { private final Activity activity; private final ActivityInfo targetActivityInfo; private final ActivityInfo stubActivityInfo; private int index = 0; private RunningActivityRecord(Activity activity, ActivityInfo targetActivityInfo, ActivityInfo stubActivityInfo, int index) { this.activity = activity; this.targetActivityInfo = targetActivityInfo; this.stubActivityInfo = stubActivityInfo; this.index = index; } } public static void onActivtyCreate(Activity activity, ActivityInfo targetActivityInfo, ActivityInfo stubActivityInfo) { synchronized (mRunningActivityList) { RunningActivityRecord value = new RunningActivityRecord(activity, targetActivityInfo, stubActivityInfo, findMaxIndex() + 1); mRunningActivityList.put(activity, value); if (targetActivityInfo.launchMode == ActivityInfo.LAUNCH_MULTIPLE) { mRunningSingleStandardActivityList.put(value.index, value); } else if (targetActivityInfo.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP) { mRunningSingleTopActivityList.put(value.index, value); } else if (targetActivityInfo.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) { mRunningSingleTaskActivityList.put(value.index, value); } else if (targetActivityInfo.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { mRunningSingleInstanceActivityList.put(value.index, value); } } } public static void onActivtyDestory(Activity activity) { synchronized (mRunningActivityList) { RunningActivityRecord value = mRunningActivityList.remove(activity); if (value != null) { ActivityInfo targetActivityInfo = value.targetActivityInfo; if (targetActivityInfo.launchMode == ActivityInfo.LAUNCH_MULTIPLE) { mRunningSingleStandardActivityList.remove(value.index); } else if (targetActivityInfo.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP) { mRunningSingleTopActivityList.remove(value.index); } else if (targetActivityInfo.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) { mRunningSingleTaskActivityList.remove(value.index); } else if (targetActivityInfo.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { mRunningSingleInstanceActivityList.remove(value.index); } } } } //在启动一个Activity时调用 public static void beforeStartActivity() { synchronized (mRunningActivityList) { for (RunningActivityRecord record : mRunningActivityList.values()) { if (record.stubActivityInfo.launchMode == ActivityInfo.LAUNCH_MULTIPLE) { continue; } else if (record.stubActivityInfo.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP) { doFinshIt(mRunningSingleTopActivityList); } else if (record.stubActivityInfo.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) { doFinshIt(mRunningSingleTopActivityList); } else if (record.stubActivityInfo.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { doFinshIt(mRunningSingleTopActivityList); } } } } private static final Comparator sRunningActivityRecordComparator = new Comparator() { @Override public int compare(RunningActivityRecord lhs, RunningActivityRecord rhs) { if (lhs != null && rhs != null) { if (lhs.index > rhs.index) { return 1; } else if (lhs.index < rhs.index) { return -1; } else { return 0; } } else if (lhs != null && rhs == null) { return 1; } else if (lhs == null && rhs != null) { return -1; } else { return 0; } } }; private static void doFinshIt(Map runningActivityList) { if (runningActivityList != null && runningActivityList.size() >= PluginManager.STUB_NO_ACTIVITY_MAX_NUM - 1) { List activitys = new ArrayList<>(runningActivityList.size()); activitys.addAll(runningActivityList.values()); Collections.sort(activitys, sRunningActivityRecordComparator); RunningActivityRecord record = activitys.get(0); if (record.activity != null && !record.activity.isFinishing()) { record.activity.finish(); // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // record.activity.finishAndRemoveTask(); // record.activity.releaseInstance(); // } else { // record.activity.finish(); // } // // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { // record.activity.finishAffinity(); // } // Log.d(TAG, "ZYActivity.finish stub=%s target=%s", record.stubActivityInfo, record.targetActivityInfo); } } } private static int findMaxIndex() { int max = 0; synchronized (mRunningActivityList) { for (RunningActivityRecord record : mRunningActivityList.values()) { if (max < record.index) { max = record.index; } } } return max; } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/am/RunningProcessList.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.am; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.ComponentInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ProviderInfo; import android.content.pm.ServiceInfo; import android.os.RemoteException; import android.text.TextUtils; import com.morgoo.droidplugin.pm.PluginManager; import com.morgoo.helper.Log; import java.text.Collator; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; /** * 正在运行的进程列表 * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/3/10. */ class RunningProcessList { private static final Collator sCollator = Collator.getInstance(); private static final String TAG = RunningProcessList.class.getSimpleName(); private static Comparator sComponentInfoComparator = new Comparator() { @Override public int compare(ComponentInfo lhs, ComponentInfo rhs) { return sCollator.compare(lhs.name, rhs.name); } }; private static Comparator sProviderInfoComparator = new Comparator() { @Override public int compare(ProviderInfo lhs, ProviderInfo rhs) { return sCollator.compare(lhs.authority, rhs.authority); } }; public String getStubProcessByTarget(ComponentInfo targetInfo) { for (ProcessItem processItem : items.values()) { if (processItem.pkgs.contains(targetInfo.packageName) && TextUtils.equals(processItem.targetProcessName, targetInfo.processName)) { return processItem.stubProcessName; } else { try { boolean signed = false; for (String pkg : processItem.pkgs) { if (PluginManager.getInstance().checkSignatures(targetInfo.packageName, pkg) == PackageManager.SIGNATURE_MATCH) { signed = true; break; } } if (signed && TextUtils.equals(processItem.targetProcessName, targetInfo.processName)) { return processItem.stubProcessName; } } catch (Exception e) { Log.e(TAG, "getStubProcessByTarget:error", e); } } } return null; } public boolean isPersistentApplication(int pid) { //是否是持久化的app。 for (ProcessItem processItem : items.values()) { if (processItem.pid == pid) { if (processItem.pkgs != null && processItem.pkgs.size() > 0) { for (String pkg : processItem.pkgs) { if (isPersistentApp(pkg)) { return true; } } } if (processItem.targetActivityInfos != null && processItem.targetActivityInfos.size() > 0) { for (ActivityInfo info : processItem.targetActivityInfos.values()) { if ((info.applicationInfo.flags & ApplicationInfo.FLAG_PERSISTENT) != 0) { return true; } else if (isPersistentApp(info.packageName)) { return true; } } } if (processItem.targetProviderInfos != null && processItem.targetProviderInfos.size() > 0) { for (ProviderInfo info : processItem.targetProviderInfos.values()) { if ((info.applicationInfo.flags & ApplicationInfo.FLAG_PERSISTENT) != 0) { return true; } else if (isPersistentApp(info.packageName)) { return true; } } } if (processItem.targetServiceInfos != null && processItem.targetServiceInfos.size() > 0) { for (ServiceInfo info : processItem.targetServiceInfos.values()) { if ((info.applicationInfo.flags & ApplicationInfo.FLAG_PERSISTENT) != 0) { return true; } else if (isPersistentApp(info.packageName)) { return true; } } } } } return false; } private boolean isPersistentApp(String packageName) { try { PackageInfo info = mHostContext.getPackageManager().getPackageInfo(packageName, PackageManager.GET_META_DATA); if (info != null && info.applicationInfo.metaData != null && info.applicationInfo.metaData.containsKey(PluginManager.EXTRA_APP_PERSISTENT)) { if ((info.applicationInfo.flags & ApplicationInfo.FLAG_PERSISTENT) != 0) { return true; } boolean isPersistentApp = info.applicationInfo.metaData.getBoolean(PluginManager.EXTRA_APP_PERSISTENT); return isPersistentApp; } } catch (Exception e) { Log.e(TAG, "isPersistentApp:error", e); } return false; } private Context mHostContext; public void setContext(Context context) { this.mHostContext = context; } /** * 正在运行的进程item *

* Created by Andy Zhang(zhangyong232@gmail.com) on 2015/3/10. */ private class ProcessItem { private String stubProcessName; private String targetProcessName; private int pid; private int uid; private long startTime; private List pkgs = new ArrayList(1); //正在运行的插件ActivityInfo //key=ActivityInfo.name, value=插件的ActivityInfo, private Map targetActivityInfos = new HashMap(4); //正在运行的插件ProviderInfo //key=ProviderInfo.authority, value=插件的ProviderInfo private Map targetProviderInfos = new HashMap(1); //正在运行的插件ServiceInfo //key=ServiceInfo.name, value=插件的ServiceInfo private Map targetServiceInfos = new HashMap(1); //正在运行的插件ActivityInfo与代理ActivityInfo的映射 //key=代理ActivityInfo.name, value=插件的ActivityInfo.name, private Map> activityInfosMap = new HashMap>(4); //正在运行的插件ProviderInfo与代理ProviderInfo的映射 //key=代理ProviderInfo.authority, value=插件的ProviderInfo.authority, private Map> providerInfosMap = new HashMap>(4); //正在运行的插件ServiceInfo与代理ServiceInfo的映射 //key=代理ServiceInfo.name, value=插件的ServiceInfo.name, private Map> serviceInfosMap = new HashMap>(4); private void updatePkgs() { ArrayList newList = new ArrayList(); for (ActivityInfo info : targetActivityInfos.values()) { newList.add(info.packageName); } for (ServiceInfo info : targetServiceInfos.values()) { newList.add(info.packageName); } for (ProviderInfo info : targetProviderInfos.values()) { newList.add(info.packageName); } pkgs.clear(); pkgs.addAll(newList); } private void addActivityInfo(String stubActivityName, ActivityInfo info) { if (!targetActivityInfos.containsKey(info.name)) { targetActivityInfos.put(info.name, info); } //pkgs if (!pkgs.contains(info.packageName)) { pkgs.add(info.packageName); } //stub map to activity info Set list = activityInfosMap.get(stubActivityName); if (list == null) { list = new TreeSet(sComponentInfoComparator); list.add(info); activityInfosMap.put(stubActivityName, list); } else { list.add(info); } } void removeActivityInfo(String stubActivityName, ActivityInfo targetInfo) { targetActivityInfos.remove(targetInfo.name); //remove form map if (stubActivityName == null) { for (Set set : activityInfosMap.values()) { set.remove(targetInfo); } } else { Set list = activityInfosMap.get(stubActivityName); if (list != null) { list.remove(targetInfo); } } updatePkgs(); } private void addServiceInfo(String stubServiceName, ServiceInfo info) { if (!targetServiceInfos.containsKey(info.name)) { targetServiceInfos.put(info.name, info); if (!pkgs.contains(info.packageName)) { pkgs.add(info.packageName); } //stub map to activity info Set list = serviceInfosMap.get(stubServiceName); if (list == null) { list = new TreeSet(sComponentInfoComparator); list.add(info); serviceInfosMap.put(stubServiceName, list); } else { list.add(info); } } } void removeServiceInfo(String stubServiceName, ServiceInfo targetInfo) { targetServiceInfos.remove(targetInfo.name); //remove form map if (stubServiceName == null) { for (Set set : serviceInfosMap.values()) { set.remove(targetInfo); } } else { Set list = serviceInfosMap.get(stubServiceName); if (list != null) { list.remove(targetInfo); } } updatePkgs(); } private void addProviderInfo(String stubAuthority, ProviderInfo info) { if (!targetProviderInfos.containsKey(info.authority)) { targetProviderInfos.put(info.authority, info); if (!pkgs.contains(info.packageName)) { pkgs.add(info.packageName); } //stub map to activity info Set list = providerInfosMap.get(stubAuthority); if (list == null) { list = new TreeSet(sProviderInfoComparator); list.add(info); providerInfosMap.put(stubAuthority, list); } else { list.add(info); } } } void removeProviderInfo(String stubAuthority, ProviderInfo targetInfo) { targetProviderInfos.remove(targetInfo.authority); //remove form map if (stubAuthority == null) { for (Set set : providerInfosMap.values()) { set.remove(targetInfo); } } else { Set list = providerInfosMap.get(stubAuthority); if (list != null) { list.remove(targetInfo); } } updatePkgs(); } } //key=pid, value=ProcessItem; private Map items = new HashMap(5); ProcessItem removeByPid(int pid) { return items.remove(pid); } List getStubServiceByPid(int pid) { ProcessItem item = items.get(pid); if (item != null && item.serviceInfosMap != null && item.serviceInfosMap.size() > 0) { return new ArrayList(item.serviceInfosMap.keySet()); } return null; } void addActivityInfo(int pid, int uid, ActivityInfo stubInfo, ActivityInfo targetInfo) { ProcessItem item = items.get(pid); if (TextUtils.isEmpty(targetInfo.processName)) { targetInfo.processName = targetInfo.packageName; } if (item == null) { item = new ProcessItem(); item.pid = pid; item.uid = uid; items.put(pid, item); } item.stubProcessName = stubInfo.processName; if (!item.pkgs.contains(targetInfo.packageName)) { item.pkgs.add(targetInfo.packageName); } item.targetProcessName = targetInfo.processName; item.addActivityInfo(stubInfo.name, targetInfo); } void removeActivityInfo(int pid, int uid, ActivityInfo stubInfo, ActivityInfo targetInfo) { ProcessItem item = items.get(pid); if (TextUtils.isEmpty(targetInfo.processName)) { targetInfo.processName = targetInfo.packageName; } if (item != null) { item.removeActivityInfo(stubInfo.name, targetInfo); } } void addServiceInfo(int pid, int uid, ServiceInfo stubInfo, ServiceInfo targetInfo) { ProcessItem item = items.get(pid); if (TextUtils.isEmpty(targetInfo.processName)) { targetInfo.processName = targetInfo.packageName; } if (item == null) { item = new ProcessItem(); item.pid = pid; item.uid = uid; items.put(pid, item); } item.stubProcessName = stubInfo.processName; if (!item.pkgs.contains(targetInfo.packageName)) { item.pkgs.add(targetInfo.packageName); } item.targetProcessName = targetInfo.processName; item.addServiceInfo(stubInfo.name, targetInfo); } void removeServiceInfo(int pid, int uid, ServiceInfo stubInfo, ServiceInfo targetInfo) { ProcessItem item = items.get(pid); if (TextUtils.isEmpty(targetInfo.processName)) { targetInfo.processName = targetInfo.packageName; } if (item != null) { if (stubInfo != null) { item.removeServiceInfo(stubInfo.name, targetInfo); } else { item.removeServiceInfo(null, targetInfo); } } } void addProviderInfo(int pid, int uid, ProviderInfo stubInfo, ProviderInfo targetInfo) { ProcessItem item = items.get(pid); if (TextUtils.isEmpty(targetInfo.processName)) { targetInfo.processName = targetInfo.packageName; } if (item == null) { item = new ProcessItem(); item.pid = pid; item.uid = uid; items.put(pid, item); } item.stubProcessName = stubInfo.processName; if (!item.pkgs.contains(targetInfo.packageName)) { item.pkgs.add(targetInfo.packageName); } item.targetProcessName = targetInfo.processName; item.addProviderInfo(stubInfo.authority, targetInfo); } void addItem(int pid, int uid) { ProcessItem item = items.get(pid); if (item == null) { item = new ProcessItem(); item.pid = pid; item.uid = uid; item.startTime = System.currentTimeMillis(); items.put(pid, item); } else { item.pid = pid; item.uid = uid; item.startTime = System.currentTimeMillis(); } } boolean isProcessRunning(String stubProcessName) { for (ProcessItem processItem : items.values()) { if (TextUtils.equals(stubProcessName, processItem.stubProcessName)) { return true; } } return false; } boolean isPkgCanRunInProcess(String packageName, String stubProcessName, String targetProcessName) throws RemoteException { for (ProcessItem item : items.values()) { if (TextUtils.equals(stubProcessName, item.stubProcessName)) { if (!TextUtils.isEmpty(item.targetProcessName) && !TextUtils.equals(item.targetProcessName, targetProcessName)) { continue; } if (item.pkgs.contains(packageName)) { return true; } boolean signed = false; for (String pkg : item.pkgs) { if (PluginManager.getInstance().checkSignatures(packageName, pkg) == PackageManager.SIGNATURE_MATCH) { signed = true; break; } } if (signed) { return true; } } } return false; } boolean isPkgEmpty(String stubProcessName) { for (ProcessItem item : items.values()) { if (TextUtils.equals(stubProcessName, item.stubProcessName)) { return item.pkgs.size() <= 0; } } return true; } boolean isStubInfoUsed(ProviderInfo stubInfo) { //TODO return false; } boolean isStubInfoUsed(ServiceInfo stubInfo) { //TODO return false; } boolean isStubInfoUsed(ActivityInfo stubInfo, ActivityInfo targetInfo, String stubProcessName) { for (Integer pid : items.keySet()) { ProcessItem item = items.get(pid); if (TextUtils.equals(item.stubProcessName, stubProcessName)) { Set infos = item.activityInfosMap.get(stubInfo.name); if (infos != null && infos.size() > 0) { for (ActivityInfo info : infos) { if (TextUtils.equals(info.name, targetInfo.name) && TextUtils.equals(info.packageName, targetInfo.packageName)) { return false; } } return true; } return false; } } return false; } List getPackageNameByPid(int pid) { ProcessItem item = items.get(pid); return item != null ? item.pkgs : new ArrayList(); } String getTargetProcessNameByPid(int pid) { ProcessItem item = items.get(pid); return item != null ? item.targetProcessName : null; } public String getStubProcessNameByPid(int pid) { ProcessItem item = items.get(pid); return item != null ? item.stubProcessName : null; } void setTargetProcessName(ComponentInfo stubInfo, ComponentInfo targetInfo) { for (ProcessItem item : items.values()) { if (TextUtils.equals(item.stubProcessName, stubInfo.processName)) { if (!item.pkgs.contains(targetInfo.packageName)) { item.pkgs.add(targetInfo.packageName); } item.targetProcessName = targetInfo.processName; } } } int getActivityCountByPid(int pid) { ProcessItem item = items.get(pid); return item != null ? item.targetActivityInfos.size() : 0; } int getServiceCountByPid(int pid) { ProcessItem item = items.get(pid); return item != null ? item.targetServiceInfos.size() : 0; } int getProviderCountByPid(int pid) { ProcessItem item = items.get(pid); return item != null ? item.targetProviderInfos.size() : 0; } void setProcessName(int pid, String stubProcessName, String targetProcessName, String targetPkg) { ProcessItem item = items.get(pid); if (item != null) { if (!item.pkgs.contains(targetPkg)) { item.pkgs.add(targetPkg); } item.targetProcessName = targetProcessName; item.stubProcessName = stubProcessName; } } void onProcessDied(int pid, int uid) { //进程死掉的时候,移除相关item items.remove(pid); } void clear() { items.clear(); } boolean isPlugin(int pid) { ProcessItem item = items.get(pid); if (item != null) { return !TextUtils.isEmpty(item.stubProcessName) && !TextUtils.isEmpty(item.targetProcessName); } return false; } void dump(String msg) { StringBuilder sb = new StringBuilder("\r\n\r\ndump[" + msg + "]RunningProcess["); for (Integer pid : items.keySet()) { ProcessItem item = items.get(pid); sb.append(" pid:").append(pid).append("\r\n"); sb.append(" Item[\r\n"); sb.append(" pid:").append(item.pid).append("\r\n"); sb.append(" uid:").append(item.uid).append("\r\n"); sb.append(" stubProcessName:").append(item.stubProcessName).append("\r\n"); sb.append(" targetProcessName:").append(item.targetProcessName).append("\r\n"); sb.append(" pkgs:").append(Arrays.toString(item.pkgs.toArray())).append("\r\n"); sb.append(" targetActivityInfos:[\r\n"); for (String name : item.targetActivityInfos.keySet()) { sb.append(" " + name + ":" + item.targetActivityInfos.get(name).name); } sb.append(" ]\r\n"); sb.append(" targetServiceInfos:[\r\n"); for (String name : item.targetServiceInfos.keySet()) { sb.append(" " + name + ":" + item.targetServiceInfos.get(name).name); } sb.append(" ]\r\n"); sb.append(" targetProviderInfos:[\r\n"); for (String name : item.targetProviderInfos.keySet()) { sb.append(" " + name + ":" + item.targetProviderInfos.get(name).name); } sb.append(" ]\r\n"); sb.append(" activityInfosMap:[\r\n"); for (String name : item.activityInfosMap.keySet()) { Set infos = item.activityInfosMap.get(name); sb.append(" " + name + ":[\r\n"); for (ActivityInfo info : infos) { sb.append(" " + info.name + ":\r\n"); } sb.append(" ]\r\n"); } sb.append(" ]\r\n"); sb.append(" serviceInfosMap:[\r\n"); for (String name : item.serviceInfosMap.keySet()) { Set infos = item.serviceInfosMap.get(name); sb.append(" " + name + ":[\r\n"); for (ServiceInfo info : infos) { sb.append(" " + info.name + ":\r\n"); } sb.append(" ]\r\n"); } sb.append(" ]\r\n"); sb.append(" activityInfosMap:[\r\n"); for (String name : item.providerInfosMap.keySet()) { Set infos = item.providerInfosMap.get(name); sb.append(" " + name + ":[\r\n"); for (ProviderInfo info : infos) { sb.append(" " + info.authority + ":\r\n"); } sb.append(" ]\r\n"); } sb.append(" ]\r\n"); } sb.append("] \r\n"); Log.e(TAG, sb.toString()); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/am/ServiceStubMap.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.am; import android.content.pm.ServiceInfo; import android.text.TextUtils; import java.text.Collator; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.TreeMap; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/3/4. */ class ServiceStubMap { private static class MyServiceInfo implements Comparable { private ServiceInfo info; MyServiceInfo(ServiceInfo info) { this.info = info; } @Override public boolean equals(Object o) { if (o instanceof MyServiceInfo) { MyServiceInfo obj1 = (MyServiceInfo) o; return TextUtils.equals(obj1.info.name, info.name); } else { return false; } } @Override public int compareTo(MyServiceInfo another) { return Collator.getInstance().compare(info.name, another.info.name); } } //key=stub service, value = plugin service info list private Map> mServiceStubMap = new TreeMap>(); List getPluginInfosByStub(ServiceInfo stubInfo) { ArrayList arrayList = new ArrayList(); if (stubInfo != null) { MyServiceInfo info = new MyServiceInfo(stubInfo); List infos = mServiceStubMap.get(info); if (infos != null && infos.size() > 0) { for (MyServiceInfo myServiceInfo : infos) { arrayList.add(myServiceInfo.info); } } } return arrayList; } ServiceInfo getStubInfoByPlugin(ServiceInfo pluginInfo) { MyServiceInfo object = new MyServiceInfo(pluginInfo); for (MyServiceInfo info : mServiceStubMap.keySet()) { List infos = mServiceStubMap.get(info); if (infos != null && infos.contains(object)) { return info.info; } } return null; } void addToMap(ServiceInfo stubInfo, ServiceInfo pluginInfo) { MyServiceInfo stub = new MyServiceInfo(stubInfo); MyServiceInfo plugin = new MyServiceInfo(pluginInfo); List list = mServiceStubMap.get(stub); if (list == null) { list = new ArrayList(1); mServiceStubMap.put(stub, list); } list.add(plugin); } void removeFormMap(ServiceInfo pluginInfo) { MyServiceInfo plugin = new MyServiceInfo(pluginInfo); for (List infos : mServiceStubMap.values()) { infos.remove(plugin); } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/am/StaticProcessList.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.am; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ComponentInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.text.TextUtils; import com.morgoo.droidplugin.stub.ActivityStub; import com.morgoo.droidplugin.stub.ContentProviderStub; import java.text.Collator; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/3/10. */ class StaticProcessList { private static final String CATEGORY_ACTIVITY_PROXY_STUB = "com.morgoo.droidplugin.category.PROXY_STUB"; //key=processName value=ProcessItem private Map items = new HashMap(10); private List mOtherProcessNames = new ArrayList<>(); /** * 我们预注册的进程item *

* Created by Andy Zhang(zhangyong232@gmail.com) on 2015/3/10. */ private class ProcessItem { private String name; //key=ActivityInfo.name,value=ActivityInfo private Map activityInfos = new HashMap(4); //key=ServiceInfo.name,value=ServiceInfo private Map serviceInfos = new HashMap(1); //key=ProviderInfo.authority,value=ProviderInfo private Map providerInfos = new HashMap(1); private void addActivityInfo(ActivityInfo info) { if (!activityInfos.containsKey(info.name)) { activityInfos.put(info.name, info); } } private void addServiceInfo(ServiceInfo info) { if (!serviceInfos.containsKey(info.name)) { serviceInfos.put(info.name, info); } } private void addProviderInfo(ProviderInfo info) { if (!providerInfos.containsKey(info.authority)) { providerInfos.put(info.authority, info); } } } void onCreate(Context mHostContext) throws NameNotFoundException { Intent intent = new Intent(Intent.ACTION_MAIN); intent.addCategory(CATEGORY_ACTIVITY_PROXY_STUB); intent.setPackage(mHostContext.getPackageName()); PackageManager pm = mHostContext.getPackageManager(); List activities = pm.queryIntentActivities(intent, PackageManager.GET_META_DATA); for (ResolveInfo activity : activities) { addActivityInfo(activity.activityInfo); } List services = pm.queryIntentServices(intent, 0); for (ResolveInfo service : services) { addServiceInfo(service.serviceInfo); } PackageInfo packageInfo = pm.getPackageInfo(mHostContext.getPackageName(), PackageManager.GET_PROVIDERS); if (packageInfo.providers != null && packageInfo.providers.length > 0) { for (ProviderInfo providerInfo : packageInfo.providers) { if (providerInfo.name != null && providerInfo.name.startsWith(ContentProviderStub.class.getName())) { addProviderInfo(providerInfo); } } } mOtherProcessNames.clear(); PackageInfo packageInfo1 = pm.getPackageInfo(mHostContext.getPackageName(), PackageManager.GET_ACTIVITIES | PackageManager.GET_RECEIVERS | PackageManager.GET_PROVIDERS | PackageManager.GET_SERVICES); if (packageInfo1.activities != null) { for (ActivityInfo info : packageInfo1.activities) { if (!mOtherProcessNames.contains(info.processName) && !items.containsKey(info.processName)) { mOtherProcessNames.add(info.processName); } } } if (packageInfo1.receivers != null) { for (ActivityInfo info : packageInfo1.receivers) { if (!mOtherProcessNames.contains(info.processName) && !items.containsKey(info.processName)) { mOtherProcessNames.add(info.processName); } } } if (packageInfo1.providers != null) { for (ProviderInfo info : packageInfo1.providers) { if (!mOtherProcessNames.contains(info.processName) && !items.containsKey(info.processName)) { mOtherProcessNames.add(info.processName); } } } if (packageInfo1.services != null) { for (ServiceInfo info : packageInfo1.services) { if (!mOtherProcessNames.contains(info.processName) && !items.containsKey(info.processName)) { mOtherProcessNames.add(info.processName); } } } } public List getOtherProcessNames() { return mOtherProcessNames; } private void addActivityInfo(ActivityInfo info) { if (TextUtils.isEmpty(info.processName)) { info.processName = info.packageName; } ProcessItem item = items.get(info.processName); if (item == null) { item = new ProcessItem(); item.name = info.processName; items.put(info.processName, item); } item.addActivityInfo(info); } ActivityInfo findActivityInfoForName(String processName, String activityName) { ProcessItem item = items.get(processName); if (item != null && item.activityInfos != null) { return item.activityInfos.get(activityName); } return null; } ActivityInfo findActivityInfoForLaunchMode(String processName, int launchMode) { ProcessItem item = items.get(processName); if (item != null && item.activityInfos != null) { for (ActivityInfo info : item.activityInfos.values()) { if (info.launchMode == launchMode) { return info; } } } return null; } private void addServiceInfo(ServiceInfo info) { if (TextUtils.isEmpty(info.processName)) { info.processName = info.packageName; } ProcessItem item = items.get(info.processName); if (item == null) { item = new ProcessItem(); item.name = info.processName; items.put(info.processName, item); } item.addServiceInfo(info); } ServiceInfo findServiceInfoForName(String processName, String serviceInfoName) { ProcessItem item = items.get(processName); if (item != null && item.serviceInfos != null) { return item.serviceInfos.get(serviceInfoName); } return null; } private void addProviderInfo(ProviderInfo info) { if (TextUtils.isEmpty(info.processName)) { info.processName = info.packageName; } ProcessItem item = items.get(info.processName); if (item == null) { item = new ProcessItem(); item.name = info.processName; items.put(info.processName, item); } item.addProviderInfo(info); } ProviderInfo findProviderInfoForAuthority(String processName, String authority) { ProcessItem item = items.get(processName); if (item != null && item.providerInfos != null) { return item.providerInfos.get(authority); } return null; } List getProcessNames() { return new ArrayList(items.keySet()); } List getActivityInfoForProcessName(String processName) { ProcessItem item = items.get(processName); ArrayList activityInfos = new ArrayList(item.activityInfos.values()); Collections.sort(activityInfos, sComponentInfoComparator); return activityInfos; } private static final Comparator sComponentInfoComparator = new Comparator() { @Override public int compare(ComponentInfo lhs, ComponentInfo rhs) { return Collator.getInstance().compare(lhs.name, rhs.name); } }; List getActivityInfoForProcessName(String processName, boolean dialogStyle) { ProcessItem item = items.get(processName); Collection values = item.activityInfos.values(); ArrayList activityInfos = new ArrayList(); for (ActivityInfo info : values) { if (dialogStyle) { if (info.name.startsWith(ActivityStub.Dialog.class.getName())) { activityInfos.add(info); } } else { if (!info.name.startsWith(ActivityStub.Dialog.class.getName())) { activityInfos.add(info); } } } Collections.sort(activityInfos, sComponentInfoComparator); return activityInfos; } List getServiceInfoForProcessName(String processName) { ProcessItem item = items.get(processName); ArrayList serviceInfos = new ArrayList(item.serviceInfos.values()); Collections.sort(serviceInfos, sComponentInfoComparator); return serviceInfos; } List getProviderInfoForProcessName(String processName) { ProcessItem item = items.get(processName); ArrayList providerInfos = new ArrayList(item.providerInfos.values()); Collections.sort(providerInfos, sComponentInfoComparator); return providerInfos; } void clear() { items.clear(); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/core/Env.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.core; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/2/6. */ public class Env { public static final String ACTION_INSTALL_SHORTCUT = "com.android.launcher.action.INSTALL_SHORTCUT"; public static final String ACTION_UNINSTALL_SHORTCUT = "com.android.launcher.action.UNINSTALL_SHORTCUT"; public static final String EXTRA_TARGET_INTENT = "com.morgoo.droidplugin.OldIntent"; public static final String EXTRA_TARGET_INTENT_URI = "com.morgoo.droidplugin.OldIntent.Uri"; public static final String EXTRA_TARGET_INFO = "com.morgoo.droidplugin.OldInfo"; public static final String EXTRA_STUB_INFO = "com.morgoo.droidplugin.NewInfo"; public static final String EXTRA_TARGET_AUTHORITY = "TargetAuthority"; public static final String EXTRA_TYPE = "com.morgoo.droidplugin.EXTRA_TYPE"; public static final String EXTRA_ACTION = "com.morgoo.droidplugin.EXTRA_ACTION"; } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/core/PluginClassLoader.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.core; import android.os.Build; import com.morgoo.helper.Log; import java.util.ArrayList; import java.util.List; import dalvik.system.DexClassLoader; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/2/4. */ public class PluginClassLoader extends DexClassLoader { public PluginClassLoader(String apkfile, String optimizedDirectory, String libraryPath, ClassLoader systemClassLoader) { super(apkfile, optimizedDirectory, libraryPath, systemClassLoader); } private static final List sPreLoader = new ArrayList<>(); static { sPreLoader.add("QIKU"); } @Override protected Class loadClass(String className, boolean resolve) throws ClassNotFoundException { if (Build.MANUFACTURER != null && sPreLoader.contains(Build.MANUFACTURER.toUpperCase())) { try { /** * FUCK QIKU! * 这里适配奇酷手机青春版。 * 因为奇酷手机自己加载了自己修改过的的Support V4库,在插件中也用了这个库的时候,ClassLoader会优先加载奇酷手机自带的Support V4库。 * 原因在于,奇酷手机没有预加载插件中打的Support V4库。详情可以研究super.loadClass(className, resolve)标准实现 * 但是这可能会导致类不兼容,出现java.lang.IncompatibleClassChangeError。因为插件编译时使用的插件的Support V4,而奇酷手机则使 * 用的是它修改过的Support V4。 * * SO,在Class Loader加载某个Class的时候,我们优先从自己的ClassLoader中加载Class,如果找不到,再从Parent Class Loader中去加载。 * 这样修改后,Class的加载顺序就跟系统的不一样了。 * */ Class clazz = findClass(className); if (clazz != null) { return clazz; } } catch (ClassNotFoundException e) { Log.e("PluginClassLoader", "UCK QIKU:error", e); } } return super.loadClass(className, resolve); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/core/PluginDirHelper.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.core; import android.content.Context; import android.os.Environment; import java.io.File; import java.util.ArrayList; import java.util.List; /** * 插件目录结构 * 基本目录: /data/data/com.HOST.PACKAGE/Plugin * 单个插件的基本目录: /data/data/com.HOST.PACKAGE/Plugin * source_dir: /data/data/com.HOST.PACKAGE/Plugin/PLUGIN.PKG/apk/apk-1.apk * 数据目录: /data/data/com.HOST.PACKAGE/Plugin/PLUGIN.PKG/data/PLUGIN.PKG * dex缓存目录: /data/data/com.HOST.PACKAGE/Plugin/PLUGIN.PKG/dalvik-cache/ *

* Created by Andy Zhang(zhangyong232@gmail.com) on 2015/2/5. */ public class PluginDirHelper { private static File sBaseDir = null; private static void init(Context context) { if (sBaseDir == null) { sBaseDir = new File(context.getCacheDir().getParentFile(), "Plugin"); enforceDirExists(sBaseDir); } } private static String enforceDirExists(File file) { if (!file.exists()) { file.mkdirs(); } return file.getPath(); } public static String makePluginBaseDir(Context context, String pluginInfoPackageName) { init(context); return enforceDirExists(new File(sBaseDir, pluginInfoPackageName)); } public static String getBaseDir(Context context) { init(context); return enforceDirExists(sBaseDir); } public static String getPluginDataDir(Context context, String pluginInfoPackageName) { return enforceDirExists(new File(makePluginBaseDir(context, pluginInfoPackageName), "data/" + pluginInfoPackageName)); } public static String getPluginSignatureDir(Context context, String pluginInfoPackageName) { return enforceDirExists(new File(makePluginBaseDir(context, pluginInfoPackageName), "Signature/")); } public static String getPluginSignatureFile(Context context, String pluginInfoPackageName, int index) { return new File(getPluginSignatureDir(context, pluginInfoPackageName), String.format("Signature_%s.key", index)).getPath(); } public static List getPluginSignatureFiles(Context context, String pluginInfoPackageName) { ArrayList files = new ArrayList(); String dir = getPluginSignatureDir(context, pluginInfoPackageName); File d = new File(dir); File[] fs = d.listFiles(); if (fs != null && fs.length > 0) { for (File f : fs) { files.add(f.getPath()); } } return files; } public static String getPluginApkDir(Context context, String pluginInfoPackageName) { return enforceDirExists(new File(makePluginBaseDir(context, pluginInfoPackageName), "apk")); } public static String getPluginApkFile(Context context, String pluginInfoPackageName) { return new File(getPluginApkDir(context, pluginInfoPackageName), "base-1.apk").getPath(); } public static String getPluginDalvikCacheDir(Context context, String pluginInfoPackageName) { return enforceDirExists(new File(makePluginBaseDir(context, pluginInfoPackageName), "dalvik-cache")); } public static String getPluginNativeLibraryDir(Context context, String pluginInfoPackageName) { return enforceDirExists(new File(makePluginBaseDir(context, pluginInfoPackageName), "lib")); } public static String getPluginDalvikCacheFile(Context context, String pluginInfoPackageName) { String dalvikCacheDir = getPluginDalvikCacheDir(context, pluginInfoPackageName); String pluginApkFile = getPluginApkFile(context, pluginInfoPackageName); String apkName = new File(pluginApkFile).getName(); String dexName = apkName.replace(File.separator, "@"); if (dexName.startsWith("@")) { dexName = dexName.substring(1); } return new File(dalvikCacheDir, dexName + "@classes.dex").getPath(); } public static String getContextDataDir(Context context) { String dataDir = new File(Environment.getDataDirectory(), "data/").getPath(); return new File(dataDir, context.getPackageName()).getPath(); } public static void cleanOptimizedDirectory(String optimizedDirectory) { try { File dir = new File(optimizedDirectory); if (dir.exists() && dir.isDirectory()) { File[] files = dir.listFiles(); if (files != null && files.length > 0) { for (File f : files) { f.delete(); } } } if (dir.exists() && dir.isFile()) { dir.delete(); dir.mkdirs(); } } catch (Throwable e) { } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/core/PluginProcessManager.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.core; import android.app.ActivityManager; import android.app.ActivityManager.RunningAppProcessInfo; import android.app.Application; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; import android.content.ContextWrapper; import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.ComponentInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ProviderInfo; import android.content.pm.ServiceInfo; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.os.Handler; import android.os.Looper; import android.text.TextUtils; import com.morgoo.droidplugin.hook.HookFactory; import com.morgoo.droidplugin.pm.PluginManager; import com.morgoo.droidplugin.reflect.FieldUtils; import com.morgoo.droidplugin.reflect.MethodUtils; import com.morgoo.droidplugin.stub.ActivityStub; import com.morgoo.droidplugin.stub.ServiceStub; import com.morgoo.helper.compat.ActivityThreadCompat; import com.morgoo.helper.compat.CompatibilityInfoCompat; import com.morgoo.helper.Log; import com.morgoo.helper.compat.ProcessCompat; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.WeakHashMap; import java.util.concurrent.atomic.AtomicBoolean; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/2/4. */ public class PluginProcessManager { private static final String TAG = "PluginProcessManager"; private static String sCurrentProcessName; private static Object sGetCurrentProcessNameLock = new Object(); private static Map sPluginClassLoaderCache = new WeakHashMap(1); private static Map sPluginLoadedApkCache = new WeakHashMap(1); public static String getCurrentProcessName(Context context) { if (context == null) return sCurrentProcessName; synchronized (sGetCurrentProcessNameLock) { if (sCurrentProcessName == null) { ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); List infos = activityManager.getRunningAppProcesses(); if (infos == null) return null; for (RunningAppProcessInfo info : infos) { if (info.pid == android.os.Process.myPid()) { sCurrentProcessName = info.processName; return sCurrentProcessName; } } } } return sCurrentProcessName; } private static List sProcessList = new ArrayList<>(); private static void initProcessList(Context context) { try { if (sProcessList.size() > 0) { return; } sProcessList.add(context.getPackageName()); PackageManager pm = context.getPackageManager(); PackageInfo packageInfo = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_RECEIVERS | PackageManager.GET_ACTIVITIES | PackageManager.GET_PROVIDERS | PackageManager.GET_SERVICES); if (packageInfo.receivers != null) { for (ActivityInfo info : packageInfo.receivers) { if (!sProcessList.contains(info.processName)) { sProcessList.add(info.processName); } } } if (packageInfo.providers != null) { for (ProviderInfo info : packageInfo.providers) { if (!sProcessList.contains(info.processName) && info.processName != null && info.authority != null && info.authority.indexOf(PluginManager.STUB_AUTHORITY_NAME) < 0) { sProcessList.add(info.processName); } } } if (packageInfo.services != null) { for (ServiceInfo info : packageInfo.services) { if (!sProcessList.contains(info.processName) && info.processName != null && info.name != null && info.name.indexOf(ServiceStub.class.getSimpleName()) < 0) { sProcessList.add(info.processName); } } } if (packageInfo.activities != null) { for (ActivityInfo info : packageInfo.activities) { if (!sProcessList.contains(info.processName) && info.processName != null && info.name != null && info.name.indexOf(ActivityStub.class.getSimpleName()) < 0) { sProcessList.add(info.processName); } } } } catch (PackageManager.NameNotFoundException e) { } } public static final boolean isPluginProcess(Context context) { String currentProcessName = getCurrentProcessName(context); if (TextUtils.equals(currentProcessName, context.getPackageName())) return false; initProcessList(context); return !sProcessList.contains(currentProcessName); } public static ClassLoader getPluginClassLoader(String pkg) throws IllegalAccessException, NoSuchMethodException, ClassNotFoundException, InvocationTargetException { ClassLoader classLoader = sPluginClassLoaderCache.get(pkg); if (classLoader == null) { Application app = getPluginContext(pkg); if (app != null) { sPluginClassLoaderCache.put(app.getPackageName(), app.getClassLoader()); } } return sPluginClassLoaderCache.get(pkg); } public static void preLoadApk(Context hostContext, ComponentInfo pluginInfo) throws IOException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, PackageManager.NameNotFoundException, ClassNotFoundException { if (pluginInfo == null && hostContext == null) { return; } if (pluginInfo != null && getPluginContext(pluginInfo.packageName) != null) { return; } /*添加插件的LoadedApk对象到ActivityThread.mPackages*/ boolean found = false; synchronized (sPluginLoadedApkCache) { Object object = ActivityThreadCompat.currentActivityThread(); if (object != null) { Object mPackagesObj = FieldUtils.readField(object, "mPackages"); Object containsKeyObj = MethodUtils.invokeMethod(mPackagesObj, "containsKey", pluginInfo.packageName); if (containsKeyObj instanceof Boolean && !(Boolean) containsKeyObj) { final Object loadedApk; if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { loadedApk = MethodUtils.invokeMethod(object, "getPackageInfoNoCheck", pluginInfo.applicationInfo, CompatibilityInfoCompat.DEFAULT_COMPATIBILITY_INFO()); } else { loadedApk = MethodUtils.invokeMethod(object, "getPackageInfoNoCheck", pluginInfo.applicationInfo); } sPluginLoadedApkCache.put(pluginInfo.packageName, loadedApk); /*添加ClassLoader LoadedApk.mClassLoader*/ String optimizedDirectory = PluginDirHelper.getPluginDalvikCacheDir(hostContext, pluginInfo.packageName); String libraryPath = PluginDirHelper.getPluginNativeLibraryDir(hostContext, pluginInfo.packageName); String apk = pluginInfo.applicationInfo.publicSourceDir; if (TextUtils.isEmpty(apk)) { pluginInfo.applicationInfo.publicSourceDir = PluginDirHelper.getPluginApkFile(hostContext, pluginInfo.packageName); apk = pluginInfo.applicationInfo.publicSourceDir; } if (apk != null) { ClassLoader classloader = null; try { classloader = new PluginClassLoader(apk, optimizedDirectory, libraryPath, hostContext.getClassLoader().getParent()); } catch (Exception e) { } if(classloader==null){ PluginDirHelper.cleanOptimizedDirectory(optimizedDirectory); classloader = new PluginClassLoader(apk, optimizedDirectory, libraryPath, hostContext.getClassLoader().getParent()); } synchronized (loadedApk) { FieldUtils.writeDeclaredField(loadedApk, "mClassLoader", classloader); } sPluginClassLoaderCache.put(pluginInfo.packageName, classloader); Thread.currentThread().setContextClassLoader(classloader); found = true; } ProcessCompat.setArgV0(pluginInfo.processName); } } } if (found) { PluginProcessManager.preMakeApplication(hostContext, pluginInfo); } } private static AtomicBoolean mExec = new AtomicBoolean(false); private static Handler sHandle = new Handler(Looper.getMainLooper()); private static void preMakeApplication(Context hostContext, ComponentInfo pluginInfo) { try { final Object loadedApk = sPluginLoadedApkCache.get(pluginInfo.packageName); if (loadedApk != null) { Object mApplication = FieldUtils.readField(loadedApk, "mApplication"); if (mApplication != null) { return; } if (Looper.getMainLooper() != Looper.myLooper()) { final Object lock = new Object(); mExec.set(false); sHandle.post(new Runnable() { @Override public void run() { try { MethodUtils.invokeMethod(loadedApk, "makeApplication", false, ActivityThreadCompat.getInstrumentation()); } catch (Exception e) { Log.e(TAG, "preMakeApplication FAIL", e); } finally { mExec.set(true); synchronized (lock) { lock.notifyAll(); } } } }); if (!mExec.get()) { synchronized (lock) { try { lock.wait(); } catch (InterruptedException e) { } } } } else { MethodUtils.invokeMethod(loadedApk, "makeApplication", false, ActivityThreadCompat.getInstrumentation()); } } } catch (Exception e) { Log.e(TAG, "preMakeApplication FAIL", e); } } public static void registerStaticReceiver(Context context, ApplicationInfo pluginApplicationInfo, ClassLoader cl) throws Exception { List infos = PluginManager.getInstance().getReceivers(pluginApplicationInfo.packageName, 0); if (infos != null && infos.size() > 0) { CharSequence myPname = null; try { myPname = PluginManager.getInstance().getProcessNameByPid(android.os.Process.myPid()); } catch (Exception e) { } for (ActivityInfo info : infos) { if (TextUtils.equals(info.processName, myPname)) { try { List filters = PluginManager.getInstance().getReceiverIntentFilter(info); for (IntentFilter filter : filters) { BroadcastReceiver receiver = (BroadcastReceiver) cl.loadClass(info.name).newInstance(); context.registerReceiver(receiver, filter); } } catch (Exception e) { Log.e(TAG, "registerStaticReceiver error=%s", e, info.name); } } } } } public static void setHookEnable(boolean enable) { HookFactory.getInstance().setHookEnable(enable); } public static void setHookEnable(boolean enable, boolean reinstallHook) { HookFactory.getInstance().setHookEnable(enable, reinstallHook); } public static void installHook(Context hostContext) throws Throwable { HookFactory.getInstance().installHook(hostContext, null); } private static HashMap sApplicationsCache = new HashMap(2); public static Application getPluginContext(String packageName) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException, ClassNotFoundException { if (!sApplicationsCache.containsKey(packageName)) { Object at = ActivityThreadCompat.currentActivityThread(); Object mAllApplications = FieldUtils.readField(at, "mAllApplications"); if (mAllApplications instanceof List) { List apps = (List) mAllApplications; for (Object o : apps) { if (o instanceof Application) { Application app = (Application) o; if (!sApplicationsCache.containsKey(app.getPackageName())) { sApplicationsCache.put(app.getPackageName(), app); } } } } } return sApplicationsCache.get(packageName); } private static Context getBaseContext(Context c) { if (c instanceof ContextWrapper) { return ((ContextWrapper) c).getBaseContext(); } return c; } private static WeakHashMap mFakedContext = new WeakHashMap(1); private static Object mServiceCache = null; private static List sSkipService = new ArrayList(); static { sSkipService.add(Context.LAYOUT_INFLATER_SERVICE); sSkipService.add(Context.NOTIFICATION_SERVICE); sSkipService.add("storage"); sSkipService.add("accessibility"); sSkipService.add("audio"); sSkipService.add("clipboard"); sSkipService.add("media_router"); sSkipService.add("wifi"); sSkipService.add("captioning"); sSkipService.add("account"); sSkipService.add("activity"); //fake这个wifiscanner服务可能会导致部分手机重启。例如三星,华为 sSkipService.add("wifiscanner"); sSkipService.add("rttmanager"); sSkipService.add("tv_input"); sSkipService.add("jobscheduler"); sSkipService.add("sensorhub"); //NSDManager init初始化anr的问题 sSkipService.add("servicediscovery"); // sSkipService.add("usagestats"); } private static void fakeSystemServiceInner(Context hostContext, Context targetContext) { try { Context baseContext = getBaseContext(targetContext); if (mFakedContext.containsValue(baseContext)) { return; } else if (mServiceCache != null) { FieldUtils.writeField(baseContext, "mServiceCache", mServiceCache); //for context ContentResolver ContentResolver cr = baseContext.getContentResolver(); if (cr != null) { Object crctx = FieldUtils.readField(cr, "mContext"); if (crctx != null) { FieldUtils.writeField(crctx, "mServiceCache", mServiceCache); } } if (!mFakedContext.containsValue(baseContext)) { mFakedContext.put(baseContext.hashCode(), baseContext); } return; } Object SYSTEM_SERVICE_MAP = null; try { SYSTEM_SERVICE_MAP = FieldUtils.readStaticField(baseContext.getClass(), "SYSTEM_SERVICE_MAP"); } catch (Exception e) { Log.w(TAG, "readStaticField(SYSTEM_SERVICE_MAP) from %s fail", e, baseContext.getClass()); } if (SYSTEM_SERVICE_MAP == null) { try { SYSTEM_SERVICE_MAP = FieldUtils.readStaticField(Class.forName("android.app.SystemServiceRegistry"), "SYSTEM_SERVICE_FETCHERS"); } catch (Exception e) { Log.e(TAG, "readStaticField(SYSTEM_SERVICE_FETCHERS) from android.app.SystemServiceRegistry fail", e); } } if (SYSTEM_SERVICE_MAP != null && (SYSTEM_SERVICE_MAP instanceof Map)) { //如没有,则创建一个新的。 Map sSYSTEM_SERVICE_MAP = (Map) SYSTEM_SERVICE_MAP; Context originContext = getBaseContext(hostContext); Object mServiceCache = FieldUtils.readField(originContext, "mServiceCache"); if (mServiceCache instanceof List) { ((List) mServiceCache).clear(); } for (Object key : sSYSTEM_SERVICE_MAP.keySet()) { if (sSkipService.contains(key)) { continue; } Object serviceFetcher = sSYSTEM_SERVICE_MAP.get(key); try { Method getService = serviceFetcher.getClass().getMethod("getService", baseContext.getClass()); getService.invoke(serviceFetcher, originContext); } catch (InvocationTargetException e) { Throwable cause = e.getCause(); if (cause != null) { Log.w(TAG, "Fake system service faile", e); } else { Log.w(TAG, "Fake system service faile", e); } } catch (Exception e) { Log.w(TAG, "Fake system service faile", e); } } mServiceCache = FieldUtils.readField(originContext, "mServiceCache"); FieldUtils.writeField(baseContext, "mServiceCache", mServiceCache); //for context ContentResolver ContentResolver cr = baseContext.getContentResolver(); if (cr != null) { Object crctx = FieldUtils.readField(cr, "mContext"); if (crctx != null) { FieldUtils.writeField(crctx, "mServiceCache", mServiceCache); } } } if (!mFakedContext.containsValue(baseContext)) { mFakedContext.put(baseContext.hashCode(), baseContext); } } catch (Exception e) { Log.e(TAG, "fakeSystemServiceOldAPI", e); } } //这里为了解决某些插件调用系统服务时,系统服务必须要求要以host包名的身份去调用的问题。 public static void fakeSystemService(Context hostContext, Context targetContext) { if (VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH_MR1 && !TextUtils.equals(hostContext.getPackageName(), targetContext.getPackageName())) { long b = System.currentTimeMillis(); fakeSystemServiceInner(hostContext, targetContext); Log.i(TAG, "Fake SystemService for originContext=%s context=%s,cost %s ms", targetContext.getPackageName(), targetContext.getPackageName(), (System.currentTimeMillis() - b)); } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/BaseHookHandle.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook; import android.content.Context; import com.morgoo.helper.Log; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.HashMap; import java.util.Map; import java.util.Set; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/2/28. */ public abstract class BaseHookHandle { protected Context mHostContext; protected Map sHookedMethodHandlers = new HashMap(5); public BaseHookHandle(Context hostContext) { mHostContext = hostContext; init(); } protected abstract void init(); public Set getHookedMethodNames(){ return sHookedMethodHandlers.keySet(); } public HookedMethodHandler getHookedMethodHandler(Method method) { if (method != null) { return sHookedMethodHandlers.get(method.getName()); } else { return null; } } protected Class getHookedClass() throws ClassNotFoundException { return null; } protected HookedMethodHandler newBaseHandler() throws ClassNotFoundException { return null; } protected void addAllMethodFromHookedClass(){ try { Class clazz = getHookedClass(); if(clazz!=null){ Method[] methods = clazz.getDeclaredMethods(); if(methods!=null && methods.length>0){ for (Method method : methods) { if(Modifier.isPublic(method.getModifiers()) && !sHookedMethodHandlers.containsKey(method.getName())){ sHookedMethodHandlers.put(method.getName(),newBaseHandler()); } } } } } catch (ClassNotFoundException e) { Log.w(getClass().getSimpleName(),"init addAllMethodFromHookedClass error",e); } }; } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/Hook.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook; import android.content.Context; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/3/2. */ public abstract class Hook { private boolean mEnable = false; protected Context mHostContext; protected BaseHookHandle mHookHandles; public void setEnable(boolean enable, boolean reInstallHook) { this.mEnable = enable; } public final void setEnable(boolean enable) { setEnable(enable, false); } public boolean isEnable() { return mEnable; } protected Hook(Context hostContext) { mHostContext = hostContext; mHookHandles = createHookHandle(); } protected abstract BaseHookHandle createHookHandle(); protected abstract void onInstall(ClassLoader classLoader) throws Throwable; protected void onUnInstall(ClassLoader classLoader) throws Throwable { } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/HookFactory.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook; import android.app.Application; import android.content.Context; import android.os.Build; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import com.morgoo.droidplugin.hook.binder.IAppOpsServiceBinderHook; import com.morgoo.droidplugin.hook.binder.IAudioServiceBinderHook; import com.morgoo.droidplugin.hook.binder.IClipboardBinderHook; import com.morgoo.droidplugin.hook.binder.IContentServiceBinderHook; import com.morgoo.droidplugin.hook.binder.IDisplayManagerBinderHook; import com.morgoo.droidplugin.hook.binder.IGraphicsStatsBinderHook; import com.morgoo.droidplugin.hook.binder.IInputMethodManagerBinderHook; import com.morgoo.droidplugin.hook.binder.ILocationManagerBinderHook; import com.morgoo.droidplugin.hook.binder.IMediaRouterServiceBinderHook; import com.morgoo.droidplugin.hook.binder.IMmsBinderHook; import com.morgoo.droidplugin.hook.binder.IMountServiceBinderHook; import com.morgoo.droidplugin.hook.binder.INotificationManagerBinderHook; import com.morgoo.droidplugin.hook.binder.IPhoneSubInfoBinderHook; import com.morgoo.droidplugin.hook.binder.ISearchManagerBinderHook; import com.morgoo.droidplugin.hook.binder.ISessionManagerBinderHook; import com.morgoo.droidplugin.hook.binder.ISmsBinderHook; import com.morgoo.droidplugin.hook.binder.ISubBinderHook; import com.morgoo.droidplugin.hook.binder.ITelephonyBinderHook; import com.morgoo.droidplugin.hook.binder.ITelephonyRegistryBinderHook; import com.morgoo.droidplugin.hook.binder.IWifiManagerBinderHook; import com.morgoo.droidplugin.hook.binder.IWindowManagerBinderHook; import com.morgoo.droidplugin.hook.proxy.IActivityManagerHook; import com.morgoo.droidplugin.hook.proxy.IPackageManagerHook; import com.morgoo.droidplugin.hook.proxy.InstrumentationHook; import com.morgoo.droidplugin.hook.proxy.LibCoreHook; import com.morgoo.droidplugin.hook.proxy.PluginCallbackHook; import com.morgoo.droidplugin.hook.xhook.SQLiteDatabaseHook; import com.morgoo.helper.Log; import com.morgoo.helper.utils.ProcessUtils; import java.util.ArrayList; import java.util.List; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/3/2. */ public class HookFactory { private static final String TAG = HookFactory.class.getSimpleName(); private static HookFactory sInstance = null; private HookFactory() { } public static HookFactory getInstance() { synchronized (HookFactory.class) { if (sInstance == null) { sInstance = new HookFactory(); } } return sInstance; } private List mHookList = new ArrayList(3); public void setHookEnable(boolean enable) { synchronized (mHookList) { for (Hook hook : mHookList) { hook.setEnable(enable); } } } public void setHookEnable(boolean enable, boolean reinstallHook) { synchronized (mHookList) { for (Hook hook : mHookList) { hook.setEnable(enable, reinstallHook); } } } public void setHookEnable(Class hookClass, boolean enable) { synchronized (mHookList) { for (Hook hook : mHookList) { if (hookClass.isInstance(hook)) { hook.setEnable(enable); } } } } public void installHook(Hook hook, ClassLoader cl) { try { hook.onInstall(cl); synchronized (mHookList) { mHookList.add(hook); } } catch (Throwable throwable) { Log.e(TAG, "installHook %s error", throwable, hook); } } public final void installHook(Context context, ClassLoader classLoader) throws Throwable { if (ProcessUtils.isMainProcess(context)) { installHook(new IActivityManagerHook(context), classLoader); installHook(new IPackageManagerHook(context), classLoader); } else { installHook(new IClipboardBinderHook(context), classLoader); //for ISearchManager installHook(new ISearchManagerBinderHook(context), classLoader); //for INotificationManager installHook(new INotificationManagerBinderHook(context), classLoader); installHook(new IMountServiceBinderHook(context), classLoader); installHook(new IAudioServiceBinderHook(context), classLoader); installHook(new IContentServiceBinderHook(context), classLoader); installHook(new IWindowManagerBinderHook(context), classLoader); if (VERSION.SDK_INT > VERSION_CODES.LOLLIPOP_MR1) { installHook(new IGraphicsStatsBinderHook(context), classLoader); } // if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) { // installHook(new WebViewFactoryProviderHook(context), classLoader); // } if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) { installHook(new IMediaRouterServiceBinderHook(context), classLoader); } if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { installHook(new ISessionManagerBinderHook(context), classLoader); } if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR2) { installHook(new IWifiManagerBinderHook(context), classLoader); } if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR2) { installHook(new IInputMethodManagerBinderHook(context), classLoader); } if (VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { installHook(new ILocationManagerBinderHook(context), classLoader); } if (VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { installHook(new ITelephonyRegistryBinderHook(context), classLoader); } if (VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { installHook(new ISubBinderHook(context), classLoader); } if (VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { installHook(new IPhoneSubInfoBinderHook(context), classLoader); } if (VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { installHook(new ITelephonyBinderHook(context), classLoader); } if (VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { installHook(new ISmsBinderHook(context), classLoader); } if (VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { installHook(new IMmsBinderHook(context), classLoader); } if (VERSION.SDK_INT >= VERSION_CODES.M) { installHook(new IAppOpsServiceBinderHook(context), classLoader); } installHook(new IActivityManagerHook(context), classLoader); installHook(new IPackageManagerHook(context), classLoader); installHook(new PluginCallbackHook(context), classLoader); installHook(new InstrumentationHook(context), classLoader); installHook(new LibCoreHook(context), classLoader); installHook(new SQLiteDatabaseHook(context), classLoader); installHook(new IDisplayManagerBinderHook(context), classLoader); } } public final void onCallApplicationOnCreate(Context context, Application app) { installHook(new SQLiteDatabaseHook(context), app.getClassLoader()); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/HookedMethodHandler.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook; import android.content.Context; import com.morgoo.helper.Log; import java.lang.reflect.Method; public class HookedMethodHandler { private static final String TAG = HookedMethodHandler.class.getSimpleName(); protected final Context mHostContext; private Object mFakedResult = null; private boolean mUseFakedResult = false; public HookedMethodHandler(Context hostContext) { this.mHostContext = hostContext; } public synchronized Object doHookInner(Object receiver, Method method, Object[] args) throws Throwable { long b = System.currentTimeMillis(); try { mUseFakedResult = false; mFakedResult = null; boolean suc = beforeInvoke(receiver, method, args); Object invokeResult = null; if (!suc) { invokeResult = method.invoke(receiver, args); } afterInvoke(receiver, method, args, invokeResult); if (mUseFakedResult) { return mFakedResult; } else { return invokeResult; } } finally { long time = System.currentTimeMillis() - b; if (time > 5) { Log.i(TAG, "doHookInner method(%s.%s) cost %s ms", method.getDeclaringClass().getName(), method.getName(), time); } } } public void setFakedResult(Object fakedResult) { this.mFakedResult = fakedResult; mUseFakedResult = true; } /** * 在某个方法被调用之前执行,如果返回true,则不执行原始的方法,否则执行原始方法 */ protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { return false; } protected void afterInvoke(Object receiver, Method method, Object[] args, Object invokeResult) throws Throwable { } public boolean isFakedResult() { return mUseFakedResult; } public Object getFakedResult() { return mFakedResult; } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/binder/BinderHook.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.binder; import android.content.Context; import android.text.TextUtils; import com.morgoo.droidplugin.hook.Hook; import com.morgoo.droidplugin.hook.HookedMethodHandler; import com.morgoo.droidplugin.reflect.Utils; import com.morgoo.helper.MyProxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; import java.util.List; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/3/2. */ abstract class BinderHook extends Hook implements InvocationHandler { private Object mOldObj; public BinderHook(Context hostContext) { super(hostContext); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { if (!isEnable()) { return method.invoke(mOldObj, args); } HookedMethodHandler hookedMethodHandler = mHookHandles.getHookedMethodHandler(method); if (hookedMethodHandler != null) { return hookedMethodHandler.doHookInner(mOldObj, method, args); } else { return method.invoke(mOldObj, args); } } catch (InvocationTargetException e) { Throwable cause = e.getTargetException(); if (cause != null && MyProxy.isMethodDeclaredThrowable(method, cause)) { throw cause; } else if (cause != null) { RuntimeException runtimeException = !TextUtils.isEmpty(cause.getMessage()) ? new RuntimeException(cause.getMessage()) : new RuntimeException(); runtimeException.initCause(cause); throw runtimeException; } else { RuntimeException runtimeException = !TextUtils.isEmpty(e.getMessage()) ? new RuntimeException(e.getMessage()) : new RuntimeException(); runtimeException.initCause(e); throw runtimeException; } } catch (IllegalArgumentException e) { try { StringBuilder sb = new StringBuilder(); sb.append(" DROIDPLUGIN{"); if (method != null) { sb.append("method[").append(method.toString()).append("]"); } else { sb.append("method[").append("NULL").append("]"); } if (args != null) { sb.append("args[").append(Arrays.toString(args)).append("]"); } else { sb.append("args[").append("NULL").append("]"); } sb.append("}"); String message = e.getMessage() + sb.toString(); throw new IllegalArgumentException(message, e); } catch (Throwable e1) { throw e; } } catch (Throwable e) { if (MyProxy.isMethodDeclaredThrowable(method, e)) { throw e; } else { RuntimeException runtimeException = !TextUtils.isEmpty(e.getMessage()) ? new RuntimeException(e.getMessage()) : new RuntimeException(); runtimeException.initCause(e); throw runtimeException; } } } abstract Object getOldObj() throws Exception; void setOldObj(Object mOldObj) { this.mOldObj = mOldObj; } public abstract String getServiceName(); @Override protected void onInstall(ClassLoader classLoader) throws Throwable { new ServiceManagerCacheBinderHook(mHostContext, getServiceName()).onInstall(classLoader); mOldObj = getOldObj(); Class clazz = mOldObj.getClass(); List> interfaces = Utils.getAllInterfaces(clazz); Class[] ifs = interfaces != null && interfaces.size() > 0 ? interfaces.toArray(new Class[interfaces.size()]) : new Class[0]; Object proxiedObj = MyProxy.newProxyInstance(clazz.getClassLoader(), ifs, this); MyServiceManager.addProxiedObj(getServiceName(), proxiedObj); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/binder/IAppOpsServiceBinderHook.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.binder; import android.content.Context; import android.os.IBinder; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.handle.IAppOpsServiceHookHandle; import com.morgoo.helper.compat.IAppOpsServiceCompat; /** * Created by Andy Zhang(zhangyong232@gmail.com) on on 16/5/11. */ public class IAppOpsServiceBinderHook extends BinderHook{ private static final String SERVICE_NAME = Context.APP_OPS_SERVICE; public IAppOpsServiceBinderHook(Context hostContext) { super(hostContext); } @Override Object getOldObj() throws Exception { IBinder iBinder = MyServiceManager.getOriginService(SERVICE_NAME); return IAppOpsServiceCompat.asInterface(iBinder); } @Override public String getServiceName() { return SERVICE_NAME; } @Override protected BaseHookHandle createHookHandle() { return new IAppOpsServiceHookHandle(mHostContext); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/binder/IAudioServiceBinderHook.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.binder; import android.content.Context; import android.os.IBinder; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.handle.IAudioServiceHookHandle; import com.morgoo.helper.compat.IAudioServiceCompat; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/3/6. */ public class IAudioServiceBinderHook extends BinderHook { private final static String SERVICE_NAME = Context.AUDIO_SERVICE; public IAudioServiceBinderHook(Context hostContext) { super(hostContext); } @Override public Object getOldObj() throws Exception { IBinder iBinder = MyServiceManager.getOriginService(SERVICE_NAME); return IAudioServiceCompat.asInterface(iBinder); } @Override public String getServiceName() { return SERVICE_NAME; } @Override protected BaseHookHandle createHookHandle() { return new IAudioServiceHookHandle(mHostContext); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/binder/IClipboardBinderHook.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.binder; import android.content.Context; import android.os.IBinder; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.handle.IClipboardHookHandle; import com.morgoo.helper.compat.IClipboardCompat; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/3/6. */ public class IClipboardBinderHook extends BinderHook { private final static String CLIPBOARD_SERVICE = "clipboard"; public IClipboardBinderHook(Context hostContext) { super(hostContext); } @Override public Object getOldObj() throws Exception{ IBinder iBinder = MyServiceManager.getOriginService(CLIPBOARD_SERVICE); return IClipboardCompat.asInterface(iBinder); } @Override public String getServiceName() { return CLIPBOARD_SERVICE; } @Override protected BaseHookHandle createHookHandle() { return new IClipboardHookHandle(mHostContext); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/binder/IContentServiceBinderHook.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.binder; import android.content.Context; import android.os.IBinder; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.handle.IContentServiceHandle; import com.morgoo.helper.compat.IContentServiceCompat; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/5/21. */ public class IContentServiceBinderHook extends BinderHook { private static final String CONTENT_SERVICE_NAME = "content"; public IContentServiceBinderHook(Context hostContext) { super(hostContext); } @Override public Object getOldObj() throws Exception { IBinder iBinder = MyServiceManager.getOriginService(CONTENT_SERVICE_NAME); return IContentServiceCompat.asInterface(iBinder); } @Override public String getServiceName() { return CONTENT_SERVICE_NAME; } @Override protected BaseHookHandle createHookHandle() { return new IContentServiceHandle(mHostContext); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/binder/IDisplayManagerBinderHook.java ================================================ package com.morgoo.droidplugin.hook.binder; import android.content.Context; import android.os.IBinder; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.handle.IDisplayManagerHookHandle; import com.morgoo.droidplugin.reflect.FieldUtils; import com.morgoo.droidplugin.reflect.MethodUtils; import com.morgoo.helper.compat.IDisplayManagerCompat; /** * IDisplayManagerBinderHook * * @author Liu Yichen * @date 16/6/13 */ public class IDisplayManagerBinderHook extends BinderHook { private static final String TAG = IDisplayManagerBinderHook.class.getSimpleName(); private static final String SERVICE_NAME = Context.DISPLAY_SERVICE; public IDisplayManagerBinderHook(Context hostContext) { super(hostContext); } @Override public String getServiceName() { return SERVICE_NAME; } @Override Object getOldObj() throws Exception { IBinder iBinder = MyServiceManager.getOriginService(SERVICE_NAME); return IDisplayManagerCompat.asInterface(iBinder); } @Override protected void onInstall(ClassLoader classLoader) throws Throwable { super.onInstall(classLoader); Class displayManagerGlobalClass = Class.forName("android.hardware.display.DisplayManagerGlobal"); Object displayManagerGlobal = MethodUtils.invokeStaticMethod(displayManagerGlobalClass, "getInstance"); FieldUtils.writeField(displayManagerGlobal, "mDm", MyServiceManager.getProxiedObj(SERVICE_NAME)); } @Override protected BaseHookHandle createHookHandle() { return new IDisplayManagerHookHandle(mHostContext); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/binder/IGraphicsStatsBinderHook.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.binder; import android.content.Context; import android.os.IBinder; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.handle.IClipboardHookHandle; import com.morgoo.droidplugin.hook.handle.IGraphicsStatsHookHandle; import com.morgoo.helper.compat.IClipboardCompat; import com.morgoo.helper.compat.IGraphicsStatsCompat; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/6/18. */ public class IGraphicsStatsBinderHook extends BinderHook { private final static String SERVICE_NAME = "graphicsstats"; public IGraphicsStatsBinderHook(Context hostContext) { super(hostContext); } @Override public Object getOldObj() throws Exception{ IBinder iBinder = MyServiceManager.getOriginService(SERVICE_NAME); return IGraphicsStatsCompat.asInterface(iBinder); } @Override public String getServiceName() { return SERVICE_NAME; } @Override protected BaseHookHandle createHookHandle() { return new IGraphicsStatsHookHandle(mHostContext); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/binder/IInputMethodManagerBinderHook.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.binder; import android.content.Context; import android.os.IBinder; import android.view.inputmethod.InputMethodManager; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.handle.IAudioServiceHookHandle; import com.morgoo.droidplugin.hook.handle.IInputMethodManagerHookHandle; import com.morgoo.droidplugin.reflect.FieldUtils; import com.morgoo.helper.compat.IAudioServiceCompat; import com.morgoo.helper.compat.IInputMethodManagerCompat; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/6/4. */ public class IInputMethodManagerBinderHook extends BinderHook { private final static String SERVICE_NAME = Context.INPUT_METHOD_SERVICE; public IInputMethodManagerBinderHook(Context hostContext) { super(hostContext); } @Override public Object getOldObj() throws Exception { IBinder iBinder = MyServiceManager.getOriginService(SERVICE_NAME); return IInputMethodManagerCompat.asInterface(iBinder); } @Override protected void onInstall(ClassLoader classLoader) throws Throwable { super.onInstall(classLoader); Object obj = FieldUtils.readStaticField(InputMethodManager.class, "sInstance"); if (obj != null) { FieldUtils.writeStaticField(InputMethodManager.class, "sInstance", null); } mHostContext.getSystemService(Context.INPUT_METHOD_SERVICE); } @Override public String getServiceName() { return SERVICE_NAME; } @Override protected BaseHookHandle createHookHandle() { return new IInputMethodManagerHookHandle(mHostContext); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/binder/ILocationManagerBinderHook.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.binder; import android.content.Context; import android.os.IBinder; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.handle.ILocationManagerHookHandle; import com.morgoo.helper.compat.ILocationManagerCompat; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2016/2/25. */ public class ILocationManagerBinderHook extends BinderHook { private final static String SERVICE_NAME = Context.LOCATION_SERVICE; public ILocationManagerBinderHook(Context hostContext) { super(hostContext); } @Override Object getOldObj() throws Exception { IBinder iBinder = MyServiceManager.getOriginService(SERVICE_NAME); return ILocationManagerCompat.asInterface(iBinder); } @Override public String getServiceName() { return SERVICE_NAME; } @Override protected BaseHookHandle createHookHandle() { return new ILocationManagerHookHandle(mHostContext); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/binder/IMediaRouterServiceBinderHook.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.binder; import android.content.Context; import android.os.IBinder; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.handle.IMediaRouterServiceHookHandle; import com.morgoo.helper.compat.IMediaRouterServiceCompat; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/3/6. */ public class IMediaRouterServiceBinderHook extends BinderHook { private final static String SERVICE_NAME =Context.MEDIA_ROUTER_SERVICE; public IMediaRouterServiceBinderHook(Context hostContext) { super(hostContext); } @Override public Object getOldObj() throws Exception{ IBinder iBinder = MyServiceManager.getOriginService(SERVICE_NAME); return IMediaRouterServiceCompat.asInterface(iBinder); } @Override public String getServiceName() { return SERVICE_NAME; } @Override protected BaseHookHandle createHookHandle() { return new IMediaRouterServiceHookHandle(mHostContext); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/binder/IMmsBinderHook.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.binder; import android.content.Context; import android.os.IBinder; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.handle.IMmsHookHandle; import com.morgoo.helper.compat.IMmsCompat; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2016/5/9. */ public class IMmsBinderHook extends BinderHook { private static final String SERVICE_NAME = "imms"; public IMmsBinderHook(Context hostContext) { super(hostContext); } @Override Object getOldObj() throws Exception { IBinder iBinder = MyServiceManager.getOriginService(SERVICE_NAME); return IMmsCompat.asInterface(iBinder); } @Override public String getServiceName() { return SERVICE_NAME; } @Override protected BaseHookHandle createHookHandle() { return new IMmsHookHandle(mHostContext); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/binder/IMountServiceBinderHook.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.binder; import android.content.Context; import android.os.IBinder; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.handle.IMountServiceHookHandle; import com.morgoo.helper.compat.IMountServiceCompat; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/3/6. */ public class IMountServiceBinderHook extends BinderHook { private final static String SERVICE_NAME = "mount"; public IMountServiceBinderHook(Context hostContext) { super(hostContext); } @Override public Object getOldObj() throws Exception{ IBinder iBinder = MyServiceManager.getOriginService(SERVICE_NAME); return IMountServiceCompat.asInterface(iBinder); } @Override public String getServiceName() { return SERVICE_NAME; } @Override protected BaseHookHandle createHookHandle() { return new IMountServiceHookHandle(mHostContext); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/binder/INotificationManagerBinderHook.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.binder; import android.content.Context; import android.os.IBinder; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.handle.INotificationManagerHookHandle; import com.morgoo.helper.compat.INotificationManagerCompat; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/3/2. */ public class INotificationManagerBinderHook extends BinderHook { public static final String SERVICE_NAME = "notification"; public INotificationManagerBinderHook(Context hostContext) { super(hostContext); } @Override protected BaseHookHandle createHookHandle() { return new INotificationManagerHookHandle(mHostContext); } public Object getOldObj() throws Exception{ IBinder iBinder = MyServiceManager.getOriginService(SERVICE_NAME); return INotificationManagerCompat.asInterface(iBinder); } public String getServiceName() { return SERVICE_NAME; } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/binder/IPhoneSubInfoBinderHook.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.binder; import android.content.Context; import android.os.IBinder; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.handle.IPhoneSubInfoHookHandle; import com.morgoo.helper.compat.IPhoneSubInfoCompat; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2016/5/6. */ public class IPhoneSubInfoBinderHook extends BinderHook { private static final String SERVICE_NAME = "iphonesubinfo"; public IPhoneSubInfoBinderHook(Context hostContext) { super(hostContext); } @Override Object getOldObj() throws Exception { IBinder iBinder = MyServiceManager.getOriginService(SERVICE_NAME); return IPhoneSubInfoCompat.asInterface(iBinder); } @Override public String getServiceName() { return SERVICE_NAME; } @Override protected BaseHookHandle createHookHandle() { return new IPhoneSubInfoHookHandle(mHostContext); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/binder/ISearchManagerBinderHook.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.binder; import android.content.Context; import android.os.IBinder; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.handle.IClipboardHookHandle; import com.morgoo.droidplugin.hook.handle.ISearchManagerHookHandle; import com.morgoo.helper.compat.ISearchManagerCompat; /** * Created by wyw on 15-9-18. */ public class ISearchManagerBinderHook extends BinderHook { private final static String SEARCH_MANAGER_SERVICE = "search"; public ISearchManagerBinderHook(Context hostContext) { super(hostContext); } @Override Object getOldObj() throws Exception { IBinder iBinder = MyServiceManager.getOriginService(SEARCH_MANAGER_SERVICE); return ISearchManagerCompat.asInterface(iBinder); } @Override public String getServiceName() { return SEARCH_MANAGER_SERVICE; } @Override protected BaseHookHandle createHookHandle() { return new ISearchManagerHookHandle(mHostContext); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/binder/ISessionManagerBinderHook.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.binder; import android.content.Context; import android.os.IBinder; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.handle.ISessionManagerHookHandle; import com.morgoo.helper.compat.ISessionManagerCompat; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/3/9. */ public class ISessionManagerBinderHook extends BinderHook { private final static String SERVICE_NAME = Context.MEDIA_SESSION_SERVICE; public ISessionManagerBinderHook(Context hostContext) { super(hostContext); } @Override public Object getOldObj() throws Exception{ IBinder iBinder = MyServiceManager.getOriginService(SERVICE_NAME); return ISessionManagerCompat.asInterface(iBinder); } @Override public String getServiceName() { return SERVICE_NAME; } @Override protected BaseHookHandle createHookHandle() { return new ISessionManagerHookHandle(mHostContext); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/binder/ISmsBinderHook.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.binder; import android.content.Context; import android.os.IBinder; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.handle.ISmsHookHandle; import com.morgoo.helper.compat.ISmsCompat; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2016/5/9. */ public class ISmsBinderHook extends BinderHook { private static final String SERVICE_NAME = "isms"; public ISmsBinderHook(Context hostContext) { super(hostContext); } @Override Object getOldObj() throws Exception { IBinder iBinder = MyServiceManager.getOriginService(SERVICE_NAME); return ISmsCompat.asInterface(iBinder); } @Override public String getServiceName() { return SERVICE_NAME; } @Override protected BaseHookHandle createHookHandle() { return new ISmsHookHandle(mHostContext); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/binder/ISubBinderHook.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.binder; import android.content.Context; import android.os.IBinder; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.handle.ISubBinderHookHandle; import com.morgoo.helper.compat.ISubCompat; import com.morgoo.helper.compat.ITelephonyRegistryCompat; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2016/5/6. */ public class ISubBinderHook extends BinderHook { private final static String SERVICE_NAME = "isub"; public ISubBinderHook(Context hostContext) { super(hostContext); } @Override Object getOldObj() throws Exception { IBinder iBinder = MyServiceManager.getOriginService(SERVICE_NAME); return ISubCompat.asInterface(iBinder); } @Override public String getServiceName() { return SERVICE_NAME; } @Override protected BaseHookHandle createHookHandle() { return new ISubBinderHookHandle(mHostContext); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/binder/ITelephonyBinderHook.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.binder; import android.content.Context; import android.os.IBinder; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.handle.ITelephonyHookHandle; import com.morgoo.helper.compat.ITelephonyCompat; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2016/5/6. */ public class ITelephonyBinderHook extends BinderHook { public ITelephonyBinderHook(Context hostContext) { super(hostContext); } private final static String SERVICE_NAME = Context.TELEPHONY_SERVICE; @Override Object getOldObj() throws Exception { IBinder iBinder = MyServiceManager.getOriginService(SERVICE_NAME); return ITelephonyCompat.asInterface(iBinder); } @Override public String getServiceName() { return SERVICE_NAME; } @Override protected BaseHookHandle createHookHandle() { return new ITelephonyHookHandle(mHostContext); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/binder/ITelephonyRegistryBinderHook.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.binder; import android.content.Context; import android.os.IBinder; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.handle.ITelephonyRegistryHookHandle; import com.morgoo.helper.compat.ITelephonyRegistryCompat; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2016/5/6. */ public class ITelephonyRegistryBinderHook extends BinderHook { private final static String SERVICE_NAME = "telephony.registry"; public ITelephonyRegistryBinderHook(Context hostContext) { super(hostContext); } @Override Object getOldObj() throws Exception { IBinder iBinder = MyServiceManager.getOriginService(SERVICE_NAME); return ITelephonyRegistryCompat.asInterface(iBinder); } @Override public String getServiceName() { return SERVICE_NAME; } @Override protected BaseHookHandle createHookHandle() { return new ITelephonyRegistryHookHandle(mHostContext); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/binder/IWifiManagerBinderHook.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.binder; import android.content.Context; import android.os.IBinder; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.handle.IWifiManagerHookHandle; import com.morgoo.droidplugin.reflect.FieldUtils; import com.morgoo.helper.Log; import com.morgoo.helper.compat.IWifiManagerCompat; import com.morgoo.helper.compat.ServiceManagerCompat; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/6/1. */ public class IWifiManagerBinderHook extends BinderHook { private final static String SERVICE_NAME = "wifi"; private static final String TAG = IWifiManagerBinderHook.class.getSimpleName(); public IWifiManagerBinderHook(Context hostContext) { super(hostContext); } @Override public Object getOldObj() throws Exception { IBinder iBinder = MyServiceManager.getOriginService(SERVICE_NAME); return IWifiManagerCompat.asInterface(iBinder); } @Override public String getServiceName() { return SERVICE_NAME; } @Override protected BaseHookHandle createHookHandle() { return new IWifiManagerHookHandle(mHostContext); } @Override protected void onInstall(ClassLoader classLoader) throws Throwable { super.onInstall(classLoader); fixZTESecurity(); } /**适配ZTE S2005机型ZTESecurity*/ private void fixZTESecurity() { try { Object proxyServiceIBinder = MyServiceManager.getProxiedObj(getServiceName()); IBinder serviceIBinder = ServiceManagerCompat.getService(getServiceName()); if (serviceIBinder != null && proxyServiceIBinder != null && "com.zte.ZTESecurity.ZTEWifiService".equals(serviceIBinder.getClass().getName())) { Object obj = FieldUtils.readField(serviceIBinder, "mIWifiManager"); setOldObj(obj); FieldUtils.writeField(serviceIBinder, "mIWifiManager", proxyServiceIBinder); } } catch (Exception e) { Log.i(TAG, "fixZTESecurity FAIL", e); } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/binder/IWindowManagerBinderHook.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.binder; import android.app.Activity; import android.content.Context; import android.os.IBinder; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.handle.IClipboardHookHandle; import com.morgoo.droidplugin.hook.handle.IWindowManagerHookHandle; import com.morgoo.droidplugin.reflect.FieldUtils; import com.morgoo.helper.Log; import com.morgoo.helper.compat.IClipboardCompat; import com.morgoo.helper.compat.IWindowManagerCompat; import java.lang.reflect.Method; import java.util.Arrays; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/6/17. */ public class IWindowManagerBinderHook extends BinderHook { private final static String SERVICE_NAME = "window"; private static final String TAG = IWindowManagerBinderHook.class.getSimpleName(); public IWindowManagerBinderHook(Context hostContext) { super(hostContext); } @Override public Object getOldObj() throws Exception { IBinder iBinder = MyServiceManager.getOriginService(SERVICE_NAME); return IWindowManagerCompat.asInterface(iBinder); } @Override public String getServiceName() { return SERVICE_NAME; } @Override protected BaseHookHandle createHookHandle() { return new IWindowManagerHookHandle(mHostContext); } @Override protected void onInstall(ClassLoader classLoader) throws Throwable { super.onInstall(classLoader); try { Class claszz = Class.forName("com.android.internal.policy.PhoneWindow$WindowManagerHolder"); FieldUtils.writeStaticField(claszz, "sWindowManager", MyServiceManager.getProxiedObj(getServiceName())); } catch (Exception e) { Log.w(TAG, "onInstall writeStaticField to sWindowManager fail", e); } } public static void fixWindowManagerHook(Activity activity) { try { Object mWindow = FieldUtils.readField(activity, "mWindow"); Class clazz = mWindow.getClass(); Class WindowManagerHolder = Class.forName(clazz.getName() + "$WindowManagerHolder"); Object obj = FieldUtils.readStaticField(WindowManagerHolder, "sWindowManager"); Object proxiedObj = MyServiceManager.getProxiedObj(SERVICE_NAME); if (obj == proxiedObj) { return; } FieldUtils.writeStaticField(WindowManagerHolder, "sWindowManager", proxiedObj); } catch (Exception e) { Log.w(TAG, "fixWindowManagerHook writeStaticField to sWindowManager fail", e); } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/binder/MyServiceManager.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.binder; import android.os.IBinder; import java.util.HashMap; import java.util.Map; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/3/2. */ public class MyServiceManager { private static Map mOriginServiceCache = new HashMap(1); private static Map mProxiedServiceCache = new HashMap(1); private static Map mProxiedObjCache = new HashMap(1); static IBinder getOriginService(String serviceName) { return mOriginServiceCache.get(serviceName); } public static void addOriginService(String serviceName, IBinder service) { mOriginServiceCache.put(serviceName, service); } static void addProxiedServiceCache(String serviceName, IBinder proxyService) { mProxiedServiceCache.put(serviceName, proxyService); } static Object getProxiedObj(String servicename) { return mProxiedObjCache.get(servicename); } static void addProxiedObj(String servicename, Object obj) { mProxiedObjCache.put(servicename, obj); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/binder/ServiceManagerBinderHook.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.binder; import android.content.Context; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.HookedMethodHandler; import com.morgoo.droidplugin.hook.proxy.ProxyHook; import com.morgoo.droidplugin.reflect.FieldUtils; import com.morgoo.droidplugin.reflect.MethodUtils; import com.morgoo.droidplugin.reflect.Utils; import com.morgoo.helper.Log; import com.morgoo.helper.MyProxy; import com.morgoo.helper.compat.ServiceManagerCompat; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.Arrays; import java.util.List; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/6/23. */ public class ServiceManagerBinderHook extends ProxyHook implements InvocationHandler { public ServiceManagerBinderHook(Context hostContext) { super(hostContext); setEnable(true); } @Override protected void onInstall(ClassLoader classLoader) throws Throwable { Object sServiceManager = FieldUtils.readStaticField(ServiceManagerCompat.Class(), "sServiceManager"); if (sServiceManager == null) { MethodUtils.invokeStaticMethod(ServiceManagerCompat.Class(), "getIServiceManager"); sServiceManager = FieldUtils.readStaticField(ServiceManagerCompat.Class(), "sServiceManager"); } setOldObj(sServiceManager); Class clazz = mOldObj.getClass(); List> interfaces = Utils.getAllInterfaces(clazz); Class[] ifs = interfaces != null && interfaces.size() > 0 ? interfaces.toArray(new Class[interfaces.size()]) : new Class[0]; Object proxiedObj = MyProxy.newProxyInstance(clazz.getClassLoader(), ifs, this); FieldUtils.writeStaticField(ServiceManagerCompat.Class(), "sServiceManager", proxiedObj); } private class ServiceManagerHookHandle extends BaseHookHandle { private ServiceManagerHookHandle(Context context) { super(context); } @Override protected void init() { sHookedMethodHandlers.put("getService", new getService(mHostContext)); sHookedMethodHandlers.put("checkService", new checkService(mHostContext)); } private class ServiceManagerHook extends HookedMethodHandler { public ServiceManagerHook(Context hostContext) { super(hostContext); } @Override protected void afterInvoke(Object receiver, Method method, Object[] args, Object invokeResult) throws Throwable { int index = 0; if (args != null && args.length > index && args[index] instanceof String) { String servicename = ((String) args[index]); Object proxiedObj = MyServiceManager.getProxiedObj(servicename); if (proxiedObj != null) { setFakedResult(proxiedObj); } } Log.e("ServiceManagerBinderHook", "%s(%s)=%s", method.getName(), Arrays.toString(args), invokeResult); super.afterInvoke(receiver, method, args, invokeResult); } } private class getService extends ServiceManagerHook { public getService(Context hostContext) { super(hostContext); } } private class checkService extends ServiceManagerHook { public checkService(Context hostContext) { super(hostContext); } } } @Override protected BaseHookHandle createHookHandle() { return new ServiceManagerHookHandle(mHostContext); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/binder/ServiceManagerCacheBinderHook.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.binder; import android.content.Context; import android.os.IBinder; import android.text.TextUtils; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.Hook; import com.morgoo.droidplugin.hook.HookedMethodHandler; import com.morgoo.droidplugin.reflect.FieldUtils; import com.morgoo.droidplugin.reflect.Utils; import com.morgoo.helper.MyProxy; import com.morgoo.helper.compat.ServiceManagerCompat; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Arrays; import java.util.List; import java.util.Map; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/3/2. */ public class ServiceManagerCacheBinderHook extends Hook implements InvocationHandler { private String mServiceName; public ServiceManagerCacheBinderHook(Context hostContext, String servicename) { super(hostContext); mServiceName = servicename; setEnable(true); } @Override protected void onInstall(ClassLoader classLoader) throws Throwable { Object sCacheObj = FieldUtils.readStaticField(ServiceManagerCompat.Class(), "sCache"); if (sCacheObj instanceof Map) { Map sCache = (Map) sCacheObj; Object Obj = sCache.get(mServiceName); if (Obj != null && false) { //FIXME 已经有了怎么处理?这里我们只是把原来的给remove掉,再添加自己的。程序下次取用的时候就变成我们hook过的了。 //但是这样有缺陷。 throw new RuntimeException("Can not install binder hook for " + mServiceName); } else { sCache.remove(mServiceName); IBinder mServiceIBinder = ServiceManagerCompat.getService(mServiceName); if (mServiceIBinder == null) { if (Obj != null && Obj instanceof IBinder && !Proxy.isProxyClass(Obj.getClass())) { mServiceIBinder = ((IBinder) Obj); } } if (mServiceIBinder != null) { MyServiceManager.addOriginService(mServiceName, mServiceIBinder); Class clazz = mServiceIBinder.getClass(); List> interfaces = Utils.getAllInterfaces(clazz); Class[] ifs = interfaces != null && interfaces.size() > 0 ? interfaces.toArray(new Class[interfaces.size()]) : new Class[0]; IBinder mProxyServiceIBinder = (IBinder) MyProxy.newProxyInstance(clazz.getClassLoader(), ifs, this); sCache.put(mServiceName, mProxyServiceIBinder); MyServiceManager.addProxiedServiceCache(mServiceName, mProxyServiceIBinder); } } } } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { IBinder originService = MyServiceManager.getOriginService(mServiceName); if (!isEnable()) { return method.invoke(originService, args); } HookedMethodHandler hookedMethodHandler = mHookHandles.getHookedMethodHandler(method); if (hookedMethodHandler != null) { return hookedMethodHandler.doHookInner(originService, method, args); } else { return method.invoke(originService, args); } } catch (InvocationTargetException e) { Throwable cause = e.getTargetException(); if (cause != null && MyProxy.isMethodDeclaredThrowable(method, cause)) { throw cause; } else if (cause != null) { RuntimeException runtimeException = !TextUtils.isEmpty(cause.getMessage()) ? new RuntimeException(cause.getMessage()) : new RuntimeException(); runtimeException.initCause(cause); throw runtimeException; } else { RuntimeException runtimeException = !TextUtils.isEmpty(e.getMessage()) ? new RuntimeException(e.getMessage()) : new RuntimeException(); runtimeException.initCause(e); throw runtimeException; } } catch (IllegalArgumentException e) { try { StringBuilder sb = new StringBuilder(); sb.append(" DROIDPLUGIN{"); if (method != null) { sb.append("method[").append(method.toString()).append("]"); } else { sb.append("method[").append("NULL").append("]"); } if (args != null) { sb.append("args[").append(Arrays.toString(args)).append("]"); } else { sb.append("args[").append("NULL").append("]"); } sb.append("}"); String message = e.getMessage() + sb.toString(); throw new IllegalArgumentException(message, e); } catch (Throwable e1) { throw e; } } catch (Throwable e) { if (MyProxy.isMethodDeclaredThrowable(method, e)) { throw e; } else { RuntimeException runtimeException = !TextUtils.isEmpty(e.getMessage()) ? new RuntimeException(e.getMessage()) : new RuntimeException(); runtimeException.initCause(e); throw runtimeException; } } } private class ServiceManagerHookHandle extends BaseHookHandle { private ServiceManagerHookHandle(Context context) { super(context); } @Override protected void init() { sHookedMethodHandlers.put("queryLocalInterface", new queryLocalInterface(mHostContext)); } class queryLocalInterface extends HookedMethodHandler { public queryLocalInterface(Context context) { super(context); } @Override protected void afterInvoke(Object receiver, Method method, Object[] args, Object invokeResult) throws Throwable { Object localInterface = invokeResult; Object proxiedObj = MyServiceManager.getProxiedObj(mServiceName); if (localInterface == null && proxiedObj != null) { setFakedResult(proxiedObj); } } } } @Override protected BaseHookHandle createHookHandle() { return new ServiceManagerHookHandle(mHostContext); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/handle/IActivityManagerHookHandle.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.handle; import android.annotation.TargetApi; import android.app.Activity; import android.app.ActivityManager; import android.app.IServiceConnection; import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.ProviderInfo; import android.content.pm.ServiceInfo; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.PixelFormat; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; import android.text.TextUtils; import com.morgoo.droidplugin.PluginManagerService; import com.morgoo.droidplugin.PluginPatchManager; import com.morgoo.droidplugin.am.RunningActivities; import com.morgoo.droidplugin.core.Env; import com.morgoo.droidplugin.core.PluginProcessManager; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.proxy.IContentProviderHook; import com.morgoo.droidplugin.pm.PluginManager; import com.morgoo.droidplugin.reflect.FieldUtils; import com.morgoo.droidplugin.reflect.MethodUtils; import com.morgoo.droidplugin.reflect.Utils; import com.morgoo.droidplugin.stub.MyFakeIBinder; import com.morgoo.droidplugin.stub.ServcesManager; import com.morgoo.droidplugin.stub.ShortcutProxyActivity; import com.morgoo.helper.Log; import com.morgoo.helper.MyProxy; import com.morgoo.helper.compat.ActivityManagerCompat; import com.morgoo.helper.compat.ContentProviderHolderCompat; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.Random; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/2/28. */ public class IActivityManagerHookHandle extends BaseHookHandle { private static final String TAG = IActivityManagerHookHandle.class.getSimpleName(); public IActivityManagerHookHandle(Context hostContext) { super(hostContext); } @Override protected void init() { sHookedMethodHandlers.put("startActivity", new startActivity(mHostContext)); sHookedMethodHandlers.put("startActivityAsUser", new startActivityAsUser(mHostContext)); sHookedMethodHandlers.put("startActivityAsCaller", new startActivityAsCaller(mHostContext)); sHookedMethodHandlers.put("startActivityAndWait", new startActivityAndWait(mHostContext)); sHookedMethodHandlers.put("startActivityWithConfig", new startActivityWithConfig(mHostContext)); sHookedMethodHandlers.put("startActivityIntentSender", new startActivityIntentSender(mHostContext)); sHookedMethodHandlers.put("startVoiceActivity", new startVoiceActivity(mHostContext)); sHookedMethodHandlers.put("startNextMatchingActivity", new startNextMatchingActivity(mHostContext)); sHookedMethodHandlers.put("startActivityFromRecents", new startActivityFromRecents(mHostContext)); sHookedMethodHandlers.put("finishActivity", new finishActivity(mHostContext)); sHookedMethodHandlers.put("registerReceiver", new registerReceiver(mHostContext)); sHookedMethodHandlers.put("broadcastIntent", new broadcastIntent(mHostContext)); sHookedMethodHandlers.put("unbroadcastIntent", new unbroadcastIntent(mHostContext)); sHookedMethodHandlers.put("getCallingPackage", new getCallingPackage(mHostContext)); sHookedMethodHandlers.put("getCallingActivity", new getCallingActivity(mHostContext)); sHookedMethodHandlers.put("getAppTasks", new getAppTasks(mHostContext)); sHookedMethodHandlers.put("addAppTask", new addAppTask(mHostContext)); sHookedMethodHandlers.put("getTasks", new getTasks(mHostContext)); sHookedMethodHandlers.put("getServices", new getServices(mHostContext)); sHookedMethodHandlers.put("getProcessesInErrorState", new getProcessesInErrorState(mHostContext)); sHookedMethodHandlers.put("getContentProvider", new getContentProvider(mHostContext)); sHookedMethodHandlers.put("getContentProviderExternal", new getContentProviderExternal(mHostContext)); sHookedMethodHandlers.put("removeContentProviderExternal", new removeContentProviderExternal(mHostContext)); sHookedMethodHandlers.put("publishContentProviders", new publishContentProviders(mHostContext)); sHookedMethodHandlers.put("getRunningServiceControlPanel", new getRunningServiceControlPanel(mHostContext)); sHookedMethodHandlers.put("startService", new startService(mHostContext)); sHookedMethodHandlers.put("stopService", new stopService(mHostContext)); sHookedMethodHandlers.put("stopServiceToken", new stopServiceToken(mHostContext)); sHookedMethodHandlers.put("setServiceForeground", new setServiceForeground(mHostContext)); sHookedMethodHandlers.put("bindService", new bindService(mHostContext)); sHookedMethodHandlers.put("publishService", new publishService(mHostContext)); sHookedMethodHandlers.put("unbindFinished", new unbindFinished(mHostContext)); sHookedMethodHandlers.put("peekService", new peekService(mHostContext)); sHookedMethodHandlers.put("bindBackupAgent", new bindBackupAgent(mHostContext)); sHookedMethodHandlers.put("backupAgentCreated", new backupAgentCreated(mHostContext)); sHookedMethodHandlers.put("unbindBackupAgent", new unbindBackupAgent(mHostContext)); sHookedMethodHandlers.put("killApplicationProcess", new killApplicationProcess(mHostContext)); sHookedMethodHandlers.put("startInstrumentation", new startInstrumentation(mHostContext)); sHookedMethodHandlers.put("getActivityClassForToken", new getActivityClassForToken(mHostContext)); sHookedMethodHandlers.put("getPackageForToken", new getPackageForToken(mHostContext)); sHookedMethodHandlers.put("getIntentSender", new getIntentSender(mHostContext)); sHookedMethodHandlers.put("clearApplicationUserData", new clearApplicationUserData(mHostContext)); sHookedMethodHandlers.put("handleIncomingUser", new handleIncomingUser(mHostContext)); sHookedMethodHandlers.put("grantUriPermission", new grantUriPermission(mHostContext)); sHookedMethodHandlers.put("getPersistedUriPermissions", new getPersistedUriPermissions(mHostContext)); sHookedMethodHandlers.put("killBackgroundProcesses", new killBackgroundProcesses(mHostContext)); sHookedMethodHandlers.put("forceStopPackage", new forceStopPackage(mHostContext)); sHookedMethodHandlers.put("getRunningAppProcesses", new getRunningAppProcesses(mHostContext)); sHookedMethodHandlers.put("getRunningExternalApplications", new getRunningExternalApplications(mHostContext)); sHookedMethodHandlers.put("getMyMemoryState", new getMyMemoryState(mHostContext)); sHookedMethodHandlers.put("crashApplication", new crashApplication(mHostContext)); sHookedMethodHandlers.put("grantUriPermissionFromOwner", new grantUriPermissionFromOwner(mHostContext)); sHookedMethodHandlers.put("checkGrantUriPermission", new checkGrantUriPermission(mHostContext)); sHookedMethodHandlers.put("startActivities", new startActivities(mHostContext)); sHookedMethodHandlers.put("getPackageScreenCompatMode", new getPackageScreenCompatMode(mHostContext)); sHookedMethodHandlers.put("setPackageScreenCompatMode", new setPackageScreenCompatMode(mHostContext)); sHookedMethodHandlers.put("getPackageAskScreenCompat", new getPackageAskScreenCompat(mHostContext)); sHookedMethodHandlers.put("setPackageAskScreenCompat", new setPackageAskScreenCompat(mHostContext)); sHookedMethodHandlers.put("navigateUpTo", new navigateUpTo(mHostContext)); sHookedMethodHandlers.put("serviceDoneExecuting", new serviceDoneExecuting(mHostContext)); } private static class startActivity extends ReplaceCallingPackageHookedMethodHandler { public startActivity(Context hostContext) { super(hostContext); } protected boolean doReplaceIntentForStartActivityAPIHigh(Object[] args) throws RemoteException { int intentOfArgIndex = findFirstIntentIndexInArgs(args); if (args != null && args.length > 1 && intentOfArgIndex >= 0) { Intent intent = (Intent) args[intentOfArgIndex]; //XXX String callingPackage = (String) args[1]; if (!PluginPatchManager.getInstance().canStartPluginActivity(intent)) { PluginPatchManager.getInstance().startPluginActivity(intent); return false; } ActivityInfo activityInfo = resolveActivity(intent); if (activityInfo != null && isPackagePlugin(activityInfo.packageName)) { ComponentName component = selectProxyActivity(intent); if (component != null) { Intent newIntent = new Intent(); try { ClassLoader pluginClassLoader = PluginProcessManager.getPluginClassLoader(component.getPackageName()); setIntentClassLoader(newIntent, pluginClassLoader); } catch (Exception e) { Log.w(TAG, "Set Class Loader to new Intent fail", e); } newIntent.setComponent(component); newIntent.putExtra(Env.EXTRA_TARGET_INTENT, intent); newIntent.setFlags(intent.getFlags()); String callingPackage = (String) args[1]; if (TextUtils.equals(mHostContext.getPackageName(), callingPackage)) { newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP && activityInfo.launchMode == ActivityInfo.LAUNCH_MULTIPLE) { // newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT); // } } // if (activityInfo.launchMode == ActivityInfo.LAUNCH_MULTIPLE) { // // } else if (activityInfo.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { // // } else if (activityInfo.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) { // newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // } else if (activityInfo.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP) { // newIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); // } args[intentOfArgIndex] = newIntent; args[1] = mHostContext.getPackageName(); } else { Log.w(TAG, "startActivity,replace selectProxyActivity fail"); } } } return true; } private void setIntentClassLoader(Intent intent, ClassLoader classLoader) { try { Bundle mExtras = (Bundle) FieldUtils.readField(intent, "mExtras"); if (mExtras != null) { mExtras.setClassLoader(classLoader); } else { Bundle value = new Bundle(); value.setClassLoader(classLoader); FieldUtils.writeField(intent, "mExtras", value); } } catch (Exception e) { } finally { intent.setExtrasClassLoader(classLoader); } } protected boolean doReplaceIntentForStartActivityAPILow(Object[] args) throws RemoteException { int intentOfArgIndex = findFirstIntentIndexInArgs(args); if (args != null && args.length > 1 && intentOfArgIndex >= 0) { Intent intent = (Intent) args[intentOfArgIndex]; ActivityInfo activityInfo = resolveActivity(intent); if (activityInfo != null && isPackagePlugin(activityInfo.packageName)) { ComponentName component = selectProxyActivity(intent); if (component != null) { Intent newIntent = new Intent(); newIntent.setComponent(component); newIntent.putExtra(Env.EXTRA_TARGET_INTENT, intent); newIntent.setFlags(intent.getFlags()); if (TextUtils.equals(mHostContext.getPackageName(), activityInfo.packageName)) { newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } args[intentOfArgIndex] = newIntent; } else { Log.w(TAG, "startActivity,replace selectProxyActivity fail"); } } } return true; } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { RunningActivities.beforeStartActivity(); boolean bRet = true; if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) { //2.3 /*public int startActivity(IApplicationThread caller, Intent intent, String resolvedType, Uri[] grantedUriPermissions, int grantedMode, IBinder resultTo, String resultWho, int requestCode, boolean onlyIfNeeded, boolean debug) throws RemoteException;*/ //api 15 /*public int startActivity(IApplicationThread caller, Intent intent, String resolvedType, Uri[] grantedUriPermissions, int grantedMode, IBinder resultTo, String resultWho, int requestCode, boolean onlyIfNeeded, boolean debug, String profileFile, ParcelFileDescriptor profileFd, boolean autoStopProfiler) throws RemoteException;*/ //api 16,17 /* public int startActivity(IApplicationThread caller, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int flags, String profileFile, ParcelFileDescriptor profileFd, Bundle options) throws RemoteException;*/ bRet = doReplaceIntentForStartActivityAPILow(args); } else { //api 18,19 /* public int startActivity(IApplicationThread caller, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int flags, String profileFile, ParcelFileDescriptor profileFd, Bundle options) throws RemoteException;*/ //api 21 /* public int startActivity(IApplicationThread caller, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int flags, ProfilerInfo profilerInfo, Bundle options) throws RemoteException;*/ bRet = doReplaceIntentForStartActivityAPIHigh(args); } if (!bRet) { setFakedResult(Activity.RESULT_CANCELED); return true; } return super.beforeInvoke(receiver, method, args); } } private static class startActivityAsUser extends startActivity { public startActivityAsUser(Context hostContext) { super(hostContext); } //API 17 /* public int startActivityAsUser(IApplicationThread caller, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int flags, String profileFile, ParcelFileDescriptor profileFd, Bundle options, int userId) throws RemoteException;*/ //API 18,19 /* public int startActivityAsUser(IApplicationThread caller, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int flags, String profileFile, ParcelFileDescriptor profileFd, Bundle options, int userId) throws RemoteException;*/ //API 21 /* public int startActivityAsUser(IApplicationThread caller, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int flags, ProfilerInfo profilerInfo, Bundle options, int userId) throws RemoteException;*/ } private static class startActivityAsCaller extends startActivity { public startActivityAsCaller(Context hostContext) { super(hostContext); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 21 /* public int startActivityAsCaller(IApplicationThread caller, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int flags, ProfilerInfo profilerInfo, Bundle options, int userId) throws RemoteException;*/ return super.beforeInvoke(receiver, method, args); } } private static class startActivityAndWait extends startActivity { public startActivityAndWait(Context hostContext) { super(hostContext); } //API 2.3 /*public WaitResult startActivityAndWait(IApplicationThread caller, Intent intent, String resolvedType, Uri[] grantedUriPermissions, int grantedMode, IBinder resultTo, String resultWho, int requestCode, boolean onlyIfNeeded, boolean debug) throws RemoteException;*/ //API 15 /* public WaitResult startActivityAndWait(IApplicationThread caller, Intent intent, String resolvedType, Uri[] grantedUriPermissions, int grantedMode, IBinder resultTo, String resultWho, int requestCode, boolean onlyIfNeeded, boolean debug, String profileFile, ParcelFileDescriptor profileFd, boolean autoStopProfiler) throws RemoteException;*/ //API 16 /* public WaitResult startActivityAndWait(IApplicationThread caller, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int flags, String profileFile, ParcelFileDescriptor profileFd, Bundle options) throws RemoteException;*/ //API 17 /* public WaitResult startActivityAndWait(IApplicationThread caller, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int flags, String profileFile, ParcelFileDescriptor profileFd, Bundle options, int userId) throws RemoteException;*/ //API 18,19 /* public WaitResult startActivityAndWait(IApplicationThread caller, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int flags, String profileFile, ParcelFileDescriptor profileFd, Bundle options, int userId) throws RemoteException;*/ //API 21 /* public WaitResult startActivityAndWait(IApplicationThread caller, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int flags, ProfilerInfo profilerInfo, Bundle options, int userId) throws RemoteException;*/ } private static class startActivityWithConfig extends startActivity { public startActivityWithConfig(Context hostContext) { super(hostContext); } //API 2.3,15 /* public int startActivityWithConfig(IApplicationThread caller, Intent intent, String resolvedType, Uri[] grantedUriPermissions, int grantedMode, IBinder resultTo, String resultWho, int requestCode, boolean onlyIfNeeded, boolean debug, Configuration newConfig) throws RemoteException;*/ //API 16 /* public int startActivityWithConfig(IApplicationThread caller, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, Configuration newConfig, Bundle options) throws RemoteException;*/ //API 17 /* public int startActivityWithConfig(IApplicationThread caller, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, Configuration newConfig, Bundle options, int userId) throws RemoteException;*/ //API 18,19,21 /* public int startActivityWithConfig(IApplicationThread caller, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, Configuration newConfig, Bundle options, int userId) throws RemoteException;*/ } private static class startActivityIntentSender extends ReplaceCallingPackageHookedMethodHandler { public startActivityIntentSender(Context hostContext) { super(hostContext); } //API 2.3,15 /* public int startActivityIntentSender(IApplicationThread caller, IntentSender intent, Intent fillInIntent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int flagsMask, int flagsValues) throws RemoteException;*/ //API 16,17,18,19,21 /* public int startActivityIntentSender(IApplicationThread caller, IntentSender intent, Intent fillInIntent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int flagsMask, int flagsValues, Bundle options) throws RemoteException;*/ //DO NOTHING } private static class startVoiceActivity extends startActivity { public startVoiceActivity(Context hostContext) { super(hostContext); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 21 /* public int startVoiceActivity(String callingPackage, int callingPid, int callingUid, Intent intent, String resolvedType, IVoiceInteractionSession session, IVoiceInteractor interactor, int flags, ProfilerInfo profilerInfo, Bundle options, int userId) throws RemoteException;*/ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { final int index = 0; if (args != null && args.length > index) { if (args[index] != null && args[index] instanceof String) { String targetPkg = (String) args[index]; if (isPackagePlugin(targetPkg)) { args[index] = mHostContext.getPackageName(); } } } doReplaceIntentForStartActivityAPIHigh(args); } return false; } } private static class startNextMatchingActivity extends startActivity { public startNextMatchingActivity(Context hostContext) { super(hostContext); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 2.3,15 /* public boolean startNextMatchingActivity(IBinder callingActivity, Intent intent) throws RemoteException;*/ //API 16,17,17,19,21 /* public boolean startNextMatchingActivity(IBinder callingActivity, Intent intent, Bundle options) throws RemoteException;*/ doReplaceIntentForStartActivityAPILow(args); return false; } } private static class startActivityFromRecents extends ReplaceCallingPackageHookedMethodHandler { public startActivityFromRecents(Context hostContext) { super(hostContext); } //API 21 /*public int startActivityFromRecents(int taskId, Bundle options) throws RemoteException;*/ //DO NOTHING } private static class finishActivity extends ReplaceCallingPackageHookedMethodHandler { public finishActivity(Context hostContext) { super(hostContext); } //API 2.3,15,16,17,18,19 /* public boolean finishActivity(IBinder token, int code, Intent data) throws RemoteException;*/ //API 21 /*public boolean finishActivity(IBinder token, int code, Intent data, boolean finishTask) throws RemoteException;*/ //FIXME 先不修改。 } private static class registerReceiver extends ReplaceCallingPackageHookedMethodHandler { public registerReceiver(Context hostContext) { super(hostContext); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 2.3 /* public Intent registerReceiver(IApplicationThread caller, IIntentReceiver receiver, IntentFilter filter, String requiredPermission) throws RemoteException;*/ //API 15,16 /* public Intent registerReceiver(IApplicationThread caller, String callerPackage, IIntentReceiver receiver, IntentFilter filter, String requiredPermission) throws RemoteException;*/ //API 17,18,19,21 /* public Intent registerReceiver(IApplicationThread caller, String callerPackage, IIntentReceiver receiver, IntentFilter filter, String requiredPermission, int userId) throws RemoteException;*/ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { if (args != null && args.length > 0) { for (int index = 0; index < args.length; index++) { if (args[index] instanceof String) { String callerPackage = (String) args[index]; if (isPackagePlugin(callerPackage)) { args[index] = mHostContext.getPackageName(); } } } } } return super.beforeInvoke(receiver, method, args); } } private static class broadcastIntent extends ReplaceCallingPackageHookedMethodHandler { public broadcastIntent(Context hostContext) { super(hostContext); } //api 2.3, 15 /* public int broadcastIntent(IApplicationThread caller, Intent intent, String resolvedType, IIntentReceiver resultTo, int resultCode, String resultData, Bundle map, String requiredPermission, boolean serialized, boolean sticky) throws RemoteExceptions //API 16,17 /* public int broadcastIntent(IApplicationThread caller, Intent intent, String resolvedType, IIntentReceiver resultTo, int resultCode, String resultData, Bundle map, String requiredPermission, boolean serialized, boolean sticky, int userId) throws RemoteException;*/ //API 18,19,21 /* public int broadcastIntent(IApplicationThread caller, Intent intent, String resolvedType, IIntentReceiver resultTo, int resultCode, String resultData, Bundle map, String requiredPermission, int appOp, boolean serialized, boolean sticky, int userId) throws RemoteException;*/ //TODO 广播相关的,研究完了再修改。 @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { final int index = 1; if (args != null && args.length > index && args[index] instanceof Intent) { Intent intent = (Intent) args[index]; checkAndProcessIntent(intent); } return super.beforeInvoke(receiver, method, args); } private boolean checkAndProcessIntent(Intent intent) throws RemoteException { if (Env.ACTION_INSTALL_SHORTCUT.equals(intent.getAction())) { //安装快捷方式的.我们都需要处理 Intent shortcutIntent = intent.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT); if (shortcutIntent != null) { ComponentName componentName = shortcutIntent.resolveActivity(mHostContext.getPackageManager()); if (componentName != null && PluginManager.getInstance().isPluginPackage(componentName.getPackageName())) { //如果是插件,就把快捷方式Intent换成插件自己的,然后我们再跳转 Intent newShortcutIntent = new Intent(PluginManager.ACTION_SHORTCUT_PROXY); newShortcutIntent.addCategory(Intent.CATEGORY_DEFAULT); newShortcutIntent.putExtra(Env.EXTRA_TARGET_INTENT, shortcutIntent); newShortcutIntent.putExtra(Env.EXTRA_TARGET_INTENT_URI, shortcutIntent.toUri(0)); intent.removeExtra(Intent.EXTRA_SHORTCUT_INTENT); intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, newShortcutIntent); //替换图标 Intent.ShortcutIconResource icon = intent.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE); if (icon != null && !TextUtils.equals(icon.packageName, mHostContext.getPackageName())) { try { Context context = PluginProcessManager.getPluginContext(icon.packageName); int resId = context.getResources().getIdentifier(icon.resourceName, "drawable", context.getPackageName()); if (resId > 0) { Drawable iconDrawable = context.getResources().getDrawable(resId); Bitmap newIcon = drawableToBitMap(iconDrawable); if (newIcon != null) { intent.removeExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE); intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, newIcon); return true; } else { throw new Resources.NotFoundException(String.format("Can not found the icon resource in plugin package:%s", icon)); } } else { throw new Resources.NotFoundException(String.format("Can not found the icon resource in plugin package:%s", icon)); } } catch (Resources.NotFoundException e) { throw e; } catch (Throwable e) { Resources.NotFoundException exception = new Resources.NotFoundException(String.format("Can not found the icon resource in plugin package:%s", icon)); exception.initCause(e); throw exception; } } } } return false; } else if (Env.ACTION_UNINSTALL_SHORTCUT.equals(intent.getAction())) { //卸载快捷方式的。我们都需要处理 Intent shortcutIntent = intent.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT); if (shortcutIntent != null) { ComponentName componentName = shortcutIntent.resolveActivity(mHostContext.getPackageManager()); if (componentName != null && PluginManager.getInstance().isPluginPackage(componentName.getPackageName())) { //如果是插件,就把快捷方式Intent换成插件自己的,然后我们再 Intent newShortcutIntent = new Intent(mHostContext, ShortcutProxyActivity.class); newShortcutIntent.putExtra(Env.EXTRA_TARGET_INTENT, shortcutIntent); intent.removeExtra(Intent.EXTRA_SHORTCUT_INTENT); intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, newShortcutIntent); } return true; } } return false; } private Bitmap drawableToBitMap(Drawable drawable) { if (drawable instanceof BitmapDrawable) { BitmapDrawable bitmapDrawable = ((BitmapDrawable) drawable); return bitmapDrawable.getBitmap(); } else { Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565); Canvas canvas = new Canvas(bitmap); drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); drawable.draw(canvas); return bitmap; } } } private static class unbroadcastIntent extends ReplaceCallingPackageHookedMethodHandler { public unbroadcastIntent(Context hostContext) { super(hostContext); } //api 2.3,15 /* public void unbroadcastIntent(IApplicationThread caller, Intent intent) throws RemoteException;*/ //API 16,17,18,19,21 /*public void unbroadcastIntent(IApplicationThread caller, Intent intent, int userId) throws RemoteException;*/ //TODO 广播相关的,研究完了再修改。 } private static class getCallingPackage extends ReplaceCallingPackageHookedMethodHandler { public getCallingPackage(Context hostContext) { super(hostContext); } //API 2.3,15,16,17,18,19,21 /* public String getCallingPackage(IBinder token) throws RemoteException;*/ //FIXME I don't know what function of this,just hook it. } private static class getCallingActivity extends ReplaceCallingPackageHookedMethodHandler { public getCallingActivity(Context hostContext) { super(hostContext); } //API 2.3,15,16,17,18,19, 21 /* public ComponentName getCallingActivity(IBinder token) throws RemoteException;*/ //FIXME I don't know what function of this,just hook it. //也不知道这个是干嘛的。是返回此Activity是由谁调起的么? } private static class getAppTasks extends ReplaceCallingPackageHookedMethodHandler { public getAppTasks(Context hostContext) { super(hostContext); } // API 21 /* public List getAppTasks(String callingPackage) throws RemoteException;*/ //FIXME I don't know what function of this,just hook it. } private static class addAppTask extends ReplaceCallingPackageHookedMethodHandler { public addAppTask(Context hostContext) { super(hostContext); } //API 21 /* public int addAppTask(IBinder activityToken, Intent intent, ActivityManager.TaskDescription description, Bitmap thumbnail) throws RemoteException;*/ //FIXME api21的不知道干嘛的,先不修改吧。 } private static class getTasks extends ReplaceCallingPackageHookedMethodHandler { public getTasks(Context hostContext) { super(hostContext); } //API 2.3,15,16,17,18 /* public List getTasks(int maxNum, int flags, IThumbnailReceiver receiver) throws RemoteException;*/ //API 19 /*public List getTasks(int maxNum, int flags, IThumbnailReceiver receiver) throws RemoteException;*/ //API 21 /* public List getTasks(int maxNum, int flags) throws RemoteException;*/ //FIXME 这里需要把原来函数返回的 List中关于代理activity修改成插件自己的。 // @Override // protected void afterInvoke(Object receiver, Method method, Object[] args, Object invokeResult) throws Throwable { // if (invokeResult instanceof List) { // List runningTaskInfo = (List) invokeResult; // if (runningTaskInfo.size() > 0) { // for (Object obj : runningTaskInfo) { // RunningTaskInfo info = (RunningTaskInfo) obj; // info.baseActivity =; // info.topActivity =; // } // } // } // super.afterInvoke(receiver, method, args, invokeResult); // } } private static class getServices extends ReplaceCallingPackageHookedMethodHandler { public getServices(Context hostContext) { super(hostContext); } @Override protected void afterInvoke(Object receiver, Method method, Object[] args, Object invokeResult) throws Throwable { //api 2.3,15,16,17,18 /*public List getServices(int maxNum, int flags) throws RemoteException;*/ //API 19,21 /*public List getServices(int maxNum, int flags) throws RemoteException;*/ if (invokeResult != null && invokeResult instanceof List) { List objectList = (List) invokeResult; for (Object obj : objectList) { if (obj instanceof ActivityManager.RunningServiceInfo) { ActivityManager.RunningServiceInfo serviceInfo = (ActivityManager.RunningServiceInfo) obj; tryfixServiceInfo(serviceInfo); } } } } } private static class getProcessesInErrorState extends ReplaceCallingPackageHookedMethodHandler { public getProcessesInErrorState(Context hostContext) { super(hostContext); } //API 2.3,15,16,17,18,19, 21 /* public List getProcessesInErrorState() throws RemoteException;*/ //FIXME I don't know what function of this,just hook it. } private static class getContentProvider extends ReplaceCallingPackageHookedMethodHandler { public getContentProvider(Context hostContext) { super(hostContext); } private ProviderInfo mStubProvider = null; private ProviderInfo mTargetProvider = null; @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { if (args != null) { final int index = 1; if (args.length > index && args[index] instanceof String) { String name = (String) args[index]; mStubProvider = null; mTargetProvider = null; ProviderInfo info = mHostContext.getPackageManager().resolveContentProvider(name, 0); mTargetProvider = PluginManager.getInstance().resolveContentProvider(name, 0); //这里有个很坑爹的事情,就是当插件的contentprovider和host的名称一样,冲突的时候处理方式。 //在Android系统上,是不会出现这种事情的,因为系统在安装的时候做了处理。而我们目前没做处理。so,在出现冲突时候的时候优先用host的。 if (mTargetProvider != null && info != null && TextUtils.equals(mTargetProvider.packageName, info.packageName)) { mStubProvider = PluginManager.getInstance().selectStubProviderInfo(name); // PluginManager.getInstance().reportMyProcessName(mStubProvider.processName, mTargetProvider.processName); // PluginProcessManager.preLoadApk(mHostContext, mTargetProvider); if (mStubProvider != null) { args[index] = mStubProvider.authority; } else { Log.w(TAG, "getContentProvider,fake fail 1"); } } else { mTargetProvider = null; Log.w(TAG, "getContentProvider,fake fail 2=%s", name); } } } return super.beforeInvoke(receiver, method, args); } @Override protected void afterInvoke(Object receiver, Method method, Object[] args, Object invokeResult) throws Throwable { if (invokeResult != null) { ProviderInfo stubProvider2 = (ProviderInfo) FieldUtils.readField(invokeResult, "info"); if (mStubProvider != null && mTargetProvider != null && TextUtils.equals(stubProvider2.authority, mStubProvider.authority)) { //FIXME 其实这里写的并不好,需要适配各种机型。这里就先这样吧。 Object fromObj = invokeResult; Object toObj = ContentProviderHolderCompat.newInstance(mTargetProvider); //toObj.provider = fromObj.provider; copyField(fromObj, toObj, "provider"); if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) { copyConnection(fromObj, toObj); } //toObj.noReleaseNeeded = fromObj.noReleaseNeeded; copyField(fromObj, toObj, "noReleaseNeeded"); Object provider = FieldUtils.readField(invokeResult, "provider"); if (provider != null) { boolean localProvider = FieldUtils.readField(toObj, "provider") == null; IContentProviderHook invocationHandler = new IContentProviderHook(mHostContext, provider, mStubProvider, mTargetProvider, localProvider); invocationHandler.setEnable(true); Class clazz = provider.getClass(); List> interfaces = Utils.getAllInterfaces(clazz); Class[] ifs = interfaces != null && interfaces.size() > 0 ? interfaces.toArray(new Class[interfaces.size()]) : new Class[0]; Object proxyprovider = MyProxy.newProxyInstance(clazz.getClassLoader(), ifs, invocationHandler); FieldUtils.writeField(invokeResult, "provider", proxyprovider); FieldUtils.writeField(toObj, "provider", proxyprovider); } setFakedResult(toObj); } else if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR2) { Object provider = FieldUtils.readField(invokeResult, "provider"); if (provider != null) { boolean localProvider = FieldUtils.readField(invokeResult, "provider") == null; IContentProviderHook invocationHandler = new IContentProviderHook(mHostContext, provider, mStubProvider, mTargetProvider, localProvider); invocationHandler.setEnable(true); Class clazz = provider.getClass(); List> interfaces = Utils.getAllInterfaces(clazz); Class[] ifs = interfaces != null && interfaces.size() > 0 ? interfaces.toArray(new Class[interfaces.size()]) : new Class[0]; Object proxyprovider = MyProxy.newProxyInstance(clazz.getClassLoader(), ifs, invocationHandler); FieldUtils.writeField(invokeResult, "provider", proxyprovider); } } mStubProvider = null; mTargetProvider = null; } } private void copyField(Object fromObj, Object toObj, String fieldName) throws IllegalAccessException { FieldUtils.writeField(toObj, fieldName, FieldUtils.readField(fromObj, fieldName)); } @TargetApi(VERSION_CODES.JELLY_BEAN) private void copyConnection(Object fromObj, Object toObj) throws IllegalAccessException { copyField(fromObj, toObj, "connection"); } } private static class getContentProviderExternal extends getContentProvider { public getContentProviderExternal(Context hostContext) { super(hostContext); } //API 16 /* public ContentProviderHolder getContentProviderExternal(String name, IBinder token) throws RemoteException;*/ //API 17,18,19,21 /* public ContentProviderHolder getContentProviderExternal(String name, int userId, IBinder token) throws RemoteException;*/ } private static class removeContentProviderExternal extends ReplaceCallingPackageHookedMethodHandler { public removeContentProviderExternal(Context hostContext) { super(hostContext); } //API 16,17,18,19, 21 /*public void removeContentProviderExternal(String name, IBinder token) throws RemoteException;*/ //TODO removeContentProviderExternal } private static class publishContentProviders extends ReplaceCallingPackageHookedMethodHandler { public publishContentProviders(Context hostContext) { super(hostContext); } //API 2.3,15,16,17,18,19, 21 /* public void publishContentProviders(IApplicationThread caller, List providers) throws RemoteException;*/ //TODO 发布ContentProvider } private static class getRunningServiceControlPanel extends ReplaceCallingPackageHookedMethodHandler { public getRunningServiceControlPanel(Context hostContext) { super(hostContext); } //API 2.3,15,16,17,18,19, 21 /* public PendingIntent getRunningServiceControlPanel(ComponentName service) throws RemoteException;*/ //FIXME 这里需要把service替换成代理服务吧?maybe. //通过服务名称拿PendingIntent?搞不懂,不改。 } private static class startService extends ReplaceCallingPackageHookedMethodHandler { public startService(Context hostContext) { super(hostContext); } private ServiceInfo info = null; @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 2.3, 15, 16 /* public ComponentName startService(IApplicationThread caller, Intent service, String resolvedType) throws RemoteException;*/ //API 17, 18, 19, 21 /*public ComponentName startService(IApplicationThread caller, Intent service, String resolvedType, int userId) throws RemoteException;*/ info = replaceFirstServiceIntentOfArgs(args); return super.beforeInvoke(receiver, method, args); } @Override protected void afterInvoke(Object receiver, Method method, Object[] args, Object invokeResult) throws Throwable { if (invokeResult instanceof ComponentName) { if (info != null) { setFakedResult(new ComponentName(info.packageName, info.name)); } } info = null; super.afterInvoke(receiver, method, args, invokeResult); } } private static class stopService extends ReplaceCallingPackageHookedMethodHandler { public stopService(Context hostContext) { super(hostContext); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 2.3, 15, 16 /* public int stopService(IApplicationThread caller, Intent service, String resolvedType) throws RemoteException;*/ //API 17, 18, 19, 21 /* public int stopService(IApplicationThread caller, Intent service, String resolvedType, int userId) throws RemoteException;*/ int index = 1; if (args != null && args.length > index && args[index] instanceof Intent) { Intent intent = (Intent) args[index]; ServiceInfo info = resolveService(intent); if (info != null && isPackagePlugin(info.packageName)) { int re = ServcesManager.getDefault().stopService(mHostContext, intent); setFakedResult(re); return true; } } return super.beforeInvoke(receiver, method, args); } } private static class stopServiceToken extends ReplaceCallingPackageHookedMethodHandler { public stopServiceToken(Context hostContext) { super(hostContext); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 2.3, 15, 16, 17, 18, 19, 21 /*public boolean stopServiceToken(ComponentName className, IBinder token, int startId) throws RemoteException;*/ if (args != null && args.length > 2) { ComponentName componentName = (ComponentName) args[0]; if (isComponentNamePlugin(componentName)) { IBinder token = (IBinder) args[1]; Integer startId = (Integer) args[2]; boolean re = ServcesManager.getDefault().stopServiceToken(componentName, token, startId); setFakedResult(re); return true; } } return super.beforeInvoke(receiver, method, args); } } private static class setServiceForeground extends ReplaceCallingPackageHookedMethodHandler { public setServiceForeground(Context hostContext) { super(hostContext); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 2.3, 15, 16, 17, 18, 19, 21 /* public void setServiceForeground(ComponentName className, IBinder token, int id, Notification notification, boolean keepNotification) throws RemoteException;*/ if (args != null && args.length > 1 && args[0] instanceof ComponentName) { ComponentName componentName = (ComponentName) args[0]; if (isComponentNamePlugin(componentName)) { args[0] = selectProxyService(componentName); } } return super.beforeInvoke(receiver, method, args); } } private static class bindService extends ReplaceCallingPackageHookedMethodHandler { public bindService(Context hostContext) { super(hostContext); } private ServiceInfo info = null; @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 2.3, 15 /* public int bindService(IApplicationThread caller, IBinder token, Intent service, String resolvedType, IServiceConnection connection, int flags) throws RemoteException;*/ //API 16, 17, 18, 19, 21 /* public int bindService(IApplicationThread caller, IBinder token, Intent service, String resolvedType, IServiceConnection connection, int flags, int userId) throws RemoteException;*/ info = replaceFirstServiceIntentOfArgs(args); if (info != null) { if (!PluginManager.getInstance().isPluginPackage(info.packageName)) { return super.beforeInvoke(receiver, method, args); } } int index = findIServiceConnectionIndex(method); if (info != null && index >= 0) { final Object oldIServiceConnection = args[index]; args[index] = new MyIServiceConnection(info) { public void connected(ComponentName name, IBinder service) { try { MethodUtils.invokeMethod(oldIServiceConnection, "connected", new ComponentName(mInfo.packageName, mInfo.name), service); } catch (Exception e) { Log.e(TAG, "invokeMethod connected", e); } } }; } return super.beforeInvoke(receiver, method, args); } private int findIServiceConnectionIndex(Method method) { Class[] parameterTypes = method.getParameterTypes(); if (parameterTypes != null && parameterTypes.length > 0) { for (int index = 0; index < parameterTypes.length; index++) { if (parameterTypes[index] != null && TextUtils.equals(parameterTypes[index].getSimpleName(), "IServiceConnection")) { return index; } } } return -1; } @Override protected void afterInvoke(Object receiver, Method method, Object[] args, Object invokeResult) throws Throwable { if (invokeResult instanceof ComponentName) { if (info != null) { setFakedResult(new ComponentName(info.packageName, info.name)); } } info = null; super.afterInvoke(receiver, method, args, invokeResult); } private abstract static class MyIServiceConnection extends IServiceConnection.Stub { protected final ServiceInfo mInfo; private MyIServiceConnection(ServiceInfo info) { mInfo = info; } } } private static class publishService extends ReplaceCallingPackageHookedMethodHandler { public publishService(Context hostContext) { super(hostContext); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 2.3, 15, 16, 17, 18, 19, 21 /* public void publishService(IBinder token, Intent intent, IBinder service) throws RemoteException;*/ replaceFirstServiceIntentOfArgs(args); int index = 0; if (args != null && args.length > index && args[index] instanceof MyFakeIBinder) { return true; } return super.beforeInvoke(receiver, method, args); } } private static class unbindFinished extends ReplaceCallingPackageHookedMethodHandler { public unbindFinished(Context hostContext) { super(hostContext); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 2.3, 15, 16, 17, 18, 19, 21 /*public void unbindFinished(IBinder token, Intent service, boolean doRebind) throws RemoteException;*/ replaceFirstServiceIntentOfArgs(args); return super.beforeInvoke(receiver, method, args); } } private static class peekService extends ReplaceCallingPackageHookedMethodHandler { public peekService(Context hostContext) { super(hostContext); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 2.3, 15, 16, 17, 18, 19, 21 /* public IBinder peekService(Intent service, String resolvedType) throws RemoteException;*/ replaceFirstServiceIntentOfArgs(args); return super.beforeInvoke(receiver, method, args); } } private static class bindBackupAgent extends ReplaceCallingPackageHookedMethodHandler { public bindBackupAgent(Context hostContext) { super(hostContext); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 2.3,15,16,17,18,19, 21 /* public boolean bindBackupAgent(ApplicationInfo appInfo, int backupRestoreMode) throws RemoteException;*/ final int index = 0; if (args != null && args.length > index) { if (args[index] != null && args[index] instanceof ApplicationInfo) { ApplicationInfo appInfo = (ApplicationInfo) args[index]; if (isPackagePlugin(appInfo.packageName)) { args[index] = mHostContext.getApplicationInfo(); } } } return super.beforeInvoke(receiver, method, args); } } private static class backupAgentCreated extends ReplaceCallingPackageHookedMethodHandler { public backupAgentCreated(Context hostContext) { super(hostContext); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 2.3,15,16,17,18,19, 21 /* public void backupAgentCreated(String packageName, IBinder agent) throws RemoteException;*/ final int index = 0; if (args != null && args.length > index) { if (args[index] != null && args[index] instanceof String) { String packageName = (String) args[index]; if (isPackagePlugin(packageName)) { args[index] = mHostContext.getPackageName(); } } } return super.beforeInvoke(receiver, method, args); } } private static class unbindBackupAgent extends ReplaceCallingPackageHookedMethodHandler { public unbindBackupAgent(Context hostContext) { super(hostContext); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 2.3,15,16,17,18,19, 21 /* public void unbindBackupAgent(ApplicationInfo appInfo) throws RemoteException;*/ final int index = 0; if (args != null && args.length > index) { if (args[index] != null && args[index] instanceof ApplicationInfo) { ApplicationInfo appInfo = (ApplicationInfo) args[index]; if (isPackagePlugin(appInfo.packageName)) { args[index] = mHostContext.getApplicationInfo(); } } } return super.beforeInvoke(receiver, method, args); } } private static class killApplicationProcess extends ReplaceCallingPackageHookedMethodHandler { public killApplicationProcess(Context hostContext) { super(hostContext); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 2.3,15,16,17,18,19, 21 /* public void killApplicationProcess(String processName, int uid) throws RemoteException;*/ final int index = 0; if (args != null && args.length > index) { if (args[index] != null && args[index] instanceof String) { String processName = (String) args[index]; if (!TextUtils.isEmpty(processName)) { String targetPkg = processName.split(":")[0]; if (isPackagePlugin(targetPkg)) { PluginManager.getInstance().killApplicationProcess(targetPkg); return true; } } } } return super.beforeInvoke(receiver, method, args); } } private static class startInstrumentation extends ReplaceCallingPackageHookedMethodHandler { public startInstrumentation(Context hostContext) { super(hostContext); } //API 2.3,15,16 /* public boolean startInstrumentation(ComponentName className, String profileFile, int flags, Bundle arguments, IInstrumentationWatcher watcher) throws RemoteException;*/ //API 17 /* public boolean startInstrumentation(ComponentName className, String profileFile, int flags, Bundle arguments, IInstrumentationWatcher watcher, int userId) throws RemoteException;*/ //API 18,19 /* public boolean startInstrumentation(ComponentName className, String profileFile, int flags, Bundle arguments, IInstrumentationWatcher watcher, IUiAutomationConnection connection, int userId) throws RemoteException;*/ //API 21 /* public boolean startInstrumentation(ComponentName className, String profileFile, int flags, Bundle arguments, IInstrumentationWatcher watcher, IUiAutomationConnection connection, int userId, String abiOverride) throws RemoteException;*/ //FIXME 单元测试用的。这个就不改了。 } private static class getActivityClassForToken extends ReplaceCallingPackageHookedMethodHandler { public getActivityClassForToken(Context hostContext) { super(hostContext); } //API 2.3,15,16,17,18,19, 21 /* public ComponentName getActivityClassForToken(IBinder token) throws RemoteException;*/ //FIXME I don't know what function of this,just hook it. //通过token拿Activity?搞不懂,不改。 } private static class getPackageForToken extends ReplaceCallingPackageHookedMethodHandler { public getPackageForToken(Context hostContext) { super(hostContext); } //API 2.3,15,16,17,18,19, 21 /* public String getPackageForToken(IBinder token) throws RemoteException;*/ //FIXME I don't know what function of this,just hook it. //通过token拿包名?搞不懂,不改。 } public static class getIntentSender extends ReplaceCallingPackageHookedMethodHandler { public getIntentSender(Context hostContext) { super(hostContext); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 2.3 /* public IIntentSender getIntentSender(int type, String packageName, IBinder token, String resultWho, int requestCode, Intent intent, String resolvedType, int flags) throws RemoteException;*/ //API 15 /*public IIntentSender getIntentSender(int type, String packageName, IBinder token, String resultWho, int requestCode, Intent[] intents, String[] resolvedTypes, int flags) throws RemoteException;*/ //API 16 /* public IIntentSender getIntentSender(int type, String packageName, IBinder token, String resultWho, int requestCode, Intent[] intents, String[] resolvedTypes, int flags, Bundle options) throws RemoteException;*/ //API 17, 18 19, 21 /* public IIntentSender getIntentSender(int type, String packageName, IBinder token, String resultWho, int requestCode, Intent[] intents, String[] resolvedTypes, int flags, Bundle options, int userId) throws RemoteException;*/ //这里添加包名是为了欺骗系统而已。 final int index = 1; if (args != null && args.length > index && args[index] != null && args[index] instanceof String) { String callerPackage = (String) args[index]; String originPackageName = mHostContext.getPackageName(); if (!TextUtils.equals(callerPackage, originPackageName)) { args[index] = originPackageName; } } //这里我们用新的逻辑,将原来的PendingIntent.getXXX(XXX,XXX, Intent, XXX)全部替换成 //PendingIntent.getService(XXX,XXX,intetn,XXX) //这样系统在处理的时候,会先调用到我们的中转服务,我们的中转服务再来处理这个事情。 final int index5 = 5; boolean hasRelacedIntent = false; if (args != null && args.length > index5 && args[index5] != null) { int type = (Integer) args[0]; if (args[index5] instanceof Intent) { Intent intent = (Intent) args[index5]; Intent replaced = replace(type, intent); if (replaced != null) { args[index5] = replaced; hasRelacedIntent = true; } } else if (args[index5] instanceof Intent[]) { Intent[] intents = (Intent[]) args[index5]; if (intents != null && intents.length > 0) { for (int i = 0; i < intents.length; i++) { Intent replaced = replace(type, intents[i]); if (replaced != null) { intents[i] = replaced; hasRelacedIntent = true; } } args[index5] = intents; } } } final int index7 = 7; if (hasRelacedIntent && args != null && args.length > index7) { if (args[index7] instanceof Integer) { args[index7] = PendingIntent.FLAG_UPDATE_CURRENT; } args[0] = ActivityManagerCompat.INTENT_SENDER_SERVICE; } return super.beforeInvoke(receiver, method, args); } private Intent replace(int type, Intent intent) throws RemoteException { if (type == ActivityManagerCompat.INTENT_SENDER_SERVICE) { ServiceInfo a = resolveService(intent); if (a != null && isPackagePlugin(a.packageName)) { Intent newIntent = new Intent(mHostContext, PluginManagerService.class); newIntent.putExtra(Env.EXTRA_TARGET_INTENT, intent); newIntent.putExtra(Env.EXTRA_TYPE, type); newIntent.putExtra(Env.EXTRA_ACTION, "PendingIntent"); return newIntent; } } else if (type == ActivityManagerCompat.INTENT_SENDER_ACTIVITY) { ActivityInfo a = resolveActivity(intent); if (a != null && isPackagePlugin(a.packageName)) { Intent newIntent = new Intent(mHostContext, PluginManagerService.class); newIntent.putExtra(Env.EXTRA_TARGET_INTENT, intent); newIntent.putExtra(Env.EXTRA_TYPE, type); newIntent.putExtra(Env.EXTRA_ACTION, "PendingIntent"); return newIntent; } } return null; } public static void handlePendingIntent(final Context context, Intent intent) { try { if (intent != null && "PendingIntent".equals(intent.getStringExtra(Env.EXTRA_ACTION))) { int type = intent.getIntExtra(Env.EXTRA_TYPE, -1); final Intent actionIntent = intent.getParcelableExtra(Env.EXTRA_TARGET_INTENT); final Handler handle = new Handler(Looper.getMainLooper()); if (type == ActivityManagerCompat.INTENT_SENDER_SERVICE && actionIntent != null) { final Runnable r = new Runnable() { @Override public void run() { try { context.startService(actionIntent); } catch (Throwable e) { Log.e(TAG, "startService for PendingIntent %s", e, actionIntent); } } }; new Thread("") { @Override public void run() { try { PluginManager.getInstance().waitForConnected(); handle.post(r); } catch (Exception e) { Log.e(TAG, "startService for PendingIntent %s", e, actionIntent); } } }.start(); } else if (type == ActivityManagerCompat.INTENT_SENDER_ACTIVITY && actionIntent != null) { actionIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); final Runnable r = new Runnable() { @Override public void run() { try { context.startActivity(actionIntent); } catch (Throwable e) { Log.e(TAG, "startActivity for PendingIntent %s", e, actionIntent); } } }; new Thread("") { @Override public void run() { try { PluginManager.getInstance().waitForConnected(); handle.post(r); } catch (Exception e) { Log.e(TAG, "startActivity for PendingIntent %s", e, actionIntent); } } }.start(); } } } catch (Exception e) { Log.e(TAG, "Exception", e); } } } private static class clearApplicationUserData extends ReplaceCallingPackageHookedMethodHandler { public clearApplicationUserData(Context hostContext) { super(hostContext); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //2.3,15 /* public boolean clearApplicationUserData(final String packageName, final IPackageDataObserver observer) throws RemoteException;*/ //API 16,17,18,19,21 /* public boolean clearApplicationUserData(final String packageName, final IPackageDataObserver observer, int userId) throws RemoteException;*/ final int index = 0; if (args != null && args.length > index) { if (args[index] != null && args[index] instanceof String) { String targetPkg = (String) args[index]; if (isPackagePlugin(targetPkg)) { Object observer = args.length > 1 ? args[1] : null; clearPluginApplicationUserData(targetPkg, observer); return true; } } } return super.beforeInvoke(receiver, method, args); } } private static class handleIncomingUser extends ReplaceCallingPackageHookedMethodHandler { public handleIncomingUser(Context hostContext) { super(hostContext); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 17,18,19, 21 /*public int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll, boolean requireFull, String name, String callerPackage) throws RemoteException;*/ //这个函数不知道是干嘛的 //插件调用这个函数会传插件自己的包名,而此插件并未被安装。就这样调用原来函数传给系统,是会出问题的。所以改成宿主程序的包名。 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { final int index = 6; if (args != null && args.length > index) { if (args[index] != null && args[index] instanceof String) { String targetPkg = (String) args[index]; if (isPackagePlugin(targetPkg)) { args[index] = mHostContext.getPackageName(); } } } } return super.beforeInvoke(receiver, method, args); } } private static class grantUriPermission extends ReplaceCallingPackageHookedMethodHandler { public grantUriPermission(Context hostContext) { super(hostContext); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 2.3,15,16,17,18,19 /* public void grantUriPermission(IApplicationThread caller, String targetPkg, Uri uri, int mode) throws RemoteException;*/ //API 21 /* public void grantUriPermission(IApplicationThread caller, String targetPkg, Uri uri, int mode, int userId) throws RemoteException;*/ //这个函数是用来给某个包授予访问某个URI的权限。 //插件调用这个函数会传插件自己的包名,而此插件并未被安装。就这样调用原来函数传给系统,是会出问题的。所以改成宿主程序的包名。 final int index = 1; if (args != null && args.length > index) { if (args[index] != null && args[index] instanceof String) { String targetPkg = (String) args[index]; if (isPackagePlugin(targetPkg)) { args[index] = mHostContext.getPackageName(); } } } return super.beforeInvoke(receiver, method, args); } } private static class getPersistedUriPermissions extends ReplaceCallingPackageHookedMethodHandler { public getPersistedUriPermissions(Context hostContext) { super(hostContext); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { // 19,21 /* public ParceledListSlice getPersistedUriPermissions( String packageName, boolean incoming) throws RemoteException;*/ //这个函数是用来检测什么权限,没搞明白。 //插件调用这个函数会传插件自己的包名,而此插件并未被安装。就这样调用原来函数传给系统,是会出问题的。所以改成宿主程序的包名。 final int index = 0; if (args != null && args.length > index) { if (args[index] != null && args[index] instanceof String) { String targetPkg = (String) args[index]; if (isPackagePlugin(targetPkg)) { args[index] = mHostContext.getPackageName(); } } } return super.beforeInvoke(receiver, method, args); } } private static class killBackgroundProcesses extends ReplaceCallingPackageHookedMethodHandler { public killBackgroundProcesses(Context hostContext) { super(hostContext); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 2.3,15,16 /*public void killBackgroundProcesses(final String packageName) throws RemoteException;*/ //API 17,18,19,21 /* public void killBackgroundProcesses(final String packageName, int userId) throws RemoteException;*/ final int index = 0; if (args != null && args.length > index) { if (args[index] != null && args[index] instanceof String) { String targetPkg = (String) args[index]; if (isPackagePlugin(targetPkg)) { PluginManager.getInstance().killBackgroundProcesses(targetPkg); return true; } } } return super.beforeInvoke(receiver, method, args); } } private static class forceStopPackage extends ReplaceCallingPackageHookedMethodHandler { public forceStopPackage(Context hostContext) { super(hostContext); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 2.3,15,16 /* public void forceStopPackage(final String packageName) throws RemoteException;*/ //API 17,18,19,21 /*public void forceStopPackage(final String packageName, int userId) throws RemoteException;*/ final int index = 0; if (args != null && args.length > index) { if (args[index] != null && args[index] instanceof String) { String targetPkg = (String) args[index]; if (isPackagePlugin(targetPkg)) { PluginManager.getInstance().forceStopPackage(targetPkg); return true; } } } return super.beforeInvoke(receiver, method, args); } } private static class getRunningAppProcesses extends ReplaceCallingPackageHookedMethodHandler { public getRunningAppProcesses(Context hostContext) { super(hostContext); } @Override protected void afterInvoke(Object receiver, Method method, Object[] args, Object invokeResult) throws Throwable { //2.3,15,16,17,18,19,21 /* public List getRunningAppProcesses() throws RemoteException;*/ //这个hook有点不同。一般插件调用这个函数是为了获取自己当前进程的名字。 //所以要把原函数返回的List中关于插件的部分的进程名字给改了,用来欺骗插件。 //不过目前这种修改方式可能有问题。 if (invokeResult != null && invokeResult instanceof List) { @SuppressWarnings("unchecked") List infos = (List) invokeResult; if (infos.size() > 0) { for (Object info : infos) { if (info instanceof ActivityManager.RunningAppProcessInfo) { ActivityManager.RunningAppProcessInfo myinfo = (ActivityManager.RunningAppProcessInfo) info; if (myinfo.uid != android.os.Process.myUid()) { continue; } List pkgs = PluginManager.getInstance().getPackageNameByPid(myinfo.pid); String processname = PluginManager.getInstance().getProcessNameByPid(myinfo.pid); if (processname != null) { myinfo.processName = processname; } if (pkgs != null && pkgs.size() > 0) { ArrayList ls = new ArrayList(); if (myinfo.pkgList != null) { for (String s : myinfo.pkgList) { if (!ls.contains(s)) { ls.add(s); } } } for (String s : pkgs) { if (!ls.contains(s)) { ls.add(s); } } myinfo.pkgList = ls.toArray(new String[ls.size()]); } } } } } } } private static class getRunningExternalApplications extends ReplaceCallingPackageHookedMethodHandler { public getRunningExternalApplications(Context hostContext) { super(hostContext); } @Override protected void afterInvoke(Object receiver, Method method, Object[] args, Object invokeResult) throws Throwable { //2.3,15,16,17,18,19,21 /* public List getRunningExternalApplications() throws RemoteException;*/ //这个hook有点不同。 //我们把原函数返回的List中关于插件的部分给改了,用来欺骗插件,让其以为自己已经被安装了。咩哈哈!! if (invokeResult != null && invokeResult instanceof List) { @SuppressWarnings("unchecked") List infos = (List) invokeResult; if (infos.size() > 0) { List pluginInfos = new ArrayList(2); for (Object info : infos) { if (info instanceof ApplicationInfo) { ApplicationInfo myinfo = (ApplicationInfo) info; if (isPackagePlugin(myinfo.packageName)) { pluginInfos.add(myinfo); } } } if (pluginInfos.size() > 0) { for (ApplicationInfo pluginInfo : pluginInfos) { int index = infos.indexOf(pluginInfo); if (index >= 0) { ApplicationInfo object = queryPluginApplicationInfo(pluginInfo.packageName); if (object != null) { infos.set(index, object); } } } } } } setFakedResult(invokeResult); } } private static class getMyMemoryState extends ReplaceCallingPackageHookedMethodHandler { public getMyMemoryState(Context hostContext) { super(hostContext); } //API 16,17,18,19,21 /* public void getMyMemoryState(ActivityManager.RunningAppProcessInfo outInfo) throws RemoteException;*/ //DO NOTHING } private static class crashApplication extends ReplaceCallingPackageHookedMethodHandler { public crashApplication(Context hostContext) { super(hostContext); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //2.3,15,16,17,18,19,21 /*public void crashApplication(int uid, int initialPid, String packageName, String message) throws RemoteException;*/ //这个函数不知道是用来干嘛的,也许是向系统报告自己崩溃了?神奇的函数。 //插件调用这个函数会传插件自己的包名,而此插件并未被安装。就这样调用原来函数传给系统,是会出问题的。所以改成宿主程序的包名。 final int index = 2; if (args != null && args.length > index) { if (args[index] != null && args[index] instanceof String) { String targetPkg = (String) args[index]; if (isPackagePlugin(targetPkg)) { args[index] = mHostContext.getPackageName(); } } } return super.beforeInvoke(receiver, method, args); } } private static class grantUriPermissionFromOwner extends ReplaceCallingPackageHookedMethodHandler { public grantUriPermissionFromOwner(Context hostContext) { super(hostContext); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //2.3,15,16,17,18,19,21 /* public void grantUriPermissionFromOwner(IBinder owner, int fromUid, String targetPkg, Uri uri, int mode) throws RemoteException;*/ //这个函数是用来给某个包授予访问某个URI的权限。 //插件调用这个函数会传插件自己的包名,而此插件并未被安装。就这样调用原来函数传给系统,是会出问题的。所以改成宿主程序的包名。 final int index = 2; if (args != null && args.length > index) { if (args[index] != null && args[index] instanceof String) { String targetPkg = (String) args[index]; if (isPackagePlugin(targetPkg)) { args[index] = mHostContext.getPackageName(); } } } return super.beforeInvoke(receiver, method, args); } } private static class checkGrantUriPermission extends ReplaceCallingPackageHookedMethodHandler { public checkGrantUriPermission(Context hostContext) { super(hostContext); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 15,16,17,18,19,21 /* public int checkGrantUriPermission(int callingUid, String targetPkg, Uri uri, int modeFlags) throws RemoteException;*/ //API 21 /* public int checkGrantUriPermission(int callingUid, String targetPkg, Uri uri, int modeFlags, int userId) throws RemoteException;*/ //这个函数是用来检测某个包是否具有访问某个URI的权限。 //插件调用这个函数会传插件自己的包名,而此插件并未被安装。就这样调用原来函数传给系统,是会出问题的。所以改成宿主程序的包名。 final int index = 1; if (args != null && args.length > index) { if (args[index] != null && args[index] instanceof String) { String targetPkg = (String) args[index]; if (isPackagePlugin(targetPkg)) { args[index] = mHostContext.getPackageName(); } } } return super.beforeInvoke(receiver, method, args); } } /*ONLY for API 15 or later*/ private static class startActivities extends ReplaceCallingPackageHookedMethodHandler { public startActivities(Context hostContext) { super(hostContext); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //Api 15 /* public int startActivities(IApplicationThread caller, Intent[] intents, String[] resolvedTypes, IBinder resultTo) throws RemoteException;*/ //Api 16 /* public int startActivities(IApplicationThread caller, Intent[] intents, String[] resolvedTypes, IBinder resultTo, Bundle options) throws RemoteException;*/ //Api 17 /* public int startActivities(IApplicationThread caller, Intent[] intents, String[] resolvedTypes, IBinder resultTo, Bundle options, int userId) throws RemoteException;*/ //API 18, 19, 21 /* public int startActivities(IApplicationThread caller, String callingPackage, Intent[] intents, String[] resolvedTypes, IBinder resultTo, Bundle options, int userId) throws RemoteException;*/ //启动一坨Activity用的,用的比较少。不过为了保险起见也改改。 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { int index = 1; String callingPackage = null; if (args != null && args.length > index && args[index] instanceof String) { if (args[index] == null) { args[index] = mHostContext.getPackageName(); } else { callingPackage = (String) args[1]; if (!TextUtils.equals(callingPackage, mHostContext.getPackageName())) { args[index] = mHostContext.getPackageName(); } } } else { Log.w(TAG, "hook startActivities,replace callingPackage fail"); } index = 2; if (args != null && args.length > index && args[index] != null && args[index] instanceof Intent[]) { Intent[] intents = (Intent[]) args[index]; for (int i = 0; i < intents.length; i++) { Intent intent = intents[i]; ComponentName component = selectProxyActivity(intent); if (component != null) { Intent newIntent = new Intent(); newIntent.setComponent(component); newIntent.putExtra(Env.EXTRA_TARGET_INTENT, intent); ActivityInfo activityInfo = resolveActivity(intent); if (activityInfo != null && TextUtils.equals(mHostContext.getPackageName(), callingPackage)) { newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } intents[i] = newIntent; } } } else { Log.w(TAG, "hook startActivities,replace intents fail"); } } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { int index = 1; if (args != null && args.length > index && args[index] != null && args[index] instanceof Intent[]) { Intent[] intents = (Intent[]) args[index]; for (int i = 0; i < intents.length; i++) { Intent intent = intents[i]; ComponentName component = selectProxyActivity(intent); if (component != null) { Intent newIntent = new Intent(); newIntent.setComponent(component); newIntent.putExtra(Env.EXTRA_TARGET_INTENT, intent); ActivityInfo activityInfo = resolveActivity(intent); // if (activityInfo != null && TextUtils.equals(mHostContext.getPackageName(), activityInfo.packageName)) { // newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // } intents[i] = newIntent; } } } else { Log.w(TAG, "hook startActivities,replace intents fail"); } } return super.beforeInvoke(receiver, method, args); } } /*ONLY for api 15 or later*/ private static class getPackageScreenCompatMode extends ReplaceCallingPackageHookedMethodHandler { public getPackageScreenCompatMode(Context hostContext) { super(hostContext); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { /* public int getPackageScreenCompatMode(String packageName) throws RemoteException;*/ //我也不知道这个函数是干嘛的,不过既然写了,我们就改一下。 //因为如果万一插件调用了这个函数,则会传插件自己的包名,而此插件并未被安装。就这样调用原来函数传给系统,是会出问题的。所以改成宿主程序的包名。 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { final int index = 0; if (args != null && args.length > index) { if (args[index] != null && args[index] instanceof String) { String packageName = (String) args[index]; if (isPackagePlugin(packageName)) { args[index] = mHostContext.getPackageName(); } } } } return super.beforeInvoke(receiver, method, args); } } /*ONLY for api 15 or later*/ private static class setPackageScreenCompatMode extends ReplaceCallingPackageHookedMethodHandler { public setPackageScreenCompatMode(Context hostContext) { super(hostContext); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { /* public void setPackageScreenCompatMode(String packageName, int mode) throws RemoteException;*/ //我也不知道这个函数是干嘛的,不过既然写了,我们就改一下。 //因为如果万一插件调用了这个函数,则会传插件自己的包名,而此插件并未被安装。就这样调用原来函数传给系统,是会出问题的。所以改成宿主程序的包名。 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { final int index = 0; if (args != null && args.length > index) { if (args[index] != null && args[index] instanceof String) { String packageName = (String) args[index]; if (isPackagePlugin(packageName)) { args[index] = mHostContext.getPackageName(); } } } } return super.beforeInvoke(receiver, method, args); } } /*ONLY for api 15 or later*/ private static class getPackageAskScreenCompat extends ReplaceCallingPackageHookedMethodHandler { public getPackageAskScreenCompat(Context hostContext) { super(hostContext); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 15, 16, 17, 18, 19, 21 /* public boolean getPackageAskScreenCompat(String packageName) throws RemoteException;*/ //我也不知道这个函数是干嘛的,不过既然写了,我们就改一下。 //因为如果万一插件调用了这个函数,则会传插件自己的包名,而此插件并未被安装。就这样调用原来函数传给系统,是会出问题的。所以改成宿主程序的包名。 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { final int index = 0; if (args != null && args.length > index) { if (args[index] != null && args[index] instanceof String) { String packageName = (String) args[index]; if (isPackagePlugin(packageName)) { args[index] = mHostContext.getPackageName(); } } } } return super.beforeInvoke(receiver, method, args); } } /*ONLY for api 15 or later*/ private static class setPackageAskScreenCompat extends ReplaceCallingPackageHookedMethodHandler { public setPackageAskScreenCompat(Context hostContext) { super(hostContext); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 15, 16, 17, 18, 19, 21 /*public void setPackageAskScreenCompat(String packageName, boolean ask) throws RemoteException;*/ //我也不知道这个函数是干嘛的,不过既然写了,我们就改一下。 //因为如果万一插件调用了这个函数,则会传插件自己的包名,而此插件并未被安装。就这样调用原来函数传给系统,是会出问题的。所以改成宿主程序的包名。 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { final int index = 0; if (args != null && args.length > index) { if (args[index] != null && args[index] instanceof String) { String packageName = (String) args[index]; if (isPackagePlugin(packageName)) { args[index] = mHostContext.getPackageName(); } } } } return super.beforeInvoke(receiver, method, args); } } /*ONLY for API 16 or later*/ private static class navigateUpTo extends ReplaceCallingPackageHookedMethodHandler { public navigateUpTo(Context hostContext) { super(hostContext); } //API 16, 17, 18, 19, 21 /* public boolean navigateUpTo(IBinder token, Intent target, int resultCode, Intent resultData) throws RemoteException;*/ //TODO replace target(Intent) to ProxyActivity //这里先不做,测试如果有问题再处理。 } private static ServiceInfo replaceFirstServiceIntentOfArgs(Object[] args) throws RemoteException { int intentOfArgIndex = findFirstIntentIndexInArgs(args); if (args != null && args.length > 1 && intentOfArgIndex >= 0) { Intent intent = (Intent) args[intentOfArgIndex]; ServiceInfo serviceInfo = resolveService(intent); if (serviceInfo != null && isPackagePlugin(serviceInfo.packageName)) { ServiceInfo proxyService = selectProxyService(intent); if (proxyService != null) { Intent newIntent = new Intent(); //FIXBUG:https://github.com/Qihoo360/DroidPlugin/issues/122 //如果插件中有两个Service:ServiceA和ServiceB,在bind ServiceA的时候会调用ServiceA的onBind并返回其IBinder对象, // 但是再次bind ServiceA的时候还是会返回ServiceA的IBinder对象,这是因为插件系统对多个Service使用了同一个StubService // 来代理,而系统对StubService的IBinder做了缓存的问题。这里设置一个Action则会穿透这种缓存。 newIntent.setAction(proxyService.name + new Random().nextInt()); newIntent.setClassName(proxyService.packageName, proxyService.name); newIntent.putExtra(Env.EXTRA_TARGET_INTENT, intent); newIntent.setFlags(intent.getFlags()); args[intentOfArgIndex] = newIntent; return serviceInfo; } } } return null; } private static int findFirstIntentIndexInArgs(Object[] args) { if (args != null && args.length > 0) { int i = 0; for (Object arg : args) { if (arg != null && arg instanceof Intent) { return i; } i++; } } return -1; } private static ComponentName selectProxyActivity(Intent intent) { try { if (intent != null) { ActivityInfo proxyInfo = PluginManager.getInstance().selectStubActivityInfo(intent); if (proxyInfo != null) { return new ComponentName(proxyInfo.packageName, proxyInfo.name); } } } catch (Exception e) { e.printStackTrace(); } return null; } private static ServiceInfo selectProxyService(Intent intent) { try { if (intent != null) { ServiceInfo proxyInfo = PluginManager.getInstance().selectStubServiceInfo(intent); if (proxyInfo != null) { return proxyInfo; } } } catch (Exception e) { e.printStackTrace(); } return null; } private static ComponentName selectProxyService(ComponentName componentName) { try { if (componentName != null) { PluginManager instance = PluginManager.getInstance(); ServiceInfo info = instance.getServiceInfo(componentName, 0); if (info != null) { ServiceInfo proxyInfo = instance.selectStubServiceInfo(info); if (proxyInfo != null) { return new ComponentName(proxyInfo.packageName, proxyInfo.name); } } } } catch (Exception e) { e.printStackTrace(); } return null; } private static ActivityInfo resolveActivity(Intent intent) throws RemoteException { return PluginManager.getInstance().resolveActivityInfo(intent, 0); } private static ServiceInfo resolveService(Intent intent) throws RemoteException { return PluginManager.getInstance().resolveServiceInfo(intent, 0); } private static boolean isPackagePlugin(String packageName) throws RemoteException { return PluginManager.getInstance().isPluginPackage(packageName); } private static boolean isComponentNamePlugin(ComponentName className) throws RemoteException { return PluginManager.getInstance().isPluginPackage(className); } private static ApplicationInfo queryPluginApplicationInfo(String packageName) throws RemoteException { return PluginManager.getInstance().getApplicationInfo(packageName, 0); } private static boolean clearPluginApplicationUserData(String packageName, final Object observer) throws RemoteException { if (observer == null) { PluginManager.getInstance().clearApplicationUserData(packageName, null); } else { PluginManager.getInstance().clearApplicationUserData(packageName, observer); } return true; } private static void tryfixServiceInfo(ActivityManager.RunningServiceInfo serviceInfo) { //传入的是代理服务,要改成插件自己服务的信息。 } private class serviceDoneExecuting extends ReplaceCallingPackageHookedMethodHandler { public serviceDoneExecuting(Context context) { super(context); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { int index = 0; if (args != null && args.length > index && args[index] instanceof MyFakeIBinder) { return true; } return super.beforeInvoke(receiver, method, args); } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/handle/IAppOpsServiceHookHandle.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.handle; import android.content.Context; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.HookedMethodHandler; import com.morgoo.helper.compat.IAppOpsServiceCompat; /** * Created by Andy Zhang(zhangyong232@gmail.com) on on 16/5/11. */ public class IAppOpsServiceHookHandle extends BaseHookHandle { public IAppOpsServiceHookHandle(Context hostContext) { super(hostContext); } @Override protected void init() { // interface IAppOpsService { // int checkOperation(int code, int uid, String packageName); // int noteOperation(int code, int uid, String packageName); // int startOperation(IBinder token, int code, int uid, String packageName); // void finishOperation(IBinder token, int code, int uid, String packageName); // void startWatchingMode(int op, String packageName, IAppOpsCallback callback); // void stopWatchingMode(IAppOpsCallback callback); // IBinder getToken(IBinder clientToken); // int permissionToOpCode(String permission); // int noteProxyOperation(int code, String proxyPackageName,int callingUid, String callingPackageName); // int checkPackage(int uid, String packageName); // List getPackagesForOps(in int[] ops); // List getOpsForPackage(int uid, String packageName, in int[] ops); // void setUidMode(int code, int uid, int mode); // void setMode(int code, int uid, String packageName, int mode); // void resetAllModes(int reqUserId, String reqPackageName); // int checkAudioOperation(int code, int usage, int uid, String packageName); // void setAudioRestriction(int code, int usage, int uid, int mode, in String[] exceptionPackages); // void setUserRestrictions(in Bundle restrictions, int userHandle); // void removeUser(int userHandle); // } sHookedMethodHandlers.put("checkOperation",new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("noteOperation",new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("startOperation",new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("finishOperation",new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("startWatchingMode",new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("stopWatchingMode",new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getToken",new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("permissionToOpCode",new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("noteProxyOperation",new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("checkPackage",new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getPackagesForOps",new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getOpsForPackage",new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("setUidMode",new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("setMode",new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("resetAllModes",new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("checkAudioOperation",new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("setAudioRestriction",new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("setUserRestrictions",new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("removeUser",new MyBaseHandler(mHostContext)); addAllMethodFromHookedClass(); } @Override protected Class getHookedClass() throws ClassNotFoundException { return IAppOpsServiceCompat.Class(); } @Override protected HookedMethodHandler newBaseHandler() throws ClassNotFoundException { return new MyBaseHandler(mHostContext); } private static class MyBaseHandler extends ReplaceCallingPackageHookedMethodHandler { public MyBaseHandler(Context context) { super(context); } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/handle/IAudioServiceHookHandle.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.handle; import android.content.Context; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.text.TextUtils; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.HookedMethodHandler; import com.morgoo.droidplugin.pm.PluginManager; import java.lang.reflect.Method; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/3/6. */ public class IAudioServiceHookHandle extends BaseHookHandle { public IAudioServiceHookHandle(Context hostContext) { super(hostContext); } @Override protected void init() { sHookedMethodHandlers.put("adjustVolume", new adjustVolume(mHostContext)); sHookedMethodHandlers.put("adjustLocalOrRemoteStreamVolume", new adjustLocalOrRemoteStreamVolume(mHostContext)); sHookedMethodHandlers.put("adjustSuggestedStreamVolume", new adjustSuggestedStreamVolume(mHostContext)); sHookedMethodHandlers.put("adjustStreamVolume", new adjustStreamVolume(mHostContext)); sHookedMethodHandlers.put("adjustMasterVolume", new adjustMasterVolume(mHostContext)); sHookedMethodHandlers.put("setStreamVolume", new setStreamVolume(mHostContext)); sHookedMethodHandlers.put("setMasterVolume", new setMasterVolume(mHostContext)); sHookedMethodHandlers.put("requestAudioFocus", new requestAudioFocus(mHostContext)); sHookedMethodHandlers.put("registerRemoteControlClient", new registerRemoteControlClient(mHostContext)); } private static class MyBaseHandler extends HookedMethodHandler { public MyBaseHandler(Context context) { super(context); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) { if (args != null && args.length > 0) { for (int index = 0; index < args.length; index++) { if (args[index] instanceof String) { String callingPkg = (String) args[index]; if (!TextUtils.equals(callingPkg, mHostContext.getPackageName()) && PluginManager.getInstance().isPluginPackage(callingPkg)) { args[index] = mHostContext.getPackageName(); } } } } } return super.beforeInvoke(receiver, method, args); } } private static class adjustVolume extends MyBaseHandler { public adjustVolume(Context context) { super(context); } } private static class adjustLocalOrRemoteStreamVolume extends MyBaseHandler { public adjustLocalOrRemoteStreamVolume(Context context) { super(context); } } private static class adjustSuggestedStreamVolume extends MyBaseHandler { public adjustSuggestedStreamVolume(Context context) { super(context); } } private static class adjustStreamVolume extends MyBaseHandler { public adjustStreamVolume(Context context) { super(context); } } private static class adjustMasterVolume extends MyBaseHandler { public adjustMasterVolume(Context context) { super(context); } } private static class setStreamVolume extends MyBaseHandler { public setStreamVolume(Context context) { super(context); } } private static class setMasterVolume extends MyBaseHandler { public setMasterVolume(Context context) { super(context); } } private static class requestAudioFocus extends MyBaseHandler { public requestAudioFocus(Context context) { super(context); } } private static class registerRemoteControlClient extends MyBaseHandler { public registerRemoteControlClient(Context context) { super(context); } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/handle/IClipboardHookHandle.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.handle; import android.content.Context; import android.text.TextUtils; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.HookedMethodHandler; import java.lang.reflect.Method; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/3/6. */ public class IClipboardHookHandle extends BaseHookHandle { public IClipboardHookHandle(Context context) { super(context); } //17 // void setPrimaryClip(in ClipData clip); // ClipData getPrimaryClip(String pkg); // ClipDescription getPrimaryClipDescription(); // boolean hasPrimaryClip(); // void addPrimaryClipChangedListener(in IOnPrimaryClipChangedListener listener); // void removePrimaryClipChangedListener(in IOnPrimaryClipChangedListener listener); // boolean hasClipboardText(); //API 21,19,18 // void setPrimaryClip(ClipData clip, String callingPackage); // ClipData getPrimaryClip(String pkg); // ClipDescription getPrimaryClipDescription(String callingPackage); // boolean hasPrimaryClip(String callingPackage); // void addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener, String callingPackage); // void removePrimaryClipChangedListener(IOnPrimaryClipChangedListener listener); // boolean hasClipboardText(String callingPackage); @Override protected void init() { sHookedMethodHandlers.put("setPrimaryClip", new setPrimaryClip(mHostContext)); sHookedMethodHandlers.put("getPrimaryClip", new getPrimaryClip(mHostContext)); sHookedMethodHandlers.put("getPrimaryClipDescription", new getPrimaryClipDescription(mHostContext)); sHookedMethodHandlers.put("hasPrimaryClip", new hasPrimaryClip(mHostContext)); sHookedMethodHandlers.put("addPrimaryClipChangedListener", new addPrimaryClipChangedListener(mHostContext)); sHookedMethodHandlers.put("removePrimaryClipChangedListener", new removePrimaryClipChangedListener(mHostContext)); sHookedMethodHandlers.put("hasClipboardText", new hasClipboardText(mHostContext)); } private class MyBaseHookedMethodHandler extends HookedMethodHandler { public MyBaseHookedMethodHandler(Context context) { super(context); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { if (args != null && args.length > 0 && args[args.length - 1] instanceof String) { String pkg = (String) args[args.length - 1]; if (!TextUtils.equals(pkg, mHostContext.getPackageName())) { args[args.length - 1] = mHostContext.getPackageName(); } } return super.beforeInvoke(receiver, method, args); } } private class setPrimaryClip extends MyBaseHookedMethodHandler { public setPrimaryClip(Context context) { super(context); } } private class getPrimaryClip extends MyBaseHookedMethodHandler { public getPrimaryClip(Context context) { super(context); } } private class getPrimaryClipDescription extends MyBaseHookedMethodHandler { public getPrimaryClipDescription(Context context) { super(context); } } private class hasPrimaryClip extends MyBaseHookedMethodHandler { public hasPrimaryClip(Context context) { super(context); } } private class addPrimaryClipChangedListener extends MyBaseHookedMethodHandler { public addPrimaryClipChangedListener(Context context) { super(context); } } private class removePrimaryClipChangedListener extends MyBaseHookedMethodHandler { public removePrimaryClipChangedListener(Context context) { super(context); } } private class hasClipboardText extends MyBaseHookedMethodHandler { public hasClipboardText(Context context) { super(context); } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/handle/IContentProviderInvokeHandle.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.handle; import android.content.Context; import android.content.pm.ProviderInfo; import android.net.Uri; import android.net.Uri.Builder; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.text.TextUtils; import com.morgoo.droidplugin.core.Env; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.HookedMethodHandler; import java.lang.reflect.Method; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/3/6. */ public class IContentProviderInvokeHandle extends BaseHookHandle { private final ProviderInfo mStubProvider; private final ProviderInfo mTargetProvider; private final boolean mLocalProvider; public IContentProviderInvokeHandle(Context hostContext, ProviderInfo stubProvider, ProviderInfo targetProvider, boolean localProvider) { super(hostContext); mStubProvider = stubProvider; mTargetProvider = targetProvider; mLocalProvider = localProvider; } @Override protected void init() { sHookedMethodHandlers.put("query", new query(mHostContext)); sHookedMethodHandlers.put("getType", new getType(mHostContext)); sHookedMethodHandlers.put("insert", new insert(mHostContext)); sHookedMethodHandlers.put("bulkInsert", new bulkInsert(mHostContext)); sHookedMethodHandlers.put("delete", new delete(mHostContext)); sHookedMethodHandlers.put("update", new update(mHostContext)); sHookedMethodHandlers.put("openFile", new openFile(mHostContext)); sHookedMethodHandlers.put("openAssetFile", new openAssetFile(mHostContext)); sHookedMethodHandlers.put("applyBatch", new applyBatch(mHostContext)); sHookedMethodHandlers.put("call", new call(mHostContext)); sHookedMethodHandlers.put("createCancellationSignal", new createCancellationSignal(mHostContext)); sHookedMethodHandlers.put("canonicalize", new canonicalize(mHostContext)); sHookedMethodHandlers.put("uncanonicalize", new uncanonicalize(mHostContext)); sHookedMethodHandlers.put("getStreamTypes", new getStreamTypes(mHostContext)); sHookedMethodHandlers.put("openTypedAssetFile", new openTypedAssetFile(mHostContext)); } private class MyHandler extends ReplaceCallingPackageHookedMethodHandler { public MyHandler(Context hostContext) { super(hostContext); } private int indexFirstUri(Object[] args) { if (args != null && args.length > 0) { for (int i = 0; i < args.length; i++) { if (args[i] instanceof Uri) { return i; } } } return -1; } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { // if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR2) { // final int index = 0; // if (args != null && args.length > index && args[index] instanceof String) { // String pkg = (String) args[index]; // if (!TextUtils.equals(pkg, mHostContext.getPackageName())) { // args[index] = mHostContext.getPackageName(); // } // } // } if (!mLocalProvider && mStubProvider != null) { final int index = indexFirstUri(args); if (index >= 0) { Uri uri = (Uri) args[index]; String authority = uri.getAuthority(); if (!TextUtils.equals(authority, mStubProvider.authority)) { Uri.Builder b = new Builder(); b.scheme(uri.getScheme()); b.authority(mStubProvider.authority); b.path(uri.getPath()); b.query(uri.getQuery()); b.appendQueryParameter(Env.EXTRA_TARGET_AUTHORITY, authority); b.fragment(uri.getFragment()); args[index] = b.build(); } } } return super.beforeInvoke(receiver, method, args); } } private class query extends MyHandler { public query(Context context) { super(context); } } private class getType extends MyHandler { public getType(Context context) { super(context); } } private class insert extends MyHandler { public insert(Context context) { super(context); } } private class bulkInsert extends MyHandler { public bulkInsert(Context context) { super(context); } } private class delete extends MyHandler { public delete(Context context) { super(context); } } private class update extends MyHandler { public update(Context context) { super(context); } } private class openFile extends MyHandler { public openFile(Context context) { super(context); } } private class openAssetFile extends MyHandler { public openAssetFile(Context context) { super(context); } } private class applyBatch extends MyHandler { public applyBatch(Context context) { super(context); } } private class call extends MyHandler { public call(Context context) { super(context); } } private class createCancellationSignal extends MyHandler { public createCancellationSignal(Context context) { super(context); } } private class canonicalize extends MyHandler { public canonicalize(Context context) { super(context); } } private class uncanonicalize extends MyHandler { public uncanonicalize(Context context) { super(context); } } private class getStreamTypes extends MyHandler { public getStreamTypes(Context context) { super(context); } } private class openTypedAssetFile extends MyHandler { public openTypedAssetFile(Context context) { super(context); } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/handle/IContentServiceHandle.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.handle; import android.content.Context; import android.content.pm.ProviderInfo; import android.net.Uri; import com.morgoo.droidplugin.core.Env; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.HookedMethodHandler; import com.morgoo.droidplugin.pm.PluginManager; import com.morgoo.helper.Log; import java.lang.reflect.Method; import java.util.Arrays; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/5/21. */ public class IContentServiceHandle extends BaseHookHandle { private static final String TAG = IContentServiceHandle.class.getSimpleName(); public IContentServiceHandle(Context hostContext) { super(hostContext); } @Override protected void init() { sHookedMethodHandlers.put("registerContentObserver", new registerContentObserver(mHostContext)); sHookedMethodHandlers.put("notifyChange", new notifyChange(mHostContext)); } private static class IContentServiceHookedMethodHandler extends HookedMethodHandler { public IContentServiceHookedMethodHandler(Context hostContext) { super(hostContext); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { if (args != null) { final int index = 1; if (args.length > index && args[index] instanceof Uri) { Uri uri = (Uri) args[index]; String authority = uri.getAuthority(); ProviderInfo provider = PluginManager.getInstance().resolveContentProvider(authority, 0); if (provider != null) { ProviderInfo info = PluginManager.getInstance().selectStubProviderInfo(authority); Uri.Builder newUri = new Uri.Builder(); newUri.scheme("content"); newUri.authority(uri.getAuthority()); newUri.path(uri.getPath()); newUri.query(uri.getQuery()); newUri.appendQueryParameter(Env.EXTRA_TARGET_AUTHORITY,authority); args[index] = newUri.build(); // return true; } else { Log.w(TAG, "getContentProvider,fake fail 2=%s", authority); } } } return super.beforeInvoke(receiver, method, args); } } private static class registerContentObserver extends IContentServiceHookedMethodHandler { public registerContentObserver(Context hostContext) { super(hostContext); } } private static class notifyChange extends IContentServiceHookedMethodHandler { public notifyChange(Context hostContext) { super(hostContext); } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/handle/IDisplayManagerHookHandle.java ================================================ package com.morgoo.droidplugin.hook.handle; import android.content.Context; import android.text.TextUtils; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.HookedMethodHandler; import java.lang.reflect.Method; /** * IDisplayManagerHookHandle * * @author Liu Yichen * @date 16/6/13 */ public class IDisplayManagerHookHandle extends BaseHookHandle { public IDisplayManagerHookHandle(Context hostContext) { super(hostContext); } @Override protected void init() { sHookedMethodHandlers.put("createVirtualDisplay", new createVirtualDisplay(mHostContext)); } private static class createVirtualDisplay extends HookedMethodHandler { public createVirtualDisplay(Context hostContext) { super(hostContext); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { final int pkgIndex = 2; if (args != null && args.length > 0 && args[pkgIndex] instanceof String) { String pkg = (String) args[pkgIndex]; if (!TextUtils.equals(pkg, mHostContext.getPackageName())) { args[pkgIndex] = mHostContext.getPackageName(); } } return super.beforeInvoke(receiver, method, args); } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/handle/IGraphicsStatsHookHandle.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.handle; import android.content.Context; import android.text.TextUtils; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.HookedMethodHandler; import java.lang.reflect.Method; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/6/18. */ public class IGraphicsStatsHookHandle extends BaseHookHandle { public IGraphicsStatsHookHandle(Context hostContext) { super(hostContext); } @Override protected void init() { sHookedMethodHandlers.put("requestBufferForProcess", new requestBufferForProcess(mHostContext)); } private class requestBufferForProcess extends HookedMethodHandler { public requestBufferForProcess(Context hostContext) { super(hostContext); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { int index = 0; if (args != null && args.length > index && args[index] instanceof String) { String pkg = ((String) args[index]); if (!TextUtils.equals(pkg, mHostContext.getPackageName())) { args[index] = mHostContext.getPackageName(); } } return super.beforeInvoke(receiver, method, args); } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/handle/IInputMethodManagerHookHandle.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.handle; import android.content.Context; import android.text.TextUtils; import android.view.inputmethod.EditorInfo; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.HookedMethodHandler; import java.lang.reflect.Method; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/6/4. */ public class IInputMethodManagerHookHandle extends BaseHookHandle { public IInputMethodManagerHookHandle(Context hostContext) { super(hostContext); } @Override protected void init() { sHookedMethodHandlers.put("startInput", new startInput(mHostContext)); sHookedMethodHandlers.put("windowGainedFocus", new windowGainedFocus(mHostContext)); sHookedMethodHandlers.put("startInputOrWindowGainedFocus", new startInputOrWindowGainedFocus(mHostContext)); } private class IInputMethodManagerHookedMethodHandler extends HookedMethodHandler { public IInputMethodManagerHookedMethodHandler(Context hostContext) { super(hostContext); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { if (args != null && args.length > 0) { for (Object arg : args) { if (arg instanceof EditorInfo) { EditorInfo info = ((EditorInfo) arg); if (!TextUtils.equals(mHostContext.getPackageName(), info.packageName)) { info.packageName = mHostContext.getPackageName(); } } } } return super.beforeInvoke(receiver, method, args); } } private class startInput extends IInputMethodManagerHookedMethodHandler { public startInput(Context hostContext) { super(hostContext); } } private class windowGainedFocus extends IInputMethodManagerHookedMethodHandler { public windowGainedFocus(Context hostContext) { super(hostContext); } } private class startInputOrWindowGainedFocus extends IInputMethodManagerHookedMethodHandler { public startInputOrWindowGainedFocus(Context hostContext) { super(hostContext); } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/handle/ILocationManagerHookHandle.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.handle; import android.content.Context; import com.morgoo.droidplugin.hook.BaseHookHandle; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2016/2/25. */ public class ILocationManagerHookHandle extends BaseHookHandle { public ILocationManagerHookHandle(Context hostContext) { super(hostContext); } @Override protected void init() { sHookedMethodHandlers.put("requestLocationUpdates", new requestLocationUpdates(mHostContext)); sHookedMethodHandlers.put("removeUpdates", new removeUpdates(mHostContext)); sHookedMethodHandlers.put("requestGeofence", new requestGeofence(mHostContext)); sHookedMethodHandlers.put("removeGeofence", new removeGeofence(mHostContext)); sHookedMethodHandlers.put("getLastLocation", new getLastLocation(mHostContext)); sHookedMethodHandlers.put("addGpsStatusListener", new addGpsStatusListener(mHostContext)); sHookedMethodHandlers.put("removeGpsStatusListener", new removeGpsStatusListener(mHostContext)); sHookedMethodHandlers.put("geocoderIsPresent", new geocoderIsPresent(mHostContext)); } private static class BaseILocationManagerHookedMethodHandler extends ReplaceCallingPackageHookedMethodHandler { public BaseILocationManagerHookedMethodHandler(Context hostContext) { super(hostContext); } } private class requestLocationUpdates extends BaseILocationManagerHookedMethodHandler { public requestLocationUpdates(Context hostContext) { super(hostContext); } } private class removeUpdates extends BaseILocationManagerHookedMethodHandler { public removeUpdates(Context hostContext) { super(hostContext); } } private class requestGeofence extends BaseILocationManagerHookedMethodHandler { public requestGeofence(Context hostContext) { super(hostContext); } } private class removeGeofence extends BaseILocationManagerHookedMethodHandler { public removeGeofence(Context hostContext) { super(hostContext); } } private class getLastLocation extends BaseILocationManagerHookedMethodHandler { public getLastLocation(Context hostContext) { super(hostContext); } } private class addGpsStatusListener extends BaseILocationManagerHookedMethodHandler { public addGpsStatusListener(Context hostContext) { super(hostContext); } } private class removeGpsStatusListener extends BaseILocationManagerHookedMethodHandler { public removeGpsStatusListener(Context hostContext) { super(hostContext); } } private class geocoderIsPresent extends BaseILocationManagerHookedMethodHandler { public geocoderIsPresent(Context hostContext) { super(hostContext); } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/handle/IMediaRouterServiceHookHandle.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.handle; import android.content.Context; import android.text.TextUtils; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.HookedMethodHandler; import java.lang.reflect.Method; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/3/6. */ public class IMediaRouterServiceHookHandle extends BaseHookHandle { public IMediaRouterServiceHookHandle(Context hostContext) { super(hostContext); } @Override protected void init() { // public void registerClientAsUser(IMediaRouterClient client, String packageName, int userId); // public void unregisterClient(IMediaRouterClient client); // public MediaRouterClientState getState(IMediaRouterClient client); // public void setDiscoveryRequest(IMediaRouterClient client, int routeTypes, boolean activeScan); // public void setSelectedRoute(IMediaRouterClient client, String routeId, boolean explicit); // public void requestSetVolume(IMediaRouterClient client, String routeId, int volume); // public void requestUpdateVolume(IMediaRouterClient client, String routeId, int direction); sHookedMethodHandlers.put("registerClientAsUser", new registerClientAsUser(mHostContext)); } private class registerClientAsUser extends HookedMethodHandler { public registerClientAsUser(Context context) { super(context); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { int index = 1; if (args != null && args.length > index && args[index] instanceof String) { String pkg = (String) args[index]; if (!TextUtils.equals(pkg, mHostContext.getPackageName())) { args[index] = mHostContext.getPackageName(); } } return super.beforeInvoke(receiver, method, args); } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/handle/IMmsHookHandle.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.handle; import android.content.Context; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.HookedMethodHandler; import com.morgoo.helper.compat.IMmsCompat; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2016/5/9. */ public class IMmsHookHandle extends BaseHookHandle { public IMmsHookHandle(Context hostContext) { super(hostContext); } @Override protected void init() { // interface IMms { // void sendMessage(int subId, String callingPkg, in Uri contentUri, // String locationUrl, in Bundle configOverrides, in PendingIntent sentIntent); // void downloadMessage(int subId, String callingPkg, String locationUrl, // in Uri contentUri, in Bundle configOverrides, // in PendingIntent downloadedIntent); // Bundle getCarrierConfigValues(int subId); // Uri importTextMessage(String callingPkg, String address, int type, String text, // long timestampMillis, boolean seen, boolean read); // Uri importMultimediaMessage(String callingPkg, in Uri contentUri, String messageId, // long timestampSecs, boolean seen, boolean read); // boolean deleteStoredMessage(String callingPkg, in Uri messageUri); /// boolean deleteStoredConversation(String callingPkg, long conversationId); // boolean updateStoredMessageStatus(String callingPkg, in Uri messageUri, // in ContentValues statusValues); // boolean archiveStoredConversation(String callingPkg, long conversationId, boolean archived); // Uri addTextMessageDraft(String callingPkg, String address, String text); // Uri addMultimediaMessageDraft(String callingPkg, in Uri contentUri); // void sendStoredMessage(int subId, String callingPkg, in Uri messageUri, // in Bundle configOverrides, in PendingIntent sentIntent); // void setAutoPersisting(String callingPkg, boolean enabled); // boolean getAutoPersisting(); // } sHookedMethodHandlers.put("sendMessage", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("downloadMessage", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getCarrierConfigValues", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("importTextMessage", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("importMultimediaMessage", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("deleteStoredMessage", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("deleteStoredConversation", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("updateStoredMessageStatus", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("archiveStoredConversation", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("addTextMessageDraft", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("addMultimediaMessageDraft", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("sendStoredMessage", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("setAutoPersisting", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getAutoPersisting", new MyBaseHandler(mHostContext)); addAllMethodFromHookedClass(); } @Override protected Class getHookedClass() throws ClassNotFoundException { return IMmsCompat.Class(); } @Override protected HookedMethodHandler newBaseHandler() throws ClassNotFoundException { return new MyBaseHandler(mHostContext); } private static class MyBaseHandler extends ReplaceCallingPackageHookedMethodHandler { public MyBaseHandler(Context context) { super(context); } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/handle/IMountServiceHookHandle.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.handle; import android.content.Context; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.text.TextUtils; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.HookedMethodHandler; import com.morgoo.droidplugin.pm.PluginManager; import com.morgoo.helper.Utils; import java.lang.reflect.Method; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/3/6. */ public class IMountServiceHookHandle extends BaseHookHandle { // private static final String ANDROID_DATA = "Android/data/"; // private static final String ANDROID_OBB = "Android/obb/"; public IMountServiceHookHandle(Context context) { super(context); } @Override protected void init() { sHookedMethodHandlers.put("mkdirs", new mkdirs(mHostContext)); } private class mkdirs extends HookedMethodHandler { public mkdirs(Context context) { super(context); } // /sdcard/Android/data/com.example.plugin/fdfdfdfd.fdfd // /sdcard/Android/data/hostpackagename/Plugin/com.example.plugin/fdfdfdfd.fdfd @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) { final int index = 0; if (args != null && args.length > index && args[index] instanceof String) { String callingPkg = (String) args[index]; if (!TextUtils.equals(callingPkg, mHostContext.getPackageName())) { args[index] = mHostContext.getPackageName(); } } //FIXME 这里这种暴力修改方式可能会产生问题。比如插件直接写死的情况。 final int index1 = 1; if (args != null && args.length > index1 && args[index1] instanceof String) { String path = (String) args[index1]; // String path1 = new File(Environment.getExternalStorageDirectory(), "Android/data/").getPath(); if (path != null && path.indexOf(mHostContext.getPackageName()) < 0) { String[] dirs = path.split("/"); if (dirs != null && dirs.length > 0) { String pluginPackageName = null; for (int i = 0; i < dirs.length; i++) { String str = dirs[i]; if (TextUtils.isEmpty(str)) { continue; } if (!Utils.validateJavaIdentifier(str)) { continue; } if (PluginManager.getInstance().isPluginPackage(str)) { pluginPackageName = str; break; } } if (pluginPackageName != null) { path = path.replaceFirst(pluginPackageName, mHostContext.getPackageName() + "/Plugin/" + pluginPackageName); args[index1] = path; } } } } } else { //FIXME 这里这种暴力修改方式可能会产生问题。比如插件直接写死的情况。 final int index1 = 0; if (args != null && args.length > index1 && args[index1] instanceof String) { String path = (String) args[index1]; // String path1 = new File(Environment.getExternalStorageDirectory(), "Android/data/").getPath(); if (path != null && path.indexOf(mHostContext.getPackageName()) < 0) { String[] dirs = path.split("/"); if (dirs != null && dirs.length > 0) { String pluginPackageName = null; for (int i = 0; i < dirs.length; i++) { String str = dirs[i]; if (TextUtils.isEmpty(str)) { continue; } if (!Utils.validateJavaIdentifier(str)) { continue; } if (PluginManager.getInstance().isPluginPackage(str)) { pluginPackageName = str; break; } } if (pluginPackageName != null) { path = path.replaceFirst(pluginPackageName, mHostContext.getPackageName() + "/Plugin/" + pluginPackageName); args[index1] = path; } } } } } return super.beforeInvoke(receiver, method, args); } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/handle/INotificationManagerHookHandle.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.handle; import android.app.Application; import android.app.Notification; import android.content.Context; import android.content.pm.ApplicationInfo; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.PixelFormat; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.text.TextUtils; import android.view.View; import android.widget.RemoteViews; import com.morgoo.droidplugin.core.PluginProcessManager; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.HookedMethodHandler; import com.morgoo.droidplugin.pm.PluginManager; import com.morgoo.droidplugin.reflect.FieldUtils; import com.morgoo.helper.Log; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/2/28. */ public class INotificationManagerHookHandle extends BaseHookHandle { private static final String TAG = INotificationManagerHookHandle.class.getSimpleName(); public INotificationManagerHookHandle(Context context) { super(context); } @Override protected void init() { init1(); sHookedMethodHandlers.put("enqueueNotification", new enqueueNotification(mHostContext)); sHookedMethodHandlers.put("cancelNotification", new cancelNotification(mHostContext)); sHookedMethodHandlers.put("cancelAllNotifications", new cancelAllNotifications(mHostContext)); sHookedMethodHandlers.put("enqueueToast", new enqueueToast(mHostContext)); sHookedMethodHandlers.put("cancelToast", new cancelToast(mHostContext)); sHookedMethodHandlers.put("enqueueNotificationWithTag", new enqueueNotificationWithTag(mHostContext)); sHookedMethodHandlers.put("enqueueNotificationWithTagPriority", new enqueueNotificationWithTagPriority(mHostContext)); sHookedMethodHandlers.put("cancelNotificationWithTag", new cancelNotificationWithTag(mHostContext)); sHookedMethodHandlers.put("setNotificationsEnabledForPackage", new setNotificationsEnabledForPackage(mHostContext)); sHookedMethodHandlers.put("areNotificationsEnabledForPackage", new areNotificationsEnabledForPackage(mHostContext)); } // public void cancelAllNotifications(String pkg, int userId); // public void enqueueToast(String pkg, ITransientNotification callback, int duration); // public void cancelToast(String pkg, ITransientNotification callback); // public void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id, Notification notification, int[] idReceived, int userId); // public void cancelNotificationWithTag(String pkg, String tag, int id, int userId); // public void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled); // public boolean areNotificationsEnabledForPackage(String pkg, int uid); // public void setPackagePriority(String pkg, int uid, int priority); // public int getPackagePriority(String pkg, int uid); // public void setPackageVisibilityOverride(String pkg, int uid, int visibility); // public int getPackageVisibilityOverride(String pkg, int uid); // public StatusBarNotification[] getActiveNotifications(String callingPkg); // public StatusBarNotification[] getHistoricalNotifications(String callingPkg, int count); // public void registerListener(INotificationListener listener, ComponentName component, int userid); // public void unregisterListener(INotificationListener listener, int userid); // public void cancelNotificationFromListener(INotificationListener token, String pkg, String tag, int id); // public void cancelNotificationsFromListener(INotificationListener token, String[] keys); // public pm.ParceledListSlice getActiveNotificationsFromListener(INotificationListener token, String[] keys, int trim); // public void requestHintsFromListener(INotificationListener token, int hints); // public int getHintsFromListener(INotificationListener token); // public void requestInterruptionFilterFromListener(INotificationListener token, int interruptionFilter); // public int getInterruptionFilterFromListener(INotificationListener token); // public void setOnNotificationPostedTrimFromListener(INotificationListener token, int trim); // public ComponentName getEffectsSuppressor(); // public boolean matchesCallFilter(android.os.Bundle extras); // public ZenModeConfig getZenModeConfig(); // public boolean setZenModeConfig(ZenModeConfig config); // public void notifyConditions(String pkg, IConditionProvider provider, Condition[] conditions); // public void requestZenModeConditions(IConditionListener callback, int relevance); // public void setZenModeCondition(Condition condition); // public void setAutomaticZenModeConditions(android.net.Uri[] conditionIds); // public Condition[] getAutomaticZenModeConditions(); private static Map sSystemLayoutResIds = new HashMap(0); private static void init1() { try { //read all com.android.internal.R Class clazz = Class.forName("com.android.internal.R$layout"); Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { //public static final if (Modifier.isPublic(field.getModifiers()) && Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers())) { try { int id = field.getInt(null); sSystemLayoutResIds.put(id, field.getName()); } catch (IllegalAccessException e) { Log.e(TAG, "read com.android.internal.R$layout.%s", e, field.getName()); } } } } catch (Exception e) { Log.e(TAG, "read com.android.internal.R$layout", e); } } public static int getResIdByName(String name) { for (Integer integer : sSystemLayoutResIds.keySet()) { if (TextUtils.equals(name, sSystemLayoutResIds.get(integer))) { return integer; } } return -1; } private class MyNotification extends HookedMethodHandler { public MyNotification(Context context) { super(context); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { int index = 0; if (args != null && args.length > index) { if (args[index] instanceof String) { String pkg = (String) args[index]; if (!TextUtils.equals(pkg, mHostContext.getPackageName())) { args[index] = mHostContext.getPackageName(); } } } return super.beforeInvoke(receiver, method, args); } } private int findFisrtNotificationIndex(Object[] args) { if (args != null && args.length > 0) { for (int i = 0; i < args.length; i++) { if (args[i] instanceof Notification) { return i; } } } return -1; } private class enqueueNotification extends MyNotification { public enqueueNotification(Context context) { super(context); } //2.3.2_r1, 4.0.1_r1 /* public void enqueueNotification(String pkg, int id, Notification notification, int[] idReceived);*/ @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { final int index = 0; if (args != null && args.length > index && args[index] instanceof String) { String pkg = (String) args[index]; if (!TextUtils.equals(pkg, mHostContext.getPackageName())) { args[index] = mHostContext.getPackageName(); } } final int index2 = findFisrtNotificationIndex(args); if (index2 >= 0) { Notification notification = (Notification) args[index2];//nobug if (isPluginNotification(notification)) { if (shouldBlock(notification)) { Log.e(TAG, "We has blocked a notification[%s]", notification); return true; } else { //这里要修改通知。 hackNotification(notification); return false; } } } return false; } } private void hackRemoteViews(RemoteViews remoteViews) throws IllegalAccessException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException { if (remoteViews != null && !TextUtils.equals(remoteViews.getPackage(), mHostContext.getPackageName())) { if (sSystemLayoutResIds.containsKey(remoteViews.getLayoutId())) { Object mActionsObj = FieldUtils.readField(remoteViews, "mActions"); if (mActionsObj instanceof Collection) { Collection mActions = (Collection) mActionsObj; String aPackage = remoteViews.getPackage(); Application pluginContent = PluginProcessManager.getPluginContext(aPackage); if (pluginContent != null) { Iterator iterable = mActions.iterator(); Class TextViewDrawableActionClass = null; try { if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) { TextViewDrawableActionClass = Class.forName(RemoteViews.class.getName() + "$TextViewDrawableAction"); } } catch (ClassNotFoundException e) { } Class ReflectionActionClass = Class.forName(RemoteViews.class.getName() + "$ReflectionAction"); while (iterable.hasNext()) { Object action = iterable.next(); if (ReflectionActionClass.isInstance(action)) {//???这里这样是对的么? String methodName = (String) FieldUtils.readField(action, "methodName"); //String methodName;,int type; Object value; if ("setImageResource".equals(methodName)) { //setInt(viewId, "setImageResource", srcId); Object BITMAP = FieldUtils.readStaticField(action.getClass(), "BITMAP"); int resId = (Integer) FieldUtils.readField(action, "value"); Bitmap bitmap = BitmapFactory.decodeResource(pluginContent.getResources(), resId); FieldUtils.writeField(action, "type", BITMAP); FieldUtils.writeField(action, "value", bitmap); FieldUtils.writeField(action, "methodName", "setImageBitmap"); } else if ("setImageURI".equals(methodName)) {//setUri(viewId, "setImageURI", uri); iterable.remove(); //TODO RemoteViews.setImageURI 其实应该适配的。 } else if ("setLabelFor".equals(methodName)) { iterable.remove(); //TODO RemoteViews.setLabelFor 其实应该适配的。 } } else if (TextViewDrawableActionClass != null && TextViewDrawableActionClass.isInstance(action)) { iterable.remove(); // if ("setTextViewCompoundDrawables".equals(methodName)) { // iterable.remove(); //TODO RemoteViews.setTextViewCompoundDrawables 其实应该适配的。 // } else if ("setTextViewCompoundDrawablesRelative".equals(methodName)) { // iterable.remove(); //TODO RemoteViews.setTextViewCompoundDrawablesRelative 其实应该适配的。 // } } } } } } if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { FieldUtils.writeField(remoteViews, "mApplication", mHostContext.getApplicationInfo()); } else { FieldUtils.writeField(remoteViews, "mPackage", mHostContext.getPackageName()); } } } private boolean shouldBlockByRemoteViews(RemoteViews remoteViews) { if (remoteViews == null) { return false; } else if (remoteViews != null && sSystemLayoutResIds.containsKey(remoteViews.getLayoutId())) { return false; } else { return true; } } private boolean shouldBlock(Notification notification) { if (shouldBlockByRemoteViews(notification.contentView)) { return true; } if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { if (shouldBlockByRemoteViews(notification.tickerView)) { return true; } } if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) { if (shouldBlockByRemoteViews(notification.bigContentView)) { return true; } } if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { if (shouldBlockByRemoteViews(notification.headsUpContentView)) { return true; } } return false; } private boolean isPluginNotification(Notification notification) { if (notification == null) { return false; } if (notification.contentView != null && !TextUtils.equals(mHostContext.getPackageName(), notification.contentView.getPackage())) { return true; } if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { if (notification.tickerView != null && !TextUtils.equals(mHostContext.getPackageName(), notification.tickerView.getPackage())) { return true; } } if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) { if (notification.bigContentView != null && !TextUtils.equals(mHostContext.getPackageName(), notification.bigContentView.getPackage())) { return true; } } if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { if (notification.headsUpContentView != null && !TextUtils.equals(mHostContext.getPackageName(), notification.headsUpContentView.getPackage())) { return true; } } if (VERSION.SDK_INT >= VERSION_CODES.M) { android.graphics.drawable.Icon icon = notification.getSmallIcon(); if (icon != null) { try { Object mString1Obj = FieldUtils.readField(icon, "mString1", true); if (mString1Obj instanceof String) { String mString1 = ((String) mString1Obj); if (PluginManager.getInstance().isPluginPackage(mString1)) { return true; } } } catch (Exception e) { Log.e(TAG, "fix Icon.smallIcon", e); } } } if (VERSION.SDK_INT >= VERSION_CODES.M) { android.graphics.drawable.Icon icon = notification.getLargeIcon(); if (icon != null) { try { Object mString1Obj = FieldUtils.readField(icon, "mString1", true); if (mString1Obj instanceof String) { String mString1 = ((String) mString1Obj); if (PluginManager.getInstance().isPluginPackage(mString1)) { return true; } } } catch (Exception e) { Log.e(TAG, "fix Icon.smallIcon", e); } } } try { Bundle mExtras = (Bundle) FieldUtils.readField(notification, "extras", true); for (String s : mExtras.keySet()) { if (mExtras.get(s) != null && mExtras.get(s) instanceof ApplicationInfo) { ApplicationInfo applicationInfo = (ApplicationInfo) mExtras.get(s); return !TextUtils.equals(mHostContext.getPackageName(), applicationInfo.packageName); } } } catch (Exception e) { e.printStackTrace(); } return false; } private Bitmap drawableToBitMap(Drawable drawable) { if (drawable instanceof BitmapDrawable) { BitmapDrawable bitmapDrawable = ((BitmapDrawable) drawable); return bitmapDrawable.getBitmap(); } else { Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565); Canvas canvas = new Canvas(bitmap); drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); drawable.draw(canvas); return bitmap; } } private void hackNotification(Notification notification) throws IllegalAccessException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException { // remoteViews com.android.internal.R.layout.notification_template_material_media // com.android.internal.R.layout.notification_template_material_big_media_narrow; // com.android.internal.R.layout.notification_template_material_big_media; // //getBaseLayoutResource // R.layout.notification_template_material_base; // //getBigBaseLayoutResource // R.layout.notification_template_material_big_base; // //getBigPictureLayoutResource // R.layout.notification_template_material_big_picture; // //getBigTextLayoutResource // R.layout.notification_template_material_big_text; // //getInboxLayoutResource // R.layout.notification_template_material_inbox; // //getActionLayoutResource // R.layout.notification_material_action; // //getActionTombstoneLayoutResource // R.layout.notification_material_action_tombstone; if (notification != null) { notification.icon = mHostContext.getApplicationInfo().icon; if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { hackRemoteViews(notification.tickerView); } hackRemoteViews(notification.contentView); if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) { hackRemoteViews(notification.bigContentView); } if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { hackRemoteViews(notification.headsUpContentView); } if (VERSION.SDK_INT >= VERSION_CODES.M) { android.graphics.drawable.Icon icon = notification.getSmallIcon(); if (icon != null) { Bitmap bitmap = drawableToBitMap(icon.loadDrawable(mHostContext)); if (bitmap != null) { android.graphics.drawable.Icon newIcon = android.graphics.drawable.Icon.createWithBitmap(bitmap); FieldUtils.writeField(notification, "mSmallIcon", newIcon, true); } } } if (VERSION.SDK_INT >= VERSION_CODES.M) { android.graphics.drawable.Icon icon = notification.getLargeIcon(); if (icon != null) { Bitmap bitmap = drawableToBitMap(icon.loadDrawable(mHostContext)); if (bitmap != null) { android.graphics.drawable.Icon newIcon = android.graphics.drawable.Icon.createWithBitmap(bitmap); FieldUtils.writeField(notification, "mLargeIcon", newIcon, true); } } } } } private class cancelNotification extends MyNotification { public cancelNotification(Context context) { super(context); } //2.3.2_r1, 4.0.1_r1 /* public void cancelNotification(String pkg, int id);*/ } private class cancelAllNotifications extends MyNotification { public cancelAllNotifications(Context context) { super(context); } //2.3.2_r1, 4.0.1_r1 /* public void cancelAllNotifications(String pkg);*/ } private class enqueueToast extends MyNotification { public enqueueToast(Context context) { super(context); } //2.3.2_r1, 4.0.1_r1 /* public void enqueueToast(String pkg, ITransientNotification callback, int duration) ;*/ @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //这里适配在android 5.0的机器上无法现实toast的问题。但是我也不知道还有那些机器需要这样做。 if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) { int index = 1; if (args != null && args.length > index) { Object obj = args[index]; View view = (View) FieldUtils.readField(obj, "mView"); View nextView = (View) FieldUtils.readField(obj, "mNextView"); if (nextView != null) { FieldUtils.writeField(nextView, "mContext", mHostContext); } if (view != null) { FieldUtils.writeField(view, "mContext", mHostContext); } } } return super.beforeInvoke(receiver, method, args); } } private class cancelToast extends MyNotification { public cancelToast(Context context) { super(context); } //2.3.2_r1, 4.0.1_r1 /* public void cancelToast(String pkg, ITransientNotification callback);*/ @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { int index = 1; if (args != null && args.length > index) { Object obj = args[index]; View view = (View) FieldUtils.readField(obj, "mView"); View nextView = (View) FieldUtils.readField(obj, "mNextView"); if (nextView != null) { FieldUtils.writeField(nextView, "mContext", mHostContext); } if (view != null) { FieldUtils.writeField(view, "mContext", mHostContext); } } return super.beforeInvoke(receiver, method, args); } } private class enqueueNotificationWithTag extends MyNotification { public enqueueNotificationWithTag(Context context) { super(context); } //2.3.2_r1, 4.0.1_r1 /*public void enqueueNotificationWithTag(String pkg, String tag, int id, Notification notification, int[] idReceived) ;*/ @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { final int index = 0; if (args != null && args.length > index && args[index] instanceof String) { String pkg = (String) args[index]; if (!TextUtils.equals(pkg, mHostContext.getPackageName())) { args[index] = mHostContext.getPackageName(); } } final int index2 = findFisrtNotificationIndex(args); if (index2 >= 0) { Notification notification = (Notification) args[index2];//nobug if (isPluginNotification(notification)) { if (shouldBlock(notification)) { Log.e(TAG, "We has blocked a notification[%s]", notification); return true; } else { //这里要修改通知。 hackNotification(notification); return false; } } } return false; } } private class enqueueNotificationWithTagPriority extends MyNotification { public enqueueNotificationWithTagPriority(Context context) { super(context); } //4.0.1_r1 /*public void enqueueNotificationWithTagPriority(String pkg, String tag, int id, int priority, Notification notification, int[] idReceived);*/ @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { final int index = 0; if (args != null && args.length > index && args[index] instanceof String) { String pkg = (String) args[index]; if (!TextUtils.equals(pkg, mHostContext.getPackageName())) { args[index] = mHostContext.getPackageName(); } } final int index2 = findFisrtNotificationIndex(args); if (index2 >= 0) { Notification notification = (Notification) args[index2];//nobug if (isPluginNotification(notification)) { if (shouldBlock(notification)) { Log.e(TAG, "We has blocked a notification[%s]", notification); return true; } else { //这里要修改通知。 hackNotification(notification); return false; } } } return false; } } private class cancelNotificationWithTag extends MyNotification { public cancelNotificationWithTag(Context context) { super(context); } //2.3.2_r1, 4.0.1_r1 /* public void cancelNotificationWithTag(String pkg, String tag, int id) ;*/ } private class setNotificationsEnabledForPackage extends MyNotification { public setNotificationsEnabledForPackage(Context context) { super(context); } } private class areNotificationsEnabledForPackage extends MyNotification { public areNotificationsEnabledForPackage(Context context) { super(context); } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/handle/IPackageManagerHookHandle.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.handle; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.os.Build; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.os.Parcelable; import android.os.RemoteException; import android.text.TextUtils; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.HookedMethodHandler; import com.morgoo.droidplugin.pm.PluginManager; import com.morgoo.droidplugin.reflect.MethodUtils; import com.morgoo.helper.Log; import com.morgoo.helper.compat.IPackageDataObserverCompat; import com.morgoo.helper.compat.ParceledListSliceCompat; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/2/28. */ public class IPackageManagerHookHandle extends BaseHookHandle { private static final String TAG = IPackageManagerHookHandle.class.getSimpleName(); public IPackageManagerHookHandle(Context hostContext) { super(hostContext); } @Override protected void init() { sHookedMethodHandlers.put("getPackageInfo", new getPackageInfo(mHostContext)); sHookedMethodHandlers.put("getPackageUid", new getPackageUid(mHostContext)); sHookedMethodHandlers.put("getPackageGids", new getPackageGids(mHostContext)); sHookedMethodHandlers.put("currentToCanonicalPackageNames", new currentToCanonicalPackageNames(mHostContext)); sHookedMethodHandlers.put("canonicalToCurrentPackageNames", new canonicalToCurrentPackageNames(mHostContext)); sHookedMethodHandlers.put("getPermissionInfo", new getPermissionInfo(mHostContext)); sHookedMethodHandlers.put("queryPermissionsByGroup", new queryPermissionsByGroup(mHostContext)); sHookedMethodHandlers.put("getPermissionGroupInfo", new getPermissionGroupInfo(mHostContext)); sHookedMethodHandlers.put("getAllPermissionGroups", new getAllPermissionGroups(mHostContext)); sHookedMethodHandlers.put("getApplicationInfo", new getApplicationInfo(mHostContext)); sHookedMethodHandlers.put("getActivityInfo", new getActivityInfo(mHostContext)); sHookedMethodHandlers.put("getReceiverInfo", new getReceiverInfo(mHostContext)); sHookedMethodHandlers.put("getServiceInfo", new getServiceInfo(mHostContext)); sHookedMethodHandlers.put("getProviderInfo", new getProviderInfo(mHostContext)); sHookedMethodHandlers.put("checkPermission", new checkPermission(mHostContext)); sHookedMethodHandlers.put("checkUidPermission", new checkUidPermission(mHostContext)); sHookedMethodHandlers.put("addPermission", new addPermission(mHostContext)); sHookedMethodHandlers.put("removePermission", new removePermission(mHostContext)); sHookedMethodHandlers.put("grantPermission", new grantPermission(mHostContext)); sHookedMethodHandlers.put("revokePermission", new revokePermission(mHostContext)); sHookedMethodHandlers.put("checkSignatures", new checkSignatures(mHostContext)); sHookedMethodHandlers.put("getPackagesForUid", new getPackagesForUid(mHostContext)); sHookedMethodHandlers.put("getNameForUid", new getNameForUid(mHostContext)); sHookedMethodHandlers.put("getUidForSharedUser", new getUidForSharedUser(mHostContext)); sHookedMethodHandlers.put("getFlagsForUid", new getFlagsForUid(mHostContext)); sHookedMethodHandlers.put("resolveIntent", new resolveIntent(mHostContext)); sHookedMethodHandlers.put("queryIntentActivities", new queryIntentActivities(mHostContext)); sHookedMethodHandlers.put("queryIntentActivityOptions", new queryIntentActivityOptions(mHostContext)); sHookedMethodHandlers.put("queryIntentReceivers", new queryIntentReceivers(mHostContext)); sHookedMethodHandlers.put("resolveService", new resolveService(mHostContext)); sHookedMethodHandlers.put("queryIntentServices", new queryIntentServices(mHostContext)); sHookedMethodHandlers.put("queryIntentContentProviders", new queryIntentContentProviders(mHostContext)); sHookedMethodHandlers.put("getInstalledPackages", new getInstalledPackages(mHostContext)); sHookedMethodHandlers.put("getPackagesHoldingPermissions", new getPackagesHoldingPermissions(mHostContext)); sHookedMethodHandlers.put("getInstalledApplications", new getInstalledApplications(mHostContext)); sHookedMethodHandlers.put("getPersistentApplications", new getPersistentApplications(mHostContext)); sHookedMethodHandlers.put("resolveContentProvider", new resolveContentProvider(mHostContext)); sHookedMethodHandlers.put("querySyncProviders", new querySyncProviders(mHostContext)); sHookedMethodHandlers.put("queryContentProviders", new queryContentProviders(mHostContext)); sHookedMethodHandlers.put("getInstrumentationInfo", new getInstrumentationInfo(mHostContext)); sHookedMethodHandlers.put("queryInstrumentation", new queryInstrumentation(mHostContext)); sHookedMethodHandlers.put("getInstallerPackageName", new getInstallerPackageName(mHostContext)); sHookedMethodHandlers.put("addPackageToPreferred", new addPackageToPreferred(mHostContext)); sHookedMethodHandlers.put("removePackageFromPreferred", new removePackageFromPreferred(mHostContext)); sHookedMethodHandlers.put("getPreferredPackages", new getPreferredPackages(mHostContext)); sHookedMethodHandlers.put("resetPreferredActivities", new resetPreferredActivities(mHostContext)); sHookedMethodHandlers.put("getLastChosenActivity", new getLastChosenActivity(mHostContext)); sHookedMethodHandlers.put("setLastChosenActivity", new setLastChosenActivity(mHostContext)); sHookedMethodHandlers.put("addPreferredActivity", new addPreferredActivity(mHostContext)); sHookedMethodHandlers.put("replacePreferredActivity", new replacePreferredActivity(mHostContext)); sHookedMethodHandlers.put("clearPackagePreferredActivities", new clearPackagePreferredActivities(mHostContext)); sHookedMethodHandlers.put("getPreferredActivities", new getPreferredActivities(mHostContext)); sHookedMethodHandlers.put("getHomeActivities", new getHomeActivities(mHostContext)); sHookedMethodHandlers.put("setComponentEnabledSetting", new setComponentEnabledSetting(mHostContext)); sHookedMethodHandlers.put("getComponentEnabledSetting", new getComponentEnabledSetting(mHostContext)); sHookedMethodHandlers.put("setApplicationEnabledSetting", new setApplicationEnabledSetting(mHostContext)); sHookedMethodHandlers.put("getApplicationEnabledSetting", new getApplicationEnabledSetting(mHostContext)); sHookedMethodHandlers.put("setPackageStoppedState", new setPackageStoppedState(mHostContext)); sHookedMethodHandlers.put("deleteApplicationCacheFiles", new deleteApplicationCacheFiles(mHostContext)); sHookedMethodHandlers.put("clearApplicationUserData", new clearApplicationUserData(mHostContext)); sHookedMethodHandlers.put("getPackageSizeInfo", new getPackageSizeInfo(mHostContext)); sHookedMethodHandlers.put("performDexOpt", new performDexOpt(mHostContext)); sHookedMethodHandlers.put("movePackage", new movePackage(mHostContext)); } private class getPackageInfo extends HookedMethodHandler { public getPackageInfo(Context context) { super(context); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 2.3, 4.01, 4.0.3_r1 /*public PackageInfo getPackageInfo(String packageName, int flags) throws RemoteException;*/ //API 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /*public PackageInfo getPackageInfo(String packageName, int flags, int userId) throws RemoteException;*/ if (args != null) { final int index0 = 0, index1 = 1; String packageName = null; if (args.length > index0) { if (args[index0] != null && args[index0] instanceof String) { packageName = (String) args[index0]; } } int flags = 0; if (args.length > index1) { if (args[index1] != null && args[index1] instanceof Integer) { flags = (Integer) args[index1]; } } if (packageName != null) { PackageInfo packageInfo = null; try { packageInfo = PluginManager.getInstance().getPackageInfo(packageName, flags); } catch (Exception e) { e.printStackTrace(); } if (packageInfo != null) { setFakedResult(packageInfo); return true; } else { Log.i(TAG, "getPackageInfo(%s) fail,pkginfo is null", packageName); } } } return super.beforeInvoke(receiver, method, args); } } private class getPackageUid extends HookedMethodHandler { public getPackageUid(Context context) { super(context); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 2.3, 4.01_r1, 4.0.3_r1 /*public int getPackageUid(String packageName) throws RemoteException;*/ //API 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /*public int getPackageUid(String packageName, int userId) throws RemoteException;*/ if (args != null) { final int index0 = 0; String packageName = null; if (args.length > index0) { if (args[index0] != null && args[index0] instanceof String) { packageName = (String) args[index0]; } if (packageName != null && PluginManager.getInstance().isPluginPackage(packageName)) { args[index0] = mHostContext.getPackageName(); } } } return super.beforeInvoke(receiver, method, args); } } private class getPackageGids extends HookedMethodHandler { public getPackageGids(Context context) { super(context); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 2.3, 4.01_r1, 4.0.3_r1, 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /*public int[] getPackageGids(String packageName) throws RemoteException;*/ if (args != null) { final int index0 = 0; String packageName = null; if (args.length > index0) { if (args[index0] != null && args[index0] instanceof String) { packageName = (String) args[index0]; } if (packageName != null && PluginManager.getInstance().isPluginPackage(packageName)) { args[index0] = mHostContext.getPackageName(); } } } return super.beforeInvoke(receiver, method, args); } } private class currentToCanonicalPackageNames extends HookedMethodHandler { public currentToCanonicalPackageNames(Context context) { super(context); } //API 2.3, 4.01_r1, 4.0.3_r1, 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /*public String[] currentToCanonicalPackageNames(String[] names) throws RemoteException;*/ } private class canonicalToCurrentPackageNames extends HookedMethodHandler { public canonicalToCurrentPackageNames(Context context) { super(context); } //API 2.3, 4.01_r1, 4.0.3_r1, 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /*public String[] canonicalToCurrentPackageNames(String[] names) throws RemoteException;*/ } private class getPermissionInfo extends HookedMethodHandler { public getPermissionInfo(Context context) { super(context); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 2.3, 4.01_r1, 4.0.3_r1, 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /*public PermissionInfo getPermissionInfo(String name, int flags) throws RemoteException;*/ if (args != null) { final int index0 = 0, index1 = 1; if (args.length > 1 && args[index0] instanceof String && args[index1] instanceof Integer) { String packageName = (String) args[index0]; int flags = (Integer) args[index1]; PermissionInfo permissionInfo = PluginManager.getInstance().getPermissionInfo(packageName, flags); if (permissionInfo != null) { setFakedResult(permissionInfo); return true; } } } return super.beforeInvoke(receiver, method, args); } } private class queryPermissionsByGroup extends HookedMethodHandler { public queryPermissionsByGroup(Context context) { super(context); } @Override protected void afterInvoke(Object receiver, Method method, Object[] args, Object invokeResult) throws Throwable { //API 2.3, 4.01_r1, 4.0.3_r1, 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /*public List queryPermissionsByGroup(String group, int flags) throws RemoteException;*/ if (args != null && invokeResult instanceof List) { final int index0 = 0, index1 = 1; if (args.length > 1 && args[index0] instanceof String && args[index1] instanceof Integer) { String group = (String) args[index0]; int flags = (Integer) args[index1]; List infos = PluginManager.getInstance().queryPermissionsByGroup(group, flags); if (infos != null && infos.size() > 0) { List old = (List) invokeResult; old.addAll(infos); } } } super.afterInvoke(receiver, method, args, invokeResult); } } private class getPermissionGroupInfo extends HookedMethodHandler { public getPermissionGroupInfo(Context context) { super(context); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 2.3, 4.01_r1, 4.0.3_r1, 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /*public PermissionGroupInfo getPermissionGroupInfo(String name, int flags) throws RemoteException;*/ if (args != null) { final int index0 = 0, index1 = 1; if (args.length > 1 && args[index0] instanceof String && args[index1] instanceof Integer) { String name = (String) args[index0]; int flags = (Integer) args[index1]; PermissionGroupInfo permissionInfo = PluginManager.getInstance().getPermissionGroupInfo(name, flags); if (permissionInfo != null) { setFakedResult(permissionInfo); return true; } } } return super.beforeInvoke(receiver, method, args); } } private class getAllPermissionGroups extends HookedMethodHandler { public getAllPermissionGroups(Context context) { super(context); } @Override protected void afterInvoke(Object receiver, Method method, Object[] args, Object invokeResult) throws Throwable { //API 2.3, 4.01_r1, 4.0.3_r1, 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /* public List getAllPermissionGroups(int flags) throws RemoteException;*/ if (args != null && invokeResult instanceof List) { final int index = 0; if (args.length > index && args[index] instanceof Integer) { int flags = (Integer) args[index]; List infos = PluginManager.getInstance().getAllPermissionGroups(flags); if (infos != null && infos.size() > 0) { List old = (List) invokeResult; old.addAll(infos); } } } super.afterInvoke(receiver, method, args, invokeResult); } } private class getApplicationInfo extends HookedMethodHandler { public getApplicationInfo(Context context) { super(context); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 2.3, 4.01, 4.0.3_r1 /* public ApplicationInfo getApplicationInfo(String packageName, int flags) throws RemoteException;*/ //API 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /* public ApplicationInfo getApplicationInfo(String packageName, int flags, int userId) throws RemoteException;*/ if (args != null) { final int index0 = 0, index1 = 1; if (args.length >= 2 && args[index0] instanceof String && args[index1] instanceof Integer) { String packageName = (String) args[index0]; int flags = (Integer) args[index1]; ApplicationInfo info = PluginManager.getInstance().getApplicationInfo(packageName, flags); if (info != null) { setFakedResult(info); return true; } } } return super.beforeInvoke(receiver, method, args); } } private class getActivityInfo extends HookedMethodHandler { public getActivityInfo(Context context) { super(context); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 2.3, 4.01, 4.0.3_r1 /* public ActivityInfo getActivityInfo(ComponentName className, int flags) throws RemoteException;*/ //API 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /*public ActivityInfo getActivityInfo(ComponentName className, int flags, int userId) throws RemoteException;*/ if (args != null) { final int index0 = 0, index1 = 1; if (args.length >= 2 && args[index0] instanceof ComponentName && args[index1] instanceof Integer) { ComponentName className = (ComponentName) args[index0]; int flags = (Integer) args[index1]; ActivityInfo info = PluginManager.getInstance().getActivityInfo(className, flags); if (info != null) { setFakedResult(info); return true; } } } return super.beforeInvoke(receiver, method, args); } } private class getReceiverInfo extends HookedMethodHandler { public getReceiverInfo(Context context) { super(context); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 2.3, 4.01, 4.0.3_r1 /*public ActivityInfo getReceiverInfo(ComponentName className, int flags) throws RemoteException;*/ //API 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /*public ActivityInfo getReceiverInfo(ComponentName className, int flags, int userId) throws RemoteException;*/ if (args != null) { final int index0 = 0, index1 = 1; if (args.length >= 2 && args[index0] instanceof ComponentName && args[index1] instanceof Integer) { ComponentName className = (ComponentName) args[index0]; int flags = (Integer) args[index1]; ActivityInfo info = PluginManager.getInstance().getReceiverInfo(className, flags); if (info != null) { setFakedResult(info); return true; } } } return super.beforeInvoke(receiver, method, args); } } private class getServiceInfo extends HookedMethodHandler { public getServiceInfo(Context context) { super(context); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 2.3, 4.01, 4.0.3_r1 /* public ServiceInfo getServiceInfo(ComponentName className, int flags) throws RemoteException;*/ //API 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /* public ServiceInfo getServiceInfo(ComponentName className, int flags, int userId) throws RemoteException;*/ if (args != null) { final int index0 = 0, index1 = 1; if (args.length >= 2 && args[index0] instanceof ComponentName && args[index1] instanceof Integer) { ComponentName className = (ComponentName) args[index0]; int flags = (Integer) args[index1]; ServiceInfo info = PluginManager.getInstance().getServiceInfo(className, flags); if (info != null) { setFakedResult(info); return true; } } } return super.beforeInvoke(receiver, method, args); } } private class getProviderInfo extends HookedMethodHandler { public getProviderInfo(Context context) { super(context); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 2.3 /*public ProviderInfo getProviderInfo(ComponentName className, int flags) throws RemoteException;*/ //API 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /* public ProviderInfo getProviderInfo(ComponentName className, int flags, int userId) throws RemoteException;*/ if (args != null) { final int index0 = 0, index1 = 1; if (args.length >= 2 && args[index0] instanceof ComponentName && args[index1] instanceof Integer) { ComponentName className = (ComponentName) args[index0]; int flags = (Integer) args[index1]; ProviderInfo info = PluginManager.getInstance().getProviderInfo(className, flags); if (info != null) { setFakedResult(info); return true; } } } return super.beforeInvoke(receiver, method, args); } } private class checkPermission extends HookedMethodHandler { public checkPermission(Context context) { super(context); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 2.3, 4.01_r1, 4.0.3_r1, 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /*public int checkPermission(String permName, String pkgName) throws RemoteException;*/ if (args != null) { final int index = 1; if (args.length > index && args[index] instanceof String) { String packageName = (String) args[index]; if (PluginManager.getInstance().isPluginPackage(packageName)) { args[index] = mHostContext.getPackageName(); } } } return super.beforeInvoke(receiver, method, args); } } private class checkUidPermission extends HookedMethodHandler { public checkUidPermission(Context context) { super(context); } //API 2.3, 4.01_r1, 4.0.3_r1, 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /*public int checkUidPermission(String permName, int uid) throws RemoteException;*/ } private class addPermission extends HookedMethodHandler { public addPermission(Context context) { super(context); } //API 2.3, 4.01_r1, 4.0.3_r1, 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /*public boolean addPermission(PermissionInfo info) throws RemoteException;*/ } private class removePermission extends HookedMethodHandler { public removePermission(Context context) { super(context); } //API 2.3, 4.01_r1, 4.0.3_r1, 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /*public void removePermission(String name) throws RemoteException;*/ } private class grantPermission extends HookedMethodHandler { public grantPermission(Context context) { super(context); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 2.3, 4.01, 4.0.3_r1 //NO //API 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /* public void grantPermission(String packageName, String permissionName) throws RemoteException;*/ if (args != null) { final int index = 0; if (args.length > index && args[index] instanceof String) { String packageName = (String) args[index]; if (PluginManager.getInstance().isPluginPackage(packageName)) { args[index] = mHostContext.getPackageName(); } } } return super.beforeInvoke(receiver, method, args); } } private class revokePermission extends HookedMethodHandler { public revokePermission(Context context) { super(context); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 2.3, 4.01, 4.0.3_r1 //NO //API 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /*public void revokePermission(String packageName, String permissionName) throws RemoteException;*/ if (args != null) { final int index = 0; if (args.length > index && args[index] instanceof String) { String packageName = (String) args[index]; if (PluginManager.getInstance().isPluginPackage(packageName)) { args[index] = mHostContext.getPackageName(); } } } return super.beforeInvoke(receiver, method, args); } } private class checkSignatures extends HookedMethodHandler { public checkSignatures(Context context) { super(context); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 2.3, 4.01_r1, 4.0.3_r1, 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /* public int checkSignatures(String pkg1, String pkg2) throws android.os.RemoteException;*/ final int index0 = 0, index1 = 1; String pkg0 = null, pkg1 = null; if (args != null && args[index0] != null && args[index0] instanceof String) { pkg0 = (String) args[index0]; } if (args != null && args[index1] != null && args[index1] instanceof String) { pkg1 = (String) args[index1]; } if (!TextUtils.isEmpty(pkg0) && !TextUtils.isEmpty(pkg1)) { PluginManager instance = PluginManager.getInstance(); if (instance.isPluginPackage(pkg0) && instance.isPluginPackage(pkg1)) { int result = instance.checkSignatures(pkg0, pkg1); setFakedResult(result); return true; } } return super.beforeInvoke(receiver, method, args); } } private class getPackagesForUid extends HookedMethodHandler { public getPackagesForUid(Context context) { super(context); } //API 2.3, 4.01_r1, 4.0.3_r1, 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /* public String[] getPackagesForUid(int uid) throws RemoteException*/ } private class getNameForUid extends HookedMethodHandler { public getNameForUid(Context context) { super(context); } //API 2.3, 4.01_r1, 4.0.3_r1, 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /*public String getNameForUid(int uid) throws RemoteException;*/ } private class getUidForSharedUser extends HookedMethodHandler { public getUidForSharedUser(Context context) { super(context); } //API 2.3, 4.01_r1, 4.0.3_r1, 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /* public int getUidForSharedUser(String sharedUserName) throws RemoteException;*/ } private class getFlagsForUid extends HookedMethodHandler { public getFlagsForUid(Context context) { super(context); } //API 2.3, 4.01, 4.0.3_r1, 4.1.1_r1, 4.2_r1, 4.3_r1 // NO //API 4.4_r1, 5.0.2_r1 /*public int getFlagsForUid(int uid) throws android.os.RemoteException;*/ } private class resolveIntent extends HookedMethodHandler { public resolveIntent(Context context) { super(context); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 2.3, 4.01, 4.0.3_r1 /* public ResolveInfo resolveIntent(Intent intent, String resolvedType, int flags) throws RemoteException;*/ //API 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /* public ResolveInfo resolveIntent(Intent intent, String resolvedType, int flags, int userId) throws RemoteException;*/ if (args != null) { final int index0 = 0, index1 = 1, index2 = 2; Intent intent = null; if (args.length > index0) { if (args[index0] instanceof Intent) { intent = (Intent) args[index0]; } } String resolvedType = null; if (args.length > index1) { if (args[index1] instanceof String) { resolvedType = (String) args[index1]; } } Integer flags = 0; if (args.length > index2) { if (args[index2] instanceof Integer) { flags = (Integer) args[index2]; } } if (intent != null) { ResolveInfo info = PluginManager.getInstance().resolveIntent(intent, resolvedType, flags); if (info != null) { setFakedResult(info); return true; } } } return super.beforeInvoke(receiver, method, args); } } private class queryIntentActivities extends HookedMethodHandler { public queryIntentActivities(Context context) { super(context); } @Override protected void afterInvoke(Object receiver, Method method, Object[] args, Object invokeResult) throws Throwable { //API 2.3, 4.01, 4.0.3_r1 /* public List queryIntentActivities(Intent intent, String resolvedType, int flags) throws RemoteException;*/ //API 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /* public List queryIntentActivities(Intent intent, String resolvedType, int flags, int userId) throws RemoteException;*/ if (args != null) { final int index0 = 0, index1 = 1, index2 = 2; Intent intent = null; if (args.length > index0) { if (args[index0] instanceof Intent) { intent = (Intent) args[index0]; } } String resolvedType = null; if (args.length > index1) { if (args[index1] instanceof String) { resolvedType = (String) args[index1]; } } Integer flags = 0; if (args.length > index2) { if (args[index2] instanceof Integer) { flags = (Integer) args[index2]; } } if (intent != null) { List infos = PluginManager.getInstance().queryIntentActivities(intent, resolvedType, flags); if (infos != null && infos.size() > 0) { if (invokeResult instanceof List) { List old = (List) invokeResult; old.addAll(infos); } else if (ParceledListSliceCompat.isParceledListSlice(invokeResult)) { Method getListMethod = ParceledListSliceCompat.Class().getMethod("getList"); List data = (List) getListMethod.invoke(invokeResult); data.addAll(infos); } } } } super.afterInvoke(receiver, method, args, invokeResult); } } private class queryIntentActivityOptions extends HookedMethodHandler { public queryIntentActivityOptions(Context context) { super(context); } //API 2.3, 4.01, 4.0.3_r1 /*public List queryIntentActivityOptions(ComponentName caller, Intent[] specifics, String[] specificTypes, Intent intent, String resolvedType, int flags) throws RemoteException;*/ //API 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /* public List queryIntentActivityOptions(ComponentName caller, Intent[] specifics, String[] specificTypes, Intent intent, String resolvedType, int flags, int userId) throws RemoteException;*/ //TODO 这里需要实现。查询插件的结果,并入到返回值中。 } private class queryIntentReceivers extends HookedMethodHandler { public queryIntentReceivers(Context context) { super(context); } @Override protected void afterInvoke(Object receiver, Method method, Object[] args, Object invokeResult) throws Throwable { //API 2.3, 4.01, 4.0.3_r1 /* public List queryIntentReceivers(Intent intent, String resolvedType, int flags) throws RemoteException;*/ //API 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /*public List queryIntentReceivers(Intent intent, String resolvedType, int flags, int userId) throws RemoteException;*/ if (args != null) { final int index0 = 0, index1 = 1, index2 = 2; Intent intent = null; if (args.length > index0) { if (args[index0] instanceof Intent) { intent = (Intent) args[index0]; } } String resolvedType = null; if (args.length > index1) { if (args[index1] instanceof String) { resolvedType = (String) args[index1]; } } Integer flags = 0; if (args.length > index2) { if (args[index2] instanceof Integer) { flags = (Integer) args[index2]; } } if (intent != null) { List infos = PluginManager.getInstance().queryIntentReceivers(intent, resolvedType, flags); if (infos != null && infos.size() > 0) { if (invokeResult instanceof List) { List old = (List) invokeResult; old.addAll(infos); } else if (ParceledListSliceCompat.isParceledListSlice(invokeResult)) { Method getListMethod = ParceledListSliceCompat.Class().getMethod("getList"); List data = (List) getListMethod.invoke(invokeResult); data.addAll(infos); } } } } super.afterInvoke(receiver, method, args, invokeResult); } } private class resolveService extends HookedMethodHandler { public resolveService(Context context) { super(context); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 2.3, 4.01, 4.0.3_r1 /* public ResolveInfo resolveService(Intent intent, String resolvedType, int flags) throws RemoteException;*/ //API 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /* public ResolveInfo resolveService(Intent intent, String resolvedType, int flags, int userId) throws RemoteException;*/ if (args != null) { final int index0 = 0, index1 = 1, index2 = 2; Intent intent = null; if (args.length > index0) { if (args[index0] instanceof Intent) { intent = (Intent) args[index0]; } } String resolvedType = null; if (args.length > index1) { if (args[index1] instanceof String) { resolvedType = (String) args[index1]; } } Integer flags = 0; if (args.length > index2) { if (args[index2] instanceof Integer) { flags = (Integer) args[index2]; } } if (intent != null) { ResolveInfo info = PluginManager.getInstance().resolveService(intent, resolvedType, flags); if (info != null) { setFakedResult(info); return true; } } } return super.beforeInvoke(receiver, method, args); } } private class queryIntentServices extends HookedMethodHandler { public queryIntentServices(Context context) { super(context); } @Override protected void afterInvoke(Object receiver, Method method, Object[] args, Object invokeResult) throws Throwable { //API 2.3 /*public List queryIntentServices(Intent intent, String resolvedType, int flags) throws RemoteException;*/ //API 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /* public List queryIntentServices(Intent intent, String resolvedType, int flags, int userId) throwsRemoteException;*/ if (args != null) { final int index0 = 0, index1 = 1, index2 = 2; Intent intent = null; if (args.length > index0) { if (args[index0] instanceof Intent) { intent = (Intent) args[index0]; } } String resolvedType = null; if (args.length > index1) { if (args[index1] instanceof String) { resolvedType = (String) args[index1]; } } Integer flags = 0; if (args.length > index2) { if (args[index2] instanceof Integer) { flags = (Integer) args[index2]; } } if (intent != null) { List infos = PluginManager.getInstance().queryIntentServices(intent, resolvedType, flags); if (infos != null && infos.size() > 0) { if (invokeResult instanceof List) { List old = (List) invokeResult; old.addAll(infos); } else if (ParceledListSliceCompat.isParceledListSlice(invokeResult)) { Method getListMethod = ParceledListSliceCompat.Class().getMethod("getList"); List data = (List) getListMethod.invoke(invokeResult); data.addAll(infos); } } } } super.afterInvoke(receiver, method, args, invokeResult); } } private class queryIntentContentProviders extends HookedMethodHandler { public queryIntentContentProviders(Context context) { super(context); } @Override protected void afterInvoke(Object receiver, Method method, Object[] args, Object invokeResult) throws Throwable { //ONLY FOR API 4.4_r1, 5.0.2_r1 /*public List queryIntentContentProviders(Intent intent, String resolvedType, int flags, int userId) throws RemoteException;*/ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { if (args != null) { final int index0 = 0, index1 = 1, index2 = 2; Intent intent = null; if (args.length > index0) { if (args[index0] instanceof Intent) { intent = (Intent) args[index0]; } } String resolvedType = null; if (args.length > index1) { if (args[index1] instanceof String) { resolvedType = (String) args[index1]; } } Integer flags = 0; if (args.length > index2) { if (args[index2] instanceof Integer) { flags = (Integer) args[index2]; } } if (intent != null) { List infos = PluginManager.getInstance().queryIntentContentProviders(intent, resolvedType, flags); if (infos != null && infos.size() > 0) { if (invokeResult instanceof List) { List old = (List) invokeResult; old.addAll(infos); } else if (ParceledListSliceCompat.isParceledListSlice(invokeResult)) { Method getListMethod = ParceledListSliceCompat.Class().getMethod("getList"); List data = (List) getListMethod.invoke(invokeResult); data.addAll(infos); } } } } } super.afterInvoke(receiver, method, args, invokeResult); } } private class getInstalledPackages extends HookedMethodHandler { public getInstalledPackages(Context context) { super(context); } @Override protected void afterInvoke(Object receiver, Method method, Object[] args, Object invokeResult) throws Throwable { //API 2.3 /* public List getInstalledPackages(int flags) throws RemoteException;*/ //API 4.01, 4.0.3_r1, 4.1.1_r1 /*public ParceledListSlice getInstalledPackages(int flags, String lastRead) */ //API 4.2_r1 /* public ParceledListSlice getInstalledPackages(int flags, String lastRead, int userId) throws RemoteException*/ //API 4.3_r1, 4.4_r1,5.0.2_r1 /*public ParceledListSlice getInstalledPackages(int flags, int userId) throws RemoteException;*/ try { if (invokeResult != null && ParceledListSliceCompat.isParceledListSlice(invokeResult)) { if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR2) { Method getListMethod = MethodUtils.getAccessibleMethod(invokeResult.getClass(), "getList"); List data = (List) getListMethod.invoke(invokeResult); final int index0 = 0; if (args.length > index0 && args[index0] instanceof Integer) { int flags = (Integer) args[index0]; List infos = PluginManager.getInstance().getInstalledPackages(flags); if (infos != null && infos.size() > 0) { data.addAll(infos); } } } else { Method isLastSliceMethod = invokeResult.getClass().getMethod("isLastSlice"); Method setLastSlice = invokeResult.getClass().getMethod("setLastSlice", boolean.class); Method appendMethod = invokeResult.getClass().getMethod("append", Parcelable.class); Method populateList = invokeResult.getClass().getMethod("populateList", List.class, Parcelable.Creator.class); if (!setLastSlice.isAccessible()) { setLastSlice.setAccessible(true); } if (!populateList.isAccessible()) { populateList.setAccessible(true); } if (!isLastSliceMethod.isAccessible()) { isLastSliceMethod.setAccessible(true); } if (!appendMethod.isAccessible()) { appendMethod.setAccessible(true); } boolean isLastSlice = (Boolean) isLastSliceMethod.invoke(invokeResult); if (isLastSlice) { final int index0 = 0; if (args.length > index0 && args[index0] instanceof Integer) { int flags = (Integer) args[index0]; List infos = PluginManager.getInstance().getInstalledPackages(flags); if (infos != null && infos.size() > 0) { final List packageInfos = new ArrayList(); populateList.invoke(invokeResult, packageInfos, PackageInfo.CREATOR); packageInfos.addAll(infos); Object parceledListSlice = invokeResult.getClass().newInstance(); for (PackageInfo packageInfo : packageInfos) { appendMethod.invoke(parceledListSlice, packageInfo); } setLastSlice.invoke(parceledListSlice, true); setFakedResult(parceledListSlice); } } } } } else if (invokeResult instanceof List) { final int index0 = 0; if (args.length > index0 && args[index0] instanceof Integer) { int flags = (Integer) args[index0]; List infos = PluginManager.getInstance().getInstalledPackages(flags); if (infos != null && infos.size() > 0) { List old = (List) invokeResult; old.addAll(infos); } } } } catch (Exception e) { e.printStackTrace(); } super.afterInvoke(receiver, method, args, invokeResult); } } private class getPackagesHoldingPermissions extends HookedMethodHandler { public getPackagesHoldingPermissions(Context context) { super(context); } //API 2.3, 4.01, 4.0.3_r1, 4.1.1_r1, 4.2_r1 NO //API 4.3_r1, 4.4_r1, 5.0.2_r1 /* public ParceledListSlice getPackagesHoldingPermissions(String[] permissions, int flags, int userId) throws RemoteException;*/ } private class getInstalledApplications extends HookedMethodHandler { public getInstalledApplications(Context context) { super(context); } @Override protected void afterInvoke(Object receiver, Method method, Object[] args, Object invokeResult) throws Throwable { //API 2.3 /* public List getInstalledApplications(int flags) throws RemoteException;*/ //API 4.01, 4.0.3_r1 /*public ParceledListSlice getInstalledApplications(int flags, java.lang.String lastRead) throws RemoteException;*/ //API 4.1.1_r1 , 4.2_r1 /* public ParceledListSlice getInstalledApplications(int flags, java.lang.String lastRead, int userId) throws RemoteException*/ //API 4.3_r1,4.4_r1, 5.0.2_r1 /* public ParceledListSlice getInstalledApplications(int flags, int userId) throws RemoteException */ try { if (ParceledListSliceCompat.isParceledListSlice(invokeResult)) { if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR2) { Method getListMethod = MethodUtils.getAccessibleMethod(invokeResult.getClass(), "getList"); List data = (List) getListMethod.invoke(invokeResult); final int index0 = 0; if (args.length > index0 && args[index0] instanceof Integer) { int flags = (Integer) args[index0]; List infos = PluginManager.getInstance().getInstalledApplications(flags); if (infos != null && infos.size() > 0) { data.addAll(infos); } } } else { Method isLastSliceMethod = invokeResult.getClass().getMethod("isLastSlice"); Method setLastSlice = invokeResult.getClass().getMethod("setLastSlice", boolean.class); Method appendMethod = invokeResult.getClass().getMethod("append", Parcelable.class); Method populateList = invokeResult.getClass().getMethod("populateList", List.class, Parcelable.Creator.class); if (!setLastSlice.isAccessible()) { setLastSlice.setAccessible(true); } if (!populateList.isAccessible()) { populateList.setAccessible(true); } if (!isLastSliceMethod.isAccessible()) { isLastSliceMethod.setAccessible(true); } if (!appendMethod.isAccessible()) { appendMethod.setAccessible(true); } boolean isLastSlice = (Boolean) isLastSliceMethod.invoke(invokeResult); if (isLastSlice) { final int index0 = 0; if (args.length > index0 && args[index0] instanceof Integer) { int flags = (Integer) args[index0]; List infos = PluginManager.getInstance().getInstalledApplications(flags); if (infos != null && infos.size() > 0) { final List packageInfos = new ArrayList(); populateList.invoke(invokeResult, packageInfos, ApplicationInfo.CREATOR); packageInfos.addAll(infos); Object parceledListSlice = invokeResult.getClass().newInstance(); for (ApplicationInfo info : packageInfos) { appendMethod.invoke(parceledListSlice, info); } setLastSlice.invoke(parceledListSlice, true); setFakedResult(parceledListSlice); } } } } } else if (invokeResult instanceof List) { final int index0 = 0; if (args.length > index0 && args[index0] instanceof Integer) { int flags = (Integer) args[index0]; List infos = PluginManager.getInstance().getInstalledApplications(flags); if (infos != null && infos.size() > 0) { List old = (List) invokeResult; old.addAll(infos); } } } } catch (Exception e) { Log.e(TAG, "fake getInstalledApplications", e); } super.afterInvoke(receiver, method, args, invokeResult); } } private class getPersistentApplications extends HookedMethodHandler { public getPersistentApplications(Context context) { super(context); } //API 2.3, 4.01, 4.0.3_r1, 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /* public List getPersistentApplications(int flags) throws RemoteException;*/ } private class resolveContentProvider extends HookedMethodHandler { public resolveContentProvider(Context context) { super(context); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 2.3, 4.01, 4.0.3_r1 /*public ProviderInfo resolveContentProvider(String name, int flags) throws RemoteException;*/ //API 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /* public ProviderInfo resolveContentProvider(String name, int flags, int userId) throws RemoteException*/ return super.beforeInvoke(receiver, method, args); } @Override protected void afterInvoke(Object receiver, Method method, Object[] args, Object invokeResult) throws Throwable { if (args != null) { if (invokeResult == null) { final int index0 = 0, index1 = 1; if (args.length >= 2 && args[index0] instanceof String && args[index1] instanceof Integer) { String name = (String) args[index0]; Integer flags = (Integer) args[index1]; ProviderInfo info = PluginManager.getInstance().resolveContentProvider(name, flags); if (info != null) { setFakedResult(info); } } } } super.afterInvoke(receiver, method, args, invokeResult); } } private class querySyncProviders extends HookedMethodHandler { public querySyncProviders(Context context) { super(context); } //API 2.3, 4.01, 4.0.3_r1, 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /* public void querySyncProviders(List outNames, List outInfo) throws RemoteException;*/ //TODO 查询插件的结果并入到返回值中。 } private class queryContentProviders extends HookedMethodHandler { public queryContentProviders(Context context) { super(context); } //API 2.3, 4.01, 4.0.3_r1, 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /*public List queryContentProviders(String processName, int uid, int flags) throws RemoteException;*/ //TODO 查询插件的结果并入到返回值中。 } private class getInstrumentationInfo extends HookedMethodHandler { public getInstrumentationInfo(Context context) { super(context); } //API 2.3, 4.01, 4.0.3_r1, 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /*public InstrumentationInfo getInstrumentationInfo(ComponentName className, int flags) throws RemoteException;*/ //FIXME 自动化测试相关的东西,先不处理。 } private class queryInstrumentation extends HookedMethodHandler { public queryInstrumentation(Context context) { super(context); } //API 2.3, 4.01, 4.0.3_r1, 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /*public List queryInstrumentation(String targetPackage, int flags) throws RemoteException;*/ //FIXME 自动化测试相关的东西,先不处理。 } private class getInstallerPackageName extends HookedMethodHandler { public getInstallerPackageName(Context context) { super(context); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 2.3, 4.01, 4.0.3_r1, 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /* public String getInstallerPackageName(String packageName) throws RemoteException;*/ if (args != null) { final int index = 0; if (args.length > index && args[index] instanceof String) { String packageName = (String) args[index]; if (PluginManager.getInstance().isPluginPackage(packageName)) { setFakedResult(mHostContext.getPackageName()); return true; } } } return super.beforeInvoke(receiver, method, args); } } private class addPackageToPreferred extends HookedMethodHandler { public addPackageToPreferred(Context context) { super(context); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 2.3, 4.01, 4.0.3_r1, 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /*public void addPackageToPreferred(String packageName) throws RemoteException;*/ if (args != null) { final int index = 0; if (args.length > index && args[index] instanceof String) { String packageName = (String) args[index]; if (PluginManager.getInstance().isPluginPackage(packageName)) { args[index] = mHostContext.getPackageName(); } } } return super.beforeInvoke(receiver, method, args); } } private class removePackageFromPreferred extends HookedMethodHandler { public removePackageFromPreferred(Context context) { super(context); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 2.3, 4.01, 4.0.3_r1, 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /*public void removePackageFromPreferred(String packageName) throws RemoteException;*/ if (args != null) { final int index = 0; if (args.length > index && args[index] instanceof String) { String packageName = (String) args[index]; if (PluginManager.getInstance().isPluginPackage(packageName)) { args[index] = mHostContext.getPackageName(); } } } return super.beforeInvoke(receiver, method, args); } } private class getPreferredPackages extends HookedMethodHandler { public getPreferredPackages(Context context) { super(context); } //API 2.3, 4.01, 4.0.3_r1, 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /*public List getPreferredPackages(int flags) throws RemoteException;*/ //这里插件没有结果的,所以就不处理了。 } private class resetPreferredActivities extends HookedMethodHandler { public resetPreferredActivities(Context context) { super(context); } //API 4.3_r1, 4.4_r1, 5.0.2_r1 /* public void resetPreferredActivities(int userId) throws android.os.RemoteException;*/ //这里插件没有结果的,所以就不处理了。 } private class getLastChosenActivity extends HookedMethodHandler { public getLastChosenActivity(Context context) { super(context); } //API 4.4_r1, 5.0.2_r1 /*public ResolveInfo getLastChosenActivity(Intent intent, String resolvedType, int flags) throws RemoteException;*/ //这里插件没有结果的,所以就不处理了。 } private class setLastChosenActivity extends HookedMethodHandler { public setLastChosenActivity(Context context) { super(context); } //API 4.4_r1, 5.0.2_r1 /*public void setLastChosenActivity(Intent intent, String resolvedType, int flags, IntentFilter filter, int match, ComponentName activity) throws RemoteException;*/ //这里插件没有结果的,所以就不处理了。 } private class addPreferredActivity extends HookedMethodHandler { public addPreferredActivity(Context context) { super(context); } //API 2.3, 4.01, 4.0.3_r1, 4.1.1_r1 /* public void addPreferredActivity(IntentFilter filter, int match, ComponentName[] set, ComponentName activity) throws RemoteException;*/ //API 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /* public void addPreferredActivity(IntentFilter filter, int match, ComponentName[] set, ComponentName activity, int userId) throws RemoteException;*/ //这里插件没有结果的,所以就不处理了。 } private class replacePreferredActivity extends HookedMethodHandler { public replacePreferredActivity(Context context) { super(context); } //API 2.3, 4.01, 4.0.3_r1, 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1 /*public void replacePreferredActivity(IntentFilter filter, int match, ComponentName[] set, ComponentName activity) throws RemoteException;*/ //API 5.0.2_r1 /*public void replacePreferredActivity(IntentFilter filter, int match, ComponentName[] set, ComponentName activity, int userId) throws RemoteException;*/ //这里插件没有结果的,所以就不处理了。 } private class clearPackagePreferredActivities extends HookedMethodHandler { public clearPackagePreferredActivities(Context context) { super(context); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 2.3, 4.01, 4.0.3_r1, 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /*public void clearPackagePreferredActivities(String packageName) throws RemoteException;*/ if (args != null) { final int index = 0; if (args.length > index && args[index] instanceof String) { String packageName = (String) args[index]; if (PluginManager.getInstance().isPluginPackage(packageName)) { args[index] = mHostContext.getPackageName(); } } } return super.beforeInvoke(receiver, method, args); } } private class getPreferredActivities extends HookedMethodHandler { public getPreferredActivities(Context context) { super(context); } //API 2.3, 4.01, 4.0.3_r1, 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /* public int getPreferredActivities(List outFilters, List outActivities, String packageName) throws RemoteException;*/ //这里插件没有结果的,所以就不处理了。 } //ONLY for 4.4_r1 or later private class getHomeActivities extends HookedMethodHandler { public getHomeActivities(Context context) { super(context); } //API 4.4_r1, 5.0.2_r1 /* public ComponentName getHomeActivities(List outHomeCandidates) throws RemoteException;*/ //这里插件没有结果的,所以就不处理了。 } private class setComponentEnabledSetting extends HookedMethodHandler { public setComponentEnabledSetting(Context context) { super(context); } //API 2.3 /* public void setComponentEnabledSetting(ComponentName componentName, int newState, int flags) throws RemoteException;*/ //API 4.01 //API 4.0.3_r1 //API 4.1.1_r1 //API 4.2_r1 //API 4.3_r1 //API 4.4_r1 //API 5.0.2_r1 @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { final int index = 0; if (args != null && args.length > index && args[index] instanceof ComponentName) { ComponentName componentName = (ComponentName) args[index]; if (PluginManager.getInstance().isPluginPackage(componentName)) { setFakedResult(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT); return true; } } return super.beforeInvoke(receiver, method, args); } } private class getComponentEnabledSetting extends HookedMethodHandler { public getComponentEnabledSetting(Context context) { super(context); } //API 2.3, 4.01, 4.0.3_r1, 4.1.1_r1 /*public int getComponentEnabledSetting(ComponentName componentName) throws RemoteException;*/ //API 4.2_r1, 4.3_r1,4.4_r1, 5.0.2_r1 /*public void setComponentEnabledSetting(ComponentName componentName, int newState, int flags, int userId) throws RemoteException*/ @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { final int index = 0; if (args != null && args.length > index && args[index] instanceof ComponentName) { ComponentName componentName = (ComponentName) args[index]; if (PluginManager.getInstance().isPluginPackage(componentName)) { setFakedResult(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT); return true; } } return super.beforeInvoke(receiver, method, args); } } private class setApplicationEnabledSetting extends HookedMethodHandler { public setApplicationEnabledSetting(Context context) { super(context); } //API 2.3 /*public void setApplicationEnabledSetting(String packageName, int newState, int flags) throws RemoteException;*/ //API 4.01, 4.0.3_r1, 4.1.1_r1, 4.2_r1 /*public void setApplicationEnabledSetting(String packageName, int newState, int flags, int userId) throws RemoteException*/ //API 4.3_r1, 4.4_r1, 5.0.2_r1 /*public void setApplicationEnabledSetting(String packageName, int newState, int flags, int userId, String callingPackage) throws RemoteException;*/ @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { final int index = 0; if (args != null && args.length > index && args[index] instanceof String) { String packageName = (String) args[index]; if (PluginManager.getInstance().isPluginPackage(packageName)) { return true; } } return super.beforeInvoke(receiver, method, args); } } private class getApplicationEnabledSetting extends HookedMethodHandler { public getApplicationEnabledSetting(Context context) { super(context); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 2.3, 4.01, 4.0.3_r1, /* public int getApplicationEnabledSetting(String packageName) throws RemoteException;*/ //API 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /*public int getApplicationEnabledSetting(String packageName, int userId) throws RemoteException;*/ if (args != null) { final int index = 0; if (args.length > index && args[index] instanceof String) { String packageName = (String) args[index]; if (PluginManager.getInstance().isPluginPackage(packageName)) { //DO NOTHING setFakedResult(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT); return true; } } } return super.beforeInvoke(receiver, method, args); } } private class setPackageStoppedState extends HookedMethodHandler { public setPackageStoppedState(Context context) { super(context); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 4.01, 4.0.3_r1 /* public void setPackageStoppedState(String packageName, boolean stopped) throws RemoteException;*/ //API 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /*public void setPackageStoppedState(java.lang.String packageName, boolean stopped, int userId) throws android.os.RemoteException;*/ if (args != null) { final int index = 0; if (args.length > index && args[index] instanceof String) { String packageName = (String) args[index]; if (PluginManager.getInstance().isPluginPackage(packageName)) { //DO NOTHING PluginManager.getInstance().forceStopPackage(packageName); return true; } } } return super.beforeInvoke(receiver, method, args); } } private class deleteApplicationCacheFiles extends HookedMethodHandler { public deleteApplicationCacheFiles(Context context) { super(context); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 2.3, 4.01, 4.0.3_r1, 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /* public void deleteApplicationCacheFiles(String packageName, IPackageDataObserver observer) throws RemoteException;*/ if (args != null) { final int index0 = 0, index1 = 1; if (args.length >= 2 && args[index0] instanceof String && IPackageDataObserverCompat.isIPackageDataObserver(args[index1])) { String packageName = (String) args[index0]; if (PluginManager.getInstance().isPluginPackage(packageName)) { final Object observer = args[index1]; PluginManager.getInstance().deleteApplicationCacheFiles(packageName, observer); return true; } } } return super.beforeInvoke(receiver, method, args); } } private class clearApplicationUserData extends HookedMethodHandler { public clearApplicationUserData(Context context) { super(context); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 2.3, 4.01, 4.0.3_r1 /* public void clearApplicationUserData(String packageName, IPackageDataObserver observer) throws RemoteException;*/ //API 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /*public void clearApplicationUserData(String packageName, IPackageDataObserver observer, int userId) throws RemoteException;*/ if (args != null) { final int index0 = 0, index1 = 1; if (args.length >= 2 && args[index0] instanceof String && IPackageDataObserverCompat.isIPackageDataObserver(args[index1])) { String packageName = (String) args[index0]; if (PluginManager.getInstance().isPluginPackage(packageName)) { final Object observer = args[index1]; PluginManager.getInstance().clearApplicationUserData(packageName, observer); return true; } } } return super.beforeInvoke(receiver, method, args); } } private class getPackageSizeInfo extends HookedMethodHandler { public getPackageSizeInfo(Context context) { super(context); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //API 2.3, 4.01, 4.0.3_r1, 4.1.1_r1 /*public void getPackageSizeInfo(String packageName, IPackageStatsObserver observer) throws RemoteException;*/ //API 4.2_r1 4.3_r1, 4.4_r1, 5.0.2_r1 /*public void getPackageSizeInfo(String packageName, int userHandle, IPackageStatsObserver observer) throws RemoteException;*/ //TODO 获取包大小。 return super.beforeInvoke(receiver, method, args); } } private class performDexOpt extends HookedMethodHandler { public performDexOpt(Context context) { super(context); } //API 2.3, 4.01, 4.0.3_r1, 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1 /* public boolean performDexOpt(String packageName) throws RemoteException;*/ //API 5.0.2_r1 NO } private class movePackage extends HookedMethodHandler { public movePackage(Context context) { super(context); } //API 2.3, 4.01, 4.0.3_r1, 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1 /* public void movePackage(String packageName, IPackageMoveObserver observer, int flags) throws RemoteException;*/ } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/handle/IPhoneSubInfoHookHandle.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.handle; import android.content.Context; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.HookedMethodHandler; import com.morgoo.helper.compat.IPhoneSubInfoCompat; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2016/5/6. */ public class IPhoneSubInfoHookHandle extends BaseHookHandle { public IPhoneSubInfoHookHandle(Context hostContext) { super(hostContext); } @Override protected void init() { // interface IPhoneSubInfo { // /** // * Retrieves the unique device ID, e.g., IMEI for GSM phones. // */ // String getDeviceId(String callingPackage); // /** // * Retrieves the unique Network Access ID // */ // String getNaiForSubscriber(int subId, String callingPackage); // /** // * Retrieves the unique device ID of a phone for the device, e.g., IMEI // * for GSM phones. // */ // String getDeviceIdForPhone(int phoneId, String callingPackage); // /** // * Retrieves the IMEI. // */ // String getImeiForSubscriber(int subId, String callingPackage); // /** // * Retrieves the software version number for the device, e.g., IMEI/SV // * for GSM phones. // */ // String getDeviceSvn(String callingPackage); // /** // * Retrieves the software version number of a subId for the device, e.g., IMEI/SV // * for GSM phones. // */ // String getDeviceSvnUsingSubId(int subId, String callingPackage); // /** // * Retrieves the unique sbuscriber ID, e.g., IMSI for GSM phones. // */ // String getSubscriberId(String callingPackage); // /** // * Retrieves the unique subscriber ID of a given subId, e.g., IMSI for GSM phones. // */ // String getSubscriberIdForSubscriber(int subId, String callingPackage); // /** // * Retrieves the Group Identifier Level1 for GSM phones. // */ // String getGroupIdLevel1(String callingPackage); // /** // * Retrieves the Group Identifier Level1 for GSM phones of a subId. // */ // String getGroupIdLevel1ForSubscriber(int subId, String callingPackage); // /** // * Retrieves the serial number of the ICC, if applicable. // */ // String getIccSerialNumber(String callingPackage); // /** // * Retrieves the serial number of a given subId. // */ // String getIccSerialNumberForSubscriber(int subId, String callingPackage); // /** // * Retrieves the phone number string for line 1. // */ // String getLine1Number(String callingPackage); // /** // * Retrieves the phone number string for line 1 of a subcription. // */ // String getLine1NumberForSubscriber(int subId, String callingPackage); // /** // * Retrieves the alpha identifier for line 1. // */ // String getLine1AlphaTag(String callingPackage); // /** // * Retrieves the alpha identifier for line 1 of a subId. // */ // String getLine1AlphaTagForSubscriber(int subId, String callingPackage); // /** // * Retrieves MSISDN Number. // */ // String getMsisdn(String callingPackage); // /** // * Retrieves the Msisdn of a subId. // */ // String getMsisdnForSubscriber(int subId, String callingPackage); // /** // * Retrieves the voice mail number. // */ // String getVoiceMailNumber(String callingPackage); // /** // * Retrieves the voice mail number of a given subId. // */ // String getVoiceMailNumberForSubscriber(int subId, String callingPackage); // /** // * Retrieves the complete voice mail number. // */ // String getCompleteVoiceMailNumber(); // /** // * Retrieves the complete voice mail number for particular subId // */ // String getCompleteVoiceMailNumberForSubscriber(int subId); // /** // * Retrieves the alpha identifier associated with the voice mail number. // */ // String getVoiceMailAlphaTag(String callingPackage); // /** // * Retrieves the alpha identifier associated with the voice mail number // * of a subId. // */ // String getVoiceMailAlphaTagForSubscriber(int subId, String callingPackage); // /** // * Returns the IMS private user identity (IMPI) that was loaded from the ISIM. // * @return the IMPI, or null if not present or not loaded // */ // String getIsimImpi(); // /** // * Returns the IMS home network domain name that was loaded from the ISIM. // * @return the IMS domain name, or null if not present or not loaded // */ // String getIsimDomain(); // /** // * Returns the IMS public user identities (IMPU) that were loaded from the ISIM. // * @return an array of IMPU strings, with one IMPU per string, or null if // * not present or not loaded // */ // String[] getIsimImpu(); // /** // * Returns the IMS Service Table (IST) that was loaded from the ISIM. // * @return IMS Service Table or null if not present or not loaded // */ // String getIsimIst(); // /** // * Returns the IMS Proxy Call Session Control Function(PCSCF) that were loaded from the ISIM. // * @return an array of PCSCF strings with one PCSCF per string, or null if // * not present or not loaded // */ // String[] getIsimPcscf(); // /** // * TODO: Deprecate and remove this interface. Superceded by getIccsimChallengeResponse. // * Returns the response of ISIM Authetification through RIL. // * @return the response of ISIM Authetification, or null if // * the Authentification hasn't been successed or isn't present iphonesubinfo. // */ // String getIsimChallengeResponse(String nonce); // /** // * Returns the response of the SIM application on the UICC to authentication // * challenge/response algorithm. The data string and challenge response are // * Base64 encoded Strings. // * Can support EAP-SIM, EAP-AKA with results encoded per 3GPP TS 31.102. // * // * @param subId subscription ID to be queried // * @param appType ICC application type (@see com.android.internal.telephony.PhoneConstants#APPTYPE_xxx) // * @param data authentication challenge data // * @return challenge response // */ // String getIccSimChallengeResponse(int subId, int appType, String data); // } sHookedMethodHandlers.put("getDeviceId", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getNaiForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getDeviceIdForPhone", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getImeiForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getDeviceSvn", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getDeviceSvnUsingSubId", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getSubscriberId", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getSubscriberIdForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getGroupIdLevel1", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getGroupIdLevel1ForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getIccSerialNumber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getIccSerialNumberForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getLine1Number", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getLine1NumberForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getLine1AlphaTag", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getLine1AlphaTagForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getMsisdn", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getMsisdnForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getVoiceMailNumber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getVoiceMailNumberForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getCompleteVoiceMailNumber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getCompleteVoiceMailNumberForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getVoiceMailAlphaTag", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getVoiceMailAlphaTagForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getIsimImpi", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getIsimDomain", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getIsimImpu", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getIsimIst", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getIsimPcscf", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getIsimChallengeResponse", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getIccSimChallengeResponse", new MyBaseHandler(mHostContext)); addAllMethodFromHookedClass(); } @Override protected Class getHookedClass() throws ClassNotFoundException { return IPhoneSubInfoCompat.Class(); } @Override protected HookedMethodHandler newBaseHandler() throws ClassNotFoundException { return new MyBaseHandler(mHostContext); } private static class MyBaseHandler extends ReplaceCallingPackageHookedMethodHandler { public MyBaseHandler(Context context) { super(context); } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/handle/ISearchManagerHookHandle.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.handle; import android.content.ComponentName; import android.content.Context; import android.content.pm.ActivityInfo; import android.text.TextUtils; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.HookedMethodHandler; import com.morgoo.droidplugin.pm.PluginManager; import com.morgoo.helper.Log; import java.lang.reflect.Method; /** * Created by wyw on 15-9-18. */ public class ISearchManagerHookHandle extends BaseHookHandle { public ISearchManagerHookHandle(Context context) { super(context); } @Override protected void init() { sHookedMethodHandlers.put("getSearchableInfo", new getSearchableInfo(mHostContext)); } private class getSearchableInfo extends HookedMethodHandler{ public getSearchableInfo(Context hostContext) { super(hostContext); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { if (args != null && args.length > 0 && args[args.length - 1] instanceof ComponentName) { ComponentName cpn = (ComponentName) args[args.length - 1]; ActivityInfo info = PluginManager.getInstance().getActivityInfo(cpn, 0); if (info != null) { ActivityInfo proxyInfo = PluginManager.getInstance().selectStubActivityInfo(info); if (proxyInfo != null) { args[args.length - 1] = new ComponentName(proxyInfo.packageName, proxyInfo.name); } } } return super.beforeInvoke(receiver, method, args); } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/handle/ISessionManagerHookHandle.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.handle; import android.content.Context; import android.text.TextUtils; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.HookedMethodHandler; import java.lang.reflect.Method; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/3/9. */ public class ISessionManagerHookHandle extends BaseHookHandle { public ISessionManagerHookHandle(Context context) { super(context); } @Override protected void init() { sHookedMethodHandlers.put("createSession", new createSession(mHostContext)); } private class createSession extends HookedMethodHandler { public createSession(Context context) { super(context); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { int index = 0; if (args != null && args.length > index && args[index] instanceof String) { String pkg = (String) args[index]; if (!TextUtils.equals(pkg, mHostContext.getPackageName())) { args[index] = mHostContext.getPackageName(); } } return super.beforeInvoke(receiver, method, args); } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/handle/ISmsHookHandle.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.handle; import android.content.Context; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.HookedMethodHandler; import com.morgoo.helper.compat.ISmsCompat; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2016/5/9. */ public class ISmsHookHandle extends BaseHookHandle{ private static final String TAG = ISmsHookHandle.class.getSimpleName(); public ISmsHookHandle(Context hostContext) { super(hostContext); } @Override protected void init() { // interface ISms { // List getAllMessagesFromIccEfForSubscriber(in int subId, String callingPkg); // boolean updateMessageOnIccEfForSubscriber(in int subId, String callingPkg, // int messageIndex, int newStatus, in byte[] pdu); // boolean copyMessageToIccEfForSubscriber(in int subId, String callingPkg, int status, // in byte[] pdu, in byte[] smsc); // void sendDataForSubscriber(int subId, String callingPkg, in String destAddr, // in String scAddr, in int destPort, in byte[] data, in PendingIntent sentIntent, // in PendingIntent deliveryIntent); // void sendDataForSubscriberWithSelfPermissions(int subId, String callingPkg, in String destAddr, // in String scAddr, in int destPort, in byte[] data, in PendingIntent sentIntent, // in PendingIntent deliveryIntent); // void sendTextForSubscriber(in int subId, String callingPkg, in String destAddr, // in String scAddr, in String text, in PendingIntent sentIntent, // in PendingIntent deliveryIntent, in boolean persistMessageForNonDefaultSmsApp); // void sendTextForSubscriberWithSelfPermissions(in int subId, String callingPkg, // in String destAddr, in String scAddr, in String text, in PendingIntent sentIntent, // in PendingIntent deliveryIntent); // void injectSmsPduForSubscriber( // int subId, in byte[] pdu, String format, in PendingIntent receivedIntent); // void sendMultipartTextForSubscriber(in int subId, String callingPkg, // in String destinationAddress, in String scAddress, // in List parts, in List sentIntents, // in List deliveryIntents, in boolean persistMessageForNonDefaultSmsApp); // boolean enableCellBroadcastForSubscriber(int subId, int messageIdentifier, int ranType); // boolean disableCellBroadcastForSubscriber(int subId, int messageIdentifier, int ranType); // boolean enableCellBroadcastRangeForSubscriber(int subId, int startMessageId, int endMessageId, // int ranType); // boolean disableCellBroadcastRangeForSubscriber(int subId, int startMessageId, // int endMessageId, int ranType); // int getPremiumSmsPermission(String packageName); // int getPremiumSmsPermissionForSubscriber(int subId, String packageName); // void setPremiumSmsPermission(String packageName, int permission); // void setPremiumSmsPermissionForSubscriber(int subId, String packageName, int permission); // boolean isImsSmsSupportedForSubscriber(int subId); // boolean isSmsSimPickActivityNeeded(int subId); // int getPreferredSmsSubscription(); // String getImsSmsFormatForSubscriber(int subId); // boolean isSMSPromptEnabled(); // void sendStoredText(int subId, String callingPkg, in Uri messageUri, String scAddress, // in PendingIntent sentIntent, in PendingIntent deliveryIntent); // void sendStoredMultipartText(int subId, String callingPkg, in Uri messageUri, // String scAddress, in List sentIntents, // in List deliveryIntents); sHookedMethodHandlers.put("getAllMessagesFromIccEfForSubscriber",new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("updateMessageOnIccEfForSubscriber",new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("copyMessageToIccEfForSubscriber",new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("sendDataForSubscriber",new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("sendDataForSubscriberWithSelfPermissions",new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("sendTextForSubscriber",new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("sendTextForSubscriberWithSelfPermissions",new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("injectSmsPduForSubscriber",new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("sendMultipartTextForSubscriber",new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("enableCellBroadcastForSubscriber",new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("disableCellBroadcastForSubscriber",new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("enableCellBroadcastRangeForSubscriber",new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("disableCellBroadcastRangeForSubscriber",new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getPremiumSmsPermission",new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getPremiumSmsPermissionForSubscriber",new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("setPremiumSmsPermission",new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("setPremiumSmsPermissionForSubscriber",new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("isImsSmsSupportedForSubscriber",new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("isSmsSimPickActivityNeeded",new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getPreferredSmsSubscription",new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getImsSmsFormatForSubscriber",new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("isSMSPromptEnabled",new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("sendStoredText",new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("sendStoredMultipartText",new MyBaseHandler(mHostContext)); addAllMethodFromHookedClass(); } @Override protected Class getHookedClass() throws ClassNotFoundException { return ISmsCompat.Class(); } @Override protected HookedMethodHandler newBaseHandler() throws ClassNotFoundException { return new MyBaseHandler(mHostContext); } private static class MyBaseHandler extends ReplaceCallingPackageHookedMethodHandler { public MyBaseHandler(Context context) { super(context); } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/handle/ISubBinderHookHandle.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.handle; import android.content.Context; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.HookedMethodHandler; import com.morgoo.helper.compat.ISubCompat; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2016/5/6. */ public class ISubBinderHookHandle extends BaseHookHandle { public ISubBinderHookHandle(Context hostContext) { super(hostContext); } @Override protected void init() { // interface ISub { // /** // * @param callingPackage The package maing the call. // * @return a list of all subscriptions in the database, this includes // * all subscriptions that have been seen. // */ // List getAllSubInfoList(String callingPackage); // /** // * @param callingPackage The package maing the call. // * @return the count of all subscriptions in the database, this includes // * all subscriptions that have been seen. // */ // int getAllSubInfoCount(String callingPackage); // /** // * Get the active SubscriptionInfo with the subId key // * @param subId The unique SubscriptionInfo key in database // * @param callingPackage The package maing the call. // * @return SubscriptionInfo, maybe null if its not active // */ // SubscriptionInfo getActiveSubscriptionInfo(int subId, String callingPackage); // /** // * Get the active SubscriptionInfo associated with the iccId // * @param iccId the IccId of SIM card // * @param callingPackage The package maing the call. // * @return SubscriptionInfo, maybe null if its not active // */ // SubscriptionInfo getActiveSubscriptionInfoForIccId(String iccId, String callingPackage); // /** // * Get the active SubscriptionInfo associated with the slotIdx // * @param slotIdx the slot which the subscription is inserted // * @param callingPackage The package maing the call. // * @return SubscriptionInfo, maybe null if its not active // */ // SubscriptionInfo getActiveSubscriptionInfoForSimSlotIndex(int slotIdx, String callingPackage); // /** // * Get the SubscriptionInfo(s) of the active subscriptions. The records will be sorted // * by {@link SubscriptionInfo#getSimSlotIndex} then by {@link SubscriptionInfo#getSubscriptionId}. // * // * @param callingPackage The package maing the call. // * @return Sorted list of the currently {@link SubscriptionInfo} records available on the device. // *
    // *
  • // * If null is returned the current state is unknown but if a {@link OnSubscriptionsChangedListener} // * has been registered {@link OnSubscriptionsChangedListener#onSubscriptionsChanged} will be // * invoked in the future. // *
  • // *
  • // * If the list is empty then there are no {@link SubscriptionInfo} records currently available. // *
  • // *
  • // * if the list is non-empty the list is sorted by {@link SubscriptionInfo#getSimSlotIndex} // * then by {@link SubscriptionInfo#getSubscriptionId}. // *
  • // *
// */ // List getActiveSubscriptionInfoList(String callingPackage); // /** // * @param callingPackage The package making the call. // * @return the number of active subscriptions // */ // int getActiveSubInfoCount(String callingPackage); // /** // * @return the maximum number of subscriptions this device will support at any one time. // */ // int getActiveSubInfoCountMax(); // /** // * Add a new SubscriptionInfo to subinfo database if needed // * @param iccId the IccId of the SIM card // * @param slotId the slot which the SIM is inserted // * @return the URL of the newly created row or the updated row // */ // int addSubInfoRecord(String iccId, int slotId); // /** // * Set SIM icon tint color by simInfo index // * @param tint the icon tint color of the SIM // * @param subId the unique SubscriptionInfo index in database // * @return the number of records updated // */ // int setIconTint(int tint, int subId); // /** // * Set display name by simInfo index // * @param displayName the display name of SIM card // * @param subId the unique SubscriptionInfo index in database // * @return the number of records updated // */ // int setDisplayName(String displayName, int subId); // /** // * Set display name by simInfo index with name source // * @param displayName the display name of SIM card // * @param subId the unique SubscriptionInfo index in database // * @param nameSource, 0: DEFAULT_SOURCE, 1: SIM_SOURCE, 2: USER_INPUT // * @return the number of records updated // */ // int setDisplayNameUsingSrc(String displayName, int subId, long nameSource); // /** // * Set phone number by subId // * @param number the phone number of the SIM // * @param subId the unique SubscriptionInfo index in database // * @return the number of records updated // */ // int setDisplayNumber(String number, int subId); // /** // * Set data roaming by simInfo index // * @param roaming 0:Don't allow data when roaming, 1:Allow data when roaming // * @param subId the unique SubscriptionInfo index in database // * @return the number of records updated // */ // int setDataRoaming(int roaming, int subId); // int getSlotId(int subId); // int[] getSubId(int slotId); // int getDefaultSubId(); // int clearSubInfo(); // int getPhoneId(int subId); // /** // * Get the default data subscription // * @return Id of the data subscription // */ // int getDefaultDataSubId(); // void setDefaultDataSubId(int subId); // int getDefaultVoiceSubId(); // void setDefaultVoiceSubId(int subId); // int getDefaultSmsSubId(); // void setDefaultSmsSubId(int subId); // void clearDefaultsForInactiveSubIds(); // int[] getActiveSubIdList(); // void setSubscriptionProperty(int subId, String propKey, String propValue); // String getSubscriptionProperty(int subId, String propKey, String callingPackage); // /** // * Get the SIM state for the slot idx // * @return SIM state as the ordinal of IccCardConstants.State // */ // int getSimStateForSlotIdx(int slotIdx); // boolean isActiveSubId(int subId); // } sHookedMethodHandlers.put("getAllSubInfoList", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getAllSubInfoCount", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getActiveSubscriptionInfo", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getActiveSubscriptionInfoForIccId", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getActiveSubscriptionInfoForSimSlotIndex", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getActiveSubscriptionInfoList", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getActiveSubInfoCountMax", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("addSubInfoRecord", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("setIconTint", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("setDisplayName", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("setDisplayNameUsingSrc", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("setDisplayNumber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("setDataRoaming", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getSlotId", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getSubId", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getDefaultSubId", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("clearSubInfo", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getPhoneId", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getDefaultDataSubId", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("etDefaultDataSubId", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getDefaultVoiceSubId", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("setDefaultVoiceSubId", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getDefaultSmsSubId", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("setDefaultSmsSubId", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("clearDefaultsForInactiveSubIds", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getActiveSubIdList", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("setSubscriptionProperty", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getSubscriptionProperty", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getSimStateForSlotIdx", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("isActiveSubId", new MyBaseHandler(mHostContext)); addAllMethodFromHookedClass(); } @Override protected Class getHookedClass() throws ClassNotFoundException { return ISubCompat.Class(); } @Override protected HookedMethodHandler newBaseHandler() throws ClassNotFoundException { return new MyBaseHandler(mHostContext); } private static class MyBaseHandler extends ReplaceCallingPackageHookedMethodHandler { public MyBaseHandler(Context context) { super(context); } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/handle/ITelephonyHookHandle.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.handle; import android.content.Context; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.HookedMethodHandler; import com.morgoo.helper.compat.ITelephonyCompat; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2016/5/6. */ public class ITelephonyHookHandle extends BaseHookHandle { public ITelephonyHookHandle(Context hostContext) { super(hostContext); } @Override protected void init() { // // interface ITelephony { // /** // * Dial a number. This doesn't place the call. It displays // * the Dialer screen. // * @param number the number to be dialed. If null, this // * would display the Dialer screen with no number pre-filled. // */ // void dial(String number); // /** // * Place a call to the specified number. // * @param callingPackage The package making the call. // * @param number the number to be called. // */ // void call(String callingPackage, String number); // /** // * End call if there is a call in progress, otherwise does nothing. // * // * @return whether it hung up // */ // boolean endCall(); // /** // * End call on particular subId or go to the Home screen // * @param subId user preferred subId. // * @return whether it hung up // */ // boolean endCallForSubscriber(int subId); // /** // * Answer the currently-ringing call. // * // * If there's already a current active call, that call will be // * automatically put on hold. If both lines are currently in use, the // * current active call will be ended. // * // * TODO: provide a flag to let the caller specify what policy to use // * if both lines are in use. (The current behavior is hardwired to // * "answer incoming, end ongoing", which is how the CALL button // * is specced to behave.) // * // * TODO: this should be a oneway call (especially since it's called // * directly from the key queue thread). // */ // void answerRingingCall(); // /** // * Answer the currently-ringing call on particular subId . // * // * If there's already a current active call, that call will be // * automatically put on hold. If both lines are currently in use, the // * current active call will be ended. // * // * TODO: provide a flag to let the caller specify what policy to use // * if both lines are in use. (The current behavior is hardwired to // * "answer incoming, end ongoing", which is how the CALL button // * is specced to behave.) // * // * TODO: this should be a oneway call (especially since it's called // * directly from the key queue thread). // */ // void answerRingingCallForSubscriber(int subId); // /** // * Silence the ringer if an incoming call is currently ringing. // * (If vibrating, stop the vibrator also.) // * // * It's safe to call this if the ringer has already been silenced, or // * even if there's no incoming call. (If so, this method will do nothing.) // * // * TODO: this should be a oneway call too (see above). // * (Actually *all* the methods here that return void can // * probably be oneway.) // */ // void silenceRinger(); // /** // * Check if we are in either an active or holding call // * @param callingPackage the name of the package making the call. // * @return true if the phone state is OFFHOOK. // */ // boolean isOffhook(String callingPackage); // /** // * Check if a particular subId has an active or holding call // * // * @param subId user preferred subId. // * @param callingPackage the name of the package making the call. // * @return true if the phone state is OFFHOOK. // */ // boolean isOffhookForSubscriber(int subId, String callingPackage); // /** // * Check if an incoming phone call is ringing or call waiting // * on a particular subId. // * // * @param subId user preferred subId. // * @param callingPackage the name of the package making the call. // * @return true if the phone state is RINGING. // */ // boolean isRingingForSubscriber(int subId, String callingPackage); // /** // * Check if an incoming phone call is ringing or call waiting. // * @param callingPackage the name of the package making the call. // * @return true if the phone state is RINGING. // */ // boolean isRinging(String callingPackage); // /** // * Check if the phone is idle. // * @param callingPackage the name of the package making the call. // * @return true if the phone state is IDLE. // */ // boolean isIdle(String callingPackage); // /** // * Check if the phone is idle on a particular subId. // * // * @param subId user preferred subId. // * @param callingPackage the name of the package making the call. // * @return true if the phone state is IDLE. // */ // boolean isIdleForSubscriber(int subId, String callingPackage); // /** // * Check to see if the radio is on or not. // * @param callingPackage the name of the package making the call. // * @return returns true if the radio is on. // */ // boolean isRadioOn(String callingPackage); // /** // * Check to see if the radio is on or not on particular subId. // * @param subId user preferred subId. // * @param callingPackage the name of the package making the call. // * @return returns true if the radio is on. // */ // boolean isRadioOnForSubscriber(int subId, String callingPackage); // /** // * Check if the SIM pin lock is enabled. // * @return true if the SIM pin lock is enabled. // * @param callingPackage The package making the call. // */ // boolean isSimPinEnabled(String callingPackage); // /** // * Supply a pin to unlock the SIM. Blocks until a result is determined. // * @param pin The pin to check. // * @return whether the operation was a success. // */ // boolean supplyPin(String pin); // /** // * Supply a pin to unlock the SIM for particular subId. // * Blocks until a result is determined. // * @param pin The pin to check. // * @param subId user preferred subId. // * @return whether the operation was a success. // */ // boolean supplyPinForSubscriber(int subId, String pin); // /** // * Supply puk to unlock the SIM and set SIM pin to new pin. // * Blocks until a result is determined. // * @param puk The puk to check. // * pin The new pin to be set in SIM // * @return whether the operation was a success. // */ // boolean supplyPuk(String puk, String pin); // /** // * Supply puk to unlock the SIM and set SIM pin to new pin. // * Blocks until a result is determined. // * @param puk The puk to check. // * pin The new pin to be set in SIM // * @param subId user preferred subId. // * @return whether the operation was a success. // */ // boolean supplyPukForSubscriber(int subId, String puk, String pin); // /** // * Supply a pin to unlock the SIM. Blocks until a result is determined. // * Returns a specific success/error code. // * @param pin The pin to check. // * @return retValue[0] = Phone.PIN_RESULT_SUCCESS on success. Otherwise error code // * retValue[1] = number of attempts remaining if known otherwise -1 // */ // int[] supplyPinReportResult(String pin); // /** // * Supply a pin to unlock the SIM. Blocks until a result is determined. // * Returns a specific success/error code. // * @param pin The pin to check. // * @return retValue[0] = Phone.PIN_RESULT_SUCCESS on success. Otherwise error code // * retValue[1] = number of attempts remaining if known otherwise -1 // */ // int[] supplyPinReportResultForSubscriber(int subId, String pin); // /** // * Supply puk to unlock the SIM and set SIM pin to new pin. // * Blocks until a result is determined. // * Returns a specific success/error code // * @param puk The puk to check // * pin The pin to check. // * @return retValue[0] = Phone.PIN_RESULT_SUCCESS on success. Otherwise error code // * retValue[1] = number of attempts remaining if known otherwise -1 // */ // int[] supplyPukReportResult(String puk, String pin); // /** // * Supply puk to unlock the SIM and set SIM pin to new pin. // * Blocks until a result is determined. // * Returns a specific success/error code // * @param puk The puk to check // * pin The pin to check. // * @return retValue[0] = Phone.PIN_RESULT_SUCCESS on success. Otherwise error code // * retValue[1] = number of attempts remaining if known otherwise -1 // */ // int[] supplyPukReportResultForSubscriber(int subId, String puk, String pin); // /** // * Handles PIN MMI commands (PIN/PIN2/PUK/PUK2), which are initiated // * without SEND (so dial is not appropriate). // * // * @param dialString the MMI command to be executed. // * @return true if MMI command is executed. // */ // boolean handlePinMmi(String dialString); // /** // * Handles PIN MMI commands (PIN/PIN2/PUK/PUK2), which are initiated // * without SEND (so dial is not appropriate) for // * a particular subId. // * @param dialString the MMI command to be executed. // * @param subId user preferred subId. // * @return true if MMI command is executed. // */ // boolean handlePinMmiForSubscriber(int subId, String dialString); // /** // * Toggles the radio on or off. // */ // void toggleRadioOnOff(); // /** // * Toggles the radio on or off on particular subId. // * @param subId user preferred subId. // */ // void toggleRadioOnOffForSubscriber(int subId); // /** // * Set the radio to on or off // */ // boolean setRadio(boolean turnOn); // /** // * Set the radio to on or off on particular subId. // * @param subId user preferred subId. // */ // boolean setRadioForSubscriber(int subId, boolean turnOn); // /** // * Set the radio to on or off unconditionally // */ // boolean setRadioPower(boolean turnOn); // /** // * Request to update location information in service state // */ // void updateServiceLocation(); // /** // * Request to update location information for a subscrition in service state // * @param subId user preferred subId. // */ // void updateServiceLocationForSubscriber(int subId); // /** // * Enable location update notifications. // */ // void enableLocationUpdates(); // /** // * Enable location update notifications. // * @param subId user preferred subId. // */ // void enableLocationUpdatesForSubscriber(int subId); // /** // * Disable location update notifications. // */ // void disableLocationUpdates(); // /** // * Disable location update notifications. // * @param subId user preferred subId. // */ // void disableLocationUpdatesForSubscriber(int subId); // /** // * Allow mobile data connections. // */ // boolean enableDataConnectivity(); // /** // * Disallow mobile data connections. // */ // boolean disableDataConnectivity(); // /** // * Report whether data connectivity is possible. // */ // boolean isDataConnectivityPossible(); // Bundle getCellLocation(String callingPkg); // /** // * Returns the neighboring cell information of the device. // */ // List getNeighboringCellInfo(String callingPkg); // int getCallState(); // /** // * Returns the call state for a subId. // */ // int getCallStateForSubscriber(int subId); // int getDataActivity(); // int getDataState(); // /** // * Returns the current active phone type as integer. // * Returns TelephonyManager.PHONE_TYPE_CDMA if RILConstants.CDMA_PHONE // * and TelephonyManager.PHONE_TYPE_GSM if RILConstants.GSM_PHONE // */ // int getActivePhoneType(); // /** // * Returns the current active phone type as integer for particular subId. // * Returns TelephonyManager.PHONE_TYPE_CDMA if RILConstants.CDMA_PHONE // * and TelephonyManager.PHONE_TYPE_GSM if RILConstants.GSM_PHONE // * @param subId user preferred subId. // */ // int getActivePhoneTypeForSubscriber(int subId); // /** // * Returns the CDMA ERI icon index to display // * @param callingPackage package making the call. // */ // int getCdmaEriIconIndex(String callingPackage); // /** // * Returns the CDMA ERI icon index to display on particular subId. // * @param subId user preferred subId. // * @param callingPackage package making the call. // */ // int getCdmaEriIconIndexForSubscriber(int subId, String callingPackage); // /** // * Returns the CDMA ERI icon mode, // * 0 - ON // * 1 - FLASHING // * @param callingPackage package making the call. // */ // int getCdmaEriIconMode(String callingPackage); // /** // * Returns the CDMA ERI icon mode on particular subId, // * 0 - ON // * 1 - FLASHING // * @param subId user preferred subId. // * @param callingPackage package making the call. // */ // int getCdmaEriIconModeForSubscriber(int subId, String callingPackage); // /** // * Returns the CDMA ERI text, // * @param callingPackage package making the call. // */ // String getCdmaEriText(String callingPackage); // /** // * Returns the CDMA ERI text for particular subId, // * @param subId user preferred subId. // * @param callingPackage package making the call. // */ // String getCdmaEriTextForSubscriber(int subId, String callingPackage); // /** // * Returns true if OTA service provisioning needs to run. // * Only relevant on some technologies, others will always // * return false. // */ // boolean needsOtaServiceProvisioning(); // /** // * Sets the voicemail number for a particular subscriber. // */ // boolean setVoiceMailNumber(int subId, String alphaTag, String number); // /** // * Returns the unread count of voicemails // */ // int getVoiceMessageCount(); // /** // * Returns the unread count of voicemails for a subId. // * @param subId user preferred subId. // * Returns the unread count of voicemails // */ // int getVoiceMessageCountForSubscriber(int subId); // /** // * Returns the network type for data transmission // * Legacy call, permission-free // */ // int getNetworkType(); // /** // * Returns the network type of a subId. // * @param subId user preferred subId. // * @param callingPackage package making the call. // */ // int getNetworkTypeForSubscriber(int subId, String callingPackage); // /** // * Returns the network type for data transmission // * @param callingPackage package making the call. // */ // int getDataNetworkType(String callingPackage); // /** // * Returns the data network type of a subId // * @param subId user preferred subId. // * @param callingPackage package making the call. // */ // int getDataNetworkTypeForSubscriber(int subId, String callingPackage); // /** // * Returns the voice network type of a subId // * @param subId user preferred subId. // * @param callingPackage package making the call. // * Returns the network type // */ // int getVoiceNetworkTypeForSubscriber(int subId, String callingPackage); // /** // * Return true if an ICC card is present // */ // boolean hasIccCard(); // /** // * Return true if an ICC card is present for a subId. // * @param slotId user preferred slotId. // * Return true if an ICC card is present // */ // boolean hasIccCardUsingSlotId(int slotId); // /** // * Return if the current radio is LTE on CDMA. This // * is a tri-state return value as for a period of time // * the mode may be unknown. // * // * @param callingPackage the name of the calling package // * @return {@link Phone#LTE_ON_CDMA_UNKNOWN}, {@link Phone#LTE_ON_CDMA_FALSE} // * or {@link PHone#LTE_ON_CDMA_TRUE} // */ // int getLteOnCdmaMode(String callingPackage); // /** // * Return if the current radio is LTE on CDMA. This // * is a tri-state return value as for a period of time // * the mode may be unknown. // * // * @param callingPackage the name of the calling package // * @return {@link Phone#LTE_ON_CDMA_UNKNOWN}, {@link Phone#LTE_ON_CDMA_FALSE} // * or {@link PHone#LTE_ON_CDMA_TRUE} // */ // int getLteOnCdmaModeForSubscriber(int subId, String callingPackage); // /** // * Returns the all observed cell information of the device. // */ // List getAllCellInfo(String callingPkg); // /** // * Sets minimum time in milli-seconds between onCellInfoChanged // */ // void setCellInfoListRate(int rateInMillis); // /** // * get default sim // * @return sim id // */ // int getDefaultSim(); // /** // * Opens a logical channel to the ICC card. // * // * Input parameters equivalent to TS 27.007 AT+CCHO command. // * // * @param AID Application id. See ETSI 102.221 and 101.220. // * @return an IccOpenLogicalChannelResponse object. // */ // IccOpenLogicalChannelResponse iccOpenLogicalChannel(String AID); // /** // * Closes a previously opened logical channel to the ICC card. // * // * Input parameters equivalent to TS 27.007 AT+CCHC command. // * // * @param channel is the channel id to be closed as retruned by a // * successful iccOpenLogicalChannel. // * @return true if the channel was closed successfully. // */ // boolean iccCloseLogicalChannel(int channel); // /** // * Transmit an APDU to the ICC card over a logical channel. // * // * Input parameters equivalent to TS 27.007 AT+CGLA command. // * // * @param channel is the channel id to be closed as retruned by a // * successful iccOpenLogicalChannel. // * @param cla Class of the APDU command. // * @param instruction Instruction of the APDU command. // * @param p1 P1 value of the APDU command. // * @param p2 P2 value of the APDU command. // * @param p3 P3 value of the APDU command. If p3 is negative a 4 byte APDU // * is sent to the SIM. // * @param data Data to be sent with the APDU. // * @return The APDU response from the ICC card with the status appended at // * the end. // */ // String iccTransmitApduLogicalChannel(int channel, int cla, int instruction, // int p1, int p2, int p3, String data); // /** // * Transmit an APDU to the ICC card over the basic channel. // * // * Input parameters equivalent to TS 27.007 AT+CSIM command. // * // * @param cla Class of the APDU command. // * @param instruction Instruction of the APDU command. // * @param p1 P1 value of the APDU command. // * @param p2 P2 value of the APDU command. // * @param p3 P3 value of the APDU command. If p3 is negative a 4 byte APDU // * is sent to the SIM. // * @param data Data to be sent with the APDU. // * @return The APDU response from the ICC card with the status appended at // * the end. // */ // String iccTransmitApduBasicChannel(int cla, int instruction, // int p1, int p2, int p3, String data); // /** // * Returns the response APDU for a command APDU sent through SIM_IO. // * // * @param fileID // * @param command // * @param p1 P1 value of the APDU command. // * @param p2 P2 value of the APDU command. // * @param p3 P3 value of the APDU command. // * @param filePath // * @return The APDU response. // */ // byte[] iccExchangeSimIO(int fileID, int command, int p1, int p2, int p3, // String filePath); // /** // * Send ENVELOPE to the SIM and returns the response. // * // * @param contents String containing SAT/USAT response in hexadecimal // * format starting with command tag. See TS 102 223 for // * details. // * @return The APDU response from the ICC card, with the last 4 bytes // * being the status word. If the command fails, returns an empty // * string. // */ // String sendEnvelopeWithStatus(String content); // /** // * Read one of the NV items defined in {@link RadioNVItems} / {@code ril_nv_items.h}. // * Used for device configuration by some CDMA operators. // * // * @param itemID the ID of the item to read. // * @return the NV item as a String, or null on any failure. // */ // String nvReadItem(int itemID); // /** // * Write one of the NV items defined in {@link RadioNVItems} / {@code ril_nv_items.h}. // * Used for device configuration by some CDMA operators. // * // * @param itemID the ID of the item to read. // * @param itemValue the value to write, as a String. // * @return true on success; false on any failure. // */ // boolean nvWriteItem(int itemID, String itemValue); // /** // * Update the CDMA Preferred Roaming List (PRL) in the radio NV storage. // * Used for device configuration by some CDMA operators. // * // * @param preferredRoamingList byte array containing the new PRL. // * @return true on success; false on any failure. // */ // boolean nvWriteCdmaPrl(in byte[] preferredRoamingList); // /** // * Perform the specified type of NV config reset. The radio will be taken offline // * and the device must be rebooted after the operation. Used for device // * configuration by some CDMA operators. // * // * @param resetType the type of reset to perform (1 == factory reset; 2 == NV-only reset). // * @return true on success; false on any failure. // */ // boolean nvResetConfig(int resetType); // /* // * Get the calculated preferred network type. // * Used for device configuration by some CDMA operators. // * @param callingPackage The package making the call. // * // * @return the calculated preferred network type, defined in RILConstants.java. // */ // int getCalculatedPreferredNetworkType(String callingPackage); // /* // * Get the preferred network type. // * Used for device configuration by some CDMA operators. // * // * @param subId the id of the subscription to query. // * @return the preferred network type, defined in RILConstants.java. // */ // int getPreferredNetworkType(int subId); // /** // * Check TETHER_DUN_REQUIRED and TETHER_DUN_APN settings, net.tethering.noprovisioning // * SystemProperty, and config_tether_apndata to decide whether DUN APN is required for // * tethering. // * // * @return 0: Not required. 1: required. 2: Not set. // */ // int getTetherApnRequired(); // /** // * Set the network selection mode to automatic. // * // * @param subId the id of the subscription to update. // */ // void setNetworkSelectionModeAutomatic(int subId); // /** // * Perform a radio scan and return the list of avialble networks. // * // * @param subId the id of the subscription. // * @return CellNetworkScanResult containing status of scan and networks. // */ // CellNetworkScanResult getCellNetworkScanResults(int subId); // /** // * Ask the radio to connect to the input network and change selection mode to manual. // * // * @param subId the id of the subscription. // * @param operatorInfo the operator to attach to. // * @param persistSelection should the selection persist till reboot or its // * turned off? Will also result in notification being not shown to // * the user if the signal is lost. // * @return true if the request suceeded. // */ // boolean setNetworkSelectionModeManual(int subId, in OperatorInfo operator, // boolean persistSelection); // /** // * Set the preferred network type. // * Used for device configuration by some CDMA operators. // * // * @param subId the id of the subscription to update. // * @param networkType the preferred network type, defined in RILConstants.java. // * @return true on success; false on any failure. // */ // boolean setPreferredNetworkType(int subId, int networkType); // /** // * User enable/disable Mobile Data. // * // * @param enable true to turn on, else false // */ // void setDataEnabled(int subId, boolean enable); // /** // * Get the user enabled state of Mobile Data. // * // * @return true on enabled // */ // boolean getDataEnabled(int subId); // /** // * Get P-CSCF address from PCO after data connection is established or modified. // * @param apnType the apnType, "ims" for IMS APN, "emergency" for EMERGENCY APN // * @param callingPackage The package making the call. // */ // String[] getPcscfAddress(String apnType, String callingPackage); // /** // * Set IMS registration state // */ // void setImsRegistrationState(boolean registered); // /** // * Return MDN string for CDMA phone. // * @param subId user preferred subId. // */ // String getCdmaMdn(int subId); // /** // * Return MIN string for CDMA phone. // * @param subId user preferred subId. // */ // String getCdmaMin(int subId); // /** // * Has the calling application been granted special privileges by the carrier. // * // * If any of the packages in the calling UID has carrier privileges, the // * call will return true. This access is granted by the owner of the UICC // * card and does not depend on the registered carrier. // * // * TODO: Add a link to documentation. // * // * @return carrier privilege status defined in TelephonyManager. // */ // int getCarrierPrivilegeStatus(); // /** // * Similar to above, but check for the package whose name is pkgName. // */ // int checkCarrierPrivilegesForPackage(String pkgName); // /** // * Similar to above, but check across all phones. // */ // int checkCarrierPrivilegesForPackageAnyPhone(String pkgName); // /** // * Returns list of the package names of the carrier apps that should handle the input intent // * and have carrier privileges for the given phoneId. // * // * @param intent Intent that will be sent. // * @param phoneId The phoneId on which the carrier app has carrier privileges. // * @return list of carrier app package names that can handle the intent on phoneId. // * Returns null if there is an error and an empty list if there // * are no matching packages. // */ // List getCarrierPackageNamesForIntentAndPhone(in Intent intent, int phoneId); // /** // * Set the line 1 phone number string and its alphatag for the current ICCID // * for display purpose only, for example, displayed in Phone Status. It won't // * change the actual MSISDN/MDN. To unset alphatag or number, pass in a null // * value. // * // * @param subId the subscriber that the alphatag and dialing number belongs to. // * @param alphaTag alpha-tagging of the dailing nubmer // * @param number The dialing number // * @return true if the operation was executed correctly. // */ // boolean setLine1NumberForDisplayForSubscriber(int subId, String alphaTag, String number); // /** // * Returns the displayed dialing number string if it was set previously via // * {@link #setLine1NumberForDisplay}. Otherwise returns null. // * // * @param subId whose dialing number for line 1 is returned. // * @param callingPackage The package making the call. // * @return the displayed dialing number if set, or null if not set. // */ // String getLine1NumberForDisplay(int subId, String callingPackage); // /** // * Returns the displayed alphatag of the dialing number if it was set // * previously via {@link #setLine1NumberForDisplay}. Otherwise returns null. // * // * @param subId whose alphatag associated with line 1 is returned. // * @param callingPackage The package making the call. // * @return the displayed alphatag of the dialing number if set, or null if // * not set. // */ // String getLine1AlphaTagForDisplay(int subId, String callingPackage); // String[] getMergedSubscriberIds(String callingPackage); // /** // * Override the operator branding for the current ICCID. // * // * Once set, whenever the SIM is present in the device, the service // * provider name (SPN) and the operator name will both be replaced by the // * brand value input. To unset the value, the same function should be // * called with a null brand value. // * // *

Requires Permission: // * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} // * or has to be carrier app - see #hasCarrierPrivileges. // * // * @param brand The brand name to display/set. // * @return true if the operation was executed correctly. // */ // boolean setOperatorBrandOverride(String brand); // /** // * Override the roaming indicator for the current ICCID. // * // * Using this call, the carrier app (see #hasCarrierPrivileges) can override // * the platform's notion of a network operator being considered roaming or not. // * The change only affects the ICCID that was active when this call was made. // * // * If null is passed as any of the input, the corresponding value is deleted. // * // *

Requires that the caller have carrier privilege. See #hasCarrierPrivileges. // * // * @param gsmRoamingList - List of MCCMNCs to be considered roaming for 3GPP RATs. // * @param gsmNonRoamingList - List of MCCMNCs to be considered not roaming for 3GPP RATs. // * @param cdmaRoamingList - List of SIDs to be considered roaming for 3GPP2 RATs. // * @param cdmaNonRoamingList - List of SIDs to be considered not roaming for 3GPP2 RATs. // * @return true if the operation was executed correctly. // */ // boolean setRoamingOverride(in List gsmRoamingList, // in List gsmNonRoamingList, in List cdmaRoamingList, // in List cdmaNonRoamingList); // /** // * Returns the result and response from RIL for oem request // * // * @param oemReq the data is sent to ril. // * @param oemResp the respose data from RIL. // * @return negative value request was not handled or get error // * 0 request was handled succesfully, but no response data // * positive value success, data length of response // */ // int invokeOemRilRequestRaw(in byte[] oemReq, out byte[] oemResp); // /** // * Check if any mobile Radios need to be shutdown. // * // * @return true is any mobile radio needs to be shutdown // */ // boolean needMobileRadioShutdown(); // /** // * Shutdown Mobile Radios // */ // void shutdownMobileRadios(); // /** // * Set phone radio type and access technology. // * // * @param rafs an RadioAccessFamily array to indicate all phone's // * new radio access family. The length of RadioAccessFamily // * must equ]]al to phone count. // */ // void setRadioCapability(in RadioAccessFamily[] rafs); // /** // * Get phone radio type and access technology. // * // * @param phoneId which phone you want to get // * @param callingPackage the name of the package making the call // * @return phone radio type and access technology // */ // int getRadioAccessFamily(in int phoneId, String callingPackage); // /** // * Enables or disables video calling. // * // * @param enable Whether to enable video calling. // */ // void enableVideoCalling(boolean enable); // /** // * Whether video calling has been enabled by the user. // * // * @param callingPackage The package making the call. // * @return {@code true} if the user has enabled video calling, {@code false} otherwise. // */ // boolean isVideoCallingEnabled(String callingPackage); // /** // * Whether the DTMF tone length can be changed. // * // * @return {@code true} if the DTMF tone length can be changed. // */ // boolean canChangeDtmfToneLength(); // /** // * Whether the device is a world phone. // * // * @return {@code true} if the devices is a world phone. // */ // boolean isWorldPhone(); // /** // * Whether the phone supports TTY mode. // * // * @return {@code true} if the device supports TTY mode. // */ // boolean isTtyModeSupported(); // /** // * Whether the phone supports hearing aid compatibility. // * // * @return {@code true} if the device supports hearing aid compatibility. // */ // boolean isHearingAidCompatibilitySupported(); // /** // * Get IMS Registration Status // */ // boolean isImsRegistered(); // /** // * Returns the Status of Wi-Fi Calling // */ // boolean isWifiCallingAvailable(); // // /** // * Returns the Status of Volte // */ // boolean isVolteAvailable(); // /** // * Returns the Status of VT (video telephony) // */ // boolean isVideoTelephonyAvailable(); // /** // * Returns the unique device ID of phone, for example, the IMEI for // * GSM and the MEID for CDMA phones. Return null if device ID is not available. // * // * @param callingPackage The package making the call. // *

Requires Permission: // * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} // */ // String getDeviceId(String callingPackage); // /** // * Returns the subscription ID associated with the specified PhoneAccount. // */ // int getSubIdForPhoneAccount(in PhoneAccount phoneAccount); // void factoryReset(int subId); // /** // * An estimate of the users's current locale based on the default SIM. // * // * The returned string will be a well formed BCP-47 language tag, or {@code null} // * if no locale could be derived. // */ // String getLocaleFromDefaultSim(); // /** // * Return the modem activity info. // */ // ModemActivityInfo getModemActivityInfo(); // } sHookedMethodHandlers.put("dial", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("call", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("endCall", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("endCallForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("answerRingingCall", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("answerRingingCallForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("silenceRinger", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("isOffhook", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("isOffhookForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("isRingingForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("isRinging", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("isIdle", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("isIdleForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("isRadioOn", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("isRadioOnForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("isSimPinEnabled", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("supplyPin", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("supplyPinForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("supplyPuk", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("supplyPukForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("supplyPinReportResult", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("supplyPinReportResultForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("supplyPukReportResult", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("supplyPukReportResultForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("handlePinMmi", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("handlePinMmiForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("toggleRadioOnOff", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("toggleRadioOnOffForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("setRadio", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("setRadioForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("setRadioPower", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("updateServiceLocation", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("updateServiceLocationForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("enableLocationUpdates", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("enableLocationUpdatesForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("disableLocationUpdates", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("disableLocationUpdatesForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("enableDataConnectivity", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("disableDataConnectivity", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("isDataConnectivityPossible", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getCellLocation", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getNeighboringCellInfo", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getCallState", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getCallStateForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getDataActivity", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getDataState", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getActivePhoneType", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getActivePhoneTypeForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getCdmaEriIconIndex", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getCdmaEriIconIndexForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getCdmaEriIconMode", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getCdmaEriIconModeForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getCdmaEriText", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getCdmaEriTextForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("needsOtaServiceProvisioning", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("setVoiceMailNumber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getVoiceMessageCount", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getVoiceMessageCountForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getNetworkType", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getNetworkTypeForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getDataNetworkType", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getDataNetworkTypeForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getVoiceNetworkTypeForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("hasIccCard", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("hasIccCardUsingSlotId", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getLteOnCdmaMode", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getLteOnCdmaModeForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getAllCellInfo", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("setCellInfoListRate", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getDefaultSim", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("IccOpenLogicalChannelResponse", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("iccOpenLogicalChannel", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("iccCloseLogicalChannel", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("iccTransmitApduLogicalChannel", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("iccTransmitApduBasicChannel", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("iccExchangeSimIO", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("sendEnvelopeWithStatus", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("nvReadItem", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("nvWriteItem", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("nvWriteCdmaPrl", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("nvResetConfig", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getCalculatedPreferredNetworkType", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getPreferredNetworkType", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getTetherApnRequired", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("setNetworkSelectionModeAutomatic", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getCellNetworkScanResults", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("setNetworkSelectionModeManual", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("setPreferredNetworkType", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("setDataEnabled", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getDataEnabled", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getPcscfAddress", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("setImsRegistrationState", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getCdmaMdn", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getCdmaMin", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getCarrierPrivilegeStatus", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("checkCarrierPrivilegesForPackage", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("checkCarrierPrivilegesForPackageAnyPhone", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getCarrierPackageNamesForIntentAndPhone", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("setLine1NumberForDisplayForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getLine1NumberForDisplay", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getLine1AlphaTagForDisplay", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getMergedSubscriberIds", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("setOperatorBrandOverride", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("setRoamingOverride", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("invokeOemRilRequestRaw", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("needMobileRadioShutdown", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("shutdownMobileRadios", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("setRadioCapability", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getRadioAccessFamily", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("enableVideoCalling", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("isVideoCallingEnabled", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("canChangeDtmfToneLength", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("isWorldPhone", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("isTtyModeSupported", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("isHearingAidCompatibilitySupported", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("isImsRegistered", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("isWifiCallingAvailable", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("isVolteAvailable", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("isVideoTelephonyAvailable", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getDeviceId", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getSubIdForPhoneAccount", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("factoryReset", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getLocaleFromDefaultSim", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("getModemActivityInfo", new MyBaseHandler(mHostContext)); addAllMethodFromHookedClass(); } @Override protected Class getHookedClass() throws ClassNotFoundException { return ITelephonyCompat.Class(); } @Override protected HookedMethodHandler newBaseHandler() throws ClassNotFoundException { return new MyBaseHandler(mHostContext); } private static class MyBaseHandler extends ReplaceCallingPackageHookedMethodHandler { public MyBaseHandler(Context context) { super(context); } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/handle/ITelephonyRegistryHookHandle.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.handle; import android.content.Context; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.HookedMethodHandler; import com.morgoo.helper.compat.ITelephonyRegistryCompat; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2016/5/6. */ public class ITelephonyRegistryHookHandle extends BaseHookHandle { public ITelephonyRegistryHookHandle(Context hostContext) { super(hostContext); } @Override protected void init() { // interface ITelephonyRegistry { // void addOnSubscriptionsChangedListener(String pkg, // IOnSubscriptionsChangedListener callback); // void removeOnSubscriptionsChangedListener(String pkg, // IOnSubscriptionsChangedListener callback); // void listen(String pkg, IPhoneStateListener callback, int events, boolean notifyNow); // void listenForSubscriber(in int subId, String pkg, IPhoneStateListener callback, int events, // boolean notifyNow); // void notifyCallState(int state, String incomingNumber); // void notifyCallStateForSubscriber(in int subId, int state, String incomingNumber); // void notifyServiceStateForPhoneId(in int phoneId, in int subId, in ServiceState state); // void notifySignalStrength(in SignalStrength signalStrength); // void notifySignalStrengthForSubscriber(in int subId, in SignalStrength signalStrength); // void notifyMessageWaitingChangedForPhoneId(in int phoneId, in int subId, in boolean mwi); // void notifyCallForwardingChanged(boolean cfi); // void notifyCallForwardingChangedForSubscriber(in int subId, boolean cfi); // void notifyDataActivity(int state); // void notifyDataActivityForSubscriber(in int subId, int state); // void notifyDataConnection(int state, boolean isDataConnectivityPossible, String reason, String apn, String apnType, in LinkProperties linkProperties, in NetworkCapabilities networkCapabilities, int networkType, boolean roaming); // void notifyDataConnectionForSubscriber(int subId, int state, boolean isDataConnectivityPossible, String reason, String apn, String apnType, in LinkProperties linkProperties, in NetworkCapabilities networkCapabilities, int networkType, boolean roaming); // void notifyDataConnectionFailed(String reason, String apnType); // void notifyDataConnectionFailedForSubscriber(int subId, String reason, String apnType); // void notifyCellLocation(in Bundle cellLocation); // void notifyCellLocationForSubscriber(in int subId, in Bundle cellLocation); // void notifyOtaspChanged(in int otaspMode); // void notifyCellInfo(in List cellInfo); // void notifyPreciseCallState(int ringingCallState, int foregroundCallState, int backgroundCallState); // void notifyDisconnectCause(int disconnectCause, int preciseDisconnectCause); // void notifyPreciseDataConnectionFailed(String reason, String apnType, String apn, String failCause); // void notifyCellInfoForSubscriber(in int subId, in List cellInfo); // void notifyDataConnectionRealTimeInfo(in DataConnectionRealTimeInfo dcRtInfo); // void notifyVoLteServiceStateChanged(in VoLteServiceState lteState); // void notifyOemHookRawEventForSubscriber(in int subId, in byte[] rawData); // void notifySubscriptionInfoChanged(); // void notifyCarrierNetworkChange(in boolean active); // } sHookedMethodHandlers.put("addOnSubscriptionsChangedListener", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("removeOnSubscriptionsChangedListener", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("listen", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("listenForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("notifyCallState", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("notifyCallStateForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("notifyServiceStateForPhoneId", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("notifySignalStrength", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("notifySignalStrengthForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("notifyMessageWaitingChangedForPhoneId", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("notifyCallForwardingChanged", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("notifyCallForwardingChangedForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("notifyDataActivity", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("notifyDataActivityForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("notifyDataConnection", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("notifyDataConnectionForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("notifyDataConnectionFailed", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("notifyDataConnectionFailedForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("notifyCellLocation", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("notifyCellLocationForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("notifyOtaspChanged", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("notifyCellInfo", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("notifyPreciseCallState", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("notifyDisconnectCause", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("notifyPreciseDataConnectionFailed", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("notifyCellInfoForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("notifyDataConnectionRealTimeInfo", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("notifyVoLteServiceStateChanged", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("notifyOemHookRawEventForSubscriber", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("notifySubscriptionInfoChanged", new MyBaseHandler(mHostContext)); sHookedMethodHandlers.put("notifyCarrierNetworkChange", new MyBaseHandler(mHostContext)); addAllMethodFromHookedClass(); } @Override protected Class getHookedClass() throws ClassNotFoundException { return ITelephonyRegistryCompat.Class(); } @Override protected HookedMethodHandler newBaseHandler() throws ClassNotFoundException { return new MyBaseHandler(mHostContext); } private static class MyBaseHandler extends ReplaceCallingPackageHookedMethodHandler { public MyBaseHandler(Context context) { super(context); } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/handle/IWifiManagerHookHandle.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.handle; import android.content.Context; import android.text.TextUtils; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.HookedMethodHandler; import com.morgoo.droidplugin.pm.PluginManager; import java.lang.reflect.Method; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/6/1. */ public class IWifiManagerHookHandle extends BaseHookHandle { public IWifiManagerHookHandle(Context hostContext) { super(hostContext); } @Override protected void init() { sHookedMethodHandlers.put("getScanResults", new getScanResults(mHostContext)); sHookedMethodHandlers.put("getBatchedScanResults", new getBatchedScanResults(mHostContext)); sHookedMethodHandlers.put("setWifiEnabled", new setWifiEnabled(mHostContext)); } private class IWifiManagerHookedMethodHandler extends HookedMethodHandler { public IWifiManagerHookedMethodHandler(Context hostContext) { super(hostContext); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { //callingPackage final int index = 0; if (args != null && args.length > index && args[index] instanceof String) { String callingPackage = (String) args[index]; if (!TextUtils.equals(callingPackage, mHostContext.getPackageName())) { args[index] = mHostContext.getPackageName(); } } return super.beforeInvoke(receiver, method, args); } } private class getScanResults extends IWifiManagerHookedMethodHandler { public getScanResults(Context hostContext) { super(hostContext); } } private class getBatchedScanResults extends IWifiManagerHookedMethodHandler { public getBatchedScanResults(Context hostContext) { super(hostContext); } } private class setWifiEnabled extends HookedMethodHandler { public setWifiEnabled(Context hostContext) { super(hostContext); } //bugfix 一个外网崩溃 // Date:2015-08-03 14:05:55 // ----------------------------------------System Infomation----------------------------------- // AppPkgName:com.qihoo.appstore // VersionCode:300030241 // VersionName:3.2.41 // Debug:false // PName:com.qihoo.appstore:PluginP01 // imei:18b2003ce37bc6fce14f8fa86351732c // Board:MSM8974 // ro.bootloader:unknown // ro.product.brand:smartisan // ro.product.cpu.abi:armeabi-v7a // ro.product.cpu.abi2:armeabi // ro.product.device:msm8974sfo_lte // ro.build.display.id:SANFRANCISCO dev-keys // ro.build.fingerprint:smartisan/msm8974sfo_lte/msm8974sfo_lte:4.4.2/SANFRANCISCO:user/dev-keys // ro.hardware:qcom // ro.build.host:build4 // ro.build.id:SANFRANCISCO // ro.product.manufacturer:smartisan // ro.product.model:SM705 // ro.product.name:msm8974sfo_lte // gsm.version.baseband:2.0.1-00154-M8974AAAAANPZM-L // ro.build.tags:dev-keys // ro.build.type:user // ro.build.user:smartcm // ro.build.version.codename:REL // ro.build.version.incremental:14 // ro.build.version.release:4.4.2 // ro.build.version.sdk:19 // com.qihoo360.mobilesafe.clean version code :65 version name :1.5 // // // // ----------------------------------Exception--------------------------------------- // // // ----------------------------Exception message:Unable to start activity ComponentInfo{com.qihoo.appstore.plugin/com.qihoo.appstore.sharenearby.NBReceiverActivity}: java.lang.SecurityException: com.qihoo.appstore.plugin from uid 10082 not allowed to perform WIFI_CHANGE // // ----------------------------Exception StackTrace: // java.lang.RuntimeException: Unable to start activity ComponentInfo{com.qihoo.appstore.plugin/com.qihoo.appstore.sharenearby.NBReceiverActivity}: java.lang.SecurityException: com.qihoo.appstore.plugin from uid 10082 not allowed to perform WIFI_CHANGE // at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2218) // at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2268) // at android.app.ActivityThread.access$800(ActivityThread.java:145) // at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1206) // at android.os.Handler.dispatchMessage(Handler.java:102) // at android.os.Looper.loop(Looper.java:136) // at android.app.ActivityThread.main(ActivityThread.java:5086) // at java.lang.reflect.Method.invokeNative(Native Method) // at java.lang.reflect.Method.invoke(Method.java:515) // at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:888) // at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:697) // at dalvik.system.NativeStart.main(Native Method) // Caused by: java.lang.SecurityException: com.qihoo.appstore.plugin from uid 10082 not allowed to perform WIFI_CHANGE // at android.os.Parcel.readException(Parcel.java:1465) // at android.os.Parcel.readException(Parcel.java:1419) // at android.net.wifi.IWifiManager$Stub$Proxy.setWifiEnabled(IWifiManager.java:809) // at java.lang.reflect.Method.invokeNative(Native Method) // at java.lang.reflect.Method.invoke(Method.java:515) // at com.morgoo.droidplugin.c.a.a.invoke(Unknown Source) // at com.morgoo.droidplugin.c.a.k.invoke(Unknown Source) // at $Proxy8.setWifiEnabled(Native Method) // at android.net.wifi.WifiManager.setWifiEnabled(WifiManager.java:1028) // at com.qihoo.a.m.b(Unknown Source) // at com.qihoo.appstore.sharenearby.NBReceiverActivity.onCreate(Unknown Source) // at android.app.Activity.performCreate(Activity.java:5231) // at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1087) // at com.morgoo.droidplugin.c.b.hq.callActivityOnCreate(Unknown Source) // at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2172) // ... 11 more // ============================ // 17个 ,总共10643个,占比0.15972939960537444% @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { if (args != null && args.length > 0) { for (int i = 0; i < args.length; i++) { Object arg = args[i]; if (arg != null && arg instanceof String) { String str = ((String) arg); if (!TextUtils.equals(str, mHostContext.getPackageName()) && PluginManager.getInstance().isPluginPackage(str)) { args[i] = mHostContext.getPackageName(); } } } } return super.beforeInvoke(receiver, method, args); } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/handle/IWindowManagerHookHandle.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.handle; import android.content.Context; import android.view.WindowManager; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.HookedMethodHandler; import com.morgoo.droidplugin.hook.proxy.IContentProviderHook; import com.morgoo.droidplugin.hook.proxy.IWindowSessionHook; import com.morgoo.droidplugin.reflect.Utils; import com.morgoo.helper.MyProxy; import java.lang.reflect.Method; import java.util.List; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/6/17. */ public class IWindowManagerHookHandle extends BaseHookHandle { public IWindowManagerHookHandle(Context hostContext) { super(hostContext); } @Override protected void init() { sHookedMethodHandlers.put("openSession", new openSession(mHostContext)); sHookedMethodHandlers.put("overridePendingAppTransition", new overridePendingAppTransition(mHostContext)); sHookedMethodHandlers.put("setAppStartingWindow", new setAppStartingWindow(mHostContext)); } private class openSession extends HookedMethodHandler { public openSession(Context hostContext) { super(hostContext); } @Override protected void afterInvoke(Object receiver, Method method, Object[] args, Object invokeResult) throws Throwable { super.afterInvoke(receiver, method, args, invokeResult); Class clazz = invokeResult.getClass(); IWindowSessionHook invocationHandler = new IWindowSessionHook(mHostContext, invokeResult); invocationHandler.setEnable(true); List> interfaces = Utils.getAllInterfaces(clazz); Class[] ifs = interfaces != null && interfaces.size() > 0 ? interfaces.toArray(new Class[interfaces.size()]) : new Class[0]; Object newProxy = MyProxy.newProxyInstance(clazz.getClassLoader(), ifs, invocationHandler); setFakedResult(newProxy); } } private class overridePendingAppTransition extends HookedMethodHandler { public overridePendingAppTransition(Context hostContext) { super(hostContext); } } private class setAppStartingWindow extends HookedMethodHandler { public setAppStartingWindow(Context hostContext) { super(hostContext); } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/handle/IWindowSessionInvokeHandle.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.handle; import android.content.Context; import android.text.TextUtils; import android.view.WindowManager; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.HookedMethodHandler; import com.morgoo.droidplugin.pm.PluginManager; import java.lang.reflect.Method; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/6/17. */ public class IWindowSessionInvokeHandle extends BaseHookHandle { public IWindowSessionInvokeHandle(Context hostContext) { super(hostContext); } @Override protected void init() { sHookedMethodHandlers.put("add", new add(mHostContext)); sHookedMethodHandlers.put("addToDisplay", new addToDisplay(mHostContext)); sHookedMethodHandlers.put("addWithoutInputChannel", new addWithoutInputChannel(mHostContext)); sHookedMethodHandlers.put("addToDisplayWithoutInputChannel", new addToDisplayWithoutInputChannel(mHostContext)); sHookedMethodHandlers.put("relayout", new relayout(mHostContext)); } private class IWindowSessionHookedMethodHandler extends HookedMethodHandler { public IWindowSessionHookedMethodHandler(Context hostContext) { super(hostContext); } int findWindowManagerLayoutParamsIndex(Object[] args) { if (args != null && args.length > 0) { for (int i = 0; i < args.length; i++) { if (args[i] instanceof WindowManager.LayoutParams) { return i; } } } return -1; } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { if (args != null && args.length > 0) { int index = findWindowManagerLayoutParamsIndex(args); if (index >= 0) { WindowManager.LayoutParams attr = ((WindowManager.LayoutParams) args[index]); // String oldPkg = attr.packageName; if (!TextUtils.equals(attr.packageName, mHostContext.getPackageName())) { attr.packageName = mHostContext.getPackageName(); } // CharSequence ctitle = attr.getTitle(); // String title = String.valueOf(ctitle); // if (title != null && title.startsWith(oldPkg + "/")) { // String newTitle = title.replace(oldPkg + "/", attr.packageName + "/"); // attr.setTitle(newTitle); // } } } return super.beforeInvoke(receiver, method, args); } } private class add extends IWindowSessionHookedMethodHandler { public add(Context hostContext) { super(hostContext); } } private class addToDisplay extends IWindowSessionHookedMethodHandler { public addToDisplay(Context hostContext) { super(hostContext); } } private class addWithoutInputChannel extends IWindowSessionHookedMethodHandler { public addWithoutInputChannel(Context hostContext) { super(hostContext); } } private class addToDisplayWithoutInputChannel extends IWindowSessionHookedMethodHandler { public addToDisplayWithoutInputChannel(Context hostContext) { super(hostContext); } } private class relayout extends IWindowSessionHookedMethodHandler { public relayout(Context hostContext) { super(hostContext); } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/handle/LibCoreHookHandle.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.handle; import android.content.Context; import android.os.Environment; import android.text.TextUtils; import com.morgoo.droidplugin.core.PluginDirHelper; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.HookedMethodHandler; import java.io.File; import java.lang.reflect.Method; /** * 重定向所有的IO操作。 *

* Created by Andy Zhang(zhangyong232@gmail.com) 2015/3/30. */ public class LibCoreHookHandle extends BaseHookHandle { public LibCoreHookHandle(Context hostContext) { super(hostContext); } private static final String TAG = LibCoreHookHandle.class.getSimpleName(); @Override protected void init() { sHookedMethodHandlers.put("access", new access(mHostContext)); sHookedMethodHandlers.put("chmod", new chmod(mHostContext)); sHookedMethodHandlers.put("chown", new chown(mHostContext)); sHookedMethodHandlers.put("execv", new execv(mHostContext)); sHookedMethodHandlers.put("execve", new execve(mHostContext)); sHookedMethodHandlers.put("mkdir", new mkdir(mHostContext)); sHookedMethodHandlers.put("open", new open(mHostContext)); sHookedMethodHandlers.put("remove", new remove(mHostContext)); sHookedMethodHandlers.put("rename", new rename(mHostContext)); sHookedMethodHandlers.put("stat", new stat(mHostContext)); sHookedMethodHandlers.put("statvfs", new statvfs(mHostContext)); sHookedMethodHandlers.put("symlink", new symlink(mHostContext)); } private abstract static class BaseLibCore extends HookedMethodHandler { private final String mDataDir; private final String mHostDataDir; private final String mHostPkg; public BaseLibCore(Context context) { super(context); mDataDir = new File(Environment.getDataDirectory(), "data/").getPath(); mHostDataDir = PluginDirHelper.getContextDataDir(context); mHostPkg = context.getPackageName(); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { // Log.i(TAG, "Old %s(%s)", method.getName(), Arrays.toString(args)); int index = 0; replace(args, index); // Log.i(TAG, "New %s(%s)", method.getName(), Arrays.toString(args)); return super.beforeInvoke(receiver, method, args); } protected void replace(Object[] args, int index) { if (args != null && args.length > index && args[index] instanceof String) { String path = (String) args[index]; String newPath = tryReplacePath(path); if (newPath != null) { args[index] = newPath; } } } //传入一个路径,比如/data/data/com.xxx.plugin/xxx 会替换成/data/data/插件宿主包名/Plugin/插件包名/data/插件包名 private String tryReplacePath(String tarDir) { //mDataDir=/data/data/ //mHostDataDir=/data/data/com.example.TestPlugin/ //mHostPkg=com.example.TestPlugin //Old stat([/data/data/com.example.TestPlugin/Plugin/com.qihoo.appstore/apk/base-1.apk]) //New stat([/data/data/com.example.TestPlugin/Plugin/com.example.TestPlugin/data/com.example.TestPlugin/Plugin/com.qihoo.appstore/apk/base-1.apk]) if (tarDir != null && tarDir.length() > mDataDir.length() && !TextUtils.equals(tarDir, mDataDir) && tarDir.startsWith(mDataDir)) { if (!tarDir.startsWith(mHostDataDir) && !TextUtils.equals(tarDir, mHostDataDir)) { String pkg = tarDir.substring(mDataDir.length() + 1); int index = pkg.indexOf("/"); if (index > 0) { pkg = pkg.substring(0, index); } if (!TextUtils.equals(pkg, mHostPkg)) { tarDir = tarDir.replace(pkg, String.format("%s/Plugin/%s/data/%s", mHostPkg, pkg, pkg)); return tarDir; } } } return null; } } private class access extends BaseLibCore { public access(Context context) { super(context); } } private class chmod extends BaseLibCore { public chmod(Context context) { super(context); } } private class chown extends BaseLibCore { public chown(Context context) { super(context); } } private class execv extends BaseLibCore { public execv(Context context) { super(context); } } private class execve extends BaseLibCore { public execve(Context context) { super(context); } } private class mkdir extends BaseLibCore { public mkdir(Context context) { super(context); } } private class open extends BaseLibCore { public open(Context context) { super(context); } } private class remove extends BaseLibCore { public remove(Context context) { super(context); } } private class rename extends BaseLibCore { public rename(Context context) { super(context); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { int index = 1; replace(args, index); return super.beforeInvoke(receiver, method, args); } } private class stat extends BaseLibCore { public stat(Context context) { super(context); } } private class statvfs extends BaseLibCore { public statvfs(Context context) { super(context); } } private class symlink extends BaseLibCore { public symlink(Context context) { super(context); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { int index = 1; replace(args, index); return super.beforeInvoke(receiver, method, args); } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/handle/PluginCallback.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.handle; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Message; import com.morgoo.droidplugin.core.Env; import com.morgoo.droidplugin.core.PluginProcessManager; import com.morgoo.droidplugin.hook.proxy.IPackageManagerHook; import com.morgoo.droidplugin.pm.PluginManager; import com.morgoo.droidplugin.reflect.FieldUtils; import com.morgoo.droidplugin.stub.ShortcutProxyActivity; import com.morgoo.helper.Log; public class PluginCallback implements Handler.Callback { private static final String TAG = "PluginCallback"; public static final int LAUNCH_ACTIVITY = 100; public static final int PAUSE_ACTIVITY = 101; public static final int PAUSE_ACTIVITY_FINISHING = 102; public static final int STOP_ACTIVITY_SHOW = 103; public static final int STOP_ACTIVITY_HIDE = 104; public static final int SHOW_WINDOW = 105; public static final int HIDE_WINDOW = 106; public static final int RESUME_ACTIVITY = 107; public static final int SEND_RESULT = 108; public static final int DESTROY_ACTIVITY = 109; public static final int BIND_APPLICATION = 110; public static final int EXIT_APPLICATION = 111; public static final int NEW_INTENT = 112; public static final int RECEIVER = 113; public static final int CREATE_SERVICE = 114; public static final int SERVICE_ARGS = 115; public static final int STOP_SERVICE = 116; public static final int REQUEST_THUMBNAIL = 117; public static final int CONFIGURATION_CHANGED = 118; public static final int CLEAN_UP_CONTEXT = 119; public static final int GC_WHEN_IDLE = 120; public static final int BIND_SERVICE = 121; public static final int UNBIND_SERVICE = 122; public static final int DUMP_SERVICE = 123; public static final int LOW_MEMORY = 124; public static final int ACTIVITY_CONFIGURATION_CHANGED = 125; public static final int RELAUNCH_ACTIVITY = 126; public static final int PROFILER_CONTROL = 127; public static final int CREATE_BACKUP_AGENT = 128; public static final int DESTROY_BACKUP_AGENT = 129; public static final int SUICIDE = 130; public static final int REMOVE_PROVIDER = 131; public static final int ENABLE_JIT = 132; public static final int DISPATCH_PACKAGE_BROADCAST = 133; public static final int SCHEDULE_CRASH = 134; public static final int DUMP_HEAP = 135; public static final int DUMP_ACTIVITY = 136; public static final int SLEEPING = 137; public static final int SET_CORE_SETTINGS = 138; public static final int UPDATE_PACKAGE_COMPATIBILITY_INFO = 139; public static final int TRIM_MEMORY = 140; public static final int DUMP_PROVIDER = 141; public static final int UNSTABLE_PROVIDER_DIED = 142; public static final int REQUEST_ASSIST_CONTEXT_EXTRAS = 143; public static final int TRANSLUCENT_CONVERSION_COMPLETE = 144; public static final int INSTALL_PROVIDER = 145; public static final int ON_NEW_ACTIVITY_OPTIONS = 146; public static final int CANCEL_VISIBLE_BEHIND = 147; public static final int BACKGROUND_VISIBLE_BEHIND_CHANGED = 148; public static final int ENTER_ANIMATION_COMPLETE = 149; String codeToString(int code) { switch (code) { case LAUNCH_ACTIVITY: return "LAUNCH_ACTIVITY"; case PAUSE_ACTIVITY: return "PAUSE_ACTIVITY"; case PAUSE_ACTIVITY_FINISHING: return "PAUSE_ACTIVITY_FINISHING"; case STOP_ACTIVITY_SHOW: return "STOP_ACTIVITY_SHOW"; case STOP_ACTIVITY_HIDE: return "STOP_ACTIVITY_HIDE"; case SHOW_WINDOW: return "SHOW_WINDOW"; case HIDE_WINDOW: return "HIDE_WINDOW"; case RESUME_ACTIVITY: return "RESUME_ACTIVITY"; case SEND_RESULT: return "SEND_RESULT"; case DESTROY_ACTIVITY: return "DESTROY_ACTIVITY"; case BIND_APPLICATION: return "BIND_APPLICATION"; case EXIT_APPLICATION: return "EXIT_APPLICATION"; case NEW_INTENT: return "NEW_INTENT"; case RECEIVER: return "RECEIVER"; case CREATE_SERVICE: return "CREATE_SERVICE"; case SERVICE_ARGS: return "SERVICE_ARGS"; case STOP_SERVICE: return "STOP_SERVICE"; case CONFIGURATION_CHANGED: return "CONFIGURATION_CHANGED"; case CLEAN_UP_CONTEXT: return "CLEAN_UP_CONTEXT"; case GC_WHEN_IDLE: return "GC_WHEN_IDLE"; case BIND_SERVICE: return "BIND_SERVICE"; case UNBIND_SERVICE: return "UNBIND_SERVICE"; case DUMP_SERVICE: return "DUMP_SERVICE"; case LOW_MEMORY: return "LOW_MEMORY"; case ACTIVITY_CONFIGURATION_CHANGED: return "ACTIVITY_CONFIGURATION_CHANGED"; case RELAUNCH_ACTIVITY: return "RELAUNCH_ACTIVITY"; case PROFILER_CONTROL: return "PROFILER_CONTROL"; case CREATE_BACKUP_AGENT: return "CREATE_BACKUP_AGENT"; case DESTROY_BACKUP_AGENT: return "DESTROY_BACKUP_AGENT"; case SUICIDE: return "SUICIDE"; case REMOVE_PROVIDER: return "REMOVE_PROVIDER"; case ENABLE_JIT: return "ENABLE_JIT"; case DISPATCH_PACKAGE_BROADCAST: return "DISPATCH_PACKAGE_BROADCAST"; case SCHEDULE_CRASH: return "SCHEDULE_CRASH"; case DUMP_HEAP: return "DUMP_HEAP"; case DUMP_ACTIVITY: return "DUMP_ACTIVITY"; case SLEEPING: return "SLEEPING"; case SET_CORE_SETTINGS: return "SET_CORE_SETTINGS"; case UPDATE_PACKAGE_COMPATIBILITY_INFO: return "UPDATE_PACKAGE_COMPATIBILITY_INFO"; case TRIM_MEMORY: return "TRIM_MEMORY"; case DUMP_PROVIDER: return "DUMP_PROVIDER"; case UNSTABLE_PROVIDER_DIED: return "UNSTABLE_PROVIDER_DIED"; case REQUEST_ASSIST_CONTEXT_EXTRAS: return "REQUEST_ASSIST_CONTEXT_EXTRAS"; case TRANSLUCENT_CONVERSION_COMPLETE: return "TRANSLUCENT_CONVERSION_COMPLETE"; case INSTALL_PROVIDER: return "INSTALL_PROVIDER"; case ON_NEW_ACTIVITY_OPTIONS: return "ON_NEW_ACTIVITY_OPTIONS"; case CANCEL_VISIBLE_BEHIND: return "CANCEL_VISIBLE_BEHIND"; case BACKGROUND_VISIBLE_BEHIND_CHANGED: return "BACKGROUND_VISIBLE_BEHIND_CHANGED"; case ENTER_ANIMATION_COMPLETE: return "ENTER_ANIMATION_COMPLETE"; } return Integer.toString(code); } private Handler mOldHandle = null; private Handler.Callback mCallback = null; private Context mHostContext; private boolean mEnable = false; public PluginCallback(Context hostContext, Handler oldHandle, Handler.Callback callback) { mOldHandle = oldHandle; mCallback = callback; mHostContext = hostContext; } public void setEnable(boolean enable) { this.mEnable = enable; } public boolean isEnable() { return mEnable; } @Override public boolean handleMessage(Message msg) { long b = System.currentTimeMillis(); try { if (!mEnable) { return false; } if (PluginProcessManager.isPluginProcess(mHostContext)) { if (!PluginManager.getInstance().isConnected()) { //这里必须要这么做。如果当前进程是插件进程,并且,还没有绑定上插件管理服务,我们则把消息延迟一段时间再处理。 //这样虽然会降低启动速度,但是可以解决在没绑定服务就启动,会导致的一系列时序问题。 Log.i(TAG, "handleMessage not isConnected post and wait,msg=%s", msg); mOldHandle.sendMessageDelayed(Message.obtain(msg), 5); //返回true,告诉下面的handle不要处理了。 return true; } } if (msg.what == LAUNCH_ACTIVITY) { return handleLaunchActivity(msg); } /*else if (msg.what == INSTALL_PROVIDER) { return handleInstallProvider(msg); } else if (msg.what == CREATE_BACKUP_AGENT) { //TODO 处理CREATE_BACKUP_AGENT } else if (msg.what == DESTROY_BACKUP_AGENT) { //TODO 处理DESTROY_BACKUP_AGENT } else if (msg.what == CREATE_SERVICE) { // return handleCreateService(msg); } else if (msg.what == BIND_SERVICE) { // return handleBindService(msg); } else if (msg.what == UNBIND_SERVICE) { // return handleUnbindService(msg); } else if (msg.what == SERVICE_ARGS) { // return handleServiceArgs(msg); }*/ if (mCallback != null) { return mCallback.handleMessage(msg); } else { return false; } } finally { Log.i(TAG, "handleMessage(%s,%s) cost %s ms", msg.what, codeToString(msg.what), (System.currentTimeMillis() - b)); } } // private boolean handleServiceArgs(Message msg) { // // handleServiceArgs((ServiceArgsData)msg.obj); // try { // Object obj = msg.obj; // Intent intent = (Intent) FieldUtils.readField(obj, "args", true); // if (intent != null) { // intent.setExtrasClassLoader(getClass().getClassLoader()); // Intent originPluginIntent = intent.getParcelableExtra(Env.EXTRA_TARGET_INTENT); // if (originPluginIntent != null) { // FieldUtils.writeDeclaredField(msg.obj, "args", originPluginIntent, true); // Log.i(TAG, "handleServiceArgs OK"); // } else { // Log.w(TAG, "handleServiceArgs pluginInfo==null"); // } // } // // } catch (Exception e) { // Log.e(TAG, "handleServiceArgs", e); // } // return false; // } // // private boolean handleUnbindService(Message msg) { // // handleUnbindService((BindServiceData)msg.obj); // try { // Object obj = msg.obj; // Intent intent = (Intent) FieldUtils.readField(obj, "intent", true); // intent.setExtrasClassLoader(getClass().getClassLoader()); // Intent originPluginIntent = intent.getParcelableExtra(Env.EXTRA_TARGET_INTENT); // if (originPluginIntent != null) { // FieldUtils.writeDeclaredField(msg.obj, "intent", originPluginIntent, true); // Log.i(TAG, "handleUnbindService OK"); // } else { // Log.w(TAG, "handleUnbindService pluginInfo==null"); // } // } catch (Exception e) { // Log.e(TAG, "handleUnbindService", e); // } // return false; // } // // private boolean handleBindService(Message msg) { // // handleBindService((BindServiceData)msg.obj); // //其实这里什么都不用做的。 // try { // Object obj = msg.obj; // Intent intent = (Intent) FieldUtils.readField(obj, "intent", true); // intent.setExtrasClassLoader(getClass().getClassLoader()); // Intent originPluginIntent = intent.getParcelableExtra(Env.EXTRA_TARGET_INTENT); // if (originPluginIntent != null) { // // // FieldUtils.writeDeclaredField(msg.obj, "intent", originPluginIntent, true); // Log.i(TAG, "handleBindService OK"); // } else { // Log.w(TAG, "handleBindService pluginInfo==null"); // } // } catch (Exception e) { // Log.e(TAG, "handleBindService", e); // } // return false; // } // // private boolean handleCreateService(Message msg) { // // handleCreateService((CreateServiceData)msg.obj); // try { // Object obj = msg.obj; // ServiceInfo info = (ServiceInfo) FieldUtils.readField(obj, "info", true); // if (info != null) { // ServiceInfo newServiceInfo = PluginManager.getInstance().getTargetServiceInfo(info); // if (newServiceInfo != null) { // FieldUtils.writeDeclaredField(msg.obj, "info", newServiceInfo, true); // } // } // } catch (Exception e) { // Log.e(TAG, "handleCreateService", e); // } // return false; // } private boolean handleLaunchActivity(Message msg) { try { Object obj = msg.obj; Intent stubIntent = (Intent) FieldUtils.readField(obj, "intent"); //ActivityInfo activityInfo = (ActivityInfo) FieldUtils.readField(obj, "activityInfo", true); stubIntent.setExtrasClassLoader(mHostContext.getClassLoader()); Intent targetIntent = stubIntent.getParcelableExtra(Env.EXTRA_TARGET_INTENT); // 这里多加一个isNotShortcutProxyActivity的判断,因为ShortcutProxyActivity的很特殊,启动它的时候, // 也会带上一个EXTRA_TARGET_INTENT的数据,就会导致这里误以为是启动插件Activity,所以这里要先做一个判断。 // 之前ShortcutProxyActivity错误复用了key,但是为了兼容,所以这里就先这么判断吧。 if (targetIntent != null && !isShortcutProxyActivity(stubIntent)) { IPackageManagerHook.fixContextPackageManager(mHostContext); ComponentName targetComponentName = targetIntent.resolveActivity(mHostContext.getPackageManager()); ActivityInfo targetActivityInfo = PluginManager.getInstance().getActivityInfo(targetComponentName, 0); if (targetActivityInfo != null) { if (targetComponentName != null && targetComponentName.getClassName().startsWith("")) { targetIntent.setClassName(targetComponentName.getPackageName(), targetComponentName.getPackageName() + targetComponentName.getClassName()); } ResolveInfo resolveInfo = mHostContext.getPackageManager().resolveActivity(stubIntent, 0); ActivityInfo stubActivityInfo = resolveInfo != null ? resolveInfo.activityInfo : null; if (stubActivityInfo != null) { PluginManager.getInstance().reportMyProcessName(stubActivityInfo.processName, targetActivityInfo.processName, targetActivityInfo.packageName); } PluginProcessManager.preLoadApk(mHostContext, targetActivityInfo); ClassLoader pluginClassLoader = PluginProcessManager.getPluginClassLoader(targetComponentName.getPackageName()); setIntentClassLoader(targetIntent, pluginClassLoader); setIntentClassLoader(stubIntent, pluginClassLoader); boolean success = false; try { targetIntent.putExtra(Env.EXTRA_TARGET_INFO, targetActivityInfo); if (stubActivityInfo != null) { targetIntent.putExtra(Env.EXTRA_STUB_INFO, stubActivityInfo); } success = true; } catch (Exception e) { Log.e(TAG, "putExtra 1 fail", e); } if (!success && Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) { try { ClassLoader oldParent = fixedClassLoader(pluginClassLoader); targetIntent.putExtras(targetIntent.getExtras()); targetIntent.putExtra(Env.EXTRA_TARGET_INFO, targetActivityInfo); if (stubActivityInfo != null) { targetIntent.putExtra(Env.EXTRA_STUB_INFO, stubActivityInfo); } fixedClassLoader(oldParent); success = true; } catch (Exception e) { Log.e(TAG, "putExtra 2 fail", e); } } if (!success) { Intent newTargetIntent = new Intent(); newTargetIntent.setComponent(targetIntent.getComponent()); newTargetIntent.putExtra(Env.EXTRA_TARGET_INFO, targetActivityInfo); if (stubActivityInfo != null) { newTargetIntent.putExtra(Env.EXTRA_STUB_INFO, stubActivityInfo); } FieldUtils.writeDeclaredField(msg.obj, "intent", newTargetIntent); } else { FieldUtils.writeDeclaredField(msg.obj, "intent", targetIntent); } FieldUtils.writeDeclaredField(msg.obj, "activityInfo", targetActivityInfo); Log.i(TAG, "handleLaunchActivity OK"); } else { Log.e(TAG, "handleLaunchActivity oldInfo==null"); } } else { Log.e(TAG, "handleLaunchActivity targetIntent==null"); } } catch (Exception e) { Log.e(TAG, "handleLaunchActivity FAIL", e); } if (mCallback != null) { return mCallback.handleMessage(msg); } else { return false; } } private boolean isShortcutProxyActivity(Intent targetIntent) { try { if (PluginManager.ACTION_SHORTCUT_PROXY.equalsIgnoreCase(targetIntent.getAction())) { return true; } PackageManager pm = mHostContext.getPackageManager(); ResolveInfo info = pm.resolveActivity(targetIntent, 0); if (info != null) { String name = info.activityInfo.name; if (name != null && name.startsWith("")) { name = info.activityInfo.packageName + info.activityInfo.name; } return ShortcutProxyActivity.class.getName().equals(name); } return false; } catch (Exception e) { return false; } } private ClassLoader fixedClassLoader(ClassLoader newParent) { ClassLoader nowClassLoader = PluginCallback.class.getClassLoader(); ClassLoader oldParent = nowClassLoader.getParent(); try { if (newParent != null && newParent != oldParent) { FieldUtils.writeField(nowClassLoader, "parent", newParent); } } catch (Exception e) { e.printStackTrace(); } return oldParent; } private void setIntentClassLoader(Intent intent, ClassLoader classLoader) { try { Bundle mExtras = (Bundle) FieldUtils.readField(intent, "mExtras"); if (mExtras != null) { mExtras.setClassLoader(classLoader); } else { Bundle value = new Bundle(); value.setClassLoader(classLoader); FieldUtils.writeField(intent, "mExtras", value); } } catch (Exception e) { } finally { intent.setExtrasClassLoader(classLoader); } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/handle/PluginInstrumentation.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.handle; import android.annotation.TargetApi; import android.app.Activity; import android.app.Application; import android.app.Instrumentation; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; import android.os.RemoteException; import android.text.TextUtils; import com.morgoo.droidplugin.am.RunningActivities; import com.morgoo.droidplugin.core.Env; import com.morgoo.droidplugin.core.PluginProcessManager; import com.morgoo.droidplugin.hook.HookFactory; import com.morgoo.droidplugin.hook.binder.IWindowManagerBinderHook; import com.morgoo.droidplugin.hook.proxy.IPackageManagerHook; import com.morgoo.droidplugin.pm.PluginManager; import com.morgoo.droidplugin.reflect.FieldUtils; import com.morgoo.helper.Log; import java.lang.reflect.Field; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2014/12/5. */ public class PluginInstrumentation extends Instrumentation { protected static final String TAG = PluginInstrumentation.class.getSimpleName(); protected Instrumentation mTarget; private final Context mHostContext; private boolean enable = true; public void setEnable(boolean enable) { this.enable = enable; this.enable = true; } public PluginInstrumentation(Context hostContext, Instrumentation target) { mTarget = target; mHostContext = hostContext; } @Override public void callActivityOnCreate(Activity activity, Bundle icicle) { if (enable) { IWindowManagerBinderHook.fixWindowManagerHook(activity); IPackageManagerHook.fixContextPackageManager(activity); try { PluginProcessManager.fakeSystemService(mHostContext, activity); } catch (Exception e) { Log.e(TAG, "callActivityOnCreate:fakeSystemService", e); } try { onActivityCreated(activity); } catch (RemoteException e) { Log.e(TAG, "callActivityOnCreate:onActivityCreated", e); } try { fixBaseContextImplOpsPackage(activity.getBaseContext()); } catch (Exception e) { Log.e(TAG, "callActivityOnCreate:fixBaseContextImplOpsPackage", e); } try { fixBaseContextImplContentResolverOpsPackage(activity.getBaseContext()); } catch (Exception e) { Log.e(TAG, "callActivityOnCreate:fixBaseContextImplContentResolverOpsPackage", e); } } if (mTarget != null) { mTarget.callActivityOnCreate(activity, icicle); } else { super.callActivityOnCreate(activity, icicle); } } private void fixBaseContextImplOpsPackage(Context context) throws IllegalAccessException { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1 && context != null && !TextUtils.equals(context.getPackageName(), mHostContext.getPackageName())) { Context baseContext = context; Class clazz = baseContext.getClass(); Field mOpPackageName = FieldUtils.getDeclaredField(clazz, "mOpPackageName", true); if (mOpPackageName != null) { Object valueObj = mOpPackageName.get(baseContext); if (valueObj instanceof String) { String opPackageName = ((String) valueObj); if (!TextUtils.equals(opPackageName, mHostContext.getPackageName())) { mOpPackageName.set(baseContext, mHostContext.getPackageName()); Log.i(TAG, "fixBaseContextImplOpsPackage OK!Context=%s,", baseContext); } } } } } private void fixBaseContextImplContentResolverOpsPackage(Context context) throws IllegalAccessException { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1 && context != null && !TextUtils.equals(context.getPackageName(), mHostContext.getPackageName())) { Context baseContext = context; Class clazz = baseContext.getClass(); Field mContentResolver = FieldUtils.getDeclaredField(clazz, "mContentResolver", true); if (mContentResolver != null) { Object valueObj = mContentResolver.get(baseContext); if (valueObj instanceof ContentResolver) { ContentResolver contentResolver = ((ContentResolver) valueObj); Field mPackageName = FieldUtils.getDeclaredField(ContentResolver.class, "mPackageName", true); Object mPackageNameValueObj = mPackageName.get(contentResolver); if (mPackageNameValueObj != null && mPackageNameValueObj instanceof String) { String packageName = ((String) mPackageNameValueObj); if (!TextUtils.equals(packageName, mHostContext.getPackageName())) { mPackageName.set(contentResolver, mHostContext.getPackageName()); Log.i(TAG, "fixBaseContextImplContentResolverOpsPackage OK!Context=%s,contentResolver=%s", baseContext, contentResolver); } } } } } } private void onActivityCreated(Activity activity) throws RemoteException { try { Intent targetIntent = activity.getIntent(); if (targetIntent != null) { ActivityInfo targetInfo = targetIntent.getParcelableExtra(Env.EXTRA_TARGET_INFO); ActivityInfo stubInfo = targetIntent.getParcelableExtra(Env.EXTRA_STUB_INFO); if (targetInfo != null && stubInfo != null) { RunningActivities.onActivtyCreate(activity, targetInfo, stubInfo); if (activity.getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED && targetInfo.screenOrientation != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) { activity.setRequestedOrientation(targetInfo.screenOrientation); } PluginManager.getInstance().onActivityCreated(stubInfo, targetInfo); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { fixTaskDescription(activity, targetInfo); } } } } catch (Exception e) { Log.i(TAG, "onActivityCreated fail", e); } } private void onActivityOnNewIntent(Activity activity, Intent intent) throws RemoteException { // try { Intent targetIntent = activity.getIntent(); if (targetIntent != null) { ActivityInfo targetInfo = targetIntent.getParcelableExtra(Env.EXTRA_TARGET_INFO); ActivityInfo stubInfo = targetIntent.getParcelableExtra(Env.EXTRA_STUB_INFO); if (targetInfo != null && stubInfo != null) { RunningActivities.onActivtyOnNewIntent(activity, targetInfo, stubInfo, intent); PluginManager.getInstance().onActivtyOnNewIntent(stubInfo, targetInfo, intent); } } } catch (Exception e) { Log.i(TAG, "onActivityCreated fail", e); } } @TargetApi(Build.VERSION_CODES.LOLLIPOP) private void fixTaskDescription(Activity activity, ActivityInfo targetInfo) { try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { PackageManager pm = mHostContext.getPackageManager(); String lablel = String.valueOf(targetInfo.loadLabel(pm)); Drawable icon = targetInfo.loadIcon(pm); Bitmap bitmap = null; if (icon instanceof BitmapDrawable) { bitmap = ((BitmapDrawable) icon).getBitmap(); } if (bitmap != null) { activity.setTaskDescription(new android.app.ActivityManager.TaskDescription(lablel, bitmap)); } else { activity.setTaskDescription(new android.app.ActivityManager.TaskDescription(lablel)); } } } catch (Throwable e) { Log.w(TAG, "fixTaskDescription fail", e); } } private void onActivityDestory(Activity activity) throws RemoteException { Intent targetIntent = activity.getIntent(); if (targetIntent != null) { ActivityInfo targetInfo = targetIntent.getParcelableExtra(Env.EXTRA_TARGET_INFO); ActivityInfo stubInfo = targetIntent.getParcelableExtra(Env.EXTRA_STUB_INFO); if (targetInfo != null && stubInfo != null) { PluginManager.getInstance().onActivityDestory(stubInfo, targetInfo); } } } @Override public void callActivityOnDestroy(Activity activity) { if (mTarget != null) { mTarget.callActivityOnDestroy(activity); } else { super.callActivityOnDestroy(activity); } RunningActivities.onActivtyDestory(activity); if (enable) { try { onActivityDestory(activity); } catch (RemoteException e) { Log.e(TAG, "callActivityOnDestroy:onActivityDestroy", e); } } } @Override public void callApplicationOnCreate(Application app) { if (enable) { IPackageManagerHook.fixContextPackageManager(app); try { PluginProcessManager.fakeSystemService(mHostContext, app); } catch (Exception e) { Log.e(TAG, "fakeSystemService", e); } try { fixBaseContextImplOpsPackage(app.getBaseContext()); } catch (Exception e) { Log.e(TAG, "callApplicationOnCreate:fixBaseContextImplOpsPackage", e); } try { fixBaseContextImplContentResolverOpsPackage(app.getBaseContext()); } catch (Exception e) { Log.e(TAG, "callActivityOnCreate:fixBaseContextImplContentResolverOpsPackage", e); } } try { HookFactory.getInstance().onCallApplicationOnCreate(mHostContext, app); } catch (Exception e) { Log.e(TAG, "onCallApplicationOnCreate", e); } if (mTarget != null) { mTarget.callApplicationOnCreate(app); } else { super.callApplicationOnCreate(app); } if (enable) { try { PluginProcessManager.registerStaticReceiver(app, app.getApplicationInfo(), app.getClassLoader()); } catch (Exception e) { Log.e(TAG, "registerStaticReceiver", e); } } } @Override public void callActivityOnNewIntent(Activity activity, Intent intent) { // if (activity != null && intent != null) { // intent.setClassName(activity.getPackageName(), activity.getClass().getName()); // } try { Intent newIntent = intent.getParcelableExtra(Env.EXTRA_TARGET_INTENT); if (newIntent != null) { intent = newIntent; } } catch (Throwable e) { Log.e(TAG, "callActivityOnNewIntent:read EXTRA_TARGET_INTENT", e); } if (enable) { try { onActivityOnNewIntent(activity, intent); } catch (RemoteException e) { Log.e(TAG, "callActivityOnNewIntent:onActivityOnNewIntent", e); } } if (mTarget != null) { mTarget.callActivityOnNewIntent(activity, intent); } else { super.callActivityOnNewIntent(activity, intent); } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/handle/ReplaceCallingPackageHookedMethodHandler.java ================================================ package com.morgoo.droidplugin.hook.handle; import android.content.Context; import android.os.Build; import android.os.RemoteException; import com.morgoo.droidplugin.hook.HookedMethodHandler; import com.morgoo.droidplugin.pm.PluginManager; import java.lang.reflect.Method; class ReplaceCallingPackageHookedMethodHandler extends HookedMethodHandler { public ReplaceCallingPackageHookedMethodHandler(Context hostContext) { super(hostContext); } @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { if (args != null && args.length > 0) { for (int index = 0; index < args.length; index++) { if (args[index] != null && (args[index] instanceof String)) { String str = ((String) args[index]); if (isPackagePlugin(str)) { args[index] = mHostContext.getPackageName(); } } } } } return super.beforeInvoke(receiver, method, args); } private static boolean isPackagePlugin(String packageName) throws RemoteException { return PluginManager.getInstance().isPluginPackage(packageName); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/handle/WebViewFactoryProviderHookHandle.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.handle; import android.content.Context; import android.os.Build; import android.webkit.WebView; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.HookedMethodHandler; import com.morgoo.droidplugin.reflect.MethodUtils; import com.morgoo.droidplugin.reflect.Utils; import com.morgoo.helper.Log; import com.morgoo.helper.MyProxy; import com.morgoo.helper.compat.WebViewFactoryCompat; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.List; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2014/10/10. */ public class WebViewFactoryProviderHookHandle extends BaseHookHandle { private static final String TAG = WebViewFactoryProviderHookHandle.class.getSimpleName(); public WebViewFactoryProviderHookHandle(Context hostContext) { super(hostContext); } @Override protected void init() { sHookedMethodHandlers.put("createWebView", new createWebView(mHostContext)); } private static Class sContentMain; private static void fixWebViewAsset(Context context) { try { if (sContentMain == null) { Object provider = WebViewFactoryCompat.getProvider(); if (provider != null) { ClassLoader cl = provider.getClass().getClassLoader(); try { sContentMain = Class.forName("org.chromium.content.app.ContentMain", true, cl); } catch (ClassNotFoundException e) { } if (sContentMain == null) { try { sContentMain = Class.forName("com.android.org.chromium.content.app.ContentMain", true, cl); } catch (ClassNotFoundException e) { } } if (sContentMain == null) { throw new ClassNotFoundException(String.format("Can not found class %s or %s in classloader %s", "org.chromium.content.app.ContentMain", "com.android.org.chromium.content.app.ContentMain", cl)); } } } if (sContentMain != null) { MethodUtils.invokeStaticMethod(sContentMain, "initApplicationContext", context.getApplicationContext()); } } catch (Exception e) { Log.e(TAG, "fixWebViewAsset error", e); } } private static class createWebView extends HookedMethodHandler { protected WebView mWebView; @Override protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { final int index = 0; if (args != null && args.length > index && args[index] instanceof WebView) { mWebView = (WebView) args[0]; } return super.beforeInvoke(receiver, method, args); } public createWebView(Context context) { super(context); } @Override protected void afterInvoke(Object receiver, Method method, Object[] args, final Object invokeResult) throws Throwable { if (mWebView != null) { fixWebViewAsset(mWebView.getContext()); Class clazz = invokeResult.getClass(); List> interfaces = Utils.getAllInterfaces(clazz); Class[] ifs = interfaces != null && interfaces.size() > 0 ? interfaces.toArray(new Class[interfaces.size()]) : new Class[0]; final Object newObj = MyProxy.newProxyInstance(clazz.getClassLoader(), ifs, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object invoke = method.invoke(invokeResult, args); fixWebViewAsset(mWebView.getContext()); return invoke; } }); setFakedResult(newObj); } super.afterInvoke(receiver, method, args, invokeResult); } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/proxy/IActivityManagerHook.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.proxy; import android.app.ActivityManager; import android.content.Context; import android.os.Build; import android.util.AndroidRuntimeException; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.handle.IActivityManagerHookHandle; import com.morgoo.droidplugin.reflect.FieldUtils; import com.morgoo.droidplugin.reflect.Utils; import com.morgoo.helper.Log; import com.morgoo.helper.MyProxy; import com.morgoo.helper.compat.ActivityManagerNativeCompat; import com.morgoo.helper.compat.IActivityManagerCompat; import com.morgoo.helper.compat.SingletonCompat; import java.lang.reflect.Method; import java.util.Arrays; import java.util.List; /** * Hook some function on IActivityManager *

* Code by Andy Zhang (zhangyong232@gmail.com) on 15/2/7. */ public class IActivityManagerHook extends ProxyHook { private static final String TAG = IActivityManagerHook.class.getSimpleName(); public IActivityManagerHook(Context hostContext) { super(hostContext); } @Override public BaseHookHandle createHookHandle() { return new IActivityManagerHookHandle(mHostContext); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { return super.invoke(proxy, method, args); } catch (SecurityException e) { String msg = String.format("msg[%s],args[%s]", e.getMessage(), Arrays.toString(args)); SecurityException e1 = new SecurityException(msg); e1.initCause(e); throw e1; } } @Override public void onInstall(ClassLoader classLoader) throws Throwable { if (Build.VERSION.SDK_INT >= 26) { // o Object singleton = FieldUtils.readStaticField(ActivityManager.class, "IActivityManagerSingleton"); Object obj1 = FieldUtils.readField(singleton, "mInstance"); if (obj1 == null) { SingletonCompat.get(singleton); obj1 = FieldUtils.readField(singleton, "mInstance"); } setOldObj(obj1); Class objClass = mOldObj.getClass(); List> interfaces = Utils.getAllInterfaces(objClass); Class[] ifs = interfaces != null && interfaces.size() > 0 ? interfaces.toArray(new Class[interfaces.size()]) : new Class[0]; Object proxiedActivityManager = MyProxy.newProxyInstance(objClass.getClassLoader(), ifs, this); FieldUtils.writeField(singleton, "mInstance", proxiedActivityManager); return; } Class cls = ActivityManagerNativeCompat.Class(); Object obj = FieldUtils.readStaticField(cls, "gDefault"); if (obj == null) { ActivityManagerNativeCompat.getDefault(); obj = FieldUtils.readStaticField(cls, "gDefault"); } if (IActivityManagerCompat.isIActivityManager(obj)) { setOldObj(obj); Class objClass = mOldObj.getClass(); List> interfaces = Utils.getAllInterfaces(objClass); Class[] ifs = interfaces != null && interfaces.size() > 0 ? interfaces.toArray(new Class[interfaces.size()]) : new Class[0]; Object proxiedActivityManager = MyProxy.newProxyInstance(objClass.getClassLoader(), ifs, this); FieldUtils.writeStaticField(cls, "gDefault", proxiedActivityManager); Log.i(TAG, "Install ActivityManager Hook 1 old=%s,new=%s", mOldObj, proxiedActivityManager); } else if (SingletonCompat.isSingleton(obj)) { Object obj1 = FieldUtils.readField(obj, "mInstance"); if (obj1 == null) { SingletonCompat.get(obj); obj1 = FieldUtils.readField(obj, "mInstance"); } setOldObj(obj1); List> interfaces = Utils.getAllInterfaces(mOldObj.getClass()); Class[] ifs = interfaces != null && interfaces.size() > 0 ? interfaces.toArray(new Class[interfaces.size()]) : new Class[0]; final Object object = MyProxy.newProxyInstance(mOldObj.getClass().getClassLoader(), ifs, IActivityManagerHook.this); Object iam1 = ActivityManagerNativeCompat.getDefault(); //这里先写一次,防止后面找不到Singleton类导致的挂钩子失败的问题。 FieldUtils.writeField(obj, "mInstance", object); //这里使用方式1,如果成功的话,会导致上面的写操作被覆盖。 FieldUtils.writeStaticField(cls, "gDefault", new android.util.Singleton() { @Override protected Object create() { Log.e(TAG, "Install ActivityManager 3 Hook old=%s,new=%s", mOldObj, object); return object; } }); Log.i(TAG, "Install ActivityManager Hook 2 old=%s,new=%s", mOldObj.toString(), object); Object iam2 = ActivityManagerNativeCompat.getDefault(); // 方式2 if (iam1 == iam2) { //这段代码是废的,没啥用,写这里只是不想改而已。 FieldUtils.writeField(obj, "mInstance", object); } } else { throw new AndroidRuntimeException("Can not install IActivityManagerNative hook"); } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/proxy/IContentProviderHook.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.proxy; import android.content.Context; import android.content.pm.ProviderInfo; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.handle.IContentProviderInvokeHandle; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/3/6. */ public class IContentProviderHook extends ProxyHook { private final ProviderInfo mStubProvider; private final ProviderInfo mTargetProvider; private final boolean mLocalProvider; public IContentProviderHook(Context context, Object oldObj, ProviderInfo stubProvider, ProviderInfo targetProvider, boolean localProvider) { super(context); setOldObj(oldObj); mStubProvider = stubProvider; mTargetProvider = targetProvider; mLocalProvider = localProvider; mHookHandles = createHookHandle(); } @Override protected BaseHookHandle createHookHandle() { return new IContentProviderInvokeHandle(mHostContext, mStubProvider, mTargetProvider, mLocalProvider); } @Override protected void onInstall(ClassLoader classLoader) throws Throwable { } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/proxy/IPackageManagerHook.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.proxy; import android.app.Activity; import android.app.Application; import android.content.Context; import android.content.pm.PackageManager; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.handle.IPackageManagerHookHandle; import com.morgoo.droidplugin.reflect.Utils; import com.morgoo.helper.Log; import com.morgoo.helper.compat.ActivityThreadCompat; import com.morgoo.helper.MyProxy; import com.morgoo.droidplugin.reflect.FieldUtils; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Arrays; import java.util.List; /** * Hook some function on IPackageManager *

* Code by Andy Zhang (zhangyong232@gmail.com) on 2015/2/5. */ public class IPackageManagerHook extends ProxyHook { private static final String TAG = IPackageManagerHook.class.getSimpleName(); public IPackageManagerHook(Context hostContext) { super(hostContext); } @Override protected BaseHookHandle createHookHandle() { return new IPackageManagerHookHandle(mHostContext); } @Override protected void onInstall(ClassLoader classLoader) throws Throwable { Object currentActivityThread = ActivityThreadCompat.currentActivityThread(); setOldObj(FieldUtils.readField(currentActivityThread, "sPackageManager")); Class iPmClass = mOldObj.getClass(); List> interfaces = Utils.getAllInterfaces(iPmClass); Class[] ifs = interfaces != null && interfaces.size() > 0 ? interfaces.toArray(new Class[interfaces.size()]) : new Class[0]; Object newPm = MyProxy.newProxyInstance(iPmClass.getClassLoader(), ifs, this); FieldUtils.writeField(currentActivityThread, "sPackageManager", newPm); PackageManager pm = mHostContext.getPackageManager(); Object mPM = FieldUtils.readField(pm, "mPM"); if (mPM != newPm) { FieldUtils.writeField(pm, "mPM", newPm); } } public static void fixContextPackageManager(Context context) { try { Object currentActivityThread = ActivityThreadCompat.currentActivityThread(); Object newPm = FieldUtils.readField(currentActivityThread, "sPackageManager"); PackageManager pm = context.getPackageManager(); Object mPM = FieldUtils.readField(pm, "mPM"); if (mPM != newPm) { FieldUtils.writeField(pm, "mPM", newPm); } } catch (Exception e) { e.printStackTrace(); } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/proxy/IWindowSessionHook.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.proxy; import android.content.Context; import android.content.pm.ProviderInfo; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.handle.IContentProviderInvokeHandle; import com.morgoo.droidplugin.hook.handle.IWindowSessionInvokeHandle; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/3/6. */ public class IWindowSessionHook extends ProxyHook { public IWindowSessionHook(Context context, Object oldObj) { super(context); setOldObj(oldObj); mHookHandles = createHookHandle(); } @Override protected BaseHookHandle createHookHandle() { return new IWindowSessionInvokeHandle(mHostContext); } @Override protected void onInstall(ClassLoader classLoader) throws Throwable { } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/proxy/InstrumentationHook.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.proxy; import android.app.Instrumentation; import android.content.Context; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.Hook; import com.morgoo.droidplugin.hook.handle.PluginCallback; import com.morgoo.droidplugin.hook.handle.PluginInstrumentation; import com.morgoo.helper.compat.ActivityThreadCompat; import com.morgoo.helper.Log; import com.morgoo.droidplugin.reflect.FieldUtils; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/3/2. */ public class InstrumentationHook extends Hook { private static final String TAG = InstrumentationHook.class.getSimpleName(); private List mPluginInstrumentations = new ArrayList(); public InstrumentationHook(Context hostContext) { super(hostContext); } @Override protected BaseHookHandle createHookHandle() { return null; } @Override public void setEnable(boolean enable, boolean reinstallHook) { if (reinstallHook) { try { onInstall(null); } catch (Throwable throwable) { Log.i(TAG, "setEnable onInstall fail", throwable); } } for (PluginInstrumentation pit : mPluginInstrumentations) { pit.setEnable(enable); } super.setEnable(enable,reinstallHook); } @Override protected void onInstall(ClassLoader classLoader) throws Throwable { Object target = ActivityThreadCompat.currentActivityThread(); Class ActivityThreadClass = ActivityThreadCompat.activityThreadClass(); /*替换ActivityThread.mInstrumentation,拦截组件调度消息*/ Field mInstrumentationField = FieldUtils.getField(ActivityThreadClass, "mInstrumentation"); Instrumentation mInstrumentation = (Instrumentation) FieldUtils.readField(mInstrumentationField, target); if (!PluginInstrumentation.class.isInstance(mInstrumentation)) { PluginInstrumentation pit = new PluginInstrumentation(mHostContext, mInstrumentation); pit.setEnable(isEnable()); mPluginInstrumentations.add(pit); FieldUtils.writeField(mInstrumentationField, target, pit); Log.i(TAG, "Install Instrumentation Hook old=%s,new=%s", mInstrumentationField, pit); } else { Log.i(TAG, "Instrumentation has installed,skip"); } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/proxy/LibCoreHook.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.proxy; import android.content.Context; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.handle.LibCoreHookHandle; import com.morgoo.droidplugin.reflect.FieldUtils; import com.morgoo.helper.Log; import com.morgoo.helper.MyProxy; import java.util.ArrayList; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/3/30. */ public class LibCoreHook extends ProxyHook { private static final String TAG = LibCoreHook.class.getSimpleName(); public LibCoreHook(Context hostContext) throws ClassNotFoundException { super(hostContext); } @Override protected BaseHookHandle createHookHandle() { return new LibCoreHookHandle(mHostContext); } private Class[] getAllInterfaces(Class clz) { ArrayList re = new ArrayList(); Class[] ifss = clz.getInterfaces(); for (Class ifs : ifss) { if (!re.contains(ifs)) { re.add(ifs); } } Class superclass = clz.getSuperclass(); while (superclass != null) { Class[] sifss = superclass.getInterfaces(); for (Class ifs : sifss) { if (!re.contains(ifs)) { re.add(ifs); } } superclass = superclass.getSuperclass(); } return re.toArray(new Class[re.size()]); } @Override protected void onInstall(ClassLoader classLoader) throws Throwable { if (!installHook1()) { installHook2(); } } private boolean installHook1() { try { Class LibCore = Class.forName("libcore.io.Libcore"); Object LibCoreOs = FieldUtils.readStaticField(LibCore, "os"); Object Posix = FieldUtils.readField(LibCoreOs, "os", true); setOldObj(Posix); Class aClass = mOldObj.getClass(); Class[] interfaces = getAllInterfaces(aClass); Object proxyObj = MyProxy.newProxyInstance(mOldObj.getClass().getClassLoader(), interfaces, this); FieldUtils.writeField(LibCoreOs, "os", proxyObj, true); return true; } catch (Throwable e) { Log.w(TAG, "installHook2 fail", e); } return false; } private void installHook2() throws ClassNotFoundException, IllegalAccessException { Class LibCore = Class.forName("libcore.io.Libcore"); Object oldObj = FieldUtils.readStaticField(LibCore, "os"); setOldObj(oldObj); Class aClass = mOldObj.getClass(); Class[] interfaces = getAllInterfaces(aClass); Object proxyObj = MyProxy.newProxyInstance(mOldObj.getClass().getClassLoader(), interfaces, this); FieldUtils.writeStaticField(LibCore, "os", proxyObj); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/proxy/PluginCallbackHook.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.proxy; import android.content.Context; import android.os.Handler; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.Hook; import com.morgoo.droidplugin.hook.handle.PluginCallback; import com.morgoo.droidplugin.reflect.FieldUtils; import com.morgoo.helper.Log; import com.morgoo.helper.compat.ActivityThreadCompat; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/3/2. */ public class PluginCallbackHook extends Hook { private static final String TAG = PluginCallbackHook.class.getSimpleName(); private List mCallbacks = new ArrayList(1); public PluginCallbackHook(Context hostContext) { super(hostContext); } @Override protected BaseHookHandle createHookHandle() { return null; } @Override public void setEnable(boolean enable, boolean reinstallHook) { if (reinstallHook) { try { onInstall(null); } catch (Throwable throwable) { Log.i(TAG, "setEnable onInstall fail", throwable); } } for (PluginCallback callback : mCallbacks) { callback.setEnable(enable); } super.setEnable(enable,reinstallHook); } @Override protected void onInstall(ClassLoader classLoader) throws Throwable { Object target = ActivityThreadCompat.currentActivityThread(); Class ActivityThreadClass = ActivityThreadCompat.activityThreadClass(); /*替换ActivityThread.mH.mCallback,拦截组件调度消息*/ Field mHField = FieldUtils.getField(ActivityThreadClass, "mH"); Handler handler = (Handler) FieldUtils.readField(mHField, target); Field mCallbackField = FieldUtils.getField(Handler.class, "mCallback"); //*这里读取出旧的callback并处理*/ Object mCallback = FieldUtils.readField(mCallbackField, handler); if (!PluginCallback.class.isInstance(mCallback)) { PluginCallback value = mCallback != null ? new PluginCallback(mHostContext, handler, (Handler.Callback) mCallback) : new PluginCallback(mHostContext, handler, null); value.setEnable(isEnable()); mCallbacks.add(value); FieldUtils.writeField(mCallbackField, handler, value); Log.i(TAG, "PluginCallbackHook has installed"); } else { Log.i(TAG, "PluginCallbackHook has installed,skip"); } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/proxy/ProxyHook.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.proxy; import android.content.Context; import android.text.TextUtils; import com.morgoo.droidplugin.hook.Hook; import com.morgoo.droidplugin.hook.HookedMethodHandler; import com.morgoo.helper.MyProxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/3/14. */ public abstract class ProxyHook extends Hook implements InvocationHandler { protected Object mOldObj; public ProxyHook(Context hostContext) { super(hostContext); } public void setOldObj(Object oldObj) { this.mOldObj = oldObj; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { if (!isEnable()) { return method.invoke(mOldObj, args); } HookedMethodHandler hookedMethodHandler = mHookHandles.getHookedMethodHandler(method); if (hookedMethodHandler != null) { return hookedMethodHandler.doHookInner(mOldObj, method, args); } return method.invoke(mOldObj, args); } catch (InvocationTargetException e) { Throwable cause = e.getTargetException(); if (cause != null && MyProxy.isMethodDeclaredThrowable(method, cause)) { throw cause; } else if (cause != null) { RuntimeException runtimeException = !TextUtils.isEmpty(cause.getMessage()) ? new RuntimeException(cause.getMessage()) : new RuntimeException(); runtimeException.initCause(cause); throw runtimeException; } else { RuntimeException runtimeException = !TextUtils.isEmpty(e.getMessage()) ? new RuntimeException(e.getMessage()) : new RuntimeException(); runtimeException.initCause(e); throw runtimeException; } } catch (IllegalArgumentException e) { try { StringBuilder sb = new StringBuilder(); sb.append(" DROIDPLUGIN{"); if (method != null) { sb.append("method[").append(method.toString()).append("]"); } else { sb.append("method[").append("NULL").append("]"); } if (args != null) { sb.append("args[").append(Arrays.toString(args)).append("]"); } else { sb.append("args[").append("NULL").append("]"); } sb.append("}"); String message = e.getMessage() + sb.toString(); throw new IllegalArgumentException(message, e); } catch (Throwable e1) { throw e; } } catch (Throwable e) { if (MyProxy.isMethodDeclaredThrowable(method, e)) { throw e; } else { RuntimeException runtimeException = !TextUtils.isEmpty(e.getMessage()) ? new RuntimeException(e.getMessage()) : new RuntimeException(); runtimeException.initCause(e); throw runtimeException; } } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/proxy/WebViewFactoryProviderHook.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.proxy; import android.content.Context; import android.os.Build; import android.webkit.WebView; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.HookedMethodHandler; import com.morgoo.droidplugin.hook.handle.WebViewFactoryProviderHookHandle; import com.morgoo.droidplugin.reflect.FieldUtils; import com.morgoo.droidplugin.reflect.MethodUtils; import com.morgoo.droidplugin.reflect.Utils; import com.morgoo.helper.MyProxy; import com.morgoo.helper.compat.WebViewFactoryCompat; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.List; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2014/10/10. */ public class WebViewFactoryProviderHook extends ProxyHook { public WebViewFactoryProviderHook(Context hostContext) { super(hostContext); } @Override protected BaseHookHandle createHookHandle() { return new WebViewFactoryProviderHookHandle(mHostContext); } @Override protected void onInstall(ClassLoader classLoader) throws Throwable { mOldObj = WebViewFactoryCompat.getProvider(); Class clazz = mOldObj.getClass(); List> interfaces = Utils.getAllInterfaces(clazz); Class[] ifs = interfaces != null && interfaces.size() > 0 ? interfaces.toArray(new Class[interfaces.size()]) : new Class[0]; Object newObj = MyProxy.newProxyInstance(clazz.getClassLoader(), ifs, this); FieldUtils.writeStaticField(WebViewFactoryCompat.Class(), "sProviderInstance", newObj); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/hook/xhook/SQLiteDatabaseHook.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.hook.xhook; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.os.Environment; import android.text.TextUtils; import com.morgoo.droidplugin.core.PluginDirHelper; import com.morgoo.droidplugin.hook.BaseHookHandle; import com.morgoo.droidplugin.hook.Hook; import java.io.File; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/4/1. */ public class SQLiteDatabaseHook extends Hook { private static final String TAG = "SQLiteDatabaseHook"; private final String mDataDir; private final String mHostDataDir; private final String mHostPkg; public SQLiteDatabaseHook(Context hostContext) { super(hostContext); mDataDir = new File(Environment.getDataDirectory(), "data/").getPath(); mHostDataDir = PluginDirHelper.getContextDataDir(hostContext); mHostPkg = hostContext.getPackageName(); } @Override protected BaseHookHandle createHookHandle() { return null; } //传入一个路径,比如/data/data/com.xxx.plugin/xxx 会替换成/data/data/插件宿主包名/Plugin/插件包名/data/插件包名 private String tryReplacePath(String tarDir) { //mDataDir=/data/data/ //mHostDataDir=/data/data/com.example.TestPlugin/ //mHostPkg=com.example.TestPlugin //Old stat([/data/data/com.example.TestPlugin/Plugin/com.qihoo.appstore/apk/base-1.apk]) //New stat([/data/data/com.example.TestPlugin/Plugin/com.example.TestPlugin/data/com.example.TestPlugin/Plugin/com.qihoo.appstore/apk/base-1.apk]) if (tarDir != null && tarDir.length() > mDataDir.length() && !TextUtils.equals(tarDir, mDataDir) && tarDir.startsWith(mDataDir)) { if (!tarDir.startsWith(mHostDataDir) && !TextUtils.equals(tarDir, mHostDataDir)) { String pkg = tarDir.substring(mDataDir.length() + 1); int index = pkg.indexOf("/"); if (index > 0) { pkg = pkg.substring(0, index); } if (!TextUtils.equals(pkg, mHostPkg)) { tarDir = tarDir.replace(pkg, String.format("%s/Plugin/%s/data/%s", mHostPkg, pkg, pkg)); return tarDir; } } } return null; } protected void replace(Object[] args, int index) { if (args != null && args.length > index && args[index] instanceof String) { String path = (String) args[index]; String newPath = tryReplacePath(path); if (newPath != null) { args[index] = newPath; } } } @Override protected void onInstall(ClassLoader classLoader) throws Throwable { // MethodHook callback = new MethodHook() { // @Override // protected void beforeHookedMethod(MethodHookParam param) throws Throwable { // replace(param.args, 0); // super.beforeHookedMethod(param); // } // }; // ZHook.hookAllMethods(SQLiteDatabase.class, "openOrCreateDatabase", callback); // ZHook.hookAllMethods(SQLiteDatabase.class, "openDatabase", callback); // if (classLoader != null) { // Class clazz = classLoader.loadClass("com.tencent.kingkong.database.SQLiteDatabase"); // ZHook.hookAllMethods(clazz, "openOrCreateDatabase", callback); // } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/pm/IPluginManagerImpl.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.pm; import android.app.ActivityManager; import android.app.ActivityManager.RunningAppProcessInfo; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.pm.Signature; import android.net.Uri; import android.os.Binder; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.os.RemoteException; import android.text.TextUtils; import com.morgoo.droidplugin.am.BaseActivityManagerService; import com.morgoo.droidplugin.am.MyActivityManagerService; import com.morgoo.droidplugin.core.PluginClassLoader; import com.morgoo.droidplugin.core.PluginDirHelper; import com.morgoo.droidplugin.pm.parser.IntentMatcher; import com.morgoo.droidplugin.pm.parser.PluginPackageParser; import com.morgoo.helper.Log; import com.morgoo.helper.Utils; import com.morgoo.helper.compat.NativeLibraryHelperCompat; import com.morgoo.helper.compat.PackageManagerCompat; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; /** * 此服务模仿系统的PackageManagerService,提供对插件简单的管理服务。 * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/2/12. */ public class IPluginManagerImpl extends IPluginManager.Stub { private static final String TAG = IPluginManagerImpl.class.getSimpleName(); private Map mPluginCache = Collections.synchronizedMap(new HashMap(20)); private Context mContext; private AtomicBoolean mHasLoadedOk = new AtomicBoolean(false); private final Object mLock = new Object(); private BaseActivityManagerService mActivityManagerService; private Set mHostRequestedPermission = new HashSet(10); private Map mSignatureCache = new HashMap(); public IPluginManagerImpl(Context context) { mContext = context; mActivityManagerService = new MyActivityManagerService(mContext); } public void onCreate() { new Thread() { @Override public void run() { onCreateInner(); } }.start(); } private void onCreateInner() { loadAllPlugin(mContext); loadHostRequestedPermission(); try { mHasLoadedOk.set(true); synchronized (mLock) { mLock.notifyAll(); } } catch (Exception e) { } } private void loadHostRequestedPermission() { try { mHostRequestedPermission.clear(); PackageManager pm = mContext.getPackageManager(); PackageInfo pms = pm.getPackageInfo(mContext.getPackageName(), PackageManager.GET_PERMISSIONS); if (pms != null && pms.requestedPermissions != null && pms.requestedPermissions.length > 0) { for (String requestedPermission : pms.requestedPermissions) { mHostRequestedPermission.add(requestedPermission); } } } catch (Exception e) { } } private void loadAllPlugin(Context context) { long b = System.currentTimeMillis(); ArrayList apkfiles = null; try { apkfiles = new ArrayList(); File baseDir = new File(PluginDirHelper.getBaseDir(context)); File[] dirs = baseDir.listFiles(); for (File dir : dirs) { if (dir.isDirectory()) { File file = new File(dir, "apk/base-1.apk"); if (file.exists()) { apkfiles.add(file); } } } } catch (Exception e) { Log.e(TAG, "scan a apk file error", e); } Log.i(TAG, "Search apk cost %s ms", (System.currentTimeMillis() - b)); b = System.currentTimeMillis(); if (apkfiles != null && apkfiles.size() > 0) { for (File pluginFile : apkfiles) { long b1 = System.currentTimeMillis(); try { PluginPackageParser pluginPackageParser = new PluginPackageParser(mContext, pluginFile); Signature[] signatures = readSignatures(pluginPackageParser.getPackageName()); if (signatures == null || signatures.length <= 0) { pluginPackageParser.collectCertificates(0); PackageInfo info = pluginPackageParser.getPackageInfo(PackageManager.GET_SIGNATURES); saveSignatures(info); } else { mSignatureCache.put(pluginPackageParser.getPackageName(), signatures); pluginPackageParser.writeSignature(signatures); } if (!mPluginCache.containsKey(pluginPackageParser.getPackageName())) { mPluginCache.put(pluginPackageParser.getPackageName(), pluginPackageParser); } } catch (Throwable e) { Log.e(TAG, "parse a apk file error %s", e, pluginFile.getPath()); } finally { Log.i(TAG, "Parse %s apk cost %s ms", pluginFile.getPath(), (System.currentTimeMillis() - b1)); } } } Log.i(TAG, "Parse all apk cost %s ms", (System.currentTimeMillis() - b)); b = System.currentTimeMillis(); try { mActivityManagerService.onCreate(IPluginManagerImpl.this); } catch (Throwable e) { Log.e(TAG, "mActivityManagerService.onCreate", e); } Log.i(TAG, "ActivityManagerService.onCreate %s ms", (System.currentTimeMillis() - b)); } private void enforcePluginFileExists() throws RemoteException { List removedPkg = new ArrayList<>(); for (String pkg : mPluginCache.keySet()) { PluginPackageParser parser = mPluginCache.get(pkg); File pluginFile = parser.getPluginFile(); if (pluginFile != null && pluginFile.exists()) { //DO NOTHING } else { removedPkg.add(pkg); } } for (String pkg : removedPkg) { deletePackage(pkg, 0); } } @Override public boolean waitForReady() { waitForReadyInner(); return true; } private void waitForReadyInner() { if (!mHasLoadedOk.get()) { synchronized (mLock) { try { mLock.wait(); } catch (InterruptedException e) { } } } } private void handleException(Exception e) throws RemoteException { RemoteException remoteException; if (VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { remoteException = new RemoteException(e.getMessage()); remoteException.initCause(e); remoteException.setStackTrace(e.getStackTrace()); } else { remoteException = new RemoteException(); remoteException.initCause(e); remoteException.setStackTrace(e.getStackTrace()); } throw remoteException; } @Override public PackageInfo getPackageInfo(String packageName, int flags) throws RemoteException { waitForReadyInner(); try { String pkg = getAndCheckCallingPkg(packageName); if (pkg != null && !TextUtils.equals(packageName, mContext.getPackageName())) { enforcePluginFileExists(); PluginPackageParser parser = mPluginCache.get(pkg); if (parser != null) { PackageInfo packageInfo = parser.getPackageInfo(flags); if (packageInfo != null && (flags & PackageManager.GET_SIGNATURES) != 0 && packageInfo.signatures == null) { packageInfo.signatures = mSignatureCache.get(packageName); } return packageInfo; } } } catch (Exception e) { handleException(e); } return null; } @Override public boolean isPluginPackage(String packageName) throws RemoteException { waitForReadyInner(); enforcePluginFileExists(); return mPluginCache.containsKey(packageName); } @Override public ActivityInfo getActivityInfo(ComponentName className, int flags) throws RemoteException { waitForReadyInner(); try { String pkg = getAndCheckCallingPkg(className.getPackageName()); if (pkg != null) { enforcePluginFileExists(); PluginPackageParser parser = mPluginCache.get(className.getPackageName()); if (parser != null) { return parser.getActivityInfo(className, flags); } } } catch (Exception e) { handleException(e); } return null; } @Override public ActivityInfo getReceiverInfo(ComponentName className, int flags) throws RemoteException { waitForReadyInner(); try { String pkg = getAndCheckCallingPkg(className.getPackageName()); if (pkg != null) { enforcePluginFileExists(); PluginPackageParser parser = mPluginCache.get(className.getPackageName()); if (parser != null) { return parser.getReceiverInfo(className, flags); } } } catch (Exception e) { handleException(e); } return null; } @Override public ServiceInfo getServiceInfo(ComponentName className, int flags) throws RemoteException { waitForReadyInner(); try { String pkg = getAndCheckCallingPkg(className.getPackageName()); if (pkg != null) { enforcePluginFileExists(); PluginPackageParser parser = mPluginCache.get(className.getPackageName()); if (parser != null) { return parser.getServiceInfo(className, flags); } } } catch (Exception e) { handleException(e); } return null; } @Override public ProviderInfo getProviderInfo(ComponentName className, int flags) throws RemoteException { waitForReadyInner(); try { String pkg = getAndCheckCallingPkg(className.getPackageName()); if (pkg != null) { enforcePluginFileExists(); PluginPackageParser parser = mPluginCache.get(className.getPackageName()); if (parser != null) { return parser.getProviderInfo(className, flags); } } } catch (Exception e) { handleException(e); } return null; } private boolean shouldNotBlockOtherInfo() { return true; // int pid = Binder.getCallingPid(); // if (pid == android.os.Process.myPid()) { // return true; // } else { // List pkgs = mActivityManagerService.getPackageNamesByPid(pid); // if (pkgs != null && pkgs.size() > 0 && !pkgs.contains(mContext.getPackageName())) { // return false; // } else { // return true; // } // } } private String getAndCheckCallingPkg(String pkg) { return pkg; // if (shouldNotBlockOtherInfo()) { // return pkg; // } else { // if (!pkgInPid(Binder.getCallingPid(), pkg)) { // return null; // } else { // return pkg; // } // } } private boolean pkgInPid(int pid, String pkg) { List pkgs = mActivityManagerService.getPackageNamesByPid(pid); if (pkgs != null && pkgs.size() > 0) { return pkgs.contains(pkg); } else { return true; } } @Override public ResolveInfo resolveIntent(Intent intent, String resolvedType, int flags) throws RemoteException { waitForReadyInner(); try { enforcePluginFileExists(); if (shouldNotBlockOtherInfo()) { List infos = IntentMatcher.resolveIntent(mContext, mPluginCache, intent, resolvedType, flags); if (infos != null && infos.size() > 0) { return IntentMatcher.findBest(infos); } } else { List pkgs = mActivityManagerService.getPackageNamesByPid(Binder.getCallingPid()); List infos = new ArrayList(); for (String pkg : pkgs) { intent.setPackage(pkg); List list = IntentMatcher.resolveIntent(mContext, mPluginCache, intent, resolvedType, flags); infos.addAll(list); } if (infos != null && infos.size() > 0) { return IntentMatcher.findBest(infos); } } } catch (Exception e) { handleException(e); } return null; } @Override public List queryIntentActivities(Intent intent, String resolvedType, int flags) throws RemoteException { waitForReadyInner(); try { enforcePluginFileExists(); if (shouldNotBlockOtherInfo()) { return IntentMatcher.resolveActivityIntent(mContext, mPluginCache, intent, resolvedType, flags); } else { List pkgs = mActivityManagerService.getPackageNamesByPid(Binder.getCallingPid()); List infos = new ArrayList(); for (String pkg : pkgs) { intent.setPackage(pkg); List list = IntentMatcher.resolveActivityIntent(mContext, mPluginCache, intent, resolvedType, flags); infos.addAll(list); } if (infos != null && infos.size() > 0) { return infos; } } } catch (Exception e) { handleException(e); } return null; } @Override public List queryIntentReceivers(Intent intent, String resolvedType, int flags) throws RemoteException { waitForReadyInner(); try { enforcePluginFileExists(); if (shouldNotBlockOtherInfo()) { return IntentMatcher.resolveReceiverIntent(mContext, mPluginCache, intent, resolvedType, flags); } else { List pkgs = mActivityManagerService.getPackageNamesByPid(Binder.getCallingPid()); List infos = new ArrayList(); for (String pkg : pkgs) { intent.setPackage(pkg); List list = IntentMatcher.resolveReceiverIntent(mContext, mPluginCache, intent, resolvedType, flags); infos.addAll(list); } if (infos != null && infos.size() > 0) { return infos; } } } catch (Exception e) { handleException(e); } return null; } @Override public ResolveInfo resolveService(Intent intent, String resolvedType, int flags) throws RemoteException { waitForReadyInner(); try { enforcePluginFileExists(); if (shouldNotBlockOtherInfo()) { List infos = IntentMatcher.resolveServiceIntent(mContext, mPluginCache, intent, resolvedType, flags); if (infos != null && infos.size() > 0) { return IntentMatcher.findBest(infos); } } else { List pkgs = mActivityManagerService.getPackageNamesByPid(Binder.getCallingPid()); List infos = new ArrayList(); for (String pkg : pkgs) { intent.setPackage(pkg); List list = IntentMatcher.resolveServiceIntent(mContext, mPluginCache, intent, resolvedType, flags); infos.addAll(list); } if (infos != null && infos.size() > 0) { return IntentMatcher.findBest(infos); } } } catch (Exception e) { handleException(e); } return null; } @Override public List queryIntentServices(Intent intent, String resolvedType, int flags) throws RemoteException { waitForReadyInner(); try { enforcePluginFileExists(); if (shouldNotBlockOtherInfo()) { return IntentMatcher.resolveServiceIntent(mContext, mPluginCache, intent, resolvedType, flags); } else { List pkgs = mActivityManagerService.getPackageNamesByPid(Binder.getCallingPid()); List infos = new ArrayList(); for (String pkg : pkgs) { intent.setPackage(pkg); List list = IntentMatcher.resolveServiceIntent(mContext, mPluginCache, intent, resolvedType, flags); infos.addAll(list); } if (infos != null && infos.size() > 0) { return infos; } } } catch (Exception e) { handleException(e); } return null; } @Override public List queryIntentContentProviders(Intent intent, String resolvedType, int flags) throws RemoteException { waitForReadyInner(); try { enforcePluginFileExists(); if (shouldNotBlockOtherInfo()) { return IntentMatcher.resolveProviderIntent(mContext, mPluginCache, intent, resolvedType, flags); } else { List pkgs = mActivityManagerService.getPackageNamesByPid(Binder.getCallingPid()); List infos = new ArrayList(); for (String pkg : pkgs) { intent.setPackage(pkg); List list = IntentMatcher.resolveProviderIntent(mContext, mPluginCache, intent, resolvedType, flags); infos.addAll(list); } if (infos != null && infos.size() > 0) { return infos; } } } catch (Exception e) { handleException(e); } return null; } @Override public List getInstalledPackages(int flags) throws RemoteException { waitForReadyInner(); try { enforcePluginFileExists(); List infos = new ArrayList(mPluginCache.size()); if (shouldNotBlockOtherInfo()) { for (PluginPackageParser pluginPackageParser : mPluginCache.values()) { infos.add(pluginPackageParser.getPackageInfo(flags)); } } else { List pkgs = mActivityManagerService.getPackageNamesByPid(Binder.getCallingPid()); for (PluginPackageParser pluginPackageParser : mPluginCache.values()) { if (pkgs.contains(pluginPackageParser.getPackageName())) { infos.add(pluginPackageParser.getPackageInfo(flags)); } } } return infos; } catch (Exception e) { handleException(e); } return null; } @Override public List getInstalledApplications(int flags) throws RemoteException { waitForReadyInner(); try { enforcePluginFileExists(); List infos = new ArrayList(mPluginCache.size()); if (shouldNotBlockOtherInfo()) { for (PluginPackageParser pluginPackageParser : mPluginCache.values()) { infos.add(pluginPackageParser.getApplicationInfo(flags)); } } else { List pkgs = mActivityManagerService.getPackageNamesByPid(Binder.getCallingPid()); for (PluginPackageParser pluginPackageParser : mPluginCache.values()) { if (pkgs.contains(pluginPackageParser.getPackageName())) { infos.add(pluginPackageParser.getApplicationInfo(flags)); } } } return infos; } catch (Exception e) { handleException(e); } return null; } @Override public PermissionInfo getPermissionInfo(String name, int flags) throws RemoteException { waitForReadyInner(); try { enforcePluginFileExists(); if (shouldNotBlockOtherInfo()) { for (PluginPackageParser pluginPackageParser : mPluginCache.values()) { List permissionInfos = pluginPackageParser.getPermissions(); for (PermissionInfo permissionInfo : permissionInfos) { if (TextUtils.equals(permissionInfo.name, name)) { return permissionInfo; } } } } else { List pkgs = mActivityManagerService.getPackageNamesByPid(Binder.getCallingPid()); for (PluginPackageParser pluginPackageParser : mPluginCache.values()) { List permissionInfos = pluginPackageParser.getPermissions(); for (PermissionInfo permissionInfo : permissionInfos) { if (TextUtils.equals(permissionInfo.name, name) && pkgs.contains(permissionInfo.packageName)) { return permissionInfo; } } } } } catch (Exception e) { handleException(e); } return null; } @Override public List queryPermissionsByGroup(String group, int flags) throws RemoteException { waitForReadyInner(); try { enforcePluginFileExists(); List list = new ArrayList(); if (shouldNotBlockOtherInfo()) { for (PluginPackageParser pluginPackageParser : mPluginCache.values()) { List permissionInfos = pluginPackageParser.getPermissions(); for (PermissionInfo permissionInfo : permissionInfos) { if (TextUtils.equals(permissionInfo.group, group) && !list.contains(permissionInfo)) { list.add(permissionInfo); } } } } else { List pkgs = mActivityManagerService.getPackageNamesByPid(Binder.getCallingPid()); for (PluginPackageParser pluginPackageParser : mPluginCache.values()) { List permissionInfos = pluginPackageParser.getPermissions(); for (PermissionInfo permissionInfo : permissionInfos) { if (pkgs.contains(permissionInfo.packageName) && TextUtils.equals(permissionInfo.group, group) && !list.contains(permissionInfo)) { list.add(permissionInfo); } } } } return list; } catch (Exception e) { handleException(e); } return null; } @Override public PermissionGroupInfo getPermissionGroupInfo(String name, int flags) throws RemoteException { waitForReadyInner(); try { enforcePluginFileExists(); if (shouldNotBlockOtherInfo()) { for (PluginPackageParser pluginPackageParser : mPluginCache.values()) { List permissionGroupInfos = pluginPackageParser.getPermissionGroups(); for (PermissionGroupInfo permissionGroupInfo : permissionGroupInfos) { if (TextUtils.equals(permissionGroupInfo.name, name)) { return permissionGroupInfo; } } } } else { List pkgs = mActivityManagerService.getPackageNamesByPid(Binder.getCallingPid()); for (PluginPackageParser pluginPackageParser : mPluginCache.values()) { List permissionGroupInfos = pluginPackageParser.getPermissionGroups(); for (PermissionGroupInfo permissionGroupInfo : permissionGroupInfos) { if (TextUtils.equals(permissionGroupInfo.name, name) && pkgs.contains(permissionGroupInfo.packageName)) { return permissionGroupInfo; } } } } } catch (Exception e) { handleException(e); } return null; } @Override public List getAllPermissionGroups(int flags) throws RemoteException { waitForReadyInner(); try { enforcePluginFileExists(); List list = new ArrayList(); if (shouldNotBlockOtherInfo()) { for (PluginPackageParser pluginPackageParser : mPluginCache.values()) { List permissionGroupInfos = pluginPackageParser.getPermissionGroups(); for (PermissionGroupInfo permissionGroupInfo : permissionGroupInfos) { if (!list.contains(permissionGroupInfo)) { list.add(permissionGroupInfo); } } } } else { List pkgs = mActivityManagerService.getPackageNamesByPid(Binder.getCallingPid()); for (PluginPackageParser pluginPackageParser : mPluginCache.values()) { List permissionGroupInfos = pluginPackageParser.getPermissionGroups(); for (PermissionGroupInfo permissionGroupInfo : permissionGroupInfos) { if (!list.contains(permissionGroupInfo) && pkgs .contains(permissionGroupInfo.packageName)) { list.add(permissionGroupInfo); } } } } return list; } catch (Exception e) { handleException(e); } return null; } @Override public ProviderInfo resolveContentProvider(String name, int flags) throws RemoteException { waitForReadyInner(); try { enforcePluginFileExists(); if (shouldNotBlockOtherInfo()) { for (PluginPackageParser pluginPackageParser : mPluginCache.values()) { List providerInfos = pluginPackageParser.getProviders(); for (ProviderInfo providerInfo : providerInfos) { if (TextUtils.equals(providerInfo.authority, name)) { return providerInfo; } } } } else { List pkgs = mActivityManagerService.getPackageNamesByPid(Binder.getCallingPid()); for (PluginPackageParser pluginPackageParser : mPluginCache.values()) { List providerInfos = pluginPackageParser.getProviders(); for (ProviderInfo providerInfo : providerInfos) { if (TextUtils.equals(providerInfo.authority, name) && pkgs.contains(providerInfo.packageName)) { return providerInfo; } } } } } catch (Exception e) { handleException(e); } return null; } @Override public void deleteApplicationCacheFiles(String packageName, IPackageDataObserver observer) throws RemoteException { boolean success = false; try { if (TextUtils.isEmpty(packageName)) { return; } PluginPackageParser parser = mPluginCache.get(packageName); if (parser == null) { return; } ApplicationInfo applicationInfo = parser.getApplicationInfo(0); Utils.deleteDir(new File(applicationInfo.dataDir, "caches").getName()); success = true; } catch (Exception e) { handleException(e); } finally { if (observer != null) { observer.onRemoveCompleted(packageName, success); } } } @Override public void clearApplicationUserData(String packageName, IPackageDataObserver observer) throws RemoteException { boolean success = false; try { if (TextUtils.isEmpty(packageName)) { return; } PluginPackageParser parser = mPluginCache.get(packageName); if (parser == null) { return; } ApplicationInfo applicationInfo = parser.getApplicationInfo(0); Utils.deleteDir(applicationInfo.dataDir); success = true; } catch (Exception e) { handleException(e); } finally { if (observer != null) { observer.onRemoveCompleted(packageName, success); } } } @Override public ApplicationInfo getApplicationInfo(String packageName, int flags) throws RemoteException { waitForReadyInner(); try { if (TextUtils.equals(packageName, mContext.getPackageName())) { return null; } PluginPackageParser parser = mPluginCache.get(packageName); if (parser != null) { return parser.getApplicationInfo(flags); } } catch (Exception e) { handleException(e); } return null; } @Override public int installPackage(String filepath, int flags) throws RemoteException { //install plugin String apkfile = null; try { PackageManager pm = mContext.getPackageManager(); PackageInfo info = pm.getPackageArchiveInfo(filepath, 0); if (info == null) { return PackageManagerCompat.INSTALL_FAILED_INVALID_APK; } apkfile = PluginDirHelper.getPluginApkFile(mContext, info.packageName); if ((flags & PackageManagerCompat.INSTALL_REPLACE_EXISTING) != 0) { forceStopPackage(info.packageName); if (mPluginCache.containsKey(info.packageName)) { deleteApplicationCacheFiles(info.packageName, null); } new File(apkfile).delete(); Utils.copyFile(filepath, apkfile); PluginPackageParser parser = new PluginPackageParser(mContext, new File(apkfile)); parser.collectCertificates(0); PackageInfo pkgInfo = parser.getPackageInfo(PackageManager.GET_PERMISSIONS | PackageManager.GET_SIGNATURES); if (pkgInfo != null && pkgInfo.requestedPermissions != null && pkgInfo.requestedPermissions.length > 0) { for (String requestedPermission : pkgInfo.requestedPermissions) { boolean b = false; try { b = pm.getPermissionInfo(requestedPermission, 0) != null; } catch (NameNotFoundException e) { } if (!mHostRequestedPermission.contains(requestedPermission) && b) { Log.w(TAG, "No Permission %s", requestedPermission); // new File(apkfile).delete(); // return PluginManager.INSTALL_FAILED_NO_REQUESTEDPERMISSION; } } } saveSignatures(pkgInfo); // if (pkgInfo.reqFeatures != null && pkgInfo.reqFeatures.length > 0) { // for (FeatureInfo reqFeature : pkgInfo.reqFeatures) { // Log.e(TAG, "reqFeature name=%s,flags=%s,glesVersion=%s", reqFeature.name, reqFeature.flags, reqFeature.getGlEsVersion()); // } // } if (copyNativeLibs(mContext, apkfile, parser.getApplicationInfo(0)) < 0) { new File(apkfile).delete(); return PackageManagerCompat.INSTALL_FAILED_NOT_SUPPORT_ABI; } dexOpt(mContext, apkfile, parser); mPluginCache.put(parser.getPackageName(), parser); mActivityManagerService.onPkgInstalled(mPluginCache, parser, parser.getPackageName()); sendInstalledBroadcast(info.packageName); return PackageManagerCompat.INSTALL_SUCCEEDED; } else { if (mPluginCache.containsKey(info.packageName)) { return PackageManagerCompat.INSTALL_FAILED_ALREADY_EXISTS; } else { forceStopPackage(info.packageName); new File(apkfile).delete(); Utils.copyFile(filepath, apkfile); PluginPackageParser parser = new PluginPackageParser(mContext, new File(apkfile)); parser.collectCertificates(0); PackageInfo pkgInfo = parser.getPackageInfo(PackageManager.GET_PERMISSIONS | PackageManager.GET_SIGNATURES); if (pkgInfo != null && pkgInfo.requestedPermissions != null && pkgInfo.requestedPermissions.length > 0) { for (String requestedPermission : pkgInfo.requestedPermissions) { boolean b = false; try { b = pm.getPermissionInfo(requestedPermission, 0) != null; } catch (NameNotFoundException e) { } if (!mHostRequestedPermission.contains(requestedPermission) && b) { Log.w(TAG, "No Permission %s", requestedPermission); // new File(apkfile).delete(); // return PluginManager.INSTALL_FAILED_NO_REQUESTEDPERMISSION; } } } saveSignatures(pkgInfo); // if (pkgInfo.reqFeatures != null && pkgInfo.reqFeatures.length > 0) { // for (FeatureInfo reqFeature : pkgInfo.reqFeatures) { // Log.e(TAG, "reqFeature name=%s,flags=%s,glesVersion=%s", reqFeature.name, reqFeature.flags, reqFeature.getGlEsVersion()); // } // } if (copyNativeLibs(mContext, apkfile, parser.getApplicationInfo(0)) < 0) { new File(apkfile).delete(); return PackageManagerCompat.INSTALL_FAILED_NOT_SUPPORT_ABI; } dexOpt(mContext, apkfile, parser); mPluginCache.put(parser.getPackageName(), parser); mActivityManagerService.onPkgInstalled(mPluginCache, parser, parser.getPackageName()); sendInstalledBroadcast(info.packageName); return PackageManagerCompat.INSTALL_SUCCEEDED; } } } catch (Exception e) { if (apkfile != null) { new File(apkfile).delete(); } handleException(e); return PackageManagerCompat.INSTALL_FAILED_INTERNAL_ERROR; } } private void dexOpt(Context hostContext, String apkfile, PluginPackageParser parser) throws Exception { String packageName = parser.getPackageName(); String optimizedDirectory = PluginDirHelper.getPluginDalvikCacheDir(hostContext, packageName); String libraryPath = PluginDirHelper.getPluginNativeLibraryDir(hostContext, packageName); ClassLoader classloader = new PluginClassLoader(apkfile, optimizedDirectory, libraryPath,hostContext.getClassLoader().getParent()); // DexFile dexFile = DexFile.loadDex(apkfile, PluginDirHelper.getPluginDalvikCacheFile(mContext, parser.getPackageName()), 0); // Log.e(TAG, "dexFile=%s,1=%s,2=%s", dexFile, DexFile.isDexOptNeeded(apkfile), DexFile.isDexOptNeeded(PluginDirHelper.getPluginDalvikCacheFile(mContext, parser.getPackageName()))); } private void saveSignatures(PackageInfo pkgInfo) { if (pkgInfo != null && pkgInfo.signatures != null) { int i = 0; for (Signature signature : pkgInfo.signatures) { File file = new File(PluginDirHelper.getPluginSignatureFile(mContext, pkgInfo.packageName, i)); try { Utils.writeToFile(file, signature.toByteArray()); Log.i(TAG, "Save %s signature of %s,md5=%s", pkgInfo.packageName, i, Utils.md5(signature.toByteArray())); } catch (Exception e) { e.printStackTrace(); Log.w(TAG, "Save signatures fail", e); file.delete(); Utils.deleteDir(PluginDirHelper.getPluginSignatureDir(mContext, pkgInfo.packageName)); break; } i++; } } } private Signature[] readSignatures(String packageName) { List fils = PluginDirHelper.getPluginSignatureFiles(mContext, packageName); List signatures = new ArrayList(fils.size()); int i = 0; for (String file : fils) { try { byte[] data = Utils.readFromFile(new File(file)); if (data != null) { Signature sin = new Signature(data); signatures.add(sin); Log.i(TAG, "Read %s signature of %s,md5=%s", packageName, i, Utils.md5(sin.toByteArray())); } else { Log.i(TAG, "Read %s signature of %s FAIL", packageName, i); return null; } i++; } catch (Exception e) { Log.i(TAG, "Read %s signature of %s FAIL", e, packageName, i); return null; } } return signatures.toArray(new Signature[signatures.size()]); } private void sendInstalledBroadcast(String packageName) { Intent intent = new Intent(PluginManager.ACTION_PACKAGE_ADDED); intent.setData(Uri.parse("package://" + packageName)); mContext.sendBroadcast(intent); } private void sendUninstalledBroadcast(String packageName) { Intent intent = new Intent(PluginManager.ACTION_PACKAGE_REMOVED); intent.setData(Uri.parse("package://" + packageName)); mContext.sendBroadcast(intent); } private int copyNativeLibs(Context context, String apkfile, ApplicationInfo applicationInfo) throws Exception { String nativeLibraryDir = PluginDirHelper.getPluginNativeLibraryDir(context, applicationInfo.packageName); return NativeLibraryHelperCompat.copyNativeBinaries(new File(apkfile), new File(nativeLibraryDir)); } @Override public int deletePackage(String packageName, int flags) throws RemoteException { try { if (mPluginCache.containsKey(packageName)) { forceStopPackage(packageName); PluginPackageParser parser; synchronized (mPluginCache) { parser = mPluginCache.remove(packageName); } Utils.deleteDir(PluginDirHelper.makePluginBaseDir(mContext, packageName)); mActivityManagerService.onPkgDeleted(mPluginCache, parser, packageName); mSignatureCache.remove(packageName); sendUninstalledBroadcast(packageName); return PackageManagerCompat.DELETE_SUCCEEDED; } } catch (Exception e) { handleException(e); } return PackageManagerCompat.DELETE_FAILED_INTERNAL_ERROR; } @Override public List getReceivers(String packageName, int flags) throws RemoteException { try { String pkg = getAndCheckCallingPkg(packageName); if (pkg != null) { PluginPackageParser parser = mPluginCache.get(packageName); if (parser != null) { return new ArrayList(parser.getReceivers()); } } } catch (Exception e) { RemoteException remoteException = new RemoteException(); remoteException.setStackTrace(e.getStackTrace()); throw remoteException; } return new ArrayList(0); } @Override public List getReceiverIntentFilter(ActivityInfo info) throws RemoteException { try { String pkg = getAndCheckCallingPkg(info.packageName); if (pkg != null) { PluginPackageParser parser = mPluginCache.get(info.packageName); if (parser != null) { List filters = parser.getReceiverIntentFilter(info); if (filters != null && filters.size() > 0) { return new ArrayList(filters); } } } return new ArrayList(0); } catch (Exception e) { RemoteException remoteException = new RemoteException(); remoteException.setStackTrace(e.getStackTrace()); throw remoteException; } } @Override public int checkSignatures(String pkg1, String pkg2) throws RemoteException { PackageManager pm = mContext.getPackageManager(); Signature[] signatures1 = new Signature[0]; try { signatures1 = getSignature(pkg1, pm); } catch (NameNotFoundException e) { return PackageManager.SIGNATURE_UNKNOWN_PACKAGE; } Signature[] signatures2 = new Signature[0]; try { signatures2 = getSignature(pkg2, pm); } catch (NameNotFoundException e) { return PackageManager.SIGNATURE_UNKNOWN_PACKAGE; } boolean pkg1Signed = signatures1 != null && signatures1.length > 0; boolean pkg2Signed = signatures2 != null && signatures2.length > 0; if (!pkg1Signed && !pkg2Signed) { return PackageManager.SIGNATURE_NEITHER_SIGNED; } else if (!pkg1Signed && pkg2Signed) { return PackageManager.SIGNATURE_FIRST_NOT_SIGNED; } else if (pkg1Signed && !pkg2Signed) { return PackageManager.SIGNATURE_SECOND_NOT_SIGNED; } else { if (signatures1.length == signatures2.length) { for (int i = 0; i < signatures1.length; i++) { Signature s1 = signatures1[i]; Signature s2 = signatures2[i]; if (!Arrays.equals(s1.toByteArray(), s2.toByteArray())) { return PackageManager.SIGNATURE_NO_MATCH; } } return PackageManager.SIGNATURE_MATCH; } else { return PackageManager.SIGNATURE_NO_MATCH; } } } private Signature[] getSignature(String pkg, PackageManager pm) throws RemoteException, NameNotFoundException { PackageInfo info = getPackageInfo(pkg, PackageManager.GET_SIGNATURES); if (info == null) { info = pm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES); } if (info == null) { throw new NameNotFoundException(); } return info.signatures; } ////////////////////////////////////// // // THIS API FOR ACTIVITY MANAGER // ////////////////////////////////////// @Override public ActivityInfo selectStubActivityInfo(ActivityInfo pluginInfo) throws RemoteException { return mActivityManagerService.selectStubActivityInfo(Binder.getCallingPid(), Binder.getCallingUid(), pluginInfo); } @Override public ActivityInfo selectStubActivityInfoByIntent(Intent intent) throws RemoteException { ActivityInfo ai = null; if (intent.getComponent() != null) { ai = getActivityInfo(intent.getComponent(), 0); } else { ResolveInfo resolveInfo = resolveIntent(intent, intent.resolveTypeIfNeeded(mContext.getContentResolver()), 0); if (resolveInfo != null && resolveInfo.activityInfo != null) { ai = resolveInfo.activityInfo; } } if (ai != null) { return selectStubActivityInfo(ai); } return null; } @Override public ServiceInfo selectStubServiceInfo(ServiceInfo targetInfo) throws RemoteException { return mActivityManagerService.selectStubServiceInfo(Binder.getCallingPid(), Binder.getCallingUid(), targetInfo); } @Override public ServiceInfo selectStubServiceInfoByIntent(Intent intent) throws RemoteException { ServiceInfo ai = null; if (intent.getComponent() != null) { ai = getServiceInfo(intent.getComponent(), 0); } else { ResolveInfo resolveInfo = resolveIntent(intent, intent.resolveTypeIfNeeded(mContext.getContentResolver()), 0); if (resolveInfo.serviceInfo != null) { ai = resolveInfo.serviceInfo; } } if (ai != null) { return selectStubServiceInfo(ai); } return null; } @Override public ServiceInfo getTargetServiceInfo(ServiceInfo targetInfo) throws RemoteException { return mActivityManagerService.getTargetServiceInfo(Binder.getCallingPid(), Binder.getCallingUid(), targetInfo); } @Override public ProviderInfo selectStubProviderInfo(String name) throws RemoteException { ProviderInfo targetInfo = resolveContentProvider(name, 0); return mActivityManagerService.selectStubProviderInfo(Binder.getCallingPid(), Binder.getCallingUid(), targetInfo); } @Override public List getPackageNameByPid(int pid) throws RemoteException { List packageNameByProcessName = mActivityManagerService.getPackageNamesByPid(pid); if (packageNameByProcessName != null) { return new ArrayList(packageNameByProcessName); } else { return null; } } @Override public String getProcessNameByPid(int pid) throws RemoteException { return mActivityManagerService.getProcessNameByPid(pid); } @Override public boolean killBackgroundProcesses(String pluginPackageName) throws RemoteException { ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); List infos = am.getRunningAppProcesses(); boolean success = false; for (RunningAppProcessInfo info : infos) { if (info.pkgList != null) { String[] pkgListCopy = Arrays.copyOf(info.pkgList, info.pkgList.length); Arrays.sort(pkgListCopy); if (Arrays.binarySearch(pkgListCopy, pluginPackageName) >= 0 && info.pid != android.os.Process.myPid()) { Log.i(TAG, "killBackgroundProcesses(%s),pkgList=%s,pid=%s", pluginPackageName, Arrays.toString(info.pkgList), info.pid); android.os.Process.killProcess(info.pid); success = true; } } } return success; } @Override public boolean killApplicationProcess(String pluginPackageName) throws RemoteException { return killBackgroundProcesses(pluginPackageName); } @Override public boolean forceStopPackage(String pluginPackageName) throws RemoteException { return killBackgroundProcesses(pluginPackageName); } @Override public boolean registerApplicationCallback(IApplicationCallback callback) throws RemoteException { return mActivityManagerService.registerApplicationCallback(Binder.getCallingPid(), Binder.getCallingUid(), callback); } @Override public boolean unregisterApplicationCallback(IApplicationCallback callback) throws RemoteException { return mActivityManagerService.unregisterApplicationCallback(Binder.getCallingPid(), Binder.getCallingUid(), callback); } @Override public void onActivityCreated(ActivityInfo stubInfo, ActivityInfo targetInfo) throws RemoteException { mActivityManagerService.onActivityCreated(Binder.getCallingPid(), Binder.getCallingUid(), stubInfo, targetInfo); } @Override public void onActivityDestory(ActivityInfo stubInfo, ActivityInfo targetInfo) throws RemoteException { mActivityManagerService.onActivityDestroy(Binder.getCallingPid(), Binder.getCallingUid(), stubInfo, targetInfo); } @Override public void onServiceCreated(ServiceInfo stubInfo, ServiceInfo targetInfo) throws RemoteException { mActivityManagerService.onServiceCreated(Binder.getCallingPid(), Binder.getCallingUid(), stubInfo, targetInfo); } @Override public void onServiceDestory(ServiceInfo stubInfo, ServiceInfo targetInfo) throws RemoteException { mActivityManagerService.onServiceDestroy(Binder.getCallingPid(), Binder.getCallingUid(), stubInfo, targetInfo); } @Override public void onProviderCreated(ProviderInfo stubInfo, ProviderInfo targetInfo) throws RemoteException { mActivityManagerService.onProviderCreated(Binder.getCallingPid(), Binder.getCallingUid(), stubInfo, targetInfo); } @Override public void reportMyProcessName(String stubProcessName, String targetProcessName, String targetPkg) throws RemoteException { mActivityManagerService.onReportMyProcessName(Binder.getCallingPid(), Binder.getCallingUid(), stubProcessName, targetProcessName, targetPkg); } public void onDestroy() { mActivityManagerService.onDestroy(); } @Override public void onActivtyOnNewIntent(ActivityInfo stubInfo, ActivityInfo targetInfo, Intent intent) throws RemoteException { mActivityManagerService.onActivityOnNewIntent(Binder.getCallingPid(), Binder.getCallingUid(), stubInfo, targetInfo, intent); } @Override public int getMyPid() { return android.os.Process.myPid(); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/pm/PluginManager.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.pm; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.net.Uri; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.text.TextUtils; import com.morgoo.droidplugin.BuildConfig; import com.morgoo.droidplugin.PluginManagerService; import com.morgoo.droidplugin.PluginServiceProvider; import com.morgoo.droidplugin.reflect.MethodUtils; import com.morgoo.helper.Log; import com.morgoo.helper.compat.BundleCompat; import com.morgoo.helper.compat.ContentProviderCompat; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; /** * 插件包管理服务的客户端实现。 *

* Code by Andy Zhang (zhangyong232@gmail.com) on 2015/2/11. */ public class PluginManager implements ServiceConnection { public static final String ACTION_PACKAGE_ADDED = "com.morgoo.droidplugin.PACKAGE_ADDED"; public static final String ACTION_PACKAGE_REMOVED = "com.morgoo.droidplugin.PACKAGE_REMOVED"; public static final String ACTION_DROIDPLUGIN_INIT = "com.morgoo.droidplugin.ACTION_DROIDPLUGIN_INIT"; public static final String ACTION_MAINACTIVITY_ONCREATE = "com.morgoo.droidplugin.ACTION_MAINACTIVITY_ONCREATE"; public static final String ACTION_MAINACTIVITY_ONDESTORY = "com.morgoo.droidplugin.ACTION_MAINACTIVITY_ONDESTORY"; public static final String ACTION_SETTING = "com.morgoo.droidplugin.ACTION_SETTING"; public static final String ACTION_SHORTCUT_PROXY = "com.morgoo.droidplugin.ACTION_SHORTCUT_PROXY"; public static final String EXTRA_PID = "com.morgoo.droidplugin.EXTRA_PID"; public static final String EXTRA_PACKAGENAME = "com.morgoo.droidplugin.EXTRA_EXTRA_PACKAGENAME"; public static final String STUB_AUTHORITY_NAME = BuildConfig.AUTHORITY_NAME; public static final String EXTRA_APP_PERSISTENT = "com.morgoo.droidplugin.EXTRA_APP_PERSISTENT"; public static final int INSTALL_FAILED_NO_REQUESTEDPERMISSION = -100001; public static final int STUB_NO_ACTIVITY_MAX_NUM = 4; private static final String TAG = PluginManager.class.getSimpleName(); private Context mHostContext; private static PluginManager sInstance = null; private List> sServiceConnection = Collections.synchronizedList(new ArrayList>(1)); @Override public void onServiceConnected(final ComponentName componentName, final IBinder iBinder) { mPluginManager = IPluginManager.Stub.asInterface(iBinder); new Thread() { @Override public void run() { try { mPluginManager.waitForReady(); mPluginManager.registerApplicationCallback(new IApplicationCallback.Stub() { @Override public Bundle onCallback(Bundle extra) throws RemoteException { return extra; } }); Iterator> iterator = sServiceConnection.iterator(); while (iterator.hasNext()) { WeakReference wsc = iterator.next(); ServiceConnection sc = wsc != null ? wsc.get() : null; if (sc != null) { sc.onServiceConnected(componentName, iBinder); } else { iterator.remove(); } } mPluginManager.asBinder().linkToDeath(new IBinder.DeathRecipient() { @Override public void binderDied() { onServiceDisconnected(componentName); } }, 0); Log.i(TAG, "PluginManager ready!"); } catch (Throwable e) { Log.e(TAG, "Lost the mPluginManager connect...", e); } finally { try { synchronized (mWaitLock) { mWaitLock.notifyAll(); } } catch (Exception e) { Log.i(TAG, "PluginManager notifyAll:" + e.getMessage()); } } } }.start(); Log.i(TAG, "onServiceConnected connected OK!"); } @Override public void onServiceDisconnected(ComponentName componentName) { Log.i(TAG, "onServiceDisconnected disconnected!"); mPluginManager = null; Iterator> iterator = sServiceConnection.iterator(); while (iterator.hasNext()) { WeakReference wsc = iterator.next(); ServiceConnection sc = wsc != null ? wsc.get() : null; if (sc != null) { sc.onServiceDisconnected(componentName); } else { iterator.remove(); } } //服务连接断开,需要重新连接。 connectToService(); } private Object mWaitLock = new Object(); public void waitForConnected() { if (isConnected()) { return; } else { try { synchronized (mWaitLock) { mWaitLock.wait(); } } catch (InterruptedException e) { Log.i(TAG, "waitForConnected:" + e.getMessage()); } Log.i(TAG, "waitForConnected finish"); } } /** * 提供超时设置的waitForConnected版本 * * @param timeout,当超时时间大于0时超时设置生效 */ public void waitForConnected(long timeout) { if (timeout > 0) { if (isConnected()) { return; } else { try { synchronized (mWaitLock) { mWaitLock.wait(timeout); } } catch (InterruptedException e) { Log.i(TAG, "waitForConnected:" + e.getMessage()); } Log.i(TAG, "waitForConnected finish"); } } else { waitForConnected(); } } private IPluginManager mPluginManager; public void connectToService() { if (mPluginManager == null) { try { Intent intent = new Intent(mHostContext, PluginManagerService.class); intent.setPackage(mHostContext.getPackageName()); mHostContext.startService(intent); String auth = mHostContext.getPackageName() + ".plugin.servicemanager"; Uri uri = Uri.parse("content://" + auth); Bundle args = new Bundle(); args.putString(PluginServiceProvider.URI_VALUE, "content://" + auth); Bundle res = ContentProviderCompat.call(mHostContext, uri, PluginServiceProvider.Method_GetManager, null, args); if (res != null) { IBinder clientBinder = BundleCompat.getBinder(res, PluginServiceProvider.Arg_Binder); onServiceConnected(intent.getComponent(), clientBinder); } else { mHostContext.bindService(intent, this, Context.BIND_AUTO_CREATE); } } catch (Exception e) { Log.e(TAG, "connectToService", e); } } } public void addServiceConnection(ServiceConnection sc) { sServiceConnection.add(new WeakReference(sc)); } public void removeServiceConnection(ServiceConnection sc) { Iterator> iterator = sServiceConnection.iterator(); while (iterator.hasNext()) { WeakReference wsc = iterator.next(); if (wsc.get() == sc) { iterator.remove(); } } } public void init(Context hostContext) { mHostContext = hostContext; connectToService(); } public Context getHostContext() { return mHostContext; } public boolean isConnected() { return mHostContext != null && mPluginManager != null; } public static PluginManager getInstance() { if (sInstance == null) { sInstance = new PluginManager(); } return sInstance; } ////////////////////////// // API ////////////////////////// public PackageInfo getPackageInfo(String packageName, int flags) throws RemoteException { try { if (mPluginManager != null) { return mPluginManager.getPackageInfo(packageName, flags); } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); } } catch (RemoteException e) { throw e; } catch (Exception e) { Log.e(TAG, "getPackageInfo", e); } return null; } public boolean isPluginPackage(String packageName) throws RemoteException { try { if (mHostContext == null) { return false; } if (TextUtils.equals(mHostContext.getPackageName(), packageName)) { return false; } if (mPluginManager != null && packageName != null) { return mPluginManager.isPluginPackage(packageName); } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); } } catch (RemoteException e) { throw e; } catch (Exception e) { Log.e(TAG, "isPluginPackage", e); } return false; } public boolean isPluginPackage(ComponentName className) throws RemoteException { if (className == null) { return false; } return isPluginPackage(className.getPackageName()); } public ActivityInfo getActivityInfo(ComponentName className, int flags) throws NameNotFoundException, RemoteException { try { if (className == null) { return null; } if (mPluginManager != null && className != null) { return mPluginManager.getActivityInfo(className, flags); } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); } } catch (RemoteException e) { Log.e(TAG, "getActivityInfo RemoteException", e); } catch (Exception e) { Log.e(TAG, "getActivityInfo", e); } return null; } public ActivityInfo getReceiverInfo(ComponentName className, int flags) throws NameNotFoundException, RemoteException { if (className == null) { return null; } try { if (mPluginManager != null && className != null) { return mPluginManager.getReceiverInfo(className, flags); } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); } } catch (RemoteException e) { throw e; } catch (Exception e) { Log.e(TAG, "getReceiverInfo", e); } return null; } public ServiceInfo getServiceInfo(ComponentName className, int flags) throws NameNotFoundException, RemoteException { if (className == null) { return null; } try { if (mPluginManager != null && className != null) { return mPluginManager.getServiceInfo(className, flags); } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); } } catch (RemoteException e) { throw e; } catch (Exception e) { Log.e(TAG, "getServiceInfo", e); } return null; } public ProviderInfo getProviderInfo(ComponentName className, int flags) throws NameNotFoundException, RemoteException { if (className == null) { return null; } try { if (mPluginManager != null && className != null) { return mPluginManager.getProviderInfo(className, flags); } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); } } catch (RemoteException e) { throw e; } catch (Exception e) { Log.e(TAG, "getProviderInfo", e); } return null; } public ResolveInfo resolveIntent(Intent intent, String resolvedType, int flags) throws RemoteException { try { if (mPluginManager != null && intent != null) { return mPluginManager.resolveIntent(intent, resolvedType, flags); } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); } } catch (RemoteException e) { throw e; } catch (Exception e) { Log.e(TAG, "resolveIntent", e); } return null; } public ResolveInfo resolveService(Intent intent, String resolvedType, Integer flags) throws RemoteException { try { if (mPluginManager != null && intent != null) { return mPluginManager.resolveService(intent, resolvedType, flags); } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); } } catch (RemoteException e) { throw e; } catch (Exception e) { Log.e(TAG, "resolveService", e); } return null; } public List queryIntentActivities(Intent intent, String resolvedType, int flags) throws RemoteException { try { if (mPluginManager != null && intent != null) { return mPluginManager.queryIntentActivities(intent, resolvedType, flags); } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); } } catch (RemoteException e) { Log.e(TAG, "queryIntentActivities RemoteException", e); } catch (Exception e) { Log.e(TAG, "queryIntentActivities", e); } return null; } public List queryIntentReceivers(Intent intent, String resolvedType, int flags) throws RemoteException { try { if (mPluginManager != null && intent != null) { return mPluginManager.queryIntentReceivers(intent, resolvedType, flags); } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); } } catch (RemoteException e) { throw e; } catch (Exception e) { Log.e(TAG, "queryIntentReceivers", e); } return null; } public List queryIntentServices(Intent intent, String resolvedType, int flags) throws RemoteException { try { if (mPluginManager != null && intent != null) { return mPluginManager.queryIntentServices(intent, resolvedType, flags); } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); } } catch (RemoteException e) { Log.e(TAG, "queryIntentServices RemoteException", e); } catch (Exception e) { Log.e(TAG, "queryIntentServices", e); } return null; } public List queryIntentContentProviders(Intent intent, String resolvedType, int flags) throws RemoteException { try { if (mPluginManager != null && intent != null) { return mPluginManager.queryIntentContentProviders(intent, resolvedType, flags); } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); } } catch (RemoteException e) { throw e; } catch (Exception e) { Log.e(TAG, "queryIntentContentProviders", e); } return null; } public List getInstalledPackages(int flags) throws RemoteException { try { if (mPluginManager != null) { return mPluginManager.getInstalledPackages(flags); } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); } } catch (RemoteException e) { Log.e(TAG, "getInstalledPackages RemoteException", e); } catch (Exception e) { Log.e(TAG, "getInstalledPackages", e); } return null; } public List getInstalledApplications(int flags) throws RemoteException { try { if (mPluginManager != null) { return mPluginManager.getInstalledApplications(flags); } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); } } catch (RemoteException e) { throw e; } catch (Exception e) { Log.e(TAG, "getInstalledApplications", e); } return null; } public PermissionInfo getPermissionInfo(String name, int flags) throws RemoteException { try { if (mPluginManager != null && name != null) { return mPluginManager.getPermissionInfo(name, flags); } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); } } catch (RemoteException e) { throw e; } catch (Exception e) { Log.e(TAG, "getPermissionInfo", e); } return null; } public List queryPermissionsByGroup(String group, int flags) throws RemoteException { try { if (mPluginManager != null && group != null) { return mPluginManager.queryPermissionsByGroup(group, flags); } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); } } catch (RemoteException e) { throw e; } catch (Exception e) { Log.e(TAG, "queryPermissionsByGroup", e); } return null; } public PermissionGroupInfo getPermissionGroupInfo(String name, int flags) throws RemoteException { try { if (mPluginManager != null && name != null) { return mPluginManager.getPermissionGroupInfo(name, flags); } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); } } catch (RemoteException e) { throw e; } catch (Exception e) { Log.e(TAG, "getPermissionGroupInfo", e); } return null; } public List getAllPermissionGroups(int flags) throws RemoteException { try { if (mPluginManager != null) { return mPluginManager.getAllPermissionGroups(flags); } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); } } catch (RemoteException e) { throw e; } catch (Exception e) { Log.e(TAG, "getAllPermissionGroups", e); } return null; } public ProviderInfo resolveContentProvider(String name, Integer flags) throws RemoteException { try { if (mPluginManager != null && name != null) { return mPluginManager.resolveContentProvider(name, flags); } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); } } catch (RemoteException e) { throw e; } catch (Exception e) { Log.e(TAG, "resolveContentProvider", e); } return null; } public void deleteApplicationCacheFiles(String packageName, final Object observer/*android.content.pm.IPackageDataObserver*/) throws RemoteException { try { if (mPluginManager != null && packageName != null) { mPluginManager.deleteApplicationCacheFiles(packageName, new com.morgoo.droidplugin.pm.IPackageDataObserver.Stub() { @Override public void onRemoveCompleted(String packageName, boolean succeeded) throws RemoteException { if (observer != null) { try { MethodUtils.invokeMethod(observer, "onRemoveCompleted", packageName, succeeded); } catch (Exception e) { RemoteException exception = new RemoteException(); exception.initCause(exception); throw exception; } } } }); } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); } } catch (RemoteException e) { throw e; } catch (Exception e) { Log.e(TAG, "deleteApplicationCacheFiles", e); } } public void clearApplicationUserData(String packageName, final Object observer/*android.content.pm.IPackageDataObserver*/) throws RemoteException { try { if (mPluginManager != null && packageName != null) { mPluginManager.clearApplicationUserData(packageName, new com.morgoo.droidplugin.pm.IPackageDataObserver.Stub() { @Override public void onRemoveCompleted(String packageName, boolean succeeded) throws RemoteException { if (observer != null) { try { MethodUtils.invokeMethod(observer, "onRemoveCompleted", packageName, succeeded); } catch (Exception e) { RemoteException exception = new RemoteException(); exception.initCause(exception); throw exception; } } } }); } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); } } catch (RemoteException e) { throw e; } catch (Exception e) { Log.e(TAG, "clearApplicationUserData", e); } } public ApplicationInfo getApplicationInfo(String packageName, int flags) throws RemoteException { try { if (mPluginManager != null && packageName != null) { return mPluginManager.getApplicationInfo(packageName, flags); } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); } } catch (RemoteException e) { Log.e(TAG, "getApplicationInfo RemoteException", e); } catch (Exception e) { Log.e(TAG, "getApplicationInfo", e); } return null; } public ActivityInfo selectStubActivityInfo(ActivityInfo pluginInfo) throws RemoteException { try { if (mPluginManager != null) { return mPluginManager.selectStubActivityInfo(pluginInfo); } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); } } catch (RemoteException e) { throw e; } catch (Exception e) { Log.e(TAG, "selectStubActivityInfo", e); } return null; } public ActivityInfo selectStubActivityInfo(Intent pluginInfo) throws RemoteException { try { if (mPluginManager != null) { return mPluginManager.selectStubActivityInfoByIntent(pluginInfo); } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); } } catch (RemoteException e) { throw e; } catch (Exception e) { Log.e(TAG, "selectStubActivityInfo", e); } return null; } public ServiceInfo selectStubServiceInfo(ServiceInfo pluginInfo) throws RemoteException { try { if (mPluginManager != null) { return mPluginManager.selectStubServiceInfo(pluginInfo); } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); } } catch (RemoteException e) { throw e; } catch (Exception e) { Log.e(TAG, "selectStubServiceInfo", e); } return null; } public ServiceInfo selectStubServiceInfo(Intent pluginInfo) throws RemoteException { try { if (mPluginManager != null) { return mPluginManager.selectStubServiceInfoByIntent(pluginInfo); } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); } } catch (RemoteException e) { throw e; } catch (Exception e) { Log.e(TAG, "selectStubServiceInfo", e); } return null; } public ProviderInfo selectStubProviderInfo(String name) throws RemoteException { try { if (mPluginManager != null) { return mPluginManager.selectStubProviderInfo(name); } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); } } catch (RemoteException e) { throw e; } catch (Exception e) { Log.e(TAG, "selectStubProviderInfo", e); } return null; } public ActivityInfo resolveActivityInfo(Intent intent, int flags) throws RemoteException { try { if (mPluginManager != null) { if (intent.getComponent() != null) { return mPluginManager.getActivityInfo(intent.getComponent(), flags); } else { ResolveInfo resolveInfo = mPluginManager.resolveIntent(intent, intent.resolveTypeIfNeeded(mHostContext.getContentResolver()), flags); if (resolveInfo != null && resolveInfo.activityInfo != null) { return resolveInfo.activityInfo; } } } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); } return null; } catch (RemoteException e) { throw e; } catch (Exception e) { Log.e(TAG, "selectStubActivityInfo", e); } return null; } public ServiceInfo resolveServiceInfo(Intent intent, int flags) throws RemoteException { try { if (mPluginManager != null) { if (intent.getComponent() != null) { return mPluginManager.getServiceInfo(intent.getComponent(), flags); } else { ResolveInfo resolveInfo = mPluginManager.resolveIntent(intent, intent.resolveTypeIfNeeded(mHostContext.getContentResolver()), flags); if (resolveInfo != null && resolveInfo.serviceInfo != null) { return resolveInfo.serviceInfo; } } } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); } return null; } catch (RemoteException e) { throw e; } catch (Exception e) { Log.e(TAG, "resolveServiceInfo", e); } return null; } public void killBackgroundProcesses(String packageName) throws RemoteException { try { if (mPluginManager != null) { mPluginManager.killBackgroundProcesses(packageName); } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); } } catch (RemoteException e) { throw e; } catch (Exception e) { Log.e(TAG, "killBackgroundProcesses", e); } } public void forceStopPackage(String packageName) throws RemoteException { try { if (mPluginManager != null) { mPluginManager.forceStopPackage(packageName); } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); } } catch (RemoteException e) { throw e; } catch (Exception e) { Log.e(TAG, "forceStopPackage", e); } } public boolean killApplicationProcess(String packageName) throws RemoteException { try { if (mPluginManager != null) { return mPluginManager.killApplicationProcess(packageName); } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); } } catch (RemoteException e) { throw e; } catch (Exception e) { Log.e(TAG, "killApplicationProcess", e); } return false; } public List getReceivers(String packageName, int flags) throws RemoteException { try { if (mPluginManager != null) { return mPluginManager.getReceivers(packageName, flags); } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); } } catch (RemoteException e) { throw e; } catch (Exception e) { Log.e(TAG, "getReceivers", e); } return null; } public List getReceiverIntentFilter(ActivityInfo info) throws RemoteException { try { if (mPluginManager != null) { return mPluginManager.getReceiverIntentFilter(info); } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); } } catch (RemoteException e) { throw e; } catch (Exception e) { Log.e(TAG, "getReceiverIntentFilter", e); } return null; } public ServiceInfo getTargetServiceInfo(ServiceInfo info) throws RemoteException { try { if (mPluginManager != null) { return mPluginManager.getTargetServiceInfo(info); } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); } } catch (RemoteException e) { throw e; } catch (Exception e) { Log.e(TAG, "getTargetServiceInfo", e); } return null; } public int installPackage(String filepath, int flags) throws RemoteException { try { if (mPluginManager != null) { int result = mPluginManager.installPackage(filepath, flags); Log.w(TAG, String.format("%s install result %d", filepath, result)); return result; } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); } } catch (RemoteException e) { throw e; } catch (Exception e) { Log.e(TAG, "forceStopPackage", e); } return -1; } public List getPackageNameByPid(int pid) throws RemoteException { try { if (mPluginManager != null) { return mPluginManager.getPackageNameByPid(pid); } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); } } catch (RemoteException e) { throw e; } catch (Exception e) { Log.e(TAG, "forceStopPackage", e); } return null; } public String getProcessNameByPid(int pid) throws RemoteException { try { if (mPluginManager != null) { return mPluginManager.getProcessNameByPid(pid); } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); } } catch (RemoteException e) { throw e; } catch (Exception e) { Log.e(TAG, "forceStopPackage", e); } return null; } public void onActivityCreated(ActivityInfo stubInfo, ActivityInfo targetInfo) throws RemoteException { try { if (mPluginManager != null) { mPluginManager.onActivityCreated(stubInfo, targetInfo); } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); } } catch (RemoteException e) { throw e; } catch (Exception e) { Log.e(TAG, "onActivityCreated", e); } } public void onActivityDestory(ActivityInfo stubInfo, ActivityInfo targetInfo) throws RemoteException { try { if (mPluginManager != null) { mPluginManager.onActivityDestory(stubInfo, targetInfo); } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); } } catch (RemoteException e) { throw e; } catch (Exception e) { Log.e(TAG, "onActivityDestroy", e); } } public void onServiceCreated(ServiceInfo stubInfo, ServiceInfo targetInfo) throws RemoteException { try { if (mPluginManager != null) { mPluginManager.onServiceCreated(stubInfo, targetInfo); } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); } } catch (RemoteException e) { throw e; } catch (Exception e) { Log.e(TAG, "onServiceCreated", e); } } public void onServiceDestory(ServiceInfo stubInfo, ServiceInfo targetInfo) { try { if (mPluginManager != null) { mPluginManager.onServiceDestory(stubInfo, targetInfo); } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); } } catch (Exception e) { Log.e(TAG, "onServiceDestroy", e); } } public void onProviderCreated(ProviderInfo stubInfo, ProviderInfo targetInfo) throws RemoteException { try { if (mPluginManager != null) { mPluginManager.onProviderCreated(stubInfo, targetInfo); } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); } } catch (RemoteException e) { throw e; } catch (Exception e) { Log.e(TAG, "onProviderCreated", e); } } public void reportMyProcessName(String stubProcessName, String targetProcessName, String targetPkg) throws RemoteException { try { if (mPluginManager != null) { mPluginManager.reportMyProcessName(stubProcessName, targetProcessName, targetPkg); } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); } } catch (RemoteException e) { throw e; } catch (Exception e) { Log.e(TAG, "reportMyProcessName", e); } } public void deletePackage(String packageName, int flags) throws RemoteException { try { if (mPluginManager != null) { mPluginManager.deletePackage(packageName, flags); } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); } } catch (RemoteException e) { throw e; } catch (Exception e) { Log.e(TAG, "deletePackage", e); } } public int checkSignatures(String pkg0, String pkg1) throws RemoteException { try { if (mPluginManager != null) { return mPluginManager.checkSignatures(pkg0, pkg1); } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); return PackageManager.SIGNATURE_NO_MATCH; } } catch (RemoteException e) { throw e; } catch (Exception e) { Log.e(TAG, "deletePackage", e); return PackageManager.SIGNATURE_NO_MATCH; } } public void onActivtyOnNewIntent(ActivityInfo stubInfo, ActivityInfo targetInfo, Intent intent) throws RemoteException { try { if (mPluginManager != null) { mPluginManager.onActivtyOnNewIntent(stubInfo, targetInfo, intent); } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); } } catch (RemoteException e) { throw e; } catch (Exception e) { Log.e(TAG, "onActivityOnNewIntent", e); } } public int getMyPid() throws RemoteException { try { if (mPluginManager != null) { return mPluginManager.getMyPid(); } else { Log.w(TAG, "Plugin Package Manager Service not be connect"); return -1; } } catch (RemoteException e) { throw e; } catch (Exception e) { Log.e(TAG, "getMyPid", e); return -1; } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/pm/parser/IntentMatcher.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.pm.parser; import android.annotation.TargetApi; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import com.morgoo.droidplugin.reflect.FieldUtils; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; /** * 负责Intent匹配。 * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/2/15. */ public class IntentMatcher { private static final String TAG = IntentMatcher.class.getSimpleName(); private static final Comparator mResolvePrioritySorter = new Comparator() { public int compare(ResolveInfo r1, ResolveInfo r2) { int v1 = r1.priority; int v2 = r2.priority; //System.out.println("Comparing: q1=" + q1 + " q2=" + q2); if (v1 != v2) { return (v1 > v2) ? -1 : 1; } v1 = r1.preferredOrder; v2 = r2.preferredOrder; if (v1 != v2) { return (v1 > v2) ? -1 : 1; } if (r1.isDefault != r2.isDefault) { return r1.isDefault ? -1 : 1; } v1 = r1.match; v2 = r2.match; //System.out.println("Comparing: m1=" + m1 + " m2=" + m2); if (v1 != v2) { return (v1 > v2) ? -1 : 1; } // if (VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH) { // if (r1.system != r2.system) { // return r1.system ? -1 : 1; // } // } return 0; } }; public static final List resolveReceiverIntent(Context context, Map pluginPackages, Intent intent, String resolvedType, int flags) throws Exception { if (intent == null || context == null) { return null; } List list = new ArrayList(1); ComponentName comp = intent.getComponent(); if (comp == null) { if (VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { if (intent.getSelector() != null) { intent = intent.getSelector(); comp = intent.getComponent(); } } } if (comp != null && comp.getPackageName() != null) { PluginPackageParser parser = pluginPackages.get(comp.getPackageName()); if (parser != null) { queryIntentReceiverForPackage(context, parser, intent, flags, list); if (list.size() > 0) { Collections.sort(list, mResolvePrioritySorter); return list; } } else { //intent指定的包名不在我们的插件列表中。 } Collections.sort(list, mResolvePrioritySorter); return list; } final String pkgName = intent.getPackage(); if (pkgName != null) { PluginPackageParser parser = pluginPackages.get(pkgName); if (parser != null) { queryIntentReceiverForPackage(context, parser, intent, flags, list); } else { //intent指定的包名不在我们的插件列表中。 } } else { for (PluginPackageParser parser : pluginPackages.values()) { queryIntentReceiverForPackage(context, parser, intent, flags, list); } } Collections.sort(list, mResolvePrioritySorter); return list; } public static final List resolveServiceIntent(Context context, Map pluginPackages, Intent intent, String resolvedType, int flags) throws Exception { if (intent == null || context == null) { return null; } List list = new ArrayList(1); ComponentName comp = intent.getComponent(); if (comp == null) { if (VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { if (intent.getSelector() != null) { intent = intent.getSelector(); comp = intent.getComponent(); } } } if (comp != null && comp.getPackageName() != null) { PluginPackageParser parser = pluginPackages.get(comp.getPackageName()); if (parser != null) { queryIntentServiceForPackage(context, parser, intent, flags, list); if (list.size() > 0) { Collections.sort(list, mResolvePrioritySorter); return list; } } else { //intent指定的包名不在我们的插件列表中。 } Collections.sort(list, mResolvePrioritySorter); return list; } final String pkgName = intent.getPackage(); if (pkgName != null) { PluginPackageParser parser = pluginPackages.get(pkgName); if (parser != null) { queryIntentServiceForPackage(context, parser, intent, flags, list); } else { //intent指定的包名不在我们的插件列表中。 } } else { for (PluginPackageParser parser : pluginPackages.values()) { queryIntentServiceForPackage(context, parser, intent, flags, list); } } Collections.sort(list, mResolvePrioritySorter); return list; } public static final List resolveProviderIntent(Context context, Map pluginPackages, Intent intent, String resolvedType, int flags) throws Exception { if (intent == null || context == null) { return null; } List list = new ArrayList(1); ComponentName comp = intent.getComponent(); if (comp == null) { if (VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { if (intent.getSelector() != null) { intent = intent.getSelector(); comp = intent.getComponent(); } } } if (comp != null && comp.getPackageName() != null) { PluginPackageParser parser = pluginPackages.get(comp.getPackageName()); if (parser != null) { queryIntentProviderForPackage(context, parser, intent, flags, list); if (list.size() > 0) { Collections.sort(list, mResolvePrioritySorter); return list; } } else { //intent指定的包名不在我们的插件列表中。 } Collections.sort(list, mResolvePrioritySorter); return list; } final String pkgName = intent.getPackage(); if (pkgName != null) { PluginPackageParser parser = pluginPackages.get(pkgName); if (parser != null) { queryIntentProviderForPackage(context, parser, intent, flags, list); } else { //intent指定的包名不在我们的插件列表中。 } } else { for (PluginPackageParser parser : pluginPackages.values()) { queryIntentProviderForPackage(context, parser, intent, flags, list); } } Collections.sort(list, mResolvePrioritySorter); return list; } public static final List resolveActivityIntent(Context context, Map pluginPackages, Intent intent, String resolvedType, int flags) throws Exception { if (intent == null || context == null) { return null; } List list = new ArrayList(1); ComponentName comp = intent.getComponent(); if (comp == null) { if (VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { if (intent.getSelector() != null) { intent = intent.getSelector(); comp = intent.getComponent(); } } } if (comp != null && comp.getPackageName() != null) { PluginPackageParser parser = pluginPackages.get(comp.getPackageName()); if (parser != null) { queryIntentActivityForPackage(context, parser, intent, flags, list); if (list.size() > 0) { Collections.sort(list, mResolvePrioritySorter); return list; } } else { //intent指定的包名不在我们的插件列表中。 } Collections.sort(list, mResolvePrioritySorter); return list; } final String pkgName = intent.getPackage(); if (pkgName != null) { PluginPackageParser parser = pluginPackages.get(pkgName); if (parser != null) { queryIntentActivityForPackage(context, parser, intent, flags, list); } else { //intent指定的包名不在我们的插件列表中。 } } else { for (PluginPackageParser parser : pluginPackages.values()) { queryIntentActivityForPackage(context, parser, intent, flags, list); } } Collections.sort(list, mResolvePrioritySorter); return list; } public static final List resolveIntent(Context context, Map pluginPackages, Intent intent, String resolvedType, int flags) throws Exception { if (intent == null || context == null) { return null; } List list = new ArrayList(1); ComponentName comp = intent.getComponent(); if (comp == null) { if (VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { if (intent.getSelector() != null) { intent = intent.getSelector(); comp = intent.getComponent(); } } } if (comp != null && comp.getPackageName() != null) { PluginPackageParser parser = pluginPackages.get(comp.getPackageName()); if (parser != null) { queryIntentActivityForPackage(context, parser, intent, flags, list); if (list.size() > 0) { Collections.sort(list, mResolvePrioritySorter); return list; } queryIntentServiceForPackage(context, parser, intent, flags, list); if (list.size() > 0) { Collections.sort(list, mResolvePrioritySorter); return list; } queryIntentProviderForPackage(context, parser, intent, flags, list); if (list.size() > 0) { Collections.sort(list, mResolvePrioritySorter); return list; } queryIntentReceiverForPackage(context, parser, intent, flags, list); if (list.size() > 0) { Collections.sort(list, mResolvePrioritySorter); return list; } } else { //intent指定的包名不在我们的插件列表中。 } Collections.sort(list, mResolvePrioritySorter); return list; } final String pkgName = intent.getPackage(); if (pkgName != null) { PluginPackageParser parser = pluginPackages.get(pkgName); if (parser != null) { queryIntentActivityForPackage(context, parser, intent, flags, list); queryIntentServiceForPackage(context, parser, intent, flags, list); queryIntentProviderForPackage(context, parser, intent, flags, list); queryIntentReceiverForPackage(context, parser, intent, flags, list); } else { //intent指定的包名不在我们的插件列表中。 } } else { for (PluginPackageParser parser : pluginPackages.values()) { queryIntentActivityForPackage(context, parser, intent, flags, list); queryIntentServiceForPackage(context, parser, intent, flags, list); queryIntentProviderForPackage(context, parser, intent, flags, list); queryIntentReceiverForPackage(context, parser, intent, flags, list); } } Collections.sort(list, mResolvePrioritySorter); return list; } private static void queryIntentReceiverForPackage(Context context, PluginPackageParser packageParser, Intent intent, int flags, List outList) throws Exception { List receivers = packageParser.getReceivers(); if (receivers != null && receivers.size() >= 0) { for (ActivityInfo receiver : receivers) { List intentFilters = packageParser.getReceiverIntentFilter(receiver); if (intentFilters != null && intentFilters.size() > 0) { for (IntentFilter intentFilter : intentFilters) { int match = intentFilter.match(context.getContentResolver(), intent, true, ""); if (match >= 0) { ActivityInfo flagInfo = packageParser.getReceiverInfo(new ComponentName(receiver.packageName, receiver.name), flags); if ((flags & PackageManager.MATCH_DEFAULT_ONLY) != 0) { if (intentFilter.hasCategory(Intent.CATEGORY_DEFAULT)) { ResolveInfo resolveInfo = newResolveInfo(flagInfo, intentFilter); resolveInfo.match = match; resolveInfo.isDefault = true; outList.add(resolveInfo); } else { //只是匹配默认。这里也算匹配不上。 } } else { ResolveInfo resolveInfo = newResolveInfo(flagInfo, intentFilter); resolveInfo.match = match; resolveInfo.isDefault = false; outList.add(resolveInfo); } } } if (outList.size() <= 0) { //没有在插件包中找到IntentFilter匹配的ACTIVITY } } else { //该插件包中没有具有IntentFilter的ACTIVITY } } } else { //该插件apk包中没有ACTIVITY } } private static void queryIntentProviderForPackage(Context context, PluginPackageParser packageParser, Intent intent, int flags, List outList) throws Exception { List providerInfos = packageParser.getProviders(); if (providerInfos != null && providerInfos.size() >= 0) { for (ProviderInfo providerInfo : providerInfos) { ComponentName className = new ComponentName(providerInfo.packageName, providerInfo.name); List intentFilters = packageParser.getProviderIntentFilter(className); if (intentFilters != null && intentFilters.size() > 0) { for (IntentFilter intentFilter : intentFilters) { int match = intentFilter.match(context.getContentResolver(), intent, true, ""); if (match >= 0) { ProviderInfo flagInfo = packageParser.getProviderInfo(new ComponentName(providerInfo.packageName, providerInfo.name), flags); if ((flags & PackageManager.MATCH_DEFAULT_ONLY) != 0) { if (intentFilter.hasCategory(Intent.CATEGORY_DEFAULT)) { ResolveInfo resolveInfo = newResolveInfo(flagInfo, intentFilter); resolveInfo.match = match; resolveInfo.isDefault = true; outList.add(resolveInfo); } else { //只是匹配默认。这里也算匹配不上。 } } else { ResolveInfo resolveInfo = newResolveInfo(flagInfo, intentFilter); resolveInfo.match = match; resolveInfo.isDefault = false; outList.add(resolveInfo); } } } if (outList.size() <= 0) { //没有在插件包中找到IntentFilter匹配的Service } } else { //该插件包中没有具有IntentFilter的Service } } } else { //该插件apk包中没有Service } } private static void queryIntentServiceForPackage(Context context, PluginPackageParser packageParser, Intent intent, int flags, List outList) throws Exception { List serviceInfos = packageParser.getServices(); if (serviceInfos != null && serviceInfos.size() >= 0) { for (ServiceInfo serviceInfo : serviceInfos) { ComponentName className = new ComponentName(serviceInfo.packageName, serviceInfo.name); List intentFilters = packageParser.getServiceIntentFilter(className); if (intentFilters != null && intentFilters.size() > 0) { for (IntentFilter intentFilter : intentFilters) { int match = intentFilter.match(context.getContentResolver(), intent, true, ""); if (match >= 0) { ServiceInfo flagServiceInfo = packageParser.getServiceInfo(new ComponentName(serviceInfo.packageName, serviceInfo.name), flags); if ((flags & PackageManager.MATCH_DEFAULT_ONLY) != 0) { if (intentFilter.hasCategory(Intent.CATEGORY_DEFAULT)) { ResolveInfo resolveInfo = newResolveInfo(flagServiceInfo, intentFilter); resolveInfo.match = match; resolveInfo.isDefault = true; outList.add(resolveInfo); } else { //只是匹配默认。这里也算匹配不上。 } } else { ResolveInfo resolveInfo = newResolveInfo(flagServiceInfo, intentFilter); resolveInfo.match = match; resolveInfo.isDefault = false; outList.add(resolveInfo); } } } if (outList.size() <= 0) { //没有在插件包中找到IntentFilter匹配的Service } } else { //该插件包中没有具有IntentFilter的Service } } } else { //该插件apk包中没有Service } } private static void queryIntentActivityForPackage(Context context, PluginPackageParser packageParser, Intent intent, int flags, List outList) throws Exception { List activityInfos = packageParser.getActivities(); if (activityInfos != null && activityInfos.size() >= 0) { for (ActivityInfo activityInfo : activityInfos) { ComponentName className = new ComponentName(activityInfo.packageName, activityInfo.name); List intentFilters = packageParser.getActivityIntentFilter(className); if (intentFilters != null && intentFilters.size() > 0) { for (IntentFilter intentFilter : intentFilters) { int match = intentFilter.match(context.getContentResolver(), intent, true, ""); if (match >= 0) { ActivityInfo flagInfo = packageParser.getActivityInfo(new ComponentName(activityInfo.packageName, activityInfo.name), flags); if ((flags & PackageManager.MATCH_DEFAULT_ONLY) != 0) { if (intentFilter.hasCategory(Intent.CATEGORY_DEFAULT)) { ResolveInfo resolveInfo = newResolveInfo(flagInfo, intentFilter); resolveInfo.match = match; resolveInfo.isDefault = true; outList.add(resolveInfo); } else { //只是匹配默认。这里也算匹配不上。 } } else { ResolveInfo resolveInfo = newResolveInfo(flagInfo, intentFilter); resolveInfo.match = match; resolveInfo.isDefault = false; outList.add(resolveInfo); } } } if (outList.size() <= 0) { //没有在插件包中找到IntentFilter匹配的ACTIVITY } } else { //该插件包中没有具有IntentFilter的ACTIVITY } } } else { //该插件apk包中没有ACTIVITY } } @TargetApi(VERSION_CODES.KITKAT) private static ResolveInfo newResolveInfo(ProviderInfo providerInfo, IntentFilter intentFilter) { ResolveInfo resolveInfo = new ResolveInfo(); resolveInfo.providerInfo = providerInfo; resolveInfo.filter = intentFilter; resolveInfo.resolvePackageName = providerInfo.packageName; resolveInfo.labelRes = providerInfo.labelRes; resolveInfo.icon = providerInfo.icon; resolveInfo.specificIndex = 1; // 默认就是false,不用再设置了。 // resolveInfo.system = false; resolveInfo.priority = intentFilter.getPriority(); resolveInfo.preferredOrder = 0; return resolveInfo; } private static ResolveInfo newResolveInfo(ServiceInfo serviceInfo, IntentFilter intentFilter) { ResolveInfo resolveInfo = new ResolveInfo(); resolveInfo.serviceInfo = serviceInfo; resolveInfo.filter = intentFilter; resolveInfo.resolvePackageName = serviceInfo.packageName; resolveInfo.labelRes = serviceInfo.labelRes; resolveInfo.icon = serviceInfo.icon; resolveInfo.specificIndex = 1; // if (VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { // 默认就是false,不用再设置了。 // resolveInfo.system = false; // } resolveInfo.priority = intentFilter.getPriority(); resolveInfo.preferredOrder = 0; return resolveInfo; } private static ResolveInfo newResolveInfo(ActivityInfo activityInfo, IntentFilter intentFilter) { ResolveInfo resolveInfo = new ResolveInfo(); resolveInfo.activityInfo = activityInfo; resolveInfo.filter = intentFilter; resolveInfo.resolvePackageName = activityInfo.packageName; resolveInfo.labelRes = activityInfo.labelRes; resolveInfo.icon = activityInfo.icon; resolveInfo.specificIndex = 1; // if (VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH) { //// 默认就是false,不用再设置了。 // resolveInfo.system = false; // } resolveInfo.priority = intentFilter.getPriority(); resolveInfo.preferredOrder = 0; return resolveInfo; } public static ResolveInfo findBest(List infos) { return infos.get(0); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/pm/parser/PackageParser.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.pm.parser; import android.content.Context; import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.InstrumentationInfo; import android.content.pm.PackageInfo; import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; import android.content.pm.ProviderInfo; import android.content.pm.ServiceInfo; import android.content.pm.Signature; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import com.morgoo.helper.compat.SystemPropertiesCompat; import java.io.File; import java.util.HashSet; import java.util.List; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/2/13. */ abstract class PackageParser { protected Context mContext; protected Object mPackageParser; PackageParser(Context context) { mContext = context; } public final static int PARSE_IS_SYSTEM = 1 << 0; public final static int PARSE_CHATTY = 1 << 1; public final static int PARSE_MUST_BE_APK = 1 << 2; public final static int PARSE_IGNORE_PROCESSES = 1 << 3; public final static int PARSE_FORWARD_LOCK = 1 << 4; public final static int PARSE_ON_SDCARD = 1 << 5; public final static int PARSE_IS_SYSTEM_DIR = 1 << 6; public final static int PARSE_IS_PRIVILEGED = 1 << 7; public final static int PARSE_COLLECT_CERTIFICATES = 1 << 8; public final static int PARSE_TRUSTED_OVERLAY = 1 << 9; public static PackageParser newPluginParser(Context context) throws Exception { if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP_MR1) { if ("1".equals(SystemPropertiesCompat.get("ro.build.version.preview_sdk", ""))) { return new PackageParserApi22Preview1(context); } else { return new PackageParserApi22(context);//API 20 } } else if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { return new PackageParserApi21(context);//API 21 } else if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1 && VERSION.SDK_INT <= VERSION_CODES.KITKAT_WATCH) { return new PackageParserApi20(context);//API 17,18,19,20 } else if (VERSION.SDK_INT == VERSION_CODES.JELLY_BEAN) { return new PackageParserApi16(context); //API 16 } else if (VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH && VERSION.SDK_INT <= VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { return new PackageParserApi15(context); //API 14,15 } else if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB && VERSION.SDK_INT <= VERSION_CODES.HONEYCOMB_MR2) { return new PackageParserApi15(context); //API 11,12,13 } else if (VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD && VERSION.SDK_INT <= VERSION_CODES.GINGERBREAD_MR1) { return new PackageParserApi15(context); //API 9,10 } else { return new PackageParserApi15(context); //API 9,10 } } public abstract void parsePackage(File file, int flags) throws Exception; public abstract void collectCertificates(int flags) throws Exception; public abstract ActivityInfo generateActivityInfo(Object activity, int flags) throws Exception; public abstract ServiceInfo generateServiceInfo(Object service, int flags) throws Exception; public abstract ProviderInfo generateProviderInfo(Object provider, int flags) throws Exception; public ActivityInfo generateReceiverInfo(Object receiver, int flags) throws Exception { return generateActivityInfo(receiver, flags); } public abstract InstrumentationInfo generateInstrumentationInfo(Object instrumentation, int flags) throws Exception; public abstract ApplicationInfo generateApplicationInfo(int flags) throws Exception; public abstract PermissionGroupInfo generatePermissionGroupInfo(Object permissionGroup, int flags) throws Exception; public abstract PermissionInfo generatePermissionInfo(Object permission, int flags) throws Exception; public abstract PackageInfo generatePackageInfo(int gids[], int flags, long firstInstallTime, long lastUpdateTime, HashSet grantedPermissions) throws Exception; public abstract List getActivities() throws Exception; public abstract List getServices() throws Exception; public abstract List getProviders() throws Exception; public abstract List getPermissions() throws Exception; public abstract List getPermissionGroups() throws Exception; public abstract List getRequestedPermissions() throws Exception; public abstract List getReceivers() throws Exception; public abstract List getInstrumentations() throws Exception; public abstract String getPackageName() throws Exception; ////////////////////// public abstract String readNameFromComponent(Object data) throws Exception; public abstract List readIntentFilterFromComponent(Object data) throws Exception; public abstract void writeSignature(Signature[] signatures) throws Exception; } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/pm/parser/PackageParserApi15.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.pm.parser; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.InstrumentationInfo; import android.content.pm.PackageInfo; import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; import android.content.pm.ProviderInfo; import android.content.pm.ServiceInfo; import com.morgoo.droidplugin.reflect.MethodUtils; import java.lang.reflect.Method; import java.util.HashSet; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/2/13. */ class PackageParserApi15 extends PackageParserApi20 { public PackageParserApi15(Context context) throws Exception { super(context); } @Override public ActivityInfo generateActivityInfo(Object activity, int flags) throws Exception { /* public static final ActivityInfo generateActivityInfo(Activity a, int flags) */ Method method = MethodUtils.getAccessibleMethod(sPackageParserClass, "generateActivityInfo", sActivityClass, int.class); return (ActivityInfo) method.invoke(null, activity, flags); } @Override public ServiceInfo generateServiceInfo(Object service, int flags) throws Exception { /* public static final ServiceInfo generateServiceInfo(Service s, int flags)*/ Method method = MethodUtils.getAccessibleMethod(sPackageParserClass, "generateServiceInfo", sServiceClass, int.class); return (ServiceInfo) method.invoke(null, service, flags); } @Override public ProviderInfo generateProviderInfo(Object provider, int flags) throws Exception { /* public static final ProviderInfo generateProviderInfo(Provider p, int flags) */ Method method = MethodUtils.getAccessibleMethod(sPackageParserClass, "generateProviderInfo", sProviderClass, int.class); return (ProviderInfo) method.invoke(null, provider, flags); } @Override public InstrumentationInfo generateInstrumentationInfo( Object instrumentation, int flags) throws Exception { /* public static final InstrumentationInfo generateInstrumentationInfo( Instrumentation i, int flags) */ return super.generateInstrumentationInfo(instrumentation, flags); } @Override public ApplicationInfo generateApplicationInfo(int flags) throws Exception { /* public static ApplicationInfo generateApplicationInfo(Package p, int flags) */ Method method = MethodUtils.getAccessibleMethod(sPackageParserClass, "generateApplicationInfo", mPackage.getClass(), int.class); return (ApplicationInfo) method.invoke(null, mPackage, flags); } @Override public PermissionGroupInfo generatePermissionGroupInfo( Object permissionGroup, int flags) throws Exception { /* public static final PermissionGroupInfo generatePermissionGroupInfo( PermissionGroup pg, int flags) */ return super.generatePermissionGroupInfo(permissionGroup, flags); } @Override public PermissionInfo generatePermissionInfo( Object permission, int flags) throws Exception { /* public static final PermissionInfo generatePermissionInfo( Permission p, int flags) */ return super.generatePermissionInfo(permission, flags); } @Override public PackageInfo generatePackageInfo( int gids[], int flags, long firstInstallTime, long lastUpdateTime, HashSet grantedPermissions) throws Exception { /* public static PackageInfo generatePackageInfo(PackageParser.Package p, int gids[], int flags, long firstInstallTime, long lastUpdateTime) */ Method method = MethodUtils.getAccessibleMethod(sPackageParserClass, "generatePackageInfo", mPackage.getClass(), int[].class, int.class, long.class, long.class); return (PackageInfo) method.invoke(null, mPackage, gids, flags, firstInstallTime, lastUpdateTime); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/pm/parser/PackageParserApi16.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.pm.parser; import android.annotation.TargetApi; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.InstrumentationInfo; import android.content.pm.PackageInfo; import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; import android.content.pm.ProviderInfo; import android.content.pm.ServiceInfo; import android.os.Build; import com.morgoo.droidplugin.reflect.MethodUtils; import java.lang.reflect.Method; import java.util.HashSet; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/2/13. */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN) class PackageParserApi16 extends PackageParserApi20 { private boolean mStopped; private int mEnabledState; public PackageParserApi16(Context context) throws Exception { super(context); mStopped = false; mEnabledState = 0; } @Override public ActivityInfo generateActivityInfo(Object activity, int flags) throws Exception { /*public static final ActivityInfo generateActivityInfo(Activity a, int flags, boolean stopped, int enabledState, int userId) */ Method method = MethodUtils.getAccessibleMethod(sPackageParserClass, "generateActivityInfo", sActivityClass, int.class, boolean.class, int.class, int.class); return (ActivityInfo) method.invoke(null, activity, flags, mStopped, mEnabledState, mUserId); } @Override public ServiceInfo generateServiceInfo(Object service, int flags) throws Exception { /*public static final ServiceInfo generateServiceInfo(Service s, int flags, boolean stopped, int enabledState, int userId)*/ Method method = MethodUtils.getAccessibleMethod(sPackageParserClass, "generateServiceInfo", sServiceClass, int.class, boolean.class, int.class, int.class); return (ServiceInfo) method.invoke(null, service, flags, mStopped, mEnabledState, mUserId); } @Override public ProviderInfo generateProviderInfo(Object provider, int flags) throws Exception { /* public static final ProviderInfo generateProviderInfo(Provider p, int flags, boolean stopped, int enabledState, int userId) */ Method method = MethodUtils.getAccessibleMethod(sPackageParserClass, "generateProviderInfo", sProviderClass, int.class, boolean.class, int.class, int.class); return (ProviderInfo) method.invoke(null, provider, flags, mStopped, mEnabledState, mUserId); } @Override public InstrumentationInfo generateInstrumentationInfo( Object instrumentation, int flags) throws Exception { /* public static final InstrumentationInfo generateInstrumentationInfo( Instrumentation i, int flags)*/ return super.generateInstrumentationInfo(instrumentation, flags); } @Override public ApplicationInfo generateApplicationInfo(int flags) throws Exception { /* public static ApplicationInfo generateApplicationInfo(Package p, int flags, boolean stopped, int enabledState, int userId) */ Method method = MethodUtils.getAccessibleMethod(sPackageParserClass, "generateApplicationInfo", mPackage.getClass(), int.class, boolean.class, int.class, int.class); return (ApplicationInfo) method.invoke(null, mPackage, flags, mStopped, mEnabledState, mUserId); } @Override public PermissionGroupInfo generatePermissionGroupInfo( Object permissionGroup, int flags) throws Exception { /* public static final PermissionGroupInfo generatePermissionGroupInfo( PermissionGroup pg, int flags) */ return super.generatePermissionGroupInfo(permissionGroup, flags); } @Override public PermissionInfo generatePermissionInfo( Object permission, int flags) throws Exception { /* public static final PermissionInfo generatePermissionInfo( Permission p, int flags) */ return super.generatePermissionInfo(permission, flags); } @Override public PackageInfo generatePackageInfo( int gids[], int flags, long firstInstallTime, long lastUpdateTime, HashSet grantedPermissions) throws Exception { /* public static PackageInfo generatePackageInfo(PackageParser.Package p, int gids[], int flags, long firstInstallTime, long lastUpdateTime, HashSet grantedPermissions, boolean stopped, int enabledState, int userId)*/ Method method = MethodUtils.getAccessibleMethod(sPackageParserClass, "generatePackageInfo", mPackage.getClass(), int[].class, int.class, long.class, long.class, HashSet.class, boolean.class, int.class, int.class); return (PackageInfo) method.invoke(null, mPackage, gids, flags, firstInstallTime, lastUpdateTime, grantedPermissions, mStopped, mEnabledState, mUserId); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/pm/parser/PackageParserApi20.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.pm.parser; import android.annotation.TargetApi; import android.content.Context; import android.os.Build; import android.util.DisplayMetrics; import com.morgoo.droidplugin.reflect.MethodUtils; import java.io.File; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/2/13. */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) class PackageParserApi20 extends PackageParserApi21 { public PackageParserApi20(Context context) throws Exception { super(context); } @Override public void parsePackage(File sourceFile, int flags) throws Exception { /* public Package parsePackage(File sourceFile, String destCodePath, DisplayMetrics metrics, int flags)*/ DisplayMetrics metrics = new DisplayMetrics(); metrics.setToDefaults(); String destCodePath = sourceFile.getPath(); mPackageParser = MethodUtils.invokeConstructor(sPackageParserClass, destCodePath); mPackage = MethodUtils.invokeMethod(mPackageParser, "parsePackage", sourceFile, destCodePath, metrics, flags); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/pm/parser/PackageParserApi21.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.pm.parser; import android.annotation.TargetApi; import android.content.Context; import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.InstrumentationInfo; import android.content.pm.PackageInfo; import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; import android.content.pm.ProviderInfo; import android.content.pm.ServiceInfo; import android.content.pm.Signature; import android.os.Build; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import com.morgoo.droidplugin.reflect.FieldUtils; import com.morgoo.droidplugin.reflect.MethodUtils; import com.morgoo.helper.Log; import com.morgoo.helper.compat.UserHandleCompat; import java.io.File; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; /** * Created by zhangyong on 2015/2/11. */ @TargetApi(Build.VERSION_CODES.LOLLIPOP) class PackageParserApi21 extends PackageParser { private static final String TAG = PackageParserApi21.class.getSimpleName(); protected Class sPackageUserStateClass; protected Class sPackageParserClass; protected Class sActivityClass; protected Class sServiceClass; protected Class sProviderClass; protected Class sInstrumentationClass; protected Class sPermissionClass; protected Class sPermissionGroupClass; protected Class sArraySetClass; protected Object mPackage; protected Object mDefaultPackageUserState; protected int mUserId; public PackageParserApi21(Context context) throws Exception { super(context); initClasses(); } private void initClasses() throws ClassNotFoundException, InstantiationException, IllegalAccessException { sPackageParserClass = Class.forName("android.content.pm.PackageParser"); sActivityClass = Class.forName("android.content.pm.PackageParser$Activity"); sServiceClass = Class.forName("android.content.pm.PackageParser$Service"); sProviderClass = Class.forName("android.content.pm.PackageParser$Provider"); sInstrumentationClass = Class.forName("android.content.pm.PackageParser$Instrumentation"); sPermissionClass = Class.forName("android.content.pm.PackageParser$Permission"); sPermissionGroupClass = Class.forName("android.content.pm.PackageParser$PermissionGroup"); try { sArraySetClass = Class.forName("android.util.ArraySet"); } catch (ClassNotFoundException e) { } if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { sPackageUserStateClass = Class.forName("android.content.pm.PackageUserState"); mDefaultPackageUserState = sPackageUserStateClass.newInstance(); mUserId = UserHandleCompat.getCallingUserId(); } } @Override public void parsePackage(File file, int flags) throws Exception { /* public Package parsePackage(File packageFile, int flags) throws PackageParserException*/ mPackageParser = sPackageParserClass.newInstance(); mPackage = MethodUtils.invokeMethod(mPackageParser, "parsePackage", file, flags); } @Override public void collectCertificates(int flags) throws Exception { // public void collectCertificates(Package pkg, int flags) throws PackageParserException Method method = MethodUtils.getAccessibleMethod(sPackageParserClass, "collectCertificates", mPackage.getClass(), int.class); method.invoke(mPackageParser, mPackage, flags); } @Override public ActivityInfo generateActivityInfo(Object activity, int flags) throws Exception { /* public static final ActivityInfo generateActivityInfo(Activity a, int flags, PackageUserState state, int userId) */ Method method = MethodUtils.getAccessibleMethod(sPackageParserClass, "generateActivityInfo", sActivityClass, int.class, sPackageUserStateClass, int.class); return (ActivityInfo) method.invoke(null, activity, flags, mDefaultPackageUserState, mUserId); } @Override public ServiceInfo generateServiceInfo(Object service, int flags) throws Exception { /* public static final ServiceInfo generateServiceInfo(Service s, int flags, PackageUserState state, int userId)*/ Method method = MethodUtils.getAccessibleMethod(sPackageParserClass, "generateServiceInfo", sServiceClass, int.class, sPackageUserStateClass, int.class); return (ServiceInfo) method.invoke(null, service, flags, mDefaultPackageUserState, mUserId); } @Override public ProviderInfo generateProviderInfo(Object provider, int flags) throws Exception { /* public static final ProviderInfo generateProviderInfo(Provider p, int flags, PackageUserState state, int userId) */ Method method = MethodUtils.getAccessibleMethod(sPackageParserClass, "generateProviderInfo", sProviderClass, int.class, sPackageUserStateClass, int.class); return (ProviderInfo) method.invoke(null, provider, flags, mDefaultPackageUserState, mUserId); } @Override public InstrumentationInfo generateInstrumentationInfo( Object instrumentation, int flags) throws Exception { /* public static final InstrumentationInfo generateInstrumentationInfo( Instrumentation i, int flags)*/ Method method = MethodUtils.getAccessibleMethod(sPackageParserClass, "generateInstrumentationInfo", sInstrumentationClass, int.class); return (InstrumentationInfo) method.invoke(null, instrumentation, flags); } @Override public ApplicationInfo generateApplicationInfo(int flags) throws Exception { /* public static ApplicationInfo generateApplicationInfo(Package p, int flags, PackageUserState state, int userId) */ Method method = MethodUtils.getAccessibleMethod(sPackageParserClass, "generateApplicationInfo", mPackage.getClass(), int.class, sPackageUserStateClass, int.class); return (ApplicationInfo) method.invoke(null, mPackage, flags, mDefaultPackageUserState, mUserId); } @Override public PermissionGroupInfo generatePermissionGroupInfo( Object permissionGroup, int flags) throws Exception { /* public static final PermissionGroupInfo generatePermissionGroupInfo( PermissionGroup pg, int flags)*/ Method method = MethodUtils.getAccessibleMethod(sPackageParserClass, "generatePermissionGroupInfo", sPermissionGroupClass, int.class); return (PermissionGroupInfo) method.invoke(null, permissionGroup, flags); } @Override public PermissionInfo generatePermissionInfo( Object permission, int flags) throws Exception { /*public static final PermissionInfo generatePermissionInfo( Permission p, int flags)*/ Method method = MethodUtils.getAccessibleMethod(sPackageParserClass, "generatePermissionInfo", sPermissionClass, int.class); return (PermissionInfo) method.invoke(null, permission, flags); } @Override public PackageInfo generatePackageInfo( int gids[], int flags, long firstInstallTime, long lastUpdateTime, HashSet grantedPermissions) throws Exception { /*public static PackageInfo generatePackageInfo(PackageParser.Package p, int gids[], int flags, long firstInstallTime, long lastUpdateTime, HashSet grantedPermissions, PackageUserState state, int userId) */ try { Method method = MethodUtils.getAccessibleMethod(sPackageParserClass, "generatePackageInfo", mPackage.getClass(), int[].class, int.class, long.class, long.class, Set.class, sPackageUserStateClass, int.class); return (PackageInfo) method.invoke(null, mPackage, gids, flags, firstInstallTime, lastUpdateTime, grantedPermissions, mDefaultPackageUserState, mUserId); } catch (NoSuchMethodException e) { Log.i(TAG, "get generatePackageInfo 1 fail", e); } try { Method method = MethodUtils.getAccessibleMethod(sPackageParserClass, "generatePackageInfo", mPackage.getClass(), int[].class, int.class, long.class, long.class, HashSet.class, sPackageUserStateClass, int.class); return (PackageInfo) method.invoke(null, mPackage, gids, flags, firstInstallTime, lastUpdateTime, grantedPermissions, mDefaultPackageUserState, mUserId); } catch (NoSuchMethodException e) { Log.i(TAG, "get generatePackageInfo 2 fail", e); } try { Method method = MethodUtils.getAccessibleMethod(sPackageParserClass, "generatePackageInfo", mPackage.getClass(), int[].class, int.class, long.class, long.class, sArraySetClass, sPackageUserStateClass, int.class); Object grantedPermissionsArray = null; try { Constructor constructor = sArraySetClass.getConstructor(Collection.class); grantedPermissionsArray = constructor.newInstance(grantedPermissions); } catch (Exception e) { } if (grantedPermissionsArray == null) { grantedPermissionsArray = grantedPermissions; } return (PackageInfo) method.invoke(null, mPackage, gids, flags, firstInstallTime, lastUpdateTime, grantedPermissionsArray, mDefaultPackageUserState, mUserId); } catch (NoSuchMethodException e) { Log.i(TAG, "get generatePackageInfo 3 fail", e); } throw new NoSuchMethodException("Can not found method generatePackageInfo"); } @Override public List getActivities() throws Exception { /*PackageParser.Package.activities*/ return (List) FieldUtils.readField(mPackage, "activities"); } @Override public List getServices() throws Exception { /*PackageParser.Package.services*/ return (List) FieldUtils.readField(mPackage, "services"); } @Override public List getProviders() throws Exception { /*PackageParser.Package.providers*/ return (List) FieldUtils.readField(mPackage, "providers"); } @Override public List getPermissions() throws Exception { /*PackageParser.Package.permissions*/ return (List) FieldUtils.readField(mPackage, "permissions"); } @Override public List getPermissionGroups() throws Exception { /*PackageParser.Package.permissionGroups*/ return (List) FieldUtils.readField(mPackage, "permissionGroups"); } @Override public List getRequestedPermissions() throws Exception { /*PackageParser.Package.requestedPermissions*/ return (List) FieldUtils.readField(mPackage, "requestedPermissions"); } @Override public List getReceivers() throws Exception { /*PackageParser.Package.requestedPermissions*/ return (List) FieldUtils.readField(mPackage, "receivers"); } @Override public List getInstrumentations() throws Exception { /*PackageParser.Package.instrumentation*/ return (List) FieldUtils.readField(mPackage, "instrumentation"); } @Override public String getPackageName() throws Exception { /*PackageParser.Package.packageName*/ return (String) FieldUtils.readField(mPackage, "packageName"); } @Override public String readNameFromComponent(Object data) throws Exception { return (String) FieldUtils.readField(data, "className"); } @Override public List readIntentFilterFromComponent(Object data) throws Exception { return (List) FieldUtils.readField(data, "intents"); } @Override public void writeSignature(Signature[] signatures) throws Exception { FieldUtils.writeField(mPackage, "mSignatures", signatures); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/pm/parser/PackageParserApi22.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.pm.parser; import android.annotation.TargetApi; import android.content.Context; import android.content.pm.PackageInfo; import android.os.Build; import com.morgoo.droidplugin.reflect.MethodUtils; import com.morgoo.helper.Log; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.Set; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/5/29. */ @TargetApi(Build.VERSION_CODES.LOLLIPOP_MR1) class PackageParserApi22 extends PackageParserApi21 { private static final String TAG = PackageParserApi22Preview1.class.getSimpleName(); PackageParserApi22(Context context) throws Exception { super(context); } @Override public PackageInfo generatePackageInfo( int gids[], int flags, long firstInstallTime, long lastUpdateTime, HashSet grantedPermissions) throws Exception { /*public static PackageInfo generatePackageInfo(PackageParser.Package p, int gids[], int flags, long firstInstallTime, long lastUpdateTime, HashSet grantedPermissions, PackageUserState state, int userId) */ try { return super.generatePackageInfo(gids, flags, firstInstallTime, lastUpdateTime, grantedPermissions); } catch (Exception e) { Log.i(TAG, "generatePackageInfo fail", e); } Method method = MethodUtils.getAccessibleMethod(sPackageParserClass, "generatePackageInfo", mPackage.getClass(), int[].class, int.class, long.class, long.class, sArraySetClass, sPackageUserStateClass, int.class); Object grantedPermissionsArray = null; try { Constructor constructor = sArraySetClass.getConstructor(Collection.class); grantedPermissionsArray = constructor.newInstance(constructor, grantedPermissions); } catch (Exception e) { e.printStackTrace(); } if (grantedPermissionsArray == null) { grantedPermissionsArray = grantedPermissions; } return (PackageInfo) method.invoke(null, mPackage, gids, flags, firstInstallTime, lastUpdateTime, grantedPermissionsArray, mDefaultPackageUserState, mUserId); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/pm/parser/PackageParserApi22Preview1.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.pm.parser; import android.content.Context; import android.content.pm.PackageInfo; import com.morgoo.droidplugin.reflect.MethodUtils; import com.morgoo.helper.Log; import java.lang.reflect.Method; import java.util.Arrays; import java.util.HashSet; import java.util.Set; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/5/29. */ //for Android M class PackageParserApi22Preview1 extends PackageParserApi21 { private static final String TAG = PackageParserApi22Preview1.class.getSimpleName(); PackageParserApi22Preview1(Context context) throws Exception { super(context); } @Override public PackageInfo generatePackageInfo( int gids[], int flags, long firstInstallTime, long lastUpdateTime, HashSet grantedPermissions) throws Exception { /*public static PackageInfo generatePackageInfo(PackageParser.Package p, int gids[], int flags, long firstInstallTime, long lastUpdateTime, HashSet grantedPermissions, PackageUserState state, int userId) */ try { return super.generatePackageInfo(gids, flags, firstInstallTime, lastUpdateTime, grantedPermissions); } catch (Exception e) { Log.i(TAG, "generatePackageInfo fail", e); } Method method = MethodUtils.getAccessibleMethod(sPackageParserClass, "generatePackageInfo", mPackage.getClass(), int[].class, int.class, long.class, long.class, Set.class, sPackageUserStateClass, int.class); return (PackageInfo) method.invoke(null, mPackage, gids, flags, firstInstallTime, lastUpdateTime, grantedPermissions, mDefaultPackageUserState, mUserId); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/pm/parser/PluginPackageParser.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.pm.parser; import android.content.ComponentName; import android.content.Context; import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.InstrumentationInfo; import android.content.pm.PackageInfo; import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; import android.content.pm.ProviderInfo; import android.content.pm.ServiceInfo; import android.content.pm.Signature; import android.os.Build; import android.text.TextUtils; import com.morgoo.droidplugin.core.PluginDirHelper; import com.morgoo.droidplugin.reflect.FieldUtils; import com.morgoo.helper.ComponentNameComparator; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.TreeMap; /** * 解析插件apk *

* Created by Andy Zhang(zhangyong232@gmail.com) on 2015/2/13. */ public class PluginPackageParser { private final File mPluginFile; private final PackageParser mParser; private final String mPackageName; private final Context mHostContext; private final PackageInfo mHostPackageInfo; private Map mActivityObjCache = new TreeMap(new ComponentNameComparator()); private Map mServiceObjCache = new TreeMap(new ComponentNameComparator()); private Map mProviderObjCache = new TreeMap(new ComponentNameComparator()); private Map mReceiversObjCache = new TreeMap(new ComponentNameComparator()); private Map mInstrumentationObjCache = new TreeMap(new ComponentNameComparator()); private Map mPermissionsObjCache = new TreeMap(new ComponentNameComparator()); private Map mPermissionGroupObjCache = new TreeMap(new ComponentNameComparator()); private ArrayList mRequestedPermissionsCache = new ArrayList(); private Map> mActivityIntentFilterCache = new TreeMap>(new ComponentNameComparator()); private Map> mServiceIntentFilterCache = new TreeMap>(new ComponentNameComparator()); private Map> mProviderIntentFilterCache = new TreeMap>(new ComponentNameComparator()); private Map> mReceiverIntentFilterCache = new TreeMap>(new ComponentNameComparator()); private Map mActivityInfoCache = new TreeMap(new ComponentNameComparator()); private Map mServiceInfoCache = new TreeMap(new ComponentNameComparator()); private Map mProviderInfoCache = new TreeMap(new ComponentNameComparator()); private Map mReceiversInfoCache = new TreeMap(new ComponentNameComparator()); private Map mInstrumentationInfoCache = new TreeMap(new ComponentNameComparator()); private Map mPermissionGroupInfoCache = new TreeMap(new ComponentNameComparator()); private Map mPermissionsInfoCache = new TreeMap(new ComponentNameComparator()); public PluginPackageParser(Context hostContext, File pluginFile) throws Exception { mHostContext = hostContext; mPluginFile = pluginFile; mParser = PackageParser.newPluginParser(hostContext); mParser.parsePackage(pluginFile, 0); mPackageName = mParser.getPackageName(); mHostPackageInfo = mHostContext.getPackageManager().getPackageInfo(mHostContext.getPackageName(), 0); List datas = mParser.getActivities(); for (Object data : datas) { ComponentName componentName = new ComponentName(mPackageName, mParser.readNameFromComponent(data)); synchronized (mActivityObjCache) { mActivityObjCache.put(componentName, data); } synchronized (mActivityInfoCache) { ActivityInfo value = mParser.generateActivityInfo(data, 0); fixApplicationInfo(value.applicationInfo); if (TextUtils.isEmpty(value.processName)) { value.processName = value.packageName; } mActivityInfoCache.put(componentName, value); } List filters = mParser.readIntentFilterFromComponent(data); synchronized (mActivityIntentFilterCache) { mActivityIntentFilterCache.remove(componentName); mActivityIntentFilterCache.put(componentName, new ArrayList(filters)); } } datas = mParser.getServices(); for (Object data : datas) { ComponentName componentName = new ComponentName(mPackageName, mParser.readNameFromComponent(data)); synchronized (mServiceObjCache) { mServiceObjCache.put(componentName, data); } synchronized (mServiceInfoCache) { ServiceInfo value = mParser.generateServiceInfo(data, 0); fixApplicationInfo(value.applicationInfo); if (TextUtils.isEmpty(value.processName)) { value.processName = value.packageName; } mServiceInfoCache.put(componentName, value); } List filters = mParser.readIntentFilterFromComponent(data); synchronized (mServiceIntentFilterCache) { mServiceIntentFilterCache.remove(componentName); mServiceIntentFilterCache.put(componentName, new ArrayList(filters)); } } datas = mParser.getProviders(); for (Object data : datas) { ComponentName componentName = new ComponentName(mPackageName, mParser.readNameFromComponent(data)); synchronized (mProviderObjCache) { mProviderObjCache.put(componentName, data); } synchronized (mProviderInfoCache) { ProviderInfo value = mParser.generateProviderInfo(data, 0); fixApplicationInfo(value.applicationInfo); if (TextUtils.isEmpty(value.processName)) { value.processName = value.packageName; } mProviderInfoCache.put(componentName, value); } List filters = mParser.readIntentFilterFromComponent(data); synchronized (mProviderIntentFilterCache) { mProviderIntentFilterCache.remove(componentName); mProviderIntentFilterCache.put(componentName, new ArrayList(filters)); } } datas = mParser.getReceivers(); for (Object data : datas) { ComponentName componentName = new ComponentName(mPackageName, mParser.readNameFromComponent(data)); synchronized (mReceiversObjCache) { mReceiversObjCache.put(componentName, data); } synchronized (mReceiversInfoCache) { ActivityInfo value = mParser.generateReceiverInfo(data, 0); fixApplicationInfo(value.applicationInfo); if (TextUtils.isEmpty(value.processName)) { value.processName = value.packageName; } mReceiversInfoCache.put(componentName, value); } List filters = mParser.readIntentFilterFromComponent(data); synchronized (mReceiverIntentFilterCache) { mReceiverIntentFilterCache.remove(componentName); mReceiverIntentFilterCache.put(componentName, new ArrayList(filters)); } } datas = mParser.getInstrumentations(); for (Object data : datas) { ComponentName componentName = new ComponentName(mPackageName, mParser.readNameFromComponent(data)); synchronized (mInstrumentationObjCache) { mInstrumentationObjCache.put(componentName, data); } } datas = mParser.getPermissions(); for (Object data : datas) { String cls = mParser.readNameFromComponent(data); if (cls != null) { ComponentName componentName = new ComponentName(mPackageName, cls); synchronized (mPermissionsObjCache) { mPermissionsObjCache.put(componentName, data); } synchronized (mPermissionsInfoCache) { PermissionInfo value = mParser.generatePermissionInfo(data, 0); mPermissionsInfoCache.put(componentName, value); } } } datas = mParser.getPermissionGroups(); for (Object data : datas) { ComponentName componentName = new ComponentName(mPackageName, mParser.readNameFromComponent(data)); synchronized (mPermissionGroupObjCache) { mPermissionGroupObjCache.put(componentName, data); } } List requestedPermissions = mParser.getRequestedPermissions(); if (requestedPermissions != null && requestedPermissions.size() > 0) { synchronized (mRequestedPermissionsCache) { mRequestedPermissionsCache.addAll(requestedPermissions); } } } public File getPluginFile() { return mPluginFile; } public void collectCertificates(int flags) throws Exception { mParser.collectCertificates(flags); } public List getActivityIntentFilter(ComponentName className) { synchronized (mActivityIntentFilterCache) { return mActivityIntentFilterCache.get(className); } } public List getServiceIntentFilter(ComponentName className) { synchronized (mServiceIntentFilterCache) { return mServiceIntentFilterCache.get(className); } } public List getProviderIntentFilter(ComponentName className) { synchronized (mProviderObjCache) { return mProviderIntentFilterCache.get(className); } } public ActivityInfo getActivityInfo(ComponentName className, int flags) throws Exception { Object data; synchronized (mActivityObjCache) { data = mActivityObjCache.get(className); } if (data != null) { ActivityInfo activityInfo = mParser.generateActivityInfo(data, flags); fixApplicationInfo(activityInfo.applicationInfo); if (TextUtils.isEmpty(activityInfo.processName)) { activityInfo.processName = activityInfo.packageName; } return activityInfo; } return null; } public ServiceInfo getServiceInfo(ComponentName className, int flags) throws Exception { Object data; synchronized (mServiceObjCache) { data = mServiceObjCache.get(className); } if (data != null) { ServiceInfo serviceInfo = mParser.generateServiceInfo(data, flags); fixApplicationInfo(serviceInfo.applicationInfo); if (TextUtils.isEmpty(serviceInfo.processName)) { serviceInfo.processName = serviceInfo.packageName; } return serviceInfo; } return null; } public ActivityInfo getReceiverInfo(ComponentName className, int flags) throws Exception { Object data; synchronized (mReceiversObjCache) { data = mReceiversObjCache.get(className); } if (data != null) { ActivityInfo activityInfo = mParser.generateReceiverInfo(data, flags); fixApplicationInfo(activityInfo.applicationInfo); if (TextUtils.isEmpty(activityInfo.processName)) { activityInfo.processName = activityInfo.packageName; } return activityInfo; } return null; } public ProviderInfo getProviderInfo(ComponentName className, int flags) throws Exception { Object data; synchronized (mProviderObjCache) { data = mProviderObjCache.get(className); } if (data != null) { ProviderInfo providerInfo = mParser.generateProviderInfo(data, flags); fixApplicationInfo(providerInfo.applicationInfo); if (TextUtils.isEmpty(providerInfo.processName)) { providerInfo.processName = providerInfo.packageName; } return providerInfo; } return null; } public InstrumentationInfo getInstrumentationInfo(ComponentName className, int flags) throws Exception { Object data; synchronized (mInstrumentationObjCache) { data = mInstrumentationObjCache.get(className); } if (data != null) { return mParser.generateInstrumentationInfo(data, flags); } return null; } public ApplicationInfo getApplicationInfo(int flags) throws Exception { ApplicationInfo applicationInfo = mParser.generateApplicationInfo(flags); fixApplicationInfo(applicationInfo); if (TextUtils.isEmpty(applicationInfo.processName)) { applicationInfo.processName = applicationInfo.packageName; } return applicationInfo; } public PermissionGroupInfo getPermissionGroupInfo(ComponentName className, int flags) throws Exception { Object data; synchronized (mPermissionGroupObjCache) { data = mPermissionGroupObjCache.get(className); } if (data != null) { return mParser.generatePermissionGroupInfo(data, flags); } return null; } public PermissionInfo getPermissionInfo(ComponentName className, int flags) throws Exception { Object data; synchronized (mPermissionsObjCache) { data = mPermissionsObjCache.get(className); } if (data != null) { return mParser.generatePermissionInfo(data, flags); } return null; } public PackageInfo getPackageInfo(int flags) throws Exception { PackageInfo packageInfo = mParser.generatePackageInfo(mHostPackageInfo.gids, flags, mPluginFile.lastModified(), mPluginFile.lastModified(), new HashSet(getRequestedPermissions())); fixPackageInfo(packageInfo); return packageInfo; } public List getActivities() throws Exception { return new ArrayList(mActivityInfoCache.values()); } public List getServices() throws Exception { return new ArrayList(mServiceInfoCache.values()); } public List getProviders() throws Exception { return new ArrayList(mProviderInfoCache.values()); } public List getReceivers() throws Exception { return new ArrayList(mReceiversInfoCache.values()); } public List getPermissions() throws Exception { return new ArrayList(mPermissionsInfoCache.values()); } public List getPermissionGroups() throws Exception { return new ArrayList(mPermissionGroupInfoCache.values()); } public List getInstrumentationInfos() { return new ArrayList(mInstrumentationInfoCache.values()); } public List getRequestedPermissions() throws Exception { synchronized (mRequestedPermissionsCache) { return new ArrayList(mRequestedPermissionsCache); } } public String getPackageName() throws Exception { return mPackageName; } private ApplicationInfo fixApplicationInfo(ApplicationInfo applicationInfo) { if (applicationInfo.sourceDir == null) { applicationInfo.sourceDir = mPluginFile.getPath(); } if (applicationInfo.publicSourceDir == null) { applicationInfo.publicSourceDir = mPluginFile.getPath(); } if (applicationInfo.dataDir == null) { applicationInfo.dataDir = PluginDirHelper.getPluginDataDir(mHostContext, applicationInfo.packageName); } try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (FieldUtils.readField(applicationInfo, "scanSourceDir", true) == null) { FieldUtils.writeField(applicationInfo, "scanSourceDir", applicationInfo.dataDir, true); } } } catch (Throwable e) { //Do nothing } try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (FieldUtils.readField(applicationInfo, "scanPublicSourceDir", true) == null) { FieldUtils.writeField(applicationInfo, "scanPublicSourceDir", applicationInfo.dataDir, true); } } } catch (Throwable e) { //Do nothing } applicationInfo.uid = mHostPackageInfo.applicationInfo.uid; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { if (applicationInfo.nativeLibraryDir == null) { applicationInfo.nativeLibraryDir = PluginDirHelper.getPluginNativeLibraryDir(mHostContext, applicationInfo.packageName); } } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (applicationInfo.splitSourceDirs == null) { applicationInfo.splitSourceDirs = new String[]{mPluginFile.getPath()}; } } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (applicationInfo.splitPublicSourceDirs == null) { applicationInfo.splitPublicSourceDirs = new String[]{mPluginFile.getPath()}; } } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { try { if (Build.VERSION.SDK_INT < 26) { FieldUtils.writeField(applicationInfo, "deviceEncryptedDataDir", applicationInfo.dataDir); FieldUtils.writeField(applicationInfo, "credentialEncryptedDataDir", applicationInfo.dataDir); } FieldUtils.writeField(applicationInfo, "deviceProtectedDataDir", applicationInfo.dataDir); FieldUtils.writeField(applicationInfo, "credentialProtectedDataDir", applicationInfo.dataDir); } catch (Exception e) { e.printStackTrace(); } } // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // if (applicationInfo.primaryCpuAbi == null) { // applicationInfo.primaryCpuAbi = mHostPackageInfo.applicationInfo.primaryCpuAbi; // } // } // // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // if (applicationInfo.secondaryCpuAbi == null) { // applicationInfo.secondaryCpuAbi = mHostPackageInfo.applicationInfo.secondaryCpuAbi; // } // } if (TextUtils.isEmpty(applicationInfo.processName)) { applicationInfo.processName = applicationInfo.packageName; } return applicationInfo; } private PackageInfo fixPackageInfo(PackageInfo packageInfo) { packageInfo.gids = mHostPackageInfo.gids; fixApplicationInfo(packageInfo.applicationInfo); return packageInfo; } public Map> getReceiverIntentFilter() { synchronized (mReceiverIntentFilterCache) { Map> map = new HashMap>(); for (ComponentName componentName : mReceiverIntentFilterCache.keySet()) { map.put(mReceiversInfoCache.get(componentName), mReceiverIntentFilterCache.get(componentName)); } return map; } } public List getReceiverIntentFilter(ActivityInfo info) { synchronized (mReceiverIntentFilterCache) { for (ComponentName componentName : mReceiverIntentFilterCache.keySet()) { if (TextUtils.equals(info.name, mReceiversInfoCache.get(componentName).name)) { return mReceiverIntentFilterCache.get(componentName); } } } return null; } public void writeSignature(Signature[] signatures) throws Exception { if (signatures != null) { mParser.writeSignature(signatures); } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/reflect/FieldUtils.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.reflect; import android.text.TextUtils; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; /** * Created by Andy Zhang(zhangyong232@gmail.com)ClassUtils on 2015/3/25. */ public class FieldUtils { private static Map sFieldCache = new HashMap(); private static String getKey(Class cls, String fieldName) { StringBuilder sb = new StringBuilder(); sb.append(cls.toString()).append("#").append(fieldName); return sb.toString(); } private static Field getField(Class cls, String fieldName, final boolean forceAccess) { Validate.isTrue(cls != null, "The class must not be null"); Validate.isTrue(!TextUtils.isEmpty(fieldName), "The field name must not be blank/empty"); String key = getKey(cls, fieldName); Field cachedField; synchronized (sFieldCache) { cachedField = sFieldCache.get(key); } if (cachedField != null) { if (forceAccess && !cachedField.isAccessible()) { cachedField.setAccessible(true); } return cachedField; } // check up the superclass hierarchy for (Class acls = cls; acls != null; acls = acls.getSuperclass()) { try { final Field field = acls.getDeclaredField(fieldName); // getDeclaredField checks for non-public scopes as well // and it returns accurate results if (!Modifier.isPublic(field.getModifiers())) { if (forceAccess) { field.setAccessible(true); } else { continue; } } synchronized (sFieldCache) { sFieldCache.put(key, field); } return field; } catch (final NoSuchFieldException ex) { // NOPMD // ignore } } // check the public interface case. This must be manually searched for // incase there is a public supersuperclass field hidden by a private/package // superclass field. Field match = null; for (final Class class1 : Utils.getAllInterfaces(cls)) { try { final Field test = class1.getField(fieldName); Validate.isTrue(match == null, "Reference to field %s is ambiguous relative to %s" + "; a matching field exists on two or more implemented interfaces.", fieldName, cls); match = test; } catch (final NoSuchFieldException ex) { // NOPMD // ignore } } synchronized (sFieldCache) { sFieldCache.put(key, match); } return match; } public static Object readField(final Field field, final Object target, final boolean forceAccess) throws IllegalAccessException { Validate.isTrue(field != null, "The field must not be null"); if (forceAccess && !field.isAccessible()) { field.setAccessible(true); } else { MemberUtils.setAccessibleWorkaround(field); } return field.get(target); } public static void writeField(final Field field, final Object target, final Object value, final boolean forceAccess) throws IllegalAccessException { Validate.isTrue(field != null, "The field must not be null"); if (forceAccess && !field.isAccessible()) { field.setAccessible(true); } else { MemberUtils.setAccessibleWorkaround(field); } field.set(target, value); } public static Object readField(final Field field, final Object target) throws IllegalAccessException { return readField(field, target, true); } public static Field getField(final Class cls, final String fieldName) { return getField(cls, fieldName, true); } public static Object readField(final Object target, final String fieldName) throws IllegalAccessException { Validate.isTrue(target != null, "target object must not be null"); final Class cls = target.getClass(); final Field field = getField(cls, fieldName, true); Validate.isTrue(field != null, "Cannot locate field %s on %s", fieldName, cls); // already forced access above, don't repeat it here: return readField(field, target, false); } public static Object readField(final Object target, final String fieldName, final boolean forceAccess) throws IllegalAccessException { Validate.isTrue(target != null, "target object must not be null"); final Class cls = target.getClass(); final Field field = getField(cls, fieldName, forceAccess); Validate.isTrue(field != null, "Cannot locate field %s on %s", fieldName, cls); // already forced access above, don't repeat it here: return readField(field, target, forceAccess); } public static void writeField(final Object target, final String fieldName, final Object value) throws IllegalAccessException { writeField(target, fieldName, value, true); } public static void writeField(final Object target, final String fieldName, final Object value, final boolean forceAccess) throws IllegalAccessException { Validate.isTrue(target != null, "target object must not be null"); final Class cls = target.getClass(); final Field field = getField(cls, fieldName, true); Validate.isTrue(field != null, "Cannot locate declared field %s.%s", cls.getName(), fieldName); // already forced access above, don't repeat it here: writeField(field, target, value, forceAccess); } public static void writeField(final Field field, final Object target, final Object value) throws IllegalAccessException { writeField(field, target, value, true); } public static Object readStaticField(final Field field, final boolean forceAccess) throws IllegalAccessException { Validate.isTrue(field != null, "The field must not be null"); Validate.isTrue(Modifier.isStatic(field.getModifiers()), "The field '%s' is not static", field.getName()); return readField(field, (Object) null, forceAccess); } public static Object readStaticField(final Class cls, final String fieldName) throws IllegalAccessException { final Field field = getField(cls, fieldName, true); Validate.isTrue(field != null, "Cannot locate field '%s' on %s", fieldName, cls); // already forced access above, don't repeat it here: return readStaticField(field, true); } public static void writeStaticField(final Field field, final Object value, final boolean forceAccess) throws IllegalAccessException { Validate.isTrue(field != null, "The field must not be null"); Validate.isTrue(Modifier.isStatic(field.getModifiers()), "The field %s.%s is not static", field.getDeclaringClass().getName(), field.getName()); writeField(field, (Object) null, value, forceAccess); } public static void writeStaticField(final Class cls, final String fieldName, final Object value) throws IllegalAccessException { final Field field = getField(cls, fieldName, true); Validate.isTrue(field != null, "Cannot locate field %s on %s", fieldName, cls); // already forced access above, don't repeat it here: writeStaticField(field, value, true); } public static Field getDeclaredField(final Class cls, final String fieldName, final boolean forceAccess) { Validate.isTrue(cls != null, "The class must not be null"); Validate.isTrue(!TextUtils.isEmpty(fieldName), "The field name must not be blank/empty"); try { // only consider the specified class by using getDeclaredField() final Field field = cls.getDeclaredField(fieldName); if (!MemberUtils.isAccessible(field)) { if (forceAccess) { field.setAccessible(true); } else { return null; } } return field; } catch (final NoSuchFieldException e) { // NOPMD // ignore } return null; } public static void writeDeclaredField(final Object target, final String fieldName, final Object value) throws IllegalAccessException { Validate.isTrue(target != null, "target object must not be null"); final Class cls = target.getClass(); final Field field = getDeclaredField(cls, fieldName, true); Validate.isTrue(field != null, "Cannot locate declared field %s.%s", cls.getName(), fieldName); // already forced access above, don't repeat it here: writeField(field, target, value, false); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/reflect/MemberUtils.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.reflect; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Member; import java.lang.reflect.Modifier; import java.util.HashMap; import java.util.Map; /** * Created by Andy Zhang(zhangyong232@gmail.com)ClassUtils on 2015/3/26. */ class MemberUtils { private static final int ACCESS_TEST = Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE; private static final Class[] ORDERED_PRIMITIVE_TYPES = {Byte.TYPE, Short.TYPE, Character.TYPE, Integer.TYPE, Long.TYPE, Float.TYPE, Double.TYPE}; private static boolean isPackageAccess(final int modifiers) { return (modifiers & ACCESS_TEST) == 0; } static boolean isAccessible(final Member m) { return m != null && Modifier.isPublic(m.getModifiers()) && !m.isSynthetic(); } static boolean setAccessibleWorkaround(final AccessibleObject o) { if (o == null || o.isAccessible()) { return false; } final Member m = (Member) o; if (!o.isAccessible() && Modifier.isPublic(m.getModifiers()) && isPackageAccess(m.getDeclaringClass().getModifiers())) { try { o.setAccessible(true); return true; } catch (final SecurityException e) { // NOPMD // ignore in favor of subsequent IllegalAccessException } } return false; } static boolean isAssignable(final Class cls, final Class toClass) { return isAssignable(cls, toClass, true); } static boolean isAssignable(Class[] classArray, Class[] toClassArray, final boolean autoboxing) { if (Utils.isSameLength(classArray, toClassArray) == false) { return false; } if (classArray == null) { classArray = Utils.EMPTY_CLASS_ARRAY; } if (toClassArray == null) { toClassArray = Utils.EMPTY_CLASS_ARRAY; } for (int i = 0; i < classArray.length; i++) { if (isAssignable(classArray[i], toClassArray[i], autoboxing) == false) { return false; } } return true; } static boolean isAssignable(Class cls, final Class toClass, final boolean autoboxing) { if (toClass == null) { return false; } // have to check for null, as isAssignableFrom doesn't if (cls == null) { return !toClass.isPrimitive(); } //autoboxing: if (autoboxing) { if (cls.isPrimitive() && !toClass.isPrimitive()) { cls = primitiveToWrapper(cls); if (cls == null) { return false; } } if (toClass.isPrimitive() && !cls.isPrimitive()) { cls = wrapperToPrimitive(cls); if (cls == null) { return false; } } } if (cls.equals(toClass)) { return true; } if (cls.isPrimitive()) { if (toClass.isPrimitive() == false) { return false; } if (Integer.TYPE.equals(cls)) { return Long.TYPE.equals(toClass) || Float.TYPE.equals(toClass) || Double.TYPE.equals(toClass); } if (Long.TYPE.equals(cls)) { return Float.TYPE.equals(toClass) || Double.TYPE.equals(toClass); } if (Boolean.TYPE.equals(cls)) { return false; } if (Double.TYPE.equals(cls)) { return false; } if (Float.TYPE.equals(cls)) { return Double.TYPE.equals(toClass); } if (Character.TYPE.equals(cls)) { return Integer.TYPE.equals(toClass) || Long.TYPE.equals(toClass) || Float.TYPE.equals(toClass) || Double.TYPE.equals(toClass); } if (Short.TYPE.equals(cls)) { return Integer.TYPE.equals(toClass) || Long.TYPE.equals(toClass) || Float.TYPE.equals(toClass) || Double.TYPE.equals(toClass); } if (Byte.TYPE.equals(cls)) { return Short.TYPE.equals(toClass) || Integer.TYPE.equals(toClass) || Long.TYPE.equals(toClass) || Float.TYPE.equals(toClass) || Double.TYPE.equals(toClass); } // should never get here return false; } return toClass.isAssignableFrom(cls); } private static final Map, Class> primitiveWrapperMap = new HashMap, Class>(); static { primitiveWrapperMap.put(Boolean.TYPE, Boolean.class); primitiveWrapperMap.put(Byte.TYPE, Byte.class); primitiveWrapperMap.put(Character.TYPE, Character.class); primitiveWrapperMap.put(Short.TYPE, Short.class); primitiveWrapperMap.put(Integer.TYPE, Integer.class); primitiveWrapperMap.put(Long.TYPE, Long.class); primitiveWrapperMap.put(Double.TYPE, Double.class); primitiveWrapperMap.put(Float.TYPE, Float.class); primitiveWrapperMap.put(Void.TYPE, Void.TYPE); } private static final Map, Class> wrapperPrimitiveMap = new HashMap, Class>(); static { for (final Class primitiveClass : primitiveWrapperMap.keySet()) { final Class wrapperClass = primitiveWrapperMap.get(primitiveClass); if (!primitiveClass.equals(wrapperClass)) { wrapperPrimitiveMap.put(wrapperClass, primitiveClass); } } } static Class primitiveToWrapper(final Class cls) { Class convertedClass = cls; if (cls != null && cls.isPrimitive()) { convertedClass = primitiveWrapperMap.get(cls); } return convertedClass; } static Class wrapperToPrimitive(final Class cls) { return wrapperPrimitiveMap.get(cls); } static int compareParameterTypes(final Class[] left, final Class[] right, final Class[] actual) { final float leftCost = getTotalTransformationCost(actual, left); final float rightCost = getTotalTransformationCost(actual, right); return leftCost < rightCost ? -1 : rightCost < leftCost ? 1 : 0; } private static float getTotalTransformationCost(final Class[] srcArgs, final Class[] destArgs) { float totalCost = 0.0f; for (int i = 0; i < srcArgs.length; i++) { Class srcClass, destClass; srcClass = srcArgs[i]; destClass = destArgs[i]; totalCost += getObjectTransformationCost(srcClass, destClass); } return totalCost; } private static float getObjectTransformationCost(Class srcClass, final Class destClass) { if (destClass.isPrimitive()) { return getPrimitivePromotionCost(srcClass, destClass); } float cost = 0.0f; while (srcClass != null && !destClass.equals(srcClass)) { if (destClass.isInterface() && isAssignable(srcClass, destClass)) { // slight penalty for interface match. // we still want an exact match to override an interface match, // but // an interface match should override anything where we have to // get a superclass. cost += 0.25f; break; } cost++; srcClass = srcClass.getSuperclass(); } /* * If the destination class is null, we've travelled all the way up to * an Object match. We'll penalize this by adding 1.5 to the cost. */ if (srcClass == null) { cost += 1.5f; } return cost; } private static float getPrimitivePromotionCost(final Class srcClass, final Class destClass) { float cost = 0.0f; Class cls = srcClass; if (!cls.isPrimitive()) { // slight unwrapping penalty cost += 0.1f; cls = wrapperToPrimitive(cls); } for (int i = 0; cls != destClass && i < ORDERED_PRIMITIVE_TYPES.length; i++) { if (cls == ORDERED_PRIMITIVE_TYPES[i]) { cost += 0.1f; if (i < ORDERED_PRIMITIVE_TYPES.length - 1) { cls = ORDERED_PRIMITIVE_TYPES[i + 1]; } } } return cost; } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/reflect/MethodUtils.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.reflect; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.HashMap; import java.util.Map; /** * Created by Andy Zhang(zhangyong232@gmail.com)ClassUtils on 2015/3/25. */ public class MethodUtils { private static Map sMethodCache = new HashMap(); private static String getKey(final Class cls, final String methodName, final Class... parameterTypes) { StringBuilder sb = new StringBuilder(); sb.append(cls.toString()).append("#").append(methodName); if (parameterTypes != null && parameterTypes.length > 0) { for (Class parameterType : parameterTypes) { sb.append(parameterType.toString()).append("#"); } } else { sb.append(Void.class.toString()); } return sb.toString(); } private static Method getAccessibleMethodFromSuperclass(final Class cls, final String methodName, final Class... parameterTypes) { Class parentClass = cls.getSuperclass(); while (parentClass != null) { if (Modifier.isPublic(parentClass.getModifiers())) { try { return parentClass.getMethod(methodName, parameterTypes); } catch (final NoSuchMethodException e) { return null; } } parentClass = parentClass.getSuperclass(); } return null; } private static Method getAccessibleMethodFromInterfaceNest(Class cls, final String methodName, final Class... parameterTypes) { // Search up the superclass chain for (; cls != null; cls = cls.getSuperclass()) { // Check the implemented interfaces of the parent class final Class[] interfaces = cls.getInterfaces(); for (int i = 0; i < interfaces.length; i++) { // Is this interface public? if (!Modifier.isPublic(interfaces[i].getModifiers())) { continue; } // Does the method exist on this interface? try { return interfaces[i].getDeclaredMethod(methodName, parameterTypes); } catch (final NoSuchMethodException e) { // NOPMD /* * Swallow, if no method is found after the loop then this * method returns null. */ } // Recursively check our parent interfaces Method method = getAccessibleMethodFromInterfaceNest(interfaces[i], methodName, parameterTypes); if (method != null) { return method; } } } return null; } private static Method getAccessibleMethod(Method method) { if (!MemberUtils.isAccessible(method)) { return null; } // If the declaring class is public, we are done final Class cls = method.getDeclaringClass(); if (Modifier.isPublic(cls.getModifiers())) { return method; } final String methodName = method.getName(); final Class[] parameterTypes = method.getParameterTypes(); // Check the implemented interfaces and subinterfaces method = getAccessibleMethodFromInterfaceNest(cls, methodName, parameterTypes); // Check the superclass chain if (method == null) { method = getAccessibleMethodFromSuperclass(cls, methodName, parameterTypes); } return method; } public static Method getAccessibleMethod(final Class cls, final String methodName, final Class... parameterTypes) throws NoSuchMethodException { String key = getKey(cls, methodName, parameterTypes); Method method; synchronized (sMethodCache) { method = sMethodCache.get(key); } if (method != null) { if (!method.isAccessible()) { method.setAccessible(true); } return method; } Method accessibleMethod = getAccessibleMethod(cls.getMethod(methodName, parameterTypes)); synchronized (sMethodCache) { sMethodCache.put(key, accessibleMethod); } return accessibleMethod; } private static Method getMatchingAccessibleMethod(final Class cls, final String methodName, final Class... parameterTypes) { String key = getKey(cls, methodName, parameterTypes); Method cachedMethod; synchronized (sMethodCache) { cachedMethod = sMethodCache.get(key); } if (cachedMethod != null) { if (!cachedMethod.isAccessible()) { cachedMethod.setAccessible(true); } return cachedMethod; } try { final Method method = cls.getMethod(methodName, parameterTypes); MemberUtils.setAccessibleWorkaround(method); synchronized (sMethodCache) { sMethodCache.put(key, method); } return method; } catch (final NoSuchMethodException e) { // NOPMD - Swallow the exception } // search through all methods Method bestMatch = null; final Method[] methods = cls.getMethods(); for (final Method method : methods) { // compare name and parameters if (method.getName().equals(methodName) && MemberUtils.isAssignable(parameterTypes, method.getParameterTypes(), true)) { // get accessible version of method final Method accessibleMethod = getAccessibleMethod(method); if (accessibleMethod != null && (bestMatch == null || MemberUtils.compareParameterTypes( accessibleMethod.getParameterTypes(), bestMatch.getParameterTypes(), parameterTypes) < 0)) { bestMatch = accessibleMethod; } } } if (bestMatch != null) { MemberUtils.setAccessibleWorkaround(bestMatch); } synchronized (sMethodCache) { sMethodCache.put(key, bestMatch); } return bestMatch; } public static Object invokeMethod(final Object object, final String methodName, Object[] args, Class[] parameterTypes) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { parameterTypes = Utils.nullToEmpty(parameterTypes); args = Utils.nullToEmpty(args); final Method method = getMatchingAccessibleMethod(object.getClass(), methodName, parameterTypes); if (method == null) { throw new NoSuchMethodException("No such accessible method: " + methodName + "() on object: " + object.getClass().getName()); } return method.invoke(object, args); } public static Object invokeStaticMethod(final Class clazz, final String methodName, Object[] args, Class[] parameterTypes) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { parameterTypes = Utils.nullToEmpty(parameterTypes); args = Utils.nullToEmpty(args); final Method method = getMatchingAccessibleMethod(clazz, methodName, parameterTypes); if (method == null) { throw new NoSuchMethodException("No such accessible method: " + methodName + "() on object: " + clazz.getName()); } return method.invoke(null, args); } public static Object invokeStaticMethod(final Class clazz, final String methodName, Object... args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { args = Utils.nullToEmpty(args); final Class[] parameterTypes = Utils.toClass(args); return invokeStaticMethod(clazz, methodName, args, parameterTypes); } public static Object invokeMethod(final Object object, final String methodName, Object... args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { args = Utils.nullToEmpty(args); final Class[] parameterTypes = Utils.toClass(args); return invokeMethod(object, methodName, args, parameterTypes); } public static T invokeConstructor(final Class cls, Object... args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { args = Utils.nullToEmpty(args); final Class parameterTypes[] = Utils.toClass(args); return invokeConstructor(cls, args, parameterTypes); } public static T invokeConstructor(final Class cls, Object[] args, Class[] parameterTypes) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { args = Utils.nullToEmpty(args); parameterTypes = Utils.nullToEmpty(parameterTypes); final Constructor ctor = getMatchingAccessibleConstructor(cls, parameterTypes); if (ctor == null) { throw new NoSuchMethodException( "No such accessible constructor on object: " + cls.getName()); } return ctor.newInstance(args); } public static Constructor getMatchingAccessibleConstructor(final Class cls, final Class... parameterTypes) { Validate.isTrue(cls != null, "class cannot be null"); // see if we can find the constructor directly // most of the time this works and it's much faster try { final Constructor ctor = cls.getConstructor(parameterTypes); MemberUtils.setAccessibleWorkaround(ctor); return ctor; } catch (final NoSuchMethodException e) { // NOPMD - Swallow } Constructor result = null; /* * (1) Class.getConstructors() is documented to return Constructor so as * long as the array is not subsequently modified, everything's fine. */ final Constructor[] ctors = cls.getConstructors(); // return best match: for (Constructor ctor : ctors) { // compare parameters if (MemberUtils.isAssignable(parameterTypes, ctor.getParameterTypes(), true)) { // get accessible version of constructor ctor = getAccessibleConstructor(ctor); if (ctor != null) { MemberUtils.setAccessibleWorkaround(ctor); if (result == null || MemberUtils.compareParameterTypes(ctor.getParameterTypes(), result .getParameterTypes(), parameterTypes) < 0) { // temporary variable for annotation, see comment above (1) @SuppressWarnings("unchecked") final Constructor constructor = (Constructor) ctor; result = constructor; } } } } return result; } private static Constructor getAccessibleConstructor(final Constructor ctor) { Validate.isTrue(ctor != null, "constructor cannot be null"); return MemberUtils.isAccessible(ctor) && isAccessible(ctor.getDeclaringClass()) ? ctor : null; } private static boolean isAccessible(final Class type) { Class cls = type; while (cls != null) { if (!Modifier.isPublic(cls.getModifiers())) { return false; } cls = cls.getEnclosingClass(); } return true; } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/reflect/Utils.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.reflect; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; /** * Created by Andy Zhang(zhangyong232@gmail.com)ClassUtils on 2015/3/26. */ public class Utils { static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; static boolean isSameLength(final Object[] array1, final Object[] array2) { if ((array1 == null && array2 != null && array2.length > 0) || (array2 == null && array1 != null && array1.length > 0) || (array1 != null && array2 != null && array1.length != array2.length)) { return false; } return true; } static Class[] toClass(final Object... array) { if (array == null) { return null; } else if (array.length == 0) { return EMPTY_CLASS_ARRAY; } final Class[] classes = new Class[array.length]; for (int i = 0; i < array.length; i++) { classes[i] = array[i] == null ? null : array[i].getClass(); } return classes; } static Class[] nullToEmpty(final Class[] array) { if (array == null || array.length == 0) { return EMPTY_CLASS_ARRAY; } return array; } static Object[] nullToEmpty(final Object[] array) { if (array == null || array.length == 0) { return EMPTY_OBJECT_ARRAY; } return array; } public static List> getAllInterfaces(final Class cls) { if (cls == null) { return null; } final LinkedHashSet> interfacesFound = new LinkedHashSet>(); getAllInterfaces(cls, interfacesFound); return new ArrayList>(interfacesFound); } private static void getAllInterfaces(Class cls, final HashSet> interfacesFound) { while (cls != null) { final Class[] interfaces = cls.getInterfaces(); for (final Class i : interfaces) { if (interfacesFound.add(i)) { getAllInterfaces(i, interfacesFound); } } cls = cls.getSuperclass(); } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/reflect/Validate.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.reflect; /** * Created by Andy Zhang(zhangyong232@gmail.com)ClassUtils on 2015/3/26. */ class Validate { static void isTrue(final boolean expression, final String message, final Object... values) { if (expression == false) { throw new IllegalArgumentException(String.format(message, values)); } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/stub/AbstractContentProviderStub.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.stub; import android.annotation.TargetApi; import android.content.ContentProvider; import android.content.ContentProviderClient; import android.content.ContentProviderOperation; import android.content.ContentProviderResult; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.OperationApplicationException; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ProviderInfo; import android.database.Cursor; import android.net.Uri; import android.net.Uri.Builder; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.Looper; import android.os.RemoteException; import android.text.TextUtils; import com.morgoo.droidplugin.core.Env; import com.morgoo.droidplugin.core.PluginProcessManager; import com.morgoo.droidplugin.pm.PluginManager; import com.morgoo.droidplugin.reflect.FieldUtils; import com.morgoo.helper.Log; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.Set; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/2/26. */ public abstract class AbstractContentProviderStub extends ContentProvider { private static final String TAG = AbstractContentProviderStub.class.getSimpleName(); private ContentResolver mContentResolver; private Map sContentProviderClients = new HashMap(); @Override public boolean onCreate() { mContentResolver = getContext().getContentResolver(); return true; } private Uri buildNewUri(Uri uri, String targetAuthority) { Uri.Builder b = new Builder(); b.scheme(uri.getScheme()); b.authority(targetAuthority); b.path(uri.getPath()); if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { Set names = uri.getQueryParameterNames(); if (names != null && names.size() > 0) { for (String name : names) { if (!TextUtils.equals(name, Env.EXTRA_TARGET_AUTHORITY)) { b.appendQueryParameter(name, uri.getQueryParameter(name)); } } } } else { b.query(uri.getQuery()); } b.fragment(uri.getFragment()); return b.build(); } private synchronized ContentProviderClient getContentProviderClient(final String targetAuthority) { ContentProviderClient client = sContentProviderClients.get(targetAuthority); if (client != null) { return client; } if (Looper.getMainLooper() != Looper.myLooper()) { PluginManager.getInstance().waitForConnected(); } ProviderInfo stubInfo = null; ProviderInfo targetInfo = null; try { String authority = getMyAuthority(); stubInfo = getContext().getPackageManager().resolveContentProvider(authority, 0); targetInfo = PluginManager.getInstance().resolveContentProvider(targetAuthority, 0); } catch (Exception e) { Log.e(TAG, "Can not reportMyProcessName on ContentProvider"); } if (stubInfo != null && targetInfo != null) { try { PluginManager.getInstance().reportMyProcessName(stubInfo.processName, targetInfo.processName, targetInfo.packageName); } catch (RemoteException e) { Log.e(TAG, "RemoteException on reportMyProcessName", e); } } try { if (targetInfo != null) { PluginProcessManager.preLoadApk(getContext(), targetInfo); } } catch (Exception e) { handleExpcetion(e); } ContentProviderClient newClient = mContentResolver.acquireContentProviderClient(targetAuthority); sContentProviderClients.put(targetAuthority, newClient); try { if (stubInfo != null && targetInfo != null) { PluginManager.getInstance().onProviderCreated(stubInfo, targetInfo); } } catch (Exception e) { Log.e(TAG, "Exception on report onProviderCreated", e); } return sContentProviderClients.get(targetAuthority); } private String getMyAuthority() throws PackageManager.NameNotFoundException, IllegalAccessException { if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { return (String) FieldUtils.readField(this, "mAuthority"); } else { Context context = getContext(); PackageInfo pkgInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_PROVIDERS); if (pkgInfo != null && pkgInfo.providers != null && pkgInfo.providers.length > 0) { for (ProviderInfo info : pkgInfo.providers) { if (TextUtils.equals(info.name, getClass().getName())) { return info.authority; } } } } return null; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { String targetAuthority = uri.getQueryParameter(Env.EXTRA_TARGET_AUTHORITY); if (!TextUtils.isEmpty(targetAuthority) && !TextUtils.equals(targetAuthority, uri.getAuthority())) { ContentProviderClient client = getContentProviderClient(targetAuthority); try { return client.query(buildNewUri(uri, targetAuthority), projection, selection, selectionArgs, sortOrder); } catch (RemoteException e) { handleExpcetion(e); } } return null; } protected void handleExpcetion(Exception e) { Log.e(TAG, "handleExpcetion", e); } @Override public String getType(Uri uri) { String targetAuthority = uri.getQueryParameter(Env.EXTRA_TARGET_AUTHORITY); if (!TextUtils.isEmpty(targetAuthority) && !TextUtils.equals(targetAuthority, uri.getAuthority())) { ContentProviderClient client = getContentProviderClient(targetAuthority); try { return client.getType(buildNewUri(uri, targetAuthority)); } catch (RemoteException e) { handleExpcetion(e); } } return null; } @Override public Uri insert(Uri uri, ContentValues contentValues) { String targetAuthority = uri.getQueryParameter(Env.EXTRA_TARGET_AUTHORITY); if (!TextUtils.isEmpty(targetAuthority) && !TextUtils.equals(targetAuthority, uri.getAuthority())) { ContentProviderClient client = getContentProviderClient(targetAuthority); try { return client.insert(buildNewUri(uri, targetAuthority), contentValues); } catch (RemoteException e) { handleExpcetion(e); } } return null; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { String targetAuthority = uri.getQueryParameter(Env.EXTRA_TARGET_AUTHORITY); if (!TextUtils.isEmpty(targetAuthority) && !TextUtils.equals(targetAuthority, uri.getAuthority())) { ContentProviderClient client = getContentProviderClient(targetAuthority); try { return client.delete(buildNewUri(uri, targetAuthority), selection, selectionArgs); } catch (RemoteException e) { handleExpcetion(e); } } return 0; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { String targetAuthority = uri.getQueryParameter(Env.EXTRA_TARGET_AUTHORITY); if (!TextUtils.isEmpty(targetAuthority) && !TextUtils.equals(targetAuthority, uri.getAuthority())) { ContentProviderClient client = getContentProviderClient(targetAuthority); try { return client.update(buildNewUri(uri, targetAuthority), values, selection, selectionArgs); } catch (RemoteException e) { handleExpcetion(e); } } return 0; } @TargetApi(VERSION_CODES.JELLY_BEAN_MR1) @Override public Bundle call(String method, String arg, Bundle extras) { String targetAuthority = extras != null ? extras.getString(Env.EXTRA_TARGET_AUTHORITY) : null; String targetMethod = extras != null ? extras.getString(Env.EXTRA_TARGET_AUTHORITY) : null; if (!TextUtils.isEmpty(targetMethod) && !TextUtils.equals(targetMethod, method)) { ContentProviderClient client = getContentProviderClient(targetAuthority); try { return client.call(targetMethod, arg, extras); } catch (RemoteException e) { handleExpcetion(e); } } return super.call(method, arg, extras); } @Override public int bulkInsert(Uri uri, ContentValues[] values) { String targetAuthority = uri.getQueryParameter(Env.EXTRA_TARGET_AUTHORITY); if (!TextUtils.isEmpty(targetAuthority) && !TextUtils.equals(targetAuthority, uri.getAuthority())) { ContentProviderClient client = getContentProviderClient(targetAuthority); try { return client.bulkInsert(buildNewUri(uri, targetAuthority), values); } catch (RemoteException e) { handleExpcetion(e); } } return super.bulkInsert(uri, values); } @Override public ContentProviderResult[] applyBatch(ArrayList operations) throws OperationApplicationException { //TODO applyBatch转发 // if (operations != null && operations.size() > 0) { // ArrayList OldOperations = new ArrayList(); // Map> newOperations = new HashMap>(); // for (ContentProviderOperation operation : operations) { // Uri uri = operation.getUri(); // String targetAuthority = uri.getQueryParameter(Env.EXTRA_TARGET_AUTHORITY); // if (!TextUtils.isEmpty(targetAuthority) && !TextUtils.equals(targetAuthority, uri.getAuthority())) { // try { // Uri newUri = buildNewUri(uri, targetAuthority); // FieldUtils.writeField(operation, "mUri", newUri, true); // ArrayList newOps = newOperations.get(targetAuthority); // if (newOps == null) { // newOps = new ArrayList(1); // newOps.add(operation); // newOperations.put(targetAuthority, newOps); // } else { // newOps.add(operation); // } // } catch (IllegalAccessException e) { // handleExpcetion(e); // } // } else { // OldOperations.add(operation); // } // } // // if (newOperations.size() > 0) { // ArrayList results = new ArrayList(operations.size()); // for (String authority : newOperations.keySet()) { // ContentProviderClient client = getContentProviderClient(authority); // ArrayList contentProviderOperations = newOperations.get(authority); // if (contentProviderOperations.size() > 0) { // ContentProviderResult[] rs = client.applyBatch(contentProviderOperations); // for (ContentProviderResult r : rs) { // results.add(r); // } // } // } // //这一步必须要在主线程中执行,这里还有bug // // } // } return super.applyBatch(operations); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/stub/AbstractServiceStub.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.stub; import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.Handler; import android.os.IBinder; import com.morgoo.helper.Log; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/3/13. */ public abstract class AbstractServiceStub extends Service { private static final String TAG = "AbstractServiceStub"; private static ServcesManager mCreator = ServcesManager.getDefault(); private boolean isRunning = false; @Override public void onCreate() { super.onCreate(); isRunning = true; } @Override public void onDestroy() { try { mCreator.onDestroy(); } catch (Exception e) { handleException(e); } super.onDestroy(); isRunning = false; try { synchronized (sLock) { sLock.notifyAll(); } } catch (Exception e) { } } public static void startKillService(Context context, Intent service) { service.putExtra("ActionKillSelf", true); context.startService(service); } @Override public void onStart(Intent intent, int startId) { try { if (intent != null) { if (intent.getBooleanExtra("ActionKillSelf", false)) { startKillSelf(); if (!ServcesManager.getDefault().hasServiceRunning()) { stopSelf(startId); boolean stopService = getApplication().stopService(intent); Log.i(TAG, "doGc Kill Process(pid=%s,uid=%s has exit) for %s onStart=%s intent=%s", android.os.Process.myPid(), android.os.Process.myUid(), getClass().getSimpleName(), stopService, intent); } else { Log.i(TAG, "doGc Kill Process(pid=%s,uid=%s has exit) for %s onStart intent=%s skip,has service running", android.os.Process.myPid(), android.os.Process.myUid(), getClass().getSimpleName(), intent); } } else { mCreator.onStart(this, intent, 0, startId); } } } catch (Throwable e) { handleException(e); } super.onStart(intent, startId); } private Object sLock = new Object(); private void startKillSelf() { if (isRunning) { try { new Thread() { @Override public void run() { synchronized (sLock) { try { sLock.wait(); } catch (Exception e) { } } Log.i(TAG, "doGc Kill Process(pid=%s,uid=%s has exit) for %s 2", android.os.Process.myPid(), android.os.Process.myUid(), getClass().getSimpleName()); android.os.Process.killProcess(android.os.Process.myPid()); } }.start(); } catch (Exception e) { e.printStackTrace(); } } } private void handleException(Throwable e) { Log.e(TAG, "handleException", e); } @Override public void onTaskRemoved(Intent rootIntent) { try { if (rootIntent != null) { mCreator.onTaskRemoved(this, rootIntent); } } catch (Exception e) { handleException(e); } } @Override public IBinder onBind(Intent intent) { try { if (intent != null) { return mCreator.onBind(this, intent); } } catch (Exception e) { handleException(e); } return null; } @Override public void onRebind(Intent intent) { try { if (intent != null) { mCreator.onRebind(this, intent); } } catch (Exception e) { handleException(e); } super.onRebind(intent); } @Override public boolean onUnbind(Intent intent) { try { if (intent != null) { return mCreator.onUnbind(intent); } } catch (Exception e) { handleException(e); } return false; } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/stub/ActivityStub.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.stub; import android.app.Activity; import android.content.res.Configuration; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/2/9. */ public abstract class ActivityStub extends Activity { private static class SingleInstanceStub extends ActivityStub { } private static class SingleTaskStub extends ActivityStub { } private static class SingleTopStub extends ActivityStub { } private static class StandardStub extends ActivityStub { } //p1 public static class P00{ public static class SingleInstance00 extends SingleInstanceStub { } public static class SingleTask00 extends SingleTaskStub { } public static class SingleTop00 extends SingleTopStub { } public static class SingleInstance01 extends SingleInstanceStub { } public static class SingleTask01 extends SingleTaskStub { } public static class SingleTop01 extends SingleTopStub { } public static class SingleInstance02 extends SingleInstanceStub { } public static class SingleTask02 extends SingleTaskStub { } public static class SingleTop02 extends SingleTopStub { } public static class SingleInstance03 extends SingleInstanceStub { } public static class SingleTask03 extends SingleTaskStub { } public static class SingleTop03 extends SingleTopStub { } public static class Standard00 extends StandardStub { } } //p2 public static class P01{ public static class SingleInstance00 extends SingleInstanceStub { } public static class SingleTask00 extends SingleTaskStub { } public static class SingleTop00 extends SingleTopStub { } public static class SingleInstance01 extends SingleInstanceStub { } public static class SingleTask01 extends SingleTaskStub { } public static class SingleTop01 extends SingleTopStub { } public static class SingleInstance02 extends SingleInstanceStub { } public static class SingleTask02 extends SingleTaskStub { } public static class SingleTop02 extends SingleTopStub { } public static class SingleInstance03 extends SingleInstanceStub { } public static class SingleTask03 extends SingleTaskStub { } public static class SingleTop03 extends SingleTopStub { } public static class Standard00 extends StandardStub { } } //p3 public static class P02{ public static class SingleInstance00 extends SingleInstanceStub { } public static class SingleTask00 extends SingleTaskStub { } public static class SingleTop00 extends SingleTopStub { } public static class SingleInstance01 extends SingleInstanceStub { } public static class SingleTask01 extends SingleTaskStub { } public static class SingleTop01 extends SingleTopStub { } public static class SingleInstance02 extends SingleInstanceStub { } public static class SingleTask02 extends SingleTaskStub { } public static class SingleTop02 extends SingleTopStub { } public static class SingleInstance03 extends SingleInstanceStub { } public static class SingleTask03 extends SingleTaskStub { } public static class SingleTop03 extends SingleTopStub { } public static class Standard00 extends StandardStub { } } //p4 public static class P03{ public static class SingleInstance00 extends SingleInstanceStub { } public static class SingleTask00 extends SingleTaskStub { } public static class SingleTop00 extends SingleTopStub { } public static class SingleInstance01 extends SingleInstanceStub { } public static class SingleTask01 extends SingleTaskStub { } public static class SingleTop01 extends SingleTopStub { } public static class SingleInstance02 extends SingleInstanceStub { } public static class SingleTask02 extends SingleTaskStub { } public static class SingleTop02 extends SingleTopStub { } public static class SingleInstance03 extends SingleInstanceStub { } public static class SingleTask03 extends SingleTaskStub { } public static class SingleTop03 extends SingleTopStub { } public static class Standard00 extends StandardStub { } } //p5 public static class P04{ public static class SingleInstance00 extends SingleInstanceStub { } public static class SingleTask00 extends SingleTaskStub { } public static class SingleTop00 extends SingleTopStub { } public static class SingleInstance01 extends SingleInstanceStub { } public static class SingleTask01 extends SingleTaskStub { } public static class SingleTop01 extends SingleTopStub { } public static class SingleInstance02 extends SingleInstanceStub { } public static class SingleTask02 extends SingleTaskStub { } public static class SingleTop02 extends SingleTopStub { } public static class SingleInstance03 extends SingleInstanceStub { } public static class SingleTask03 extends SingleTaskStub { } public static class SingleTop03 extends SingleTopStub { } public static class Standard00 extends StandardStub { } } //p6 public static class P05{ public static class SingleInstance00 extends SingleInstanceStub { } public static class SingleTask00 extends SingleTaskStub { } public static class SingleTop00 extends SingleTopStub { } public static class SingleInstance01 extends SingleInstanceStub { } public static class SingleTask01 extends SingleTaskStub { } public static class SingleTop01 extends SingleTopStub { } public static class SingleInstance02 extends SingleInstanceStub { } public static class SingleTask02 extends SingleTaskStub { } public static class SingleTop02 extends SingleTopStub { } public static class SingleInstance03 extends SingleInstanceStub { } public static class SingleTask03 extends SingleTaskStub { } public static class SingleTop03 extends SingleTopStub { } public static class Standard00 extends StandardStub { } } //p7 public static class P06{ public static class SingleInstance00 extends SingleInstanceStub { } public static class SingleTask00 extends SingleTaskStub { } public static class SingleTop00 extends SingleTopStub { } public static class SingleInstance01 extends SingleInstanceStub { } public static class SingleTask01 extends SingleTaskStub { } public static class SingleTop01 extends SingleTopStub { } public static class SingleInstance02 extends SingleInstanceStub { } public static class SingleTask02 extends SingleTaskStub { } public static class SingleTop02 extends SingleTopStub { } public static class SingleInstance03 extends SingleInstanceStub { } public static class SingleTask03 extends SingleTaskStub { } public static class SingleTop03 extends SingleTopStub { } public static class Standard00 extends StandardStub { } } //p8 public static class P07{ public static class SingleInstance00 extends SingleInstanceStub { } public static class SingleTask00 extends SingleTaskStub { } public static class SingleTop00 extends SingleTopStub { } public static class SingleInstance01 extends SingleInstanceStub { } public static class SingleTask01 extends SingleTaskStub { } public static class SingleTop01 extends SingleTopStub { } public static class SingleInstance02 extends SingleInstanceStub { } public static class SingleTask02 extends SingleTaskStub { } public static class SingleTop02 extends SingleTopStub { } public static class SingleInstance03 extends SingleInstanceStub { } public static class SingleTask03 extends SingleTaskStub { } public static class SingleTop03 extends SingleTopStub { } public static class Standard00 extends StandardStub { } } //p9 public static class P08{ public static class SingleInstance00 extends SingleInstanceStub { } public static class SingleTask00 extends SingleTaskStub { } public static class SingleTop00 extends SingleTopStub { } public static class SingleInstance01 extends SingleInstanceStub { } public static class SingleTask01 extends SingleTaskStub { } public static class SingleTop01 extends SingleTopStub { } public static class SingleInstance02 extends SingleInstanceStub { } public static class SingleTask02 extends SingleTaskStub { } public static class SingleTop02 extends SingleTopStub { } public static class SingleInstance03 extends SingleInstanceStub { } public static class SingleTask03 extends SingleTaskStub { } public static class SingleTop03 extends SingleTopStub { } public static class Standard00 extends StandardStub { } } public static class Dialog { //p1 public static class P00{ public static class SingleInstance00 extends SingleInstanceStub { } public static class SingleTask00 extends SingleTaskStub { } public static class SingleTop00 extends SingleTopStub { } public static class SingleInstance01 extends SingleInstanceStub { } public static class SingleTask01 extends SingleTaskStub { } public static class SingleTop01 extends SingleTopStub { } public static class SingleInstance02 extends SingleInstanceStub { } public static class SingleTask02 extends SingleTaskStub { } public static class SingleTop02 extends SingleTopStub { } public static class SingleInstance03 extends SingleInstanceStub { } public static class SingleTask03 extends SingleTaskStub { } public static class SingleTop03 extends SingleTopStub { } public static class Standard00 extends StandardStub { } } //p2 public static class P01{ public static class SingleInstance00 extends SingleInstanceStub { } public static class SingleTask00 extends SingleTaskStub { } public static class SingleTop00 extends SingleTopStub { } public static class SingleInstance01 extends SingleInstanceStub { } public static class SingleTask01 extends SingleTaskStub { } public static class SingleTop01 extends SingleTopStub { } public static class SingleInstance02 extends SingleInstanceStub { } public static class SingleTask02 extends SingleTaskStub { } public static class SingleTop02 extends SingleTopStub { } public static class SingleInstance03 extends SingleInstanceStub { } public static class SingleTask03 extends SingleTaskStub { } public static class SingleTop03 extends SingleTopStub { } public static class Standard00 extends StandardStub { } } //p3 public static class P02{ public static class SingleInstance00 extends SingleInstanceStub { } public static class SingleTask00 extends SingleTaskStub { } public static class SingleTop00 extends SingleTopStub { } public static class SingleInstance01 extends SingleInstanceStub { } public static class SingleTask01 extends SingleTaskStub { } public static class SingleTop01 extends SingleTopStub { } public static class SingleInstance02 extends SingleInstanceStub { } public static class SingleTask02 extends SingleTaskStub { } public static class SingleTop02 extends SingleTopStub { } public static class SingleInstance03 extends SingleInstanceStub { } public static class SingleTask03 extends SingleTaskStub { } public static class SingleTop03 extends SingleTopStub { } public static class Standard00 extends StandardStub { } } //p4 public static class P03{ public static class SingleInstance00 extends SingleInstanceStub { } public static class SingleTask00 extends SingleTaskStub { } public static class SingleTop00 extends SingleTopStub { } public static class SingleInstance01 extends SingleInstanceStub { } public static class SingleTask01 extends SingleTaskStub { } public static class SingleTop01 extends SingleTopStub { } public static class SingleInstance02 extends SingleInstanceStub { } public static class SingleTask02 extends SingleTaskStub { } public static class SingleTop02 extends SingleTopStub { } public static class SingleInstance03 extends SingleInstanceStub { } public static class SingleTask03 extends SingleTaskStub { } public static class SingleTop03 extends SingleTopStub { } public static class Standard00 extends StandardStub { } } //p5 public static class P04{ public static class SingleInstance00 extends SingleInstanceStub { } public static class SingleTask00 extends SingleTaskStub { } public static class SingleTop00 extends SingleTopStub { } public static class SingleInstance01 extends SingleInstanceStub { } public static class SingleTask01 extends SingleTaskStub { } public static class SingleTop01 extends SingleTopStub { } public static class SingleInstance02 extends SingleInstanceStub { } public static class SingleTask02 extends SingleTaskStub { } public static class SingleTop02 extends SingleTopStub { } public static class SingleInstance03 extends SingleInstanceStub { } public static class SingleTask03 extends SingleTaskStub { } public static class SingleTop03 extends SingleTopStub { } public static class Standard00 extends StandardStub { } } //p6 public static class P05{ public static class SingleInstance00 extends SingleInstanceStub { } public static class SingleTask00 extends SingleTaskStub { } public static class SingleTop00 extends SingleTopStub { } public static class SingleInstance01 extends SingleInstanceStub { } public static class SingleTask01 extends SingleTaskStub { } public static class SingleTop01 extends SingleTopStub { } public static class SingleInstance02 extends SingleInstanceStub { } public static class SingleTask02 extends SingleTaskStub { } public static class SingleTop02 extends SingleTopStub { } public static class SingleInstance03 extends SingleInstanceStub { } public static class SingleTask03 extends SingleTaskStub { } public static class SingleTop03 extends SingleTopStub { } public static class Standard00 extends StandardStub { } } //p7 public static class P06{ public static class SingleInstance00 extends SingleInstanceStub { } public static class SingleTask00 extends SingleTaskStub { } public static class SingleTop00 extends SingleTopStub { } public static class SingleInstance01 extends SingleInstanceStub { } public static class SingleTask01 extends SingleTaskStub { } public static class SingleTop01 extends SingleTopStub { } public static class SingleInstance02 extends SingleInstanceStub { } public static class SingleTask02 extends SingleTaskStub { } public static class SingleTop02 extends SingleTopStub { } public static class SingleInstance03 extends SingleInstanceStub { } public static class SingleTask03 extends SingleTaskStub { } public static class SingleTop03 extends SingleTopStub { } public static class Standard00 extends StandardStub { } } //p8 public static class P07{ public static class SingleInstance00 extends SingleInstanceStub { } public static class SingleTask00 extends SingleTaskStub { } public static class SingleTop00 extends SingleTopStub { } public static class SingleInstance01 extends SingleInstanceStub { } public static class SingleTask01 extends SingleTaskStub { } public static class SingleTop01 extends SingleTopStub { } public static class SingleInstance02 extends SingleInstanceStub { } public static class SingleTask02 extends SingleTaskStub { } public static class SingleTop02 extends SingleTopStub { } public static class SingleInstance03 extends SingleInstanceStub { } public static class SingleTask03 extends SingleTaskStub { } public static class SingleTop03 extends SingleTopStub { } public static class Standard00 extends StandardStub { } } //p9 public static class P08{ public static class SingleInstance00 extends SingleInstanceStub { } public static class SingleTask00 extends SingleTaskStub { } public static class SingleTop00 extends SingleTopStub { } public static class SingleInstance01 extends SingleInstanceStub { } public static class SingleTask01 extends SingleTaskStub { } public static class SingleTop01 extends SingleTopStub { } public static class SingleInstance02 extends SingleInstanceStub { } public static class SingleTask02 extends SingleTaskStub { } public static class SingleTop02 extends SingleTopStub { } public static class SingleInstance03 extends SingleInstanceStub { } public static class SingleTask03 extends SingleTaskStub { } public static class SingleTop03 extends SingleTopStub { } public static class Standard00 extends StandardStub { } } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/stub/ContentProviderStub.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.stub; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/2/12. */ public abstract class ContentProviderStub extends AbstractContentProviderStub { public static class StubP00 extends ContentProviderStub { } public static class StubP01 extends ContentProviderStub { } public static class StubP02 extends ContentProviderStub { } public static class StubP03 extends ContentProviderStub { } public static class StubP04 extends ContentProviderStub { } public static class StubP05 extends ContentProviderStub { } public static class StubP06 extends ContentProviderStub { } public static class StubP07 extends ContentProviderStub { } public static class StubP08 extends ContentProviderStub { } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/stub/MyFakeIBinder.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.stub; import android.os.IBinder; import android.os.IInterface; import android.os.Parcel; import android.os.RemoteException; import java.io.FileDescriptor; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/3/4. */ public class MyFakeIBinder implements IBinder { @Override public String getInterfaceDescriptor() throws RemoteException { return null; } @Override public boolean pingBinder() { return false; } @Override public boolean isBinderAlive() { return false; } @Override public IInterface queryLocalInterface(String s) { return null; } @Override public void dump(FileDescriptor fileDescriptor, String[] strings) throws RemoteException { } @Override public void dumpAsync(FileDescriptor fileDescriptor, String[] strings) throws RemoteException { } @Override public boolean transact(int i, Parcel parcel, Parcel parcel1, int i1) throws RemoteException { return false; } @Override public void linkToDeath(DeathRecipient deathRecipient, int i) throws RemoteException { } @Override public boolean unlinkToDeath(DeathRecipient deathRecipient, int i) { return false; } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/stub/ServcesManager.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.stub; import android.app.Service; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.os.IBinder; import com.morgoo.droidplugin.core.Env; import com.morgoo.droidplugin.core.PluginProcessManager; import com.morgoo.droidplugin.pm.PluginManager; import com.morgoo.droidplugin.reflect.FieldUtils; import com.morgoo.droidplugin.reflect.MethodUtils; import com.morgoo.helper.compat.ActivityThreadCompat; import com.morgoo.helper.compat.CompatibilityInfoCompat; import com.morgoo.helper.compat.QueuedWorkCompat; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/2/9. */ public class ServcesManager { private Map mTokenServices = new HashMap(); private Map mNameService = new HashMap(); private Map mServiceTaskIds = new HashMap(); private ServcesManager() { } private static ServcesManager sServcesManager; public static ServcesManager getDefault() { synchronized (ServcesManager.class) { if (sServcesManager == null) { sServcesManager = new ServcesManager(); } } return sServcesManager; } public boolean hasServiceRunning() { return mTokenServices.size() > 0 && mNameService.size() > 0; } private Object findTokenByService(Service service) { for (Object s : mTokenServices.keySet()) { if (mTokenServices.get(s) == service) { return s; } } return null; } private ClassLoader getClassLoader(ApplicationInfo pluginApplicationInfo) throws Exception { Object object = ActivityThreadCompat.currentActivityThread(); if (object != null) { final Object obj; if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { obj = MethodUtils.invokeMethod(object, "getPackageInfoNoCheck", pluginApplicationInfo, CompatibilityInfoCompat.DEFAULT_COMPATIBILITY_INFO()); } else { obj = MethodUtils.invokeMethod(object, "getPackageInfoNoCheck", pluginApplicationInfo); } /*添加ClassLoader LoadedApk.mClassLoader*/ return (ClassLoader) MethodUtils.invokeMethod(obj, "getClassLoader"); } return null; } //这个需要适配,目前只是适配android api 21 private void handleCreateServiceOne(Context hostContext, Intent stubIntent, ServiceInfo info) throws Exception { // CreateServiceData data = new CreateServiceData(); // data.token = fakeToken;// IBinder // data.info =; //ServiceInfo // data.compatInfo =; //CompatibilityInfo // data.intent =; //Intent // activityThread.handleCreateServiceOne(data); // service = activityThread.mTokenServices.get(fakeToken); // activityThread.mTokenServices.remove(fakeToken); ResolveInfo resolveInfo = hostContext.getPackageManager().resolveService(stubIntent, 0); ServiceInfo stubInfo = resolveInfo != null ? resolveInfo.serviceInfo : null; PluginManager.getInstance().reportMyProcessName(stubInfo.processName, info.processName, info.packageName); PluginProcessManager.preLoadApk(hostContext, info); Object activityThread = ActivityThreadCompat.currentActivityThread(); IBinder fakeToken = new MyFakeIBinder(); Class CreateServiceData = Class.forName(ActivityThreadCompat.activityThreadClass().getName() + "$CreateServiceData"); Constructor init = CreateServiceData.getDeclaredConstructor(); if (!init.isAccessible()) { init.setAccessible(true); } Object data = init.newInstance(); FieldUtils.writeField(data, "token", fakeToken); FieldUtils.writeField(data, "info", info); if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { FieldUtils.writeField(data, "compatInfo", CompatibilityInfoCompat.DEFAULT_COMPATIBILITY_INFO()); } Method method = activityThread.getClass().getDeclaredMethod("handleCreateService", CreateServiceData); if (!method.isAccessible()) { method.setAccessible(true); } method.invoke(activityThread, data); Object mService = FieldUtils.readField(activityThread, "mServices"); Service service = (Service) MethodUtils.invokeMethod(mService, "get", fakeToken); MethodUtils.invokeMethod(mService, "remove", fakeToken); mTokenServices.put(fakeToken, service); mNameService.put(info.name, service); if (stubInfo != null) { PluginManager.getInstance().onServiceCreated(stubInfo, info); } } private void handleOnStartOne(Intent intent, int flags, int startIds) throws Exception { ServiceInfo info = PluginManager.getInstance().resolveServiceInfo(intent, 0); if (info != null) { Service service = mNameService.get(info.name); if (service != null) { ClassLoader classLoader = getClassLoader(info.applicationInfo); intent.setExtrasClassLoader(classLoader); Object token = findTokenByService(service); Integer integer = mServiceTaskIds.get(token); if (integer == null) { integer = -1; } int startId = integer + 1; mServiceTaskIds.put(token, startId); int res = service.onStartCommand(intent, flags, startId); QueuedWorkCompat.waitToFinish(); } } } private void handleOnTaskRemovedOne(Intent intent) throws Exception { if (VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH) { ServiceInfo info = PluginManager.getInstance().resolveServiceInfo(intent, 0); if (info != null) { Service service = mNameService.get(info.name); if (service != null) { ClassLoader classLoader = getClassLoader(info.applicationInfo); intent.setExtrasClassLoader(classLoader); service.onTaskRemoved(intent); QueuedWorkCompat.waitToFinish(); } QueuedWorkCompat.waitToFinish(); } } } private void handleOnDestroyOne(ServiceInfo targetInfo) { Service service = mNameService.get(targetInfo.name); if (service != null) { service.onDestroy(); mNameService.remove(targetInfo.name); Object token = findTokenByService(service); mTokenServices.remove(token); mServiceTaskIds.remove(token); service = null; QueuedWorkCompat.waitToFinish(); PluginManager.getInstance().onServiceDestory(null, targetInfo); } QueuedWorkCompat.waitToFinish(); } private IBinder handleOnBindOne(Intent intent) throws Exception { ServiceInfo info = PluginManager.getInstance().resolveServiceInfo(intent, 0); if (info != null) { Service service = mNameService.get(info.name); if (service != null) { ClassLoader classLoader = getClassLoader(info.applicationInfo); intent.setExtrasClassLoader(classLoader); return service.onBind(intent); } } return null; } private void handleOnRebindOne(Intent intent) throws Exception { ServiceInfo info = PluginManager.getInstance().resolveServiceInfo(intent, 0); if (info != null) { Service service = mNameService.get(info.name); if (service != null) { ClassLoader classLoader = getClassLoader(info.applicationInfo); intent.setExtrasClassLoader(classLoader); service.onRebind(intent); } } } private boolean handleOnUnbindOne(Intent intent) throws Exception { ServiceInfo info = PluginManager.getInstance().resolveServiceInfo(intent, 0); if (info != null) { Service service = mNameService.get(info.name); if (service != null) { ClassLoader classLoader = getClassLoader(info.applicationInfo); intent.setExtrasClassLoader(classLoader); return service.onUnbind(intent); } } return false; } public int onStart(Context context, Intent intent, int flags, int startId) throws Exception { Intent targetIntent = intent.getParcelableExtra(Env.EXTRA_TARGET_INTENT); if (targetIntent != null) { ServiceInfo targetInfo = PluginManager.getInstance().resolveServiceInfo(targetIntent, 0); if (targetInfo != null) { Service service = mNameService.get(targetInfo.name); if (service == null) { handleCreateServiceOne(context, intent, targetInfo); } handleOnStartOne(targetIntent, flags, startId); } } return -1; } public void onTaskRemoved(Context context, Intent intent) throws Exception { Intent targetIntent = intent.getParcelableExtra(Env.EXTRA_TARGET_INTENT); if (targetIntent != null) { ServiceInfo info = PluginManager.getInstance().resolveServiceInfo(targetIntent, 0); Service service = mNameService.get(info.name); if (service == null) { handleCreateServiceOne(context, intent, info); } handleOnTaskRemovedOne(targetIntent); } } public IBinder onBind(Context context, Intent intent) throws Exception { Intent targetIntent = intent.getParcelableExtra(Env.EXTRA_TARGET_INTENT); if (targetIntent != null) { ServiceInfo info = PluginManager.getInstance().resolveServiceInfo(targetIntent, 0); Service service = mNameService.get(info.name); if (service == null) { handleCreateServiceOne(context, intent, info); } return handleOnBindOne(targetIntent); } return null; } public void onRebind(Context context, Intent intent) throws Exception { Intent targetIntent = intent.getParcelableExtra(Env.EXTRA_TARGET_INTENT); if (targetIntent != null) { ServiceInfo info = PluginManager.getInstance().resolveServiceInfo(targetIntent, 0); Service service = mNameService.get(info.name); if (service == null) { handleCreateServiceOne(context, intent, info); } handleOnRebindOne(targetIntent); } } public boolean onUnbind(Intent intent) throws Exception { Intent targetIntent = intent.getParcelableExtra(Env.EXTRA_TARGET_INTENT); if (targetIntent != null) { ServiceInfo info = PluginManager.getInstance().resolveServiceInfo(targetIntent, 0); Service service = mNameService.get(info.name); if (service != null) { return handleOnUnbindOne(targetIntent); } } return false; } public int stopService(Context context, Intent intent) throws Exception { ServiceInfo targetInfo = PluginManager.getInstance().resolveServiceInfo(intent, 0); if (targetInfo != null) { handleOnUnbindOne(intent); handleOnDestroyOne(targetInfo); return 1; } return 0; } public boolean stopServiceToken(ComponentName cn, IBinder token, int startId) throws Exception { Service service = mTokenServices.get(token); if (service != null) { Integer lastId = mServiceTaskIds.get(token); if (lastId == null) { return false; } if (startId != lastId) { return false; } Intent intent = new Intent(); intent.setComponent(cn); ServiceInfo info = PluginManager.getInstance().resolveServiceInfo(intent, 0); if (info != null) { handleOnUnbindOne(intent); handleOnDestroyOne(info); return true; } } return false; } public void onDestroy() { for (Service service : mTokenServices.values()) { service.onDestroy(); } mTokenServices.clear(); mServiceTaskIds.clear(); mNameService.clear(); QueuedWorkCompat.waitToFinish(); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/stub/ServiceStub.java ================================================ /* ** copyright (c) 2015 Andy Zhang(Zhangyong232@gmail.com) ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.stub; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/2/9. */ public abstract class ServiceStub extends AbstractServiceStub { public abstract static class StubP00 extends ServiceStub { public static class P00 extends StubP00 { } } public abstract static class StubP01 extends ServiceStub { public static class P00 extends StubP01 { } } public abstract static class StubP02 extends ServiceStub { public static class P00 extends StubP01 { } } public abstract static class StubP03 extends ServiceStub { public static class P00 extends StubP01 { } } public abstract static class StubP04 extends ServiceStub { public static class P00 extends StubP01 { } } public abstract static class StubP05 extends ServiceStub { public static class P00 extends StubP01 { } } public abstract static class StubP06 extends ServiceStub { public static class P00 extends StubP01 { } } public abstract static class StubP07 extends ServiceStub { public static class P00 extends StubP01 { } } public abstract static class StubP08 extends ServiceStub { public static class P00 extends StubP01 { } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/droidplugin/stub/ShortcutProxyActivity.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.droidplugin.stub; import android.app.Activity; import android.content.Intent; import android.content.pm.ResolveInfo; import android.os.Build; import android.os.Bundle; import com.morgoo.droidplugin.core.Env; import com.morgoo.droidplugin.pm.PluginManager; import java.net.URISyntaxException; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/5/27. */ public class ShortcutProxyActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { try { super.onCreate(savedInstanceState); Intent intent = getIntent(); if (intent != null) { Intent forwordIntent = getForwarIntent(); if (forwordIntent != null) { forwordIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); forwordIntent.putExtras(intent); //安全审核问题 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { forwordIntent.setSelector(null); } if (PluginManager.getInstance().isConnected()) { if (isPlugin(forwordIntent)) { execStartForwordIntent(forwordIntent); } finish(); } else { waitAndStart(forwordIntent); } } else { finish(); } } else { finish(); } } catch (Exception e) { e.printStackTrace(); finish(); } } protected void execStartForwordIntent(Intent forwordIntent) { startActivity(forwordIntent); } private boolean isPlugin(Intent intent) { try { String pkg = null; if (intent.getComponent() != null && intent.getComponent().getPackageName() != null) { pkg = intent.getComponent().getPackageName(); } else { ResolveInfo info = PluginManager.getInstance().resolveIntent(intent, null, 0); pkg = info.resolvePackageName; } return pkg != null && PluginManager.getInstance().isPluginPackage(pkg); } catch (Exception e) { return false; } } private void waitAndStart(final Intent forwordIntent) { new Thread() { @Override public void run() { try { PluginManager.getInstance().waitForConnected(); if (isPlugin(forwordIntent)) { execStartForwordIntent(forwordIntent); } } catch (Exception e) { e.printStackTrace(); } finally { finish(); } } }.start(); } private Intent getForwarIntent() { Intent intent = getIntent(); try { if (intent != null) { Intent forwordIntent = intent.getParcelableExtra(Env.EXTRA_TARGET_INTENT); String intentUri = intent.getStringExtra(Env.EXTRA_TARGET_INTENT_URI); if (intentUri != null) { try { Intent res = Intent.parseUri(intentUri, 0); return res; } catch (URISyntaxException e) { } } else if (forwordIntent != null) { return forwordIntent; } } } catch (Exception e) { } return null; } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/AttributeCache.java ================================================ /* ** ** Copyright 2007, The Android Open Source Project ** ** Licensed under the Apache License, Version 2.0 (the "License"); ** you may not use this file except in compliance with the License. ** You may obtain a copy of the License at ** ** http://www.apache.org/licenses/LICENSE-2.0 ** ** Unless required by applicable law or agreed to in writing, software ** distributed under the License is distributed on an "AS IS" BASIS, ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ** See the License for the specific language governing permissions and ** limitations under the License. */ package com.morgoo.helper; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.os.UserHandle; import android.util.SparseArray; import java.util.HashMap; import java.util.WeakHashMap; /** * TODO: This should be better integrated into the system so it doesn't need * special calls from the activity manager to clear it. */ public final class AttributeCache { private static AttributeCache sInstance = null; private final Context mContext; private final WeakHashMap mPackages = new WeakHashMap(); private final Configuration mConfiguration = new Configuration(); public final static class Package { public final Context context; private final SparseArray> mMap = new SparseArray>(); public Package(Context c) { context = c; } } public final static class Entry { public final Context context; public final TypedArray array; public Entry(Context c, TypedArray ta) { context = c; array = ta; } } public static void init(Context context) { if (sInstance == null) { sInstance = new AttributeCache(context); } } public static AttributeCache instance() { return sInstance; } public AttributeCache(Context context) { mContext = context; } public void removePackage(String packageName) { synchronized (this) { mPackages.remove(packageName); } } public void updateConfiguration(Configuration config) { synchronized (this) { int changes = mConfiguration.updateFrom(config); if ((changes & ~(ActivityInfo.CONFIG_FONT_SCALE | ActivityInfo.CONFIG_KEYBOARD_HIDDEN | ActivityInfo.CONFIG_ORIENTATION)) != 0) { // The configurations being masked out are ones that commonly // change so we don't want flushing the cache... all others // will flush the cache. mPackages.clear(); } } } public Entry get(String packageName, int resId, int[] styleable) { synchronized (this) { Package pkg = mPackages.get(packageName); HashMap map = null; Entry ent = null; if (pkg != null) { map = pkg.mMap.get(resId); if (map != null) { ent = map.get(styleable); if (ent != null) { return ent; } } } else { Context context; try { context = mContext.createPackageContext(packageName, 0); if (context == null) { return null; } } catch (PackageManager.NameNotFoundException e) { return null; } pkg = new Package(context); mPackages.put(packageName, pkg); } if (map == null) { map = new HashMap(); pkg.mMap.put(resId, map); } try { ent = new Entry(pkg.context, pkg.context.obtainStyledAttributes(resId, styleable)); map.put(styleable, ent); } catch (Resources.NotFoundException e) { return null; } return ent; } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/ComponentNameComparator.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.helper; import android.content.ComponentName; import android.text.TextUtils; import java.util.Comparator; public class ComponentNameComparator implements Comparator { @Override public int compare(ComponentName lhs, ComponentName rhs) { if (lhs == null && rhs == null) { return 0; } else if (lhs != null && rhs == null) { return 1; } else if (lhs == null && rhs != null) { return -1; } else { if (TextUtils.equals(lhs.getPackageName(), rhs.getPackageName()) && TextUtils.equals(lhs.getShortClassName(), rhs.getShortClassName())) { return 0; } else { return lhs.compareTo(rhs); } } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/Log.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.helper; import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; import android.os.Process; import com.morgoo.droidplugin.hook.HookFactory; import com.morgoo.droidplugin.hook.proxy.LibCoreHook; import java.io.File; import java.io.FileWriter; import java.io.PrintWriter; import java.text.SimpleDateFormat; import java.util.Date; /** * Created by zhangyong on 14/10/18. */ public class Log { private static final String TAG = "Log"; private static final int VERBOSE = android.util.Log.VERBOSE; private static final int DEBUG = android.util.Log.DEBUG; private static final int INFO = android.util.Log.INFO; private static final int WARN = android.util.Log.WARN; private static final int ERROR = android.util.Log.ERROR; private static final int ASSERT = android.util.Log.ASSERT; private static final long MAX_LOG_FILE = 1024 * 1024 * 8; //8MB private static boolean sDebug = true; private static boolean sFileLog = false; private static final SimpleDateFormat sFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); private static final SimpleDateFormat sFormat1 = new SimpleDateFormat("yyyyMMdd"); private Log() { } private static final File sDir = new File(Environment.getExternalStorageDirectory(), "360Log/Plugin/"); static { sFileLog = sDir.exists() && sDir.isDirectory(); } public static boolean isDebug() { return sDebug; } private static boolean isFileLog() { return sFileLog; } public static boolean isLoggable(int i) { return isDebug(); } public static boolean isLoggable() { return isDebug(); } private static String levelToStr(int level) { switch (level) { case VERBOSE: return "V"; case DEBUG: return "D"; case INFO: return "I"; case WARN: return "W"; case ERROR: return "E"; case ASSERT: return "A"; default: return "UNKNOWN"; } } private static File getLogFile() { File file = new File(Environment.getExternalStorageDirectory(), String.format("360Log/Plugin/Log_%s_%s.log", sFormat1.format(new Date()), android.os.Process.myPid())); File dir = file.getParentFile(); if (!dir.exists()) { dir.mkdirs(); } return file; } private static HandlerThread sHandlerThread; private static Handler sHandler; static { sHandlerThread = new HandlerThread("DroidPlugin@FileLogThread"); sHandlerThread.start(); sHandler = new Handler(sHandlerThread.getLooper()); } private static void logToFile(final int level, final String tag, final String format, final Object[] args, final Throwable tr) { sHandler.post(new Runnable() { @Override public void run() { logToFileInner(level, tag, format, args, tr); } }); } private static void logToFileInner(int level, String tag, String format, Object[] args, Throwable tr) { PrintWriter writer = null; try { if (!isFileLog()) { return; } //禁用LibCoreHook,防止方法循环调用。 HookFactory.getInstance().setHookEnable(LibCoreHook.class, false); File logFile = getLogFile(); if (logFile.length() > MAX_LOG_FILE) { logFile.delete(); } writer = new PrintWriter(new FileWriter(logFile, true)); String msg = String.format(format, args); String log = String.format("%s %s-%s/%s %s/%s %s", sFormat.format(new Date()), Process.myPid(), Process.myUid(), getProcessName(), levelToStr(level), tag, msg); writer.println(log); if (tr != null) { tr.printStackTrace(writer); writer.println(); } } catch (Throwable e) { e.printStackTrace(); } finally { if (writer != null) { try { writer.close(); } catch (Throwable e) { } } HookFactory.getInstance().setHookEnable(LibCoreHook.class, true); } } private static String getProcessName() { return "?"; } private static void println(final int level, final String tag, final String format, final Object[] args, final Throwable tr) { logToFile(level, tag, format, args, tr); String message; if (args != null && args.length > 0) { message = String.format(format, args); } else { message = format; } if (tr != null) { message += android.util.Log.getStackTraceString(tr); } android.util.Log.println(level, tag, message); } public static void v(String tag, String format, Object... args) { v(tag, format, null, args); } public static void v(String tag, String format, Throwable tr, Object... args) { if (!isLoggable(VERBOSE)) { return; } println(VERBOSE, tag, format, args, tr); } public static void d(String tag, String format, Object... args) { d(tag, format, null, args); } public static void d(String tag, String format, Throwable tr, Object... args) { if (!isLoggable(DEBUG)) { return; } println(DEBUG, tag, format, args, tr); } public static void i(String tag, String format, Object... args) { i(tag, format, null, args); } public static void i(String tag, String format, Throwable tr, Object... args) { if (!isLoggable(INFO)) { return; } println(INFO, tag, format, args, tr); } public static void w(String tag, String format, Object... args) { w(tag, format, null, args); } public static void w(String tag, String format, Throwable tr, Object... args) { if (!isLoggable(WARN)) { return; } println(WARN, tag, format, args, tr); } public static void w(String tag, Throwable tr) { w(tag, "Log.warn", tr); } public static void e(String tag, String format, Object... args) { e(tag, format, null, args); } public static void e(String tag, String format, Throwable tr, Object... args) { if (!isLoggable(ERROR)) { return; } println(ERROR, tag, format, args, tr); } public static void wtf(String tag, String format, Object... args) { wtf(tag, format, null, args); } public static void wtf(String tag, Throwable tr) { wtf(tag, "wtf", tr); } public static void wtf(String tag, String format, Throwable tr, Object... args) { if (!isLoggable()) { return; } println(ASSERT, tag, format, args, tr); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/MyProxy.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.helper; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.net.SocketException; /** * Created by Andy Zhang(zhangyong232@gmail.com)ClassUtils on 2015/3/25. */ public class MyProxy { public static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler invocationHandler) { return Proxy.newProxyInstance(loader, interfaces, invocationHandler); } /** * 判断某个异常是否已经在某个方法上声明了。 */ public static boolean isMethodDeclaredThrowable(Method method, Throwable e) { if (e instanceof RuntimeException) { return true; } if (method == null || e == null) { return false; } Class[] es = method.getExceptionTypes(); if (es == null && es.length <= 0) { return false; } //bugfix,这个问题我也不知道为什么出现,先这么处理吧。 // java.lang.RuntimeException: Socket closed // at com.morgoo.droidplugin.c.c.i.invoke(Unknown Source) // at $Proxy9.accept(Native Method) // at java.net.PlainSocketImpl.accept(PlainSocketImpl.java:98) // at java.net.ServerSocket.implAccept(ServerSocket.java:202) // at java.net.ServerSocket.accept(ServerSocket.java:127) // at com.qihoo.appstore.h.b.run(Unknown Source) // at java.lang.Thread.run(Thread.java:864) // Caused by: java.net.SocketException: Socket closed // at libcore.io.Posix.accept(Native Method) // at libcore.io.BlockGuardOs.accept(BlockGuardOs.java:55) // at java.lang.reflect.Method.invokeNative(Native Method) // at java.lang.reflect.Method.invoke(Method.java:511) // ... 7 more try { String methodName = method.getName(); boolean va = "accept".equals(methodName) || "sendto".equals(methodName); if (e instanceof SocketException && va && method.getDeclaringClass().getName().indexOf("libcore") >= 0) { return true; } } catch (Throwable e1) { //DO NOTHING } for (Class aClass : es) { if (aClass.isInstance(e) || aClass.isAssignableFrom(e.getClass())) { return true; } } return false; } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/Utils.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.helper; import android.app.ActivityManager; import android.content.Context; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.List; import java.util.regex.Pattern; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/2/26. */ public class Utils { private static final String TAG = Utils.class.getSimpleName(); private static final String VALID_JAVA_IDENTIFIER = "(\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*\\.)*\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*"; private static final Pattern ANDROID_DATA_PATTERN = Pattern.compile(VALID_JAVA_IDENTIFIER); public static boolean validateJavaIdentifier(String identifier) { return ANDROID_DATA_PATTERN.matcher(identifier).matches(); } public static void copyFile(String src, String dst) throws IOException { BufferedInputStream in = null; BufferedOutputStream ou = null; try { in = new BufferedInputStream(new FileInputStream(src)); ou = new BufferedOutputStream(new FileOutputStream(dst)); byte[] buffer = new byte[8192]; int read = 0; while ((read = in.read(buffer)) != -1) { ou.write(buffer, 0, read); } } finally { if (in != null) { try { in.close(); } catch (Exception e) { } } if (ou != null) { try { ou.close(); } catch (Exception e) { } } } } public static void deleteDir(String file) { deleteFile(new File(file)); } private static void deleteFile(File file) { if (file.isDirectory()) { File[] files = file.listFiles(); for (int i = 0; i < files.length; i++) { deleteFile(files[i]); } } file.delete(); } public static void writeToFile(File file, byte[] data) throws IOException { FileOutputStream fou = null; try { fou = new FileOutputStream(file); fou.write(data); } finally { if (fou != null) { try { fou.close(); } catch (IOException e) { } } } } public static byte[] readFromFile(File file) throws IOException { FileInputStream fin = null; try { ByteArrayOutputStream out = new ByteArrayOutputStream(); fin = new FileInputStream(file); byte[] buffer = new byte[8192]; int read = 0; while ((read = fin.read(buffer)) != -1) { out.write(buffer, 0, read); } byte[] data = out.toByteArray(); out.close(); return data; } finally { if (fin != null) { try { fin.close(); } catch (IOException e) { } } } } public static String md5(byte[] data) { try { MessageDigest md = MessageDigest.getInstance(ALGORITHM); byte[] digest = md.digest(data); return toHex(digest); } catch (NoSuchAlgorithmException e) { Log.e(TAG, "Md5 Fail"); } return null; } private static String toHex(byte[] b) { StringBuilder builder = new StringBuilder(); for (int i = 0; i < b.length; i++) { int v = b[i]; builder.append(HEX[(0xF0 & v) >> 4]); builder.append(HEX[0x0F & v]); } return builder.toString(); } private static final char[] HEX = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; private static final String ALGORITHM = "MD5"; public static String getProcessName(Context context, int pid) { ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); List raps = am.getRunningAppProcesses(); for (ActivityManager.RunningAppProcessInfo rap : raps) { if (rap != null && rap.pid == pid) { return rap.processName; } } return null; } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/compat/ActivityManagerCompat.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.helper.compat; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/4/13. */ public class ActivityManagerCompat { public static final int INTENT_SENDER_SERVICE = 4; public static final int INTENT_SENDER_ACTIVITY = 2; } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/compat/ActivityManagerNativeCompat.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.helper.compat; import com.morgoo.droidplugin.reflect.MethodUtils; import java.lang.reflect.InvocationTargetException; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/5/1. */ public class ActivityManagerNativeCompat { private static Class sClass; public static Class Class() throws ClassNotFoundException { if (sClass == null) { sClass = Class.forName("android.app.ActivityManagerNative"); } return sClass; } public static Object getDefault() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { return MethodUtils.invokeStaticMethod(Class(), "getDefault"); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/compat/ActivityThreadCompat.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.helper.compat; import android.app.Instrumentation; import android.os.Handler; import android.os.Looper; import com.morgoo.droidplugin.reflect.MethodUtils; import java.lang.reflect.InvocationTargetException; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/3/14. */ public class ActivityThreadCompat { private static Object sActivityThread; private static Class sClass = null; public synchronized static final Object currentActivityThread() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { if (sActivityThread == null) { sActivityThread = MethodUtils.invokeStaticMethod(activityThreadClass(), "currentActivityThread"); if (sActivityThread == null) { sActivityThread = currentActivityThread2(); } } return sActivityThread; } public static final Class activityThreadClass() throws ClassNotFoundException { if (sClass == null) { sClass = Class.forName("android.app.ActivityThread"); } return sClass; } private static Object currentActivityThread2() { Handler handler = new Handler(Looper.getMainLooper()); final Object sLock = new Object(); handler.post(new Runnable() { @Override public void run() { try { sActivityThread = MethodUtils.invokeStaticMethod(activityThreadClass(), "currentActivityThread"); } catch (Exception e) { e.printStackTrace(); } finally { synchronized (sLock) { sLock.notify(); } } } }); if (sActivityThread == null && Looper.getMainLooper() != Looper.myLooper()) { synchronized (sLock) { try { sLock.wait(300); } catch (InterruptedException e) { } } } return null; } public static Instrumentation getInstrumentation() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, ClassNotFoundException { Object obj = currentActivityThread(); return (Instrumentation) MethodUtils.invokeMethod(obj, "getInstrumentation"); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/compat/BuildCompat.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.helper.compat; import android.os.Build; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2016/2/4. */ public class BuildCompat { public final static String[] SUPPORTED_ABIS; public final static String[] SUPPORTED_32_BIT_ABIS; public static final String[] SUPPORTED_64_BIT_ABIS; static { //init SUPPORTED_ABIS if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (Build.SUPPORTED_ABIS != null) { SUPPORTED_ABIS = new String[Build.SUPPORTED_ABIS.length]; System.arraycopy(Build.SUPPORTED_ABIS, 0, SUPPORTED_ABIS, 0, SUPPORTED_ABIS.length); } else { SUPPORTED_ABIS = new String[]{Build.CPU_ABI, Build.CPU_ABI2}; } } else { SUPPORTED_ABIS = new String[]{Build.CPU_ABI, Build.CPU_ABI2}; } //init SUPPORTED_32_BIT_ABIS if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (Build.SUPPORTED_32_BIT_ABIS != null) { SUPPORTED_32_BIT_ABIS = new String[Build.SUPPORTED_32_BIT_ABIS.length]; System.arraycopy(Build.SUPPORTED_32_BIT_ABIS, 0, SUPPORTED_32_BIT_ABIS, 0, SUPPORTED_32_BIT_ABIS.length); } else { SUPPORTED_32_BIT_ABIS = new String[]{Build.CPU_ABI, Build.CPU_ABI2}; } } else { SUPPORTED_32_BIT_ABIS = new String[]{Build.CPU_ABI, Build.CPU_ABI2}; } //init SUPPORTED_64_BIT_ABIS if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (Build.SUPPORTED_64_BIT_ABIS != null) { SUPPORTED_64_BIT_ABIS = new String[Build.SUPPORTED_64_BIT_ABIS.length]; System.arraycopy(Build.SUPPORTED_64_BIT_ABIS, 0, SUPPORTED_64_BIT_ABIS, 0, SUPPORTED_64_BIT_ABIS.length); } else { SUPPORTED_64_BIT_ABIS = new String[]{Build.CPU_ABI, Build.CPU_ABI2}; } } else { SUPPORTED_64_BIT_ABIS = new String[]{Build.CPU_ABI, Build.CPU_ABI2}; } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/compat/BundleCompat.java ================================================ package com.morgoo.helper.compat; import android.os.Build; import android.os.Bundle; import android.os.IBinder; import java.lang.reflect.Method; /** * @author Lody */ public class BundleCompat { static Method getIBinder, putIBinder; static { if (Build.VERSION.SDK_INT < 18) { try { getIBinder = Bundle.class.getDeclaredMethod("getIBinder", String.class); } catch (NoSuchMethodException e) { //ignore } try { putIBinder = Bundle.class.getDeclaredMethod("getIBinder", String.class, IBinder.class); } catch (NoSuchMethodException e) { //ignore } } } public static IBinder getBinder(Bundle bundle, String key) { if (Build.VERSION.SDK_INT >= 18) { return bundle.getBinder(key); } else { if(getIBinder != null) { try { return (IBinder) getIBinder.invoke(bundle, key); } catch (Exception e) { e.printStackTrace(); } } return null; } } public static void putBinder(Bundle bundle, String key, IBinder value) { if (Build.VERSION.SDK_INT >= 18) { bundle.putBinder(key, value); } else { if(putIBinder != null) { try { putIBinder.invoke(bundle, key, value); } catch (Exception e) { e.printStackTrace(); } } } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/compat/CompatibilityInfoCompat.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.helper.compat; import com.morgoo.droidplugin.reflect.FieldUtils; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/5/1. */ public class CompatibilityInfoCompat { private static Class sClass; private static Class getMyClass() throws ClassNotFoundException { if (sClass == null) { sClass = Class.forName("android.content.res.CompatibilityInfo"); } return sClass; } private static Object sDefaultCompatibilityInfo; public static Object DEFAULT_COMPATIBILITY_INFO() throws IllegalAccessException, ClassNotFoundException { if (sDefaultCompatibilityInfo==null) { sDefaultCompatibilityInfo = FieldUtils.readStaticField(getMyClass(), "DEFAULT_COMPATIBILITY_INFO"); } return sDefaultCompatibilityInfo; } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/compat/ContentProviderCompat.java ================================================ package com.morgoo.helper.compat; import android.content.ContentProviderClient; import android.content.Context; import android.net.Uri; import android.os.Build; import android.os.Build.VERSION; import android.os.Bundle; import android.os.RemoteException; import android.os.SystemClock; /** * @author Lody */ public class ContentProviderCompat { public static Bundle call(Context context, Uri uri, String method, String arg, Bundle extras) { if (VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) { return context.getContentResolver().call(uri, method, arg, extras); } ContentProviderClient client = crazyAcquireContentProvider(context, uri); Bundle res = null; try { res = client.call(method, arg, extras); } catch (RemoteException e) { e.printStackTrace(); } finally { releaseQuietly(client); } return res; } private static ContentProviderClient acquireContentProviderClient(Context context, Uri uri) { if (VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { return context.getContentResolver().acquireUnstableContentProviderClient(uri); } return context.getContentResolver().acquireContentProviderClient(uri); } public static ContentProviderClient crazyAcquireContentProvider(Context context, Uri uri) { ContentProviderClient client = acquireContentProviderClient(context, uri); if (client == null) { int retry = 0; while (retry < 5 && client == null) { SystemClock.sleep(100); retry++; client = acquireContentProviderClient(context, uri); } } return client; } public static ContentProviderClient crazyAcquireContentProvider(Context context, String name) { ContentProviderClient client = acquireContentProviderClient(context, name); if (client == null) { int retry = 0; while (retry < 5 && client == null) { SystemClock.sleep(100); retry++; client = acquireContentProviderClient(context, name); } } return client; } private static ContentProviderClient acquireContentProviderClient(Context context, String name) { if (VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { return context.getContentResolver().acquireUnstableContentProviderClient(name); } return context.getContentResolver().acquireContentProviderClient(name); } public static void releaseQuietly(ContentProviderClient client) { if (client != null) { try { if (VERSION.SDK_INT >= Build.VERSION_CODES.N) { client.close(); } else { client.release(); } } catch (Exception ignored) { } } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/compat/ContentProviderHolderCompat.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.helper.compat; import android.content.pm.ProviderInfo; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/5/4. */ public class ContentProviderHolderCompat { private static Class sClass; public static Class Class() throws ClassNotFoundException { if (sClass == null) { sClass = Class.forName("android.app.IActivityManager$ContentProviderHolder"); } return sClass; } public static Object newInstance(Object target) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, ClassNotFoundException { Class clazz = Class(); Constructor constructor = clazz.getConstructor(ProviderInfo.class); return constructor.newInstance(target); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/compat/IActivityManagerCompat.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.helper.compat; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/5/1. */ public class IActivityManagerCompat { private static Class sClass; public static Class Class() throws ClassNotFoundException { if (sClass == null) { sClass = Class.forName("android.app.IActivityManager"); } return sClass; } public static boolean isIActivityManager(Object obj){ if (obj == null) { return false; } else { try { Class clazz = Class(); return clazz.isInstance(obj); } catch (ClassNotFoundException e) { return false; } } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/compat/IAppOpsServiceCompat.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.helper.compat; import android.os.IBinder; import com.morgoo.droidplugin.reflect.MethodUtils; import java.lang.reflect.InvocationTargetException; /** * Created by Andy Zhang(zhangyong232@gmail.com) on on 16/5/11. */ public class IAppOpsServiceCompat { private static Class sClass; public static Class Class() throws ClassNotFoundException { if (sClass == null) { sClass = Class.forName("com.android.internal.app.IAppOpsService"); } return sClass; } public static Object asInterface(IBinder binder) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { Class clazz = Class.forName("com.android.internal.app.IAppOpsService$Stub"); return MethodUtils.invokeStaticMethod(clazz, "asInterface", binder); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/compat/IAudioServiceCompat.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.helper.compat; import android.os.IBinder; import com.morgoo.droidplugin.reflect.MethodUtils; import java.lang.reflect.InvocationTargetException; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/5/4. */ public class IAudioServiceCompat { private static Class sClass; public static Class Class() throws ClassNotFoundException { if (sClass == null) { sClass = Class.forName("android.media.IAudioService"); } return sClass; } public static Object asInterface( IBinder binder) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { Class clazz = Class.forName("android.media.IAudioService$Stub"); return MethodUtils.invokeStaticMethod(clazz, "asInterface", binder); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/compat/IClipboardCompat.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.helper.compat; import android.os.IBinder; import com.morgoo.droidplugin.reflect.MethodUtils; import java.lang.reflect.InvocationTargetException; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/5/4. */ public class IClipboardCompat { private static Class sClass; public static Class Class() throws ClassNotFoundException { if (sClass == null) { sClass = Class.forName("android.content.IClipboard"); } return sClass; } public static Object asInterface( IBinder binder) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { Class clazz = Class.forName("android.content.IClipboard$Stub"); return MethodUtils.invokeStaticMethod(clazz, "asInterface", binder); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/compat/IContentServiceCompat.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.helper.compat; import android.os.IBinder; import com.morgoo.droidplugin.reflect.MethodUtils; import java.lang.reflect.InvocationTargetException; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/5/21. */ public class IContentServiceCompat { private static Class sClass; public static Class Class() throws ClassNotFoundException { if (sClass == null) { sClass = Class.forName("android.content.IContentService"); } return sClass; } public static Object asInterface( IBinder binder) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { Class clazz = Class.forName("android.content.IContentService$Stub"); return MethodUtils.invokeStaticMethod(clazz, "asInterface", binder); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/compat/IDisplayManagerCompat.java ================================================ package com.morgoo.helper.compat; import android.os.IBinder; import com.morgoo.droidplugin.reflect.MethodUtils; import java.lang.reflect.InvocationTargetException; /** * IDisplayManagerCompat * * @author Liu Yichen * @date 16/6/13 */ public class IDisplayManagerCompat { private static Class sClass; public static Class Class() throws ClassNotFoundException { if (sClass == null) { sClass = Class.forName("android.hardware.display.IDisplayManager"); } return sClass; } public static Object asInterface(IBinder binder) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { Class clazz = Class.forName("android.hardware.display.IDisplayManager$Stub"); return MethodUtils.invokeStaticMethod(clazz, "asInterface", binder); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/compat/IGraphicsStatsCompat.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.helper.compat; import android.os.IBinder; import com.morgoo.droidplugin.reflect.MethodUtils; import java.lang.reflect.InvocationTargetException; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/6/18. */ public class IGraphicsStatsCompat { private static Class sClass; public static Class Class() throws ClassNotFoundException { if (sClass == null) { sClass = Class.forName("android.view.IGraphicsStats"); } return sClass; } public static Object asInterface( IBinder binder) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { Class clazz = Class.forName("android.view.IGraphicsStats$Stub"); return MethodUtils.invokeStaticMethod(clazz, "asInterface", binder); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/compat/IInputMethodManagerCompat.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.helper.compat; import android.os.IBinder; import com.morgoo.droidplugin.reflect.MethodUtils; import java.lang.reflect.InvocationTargetException; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/5/4. */ public class IInputMethodManagerCompat { private static Class sClass; public static Class Class() throws ClassNotFoundException { if (sClass == null) { sClass = Class.forName("com.android.internal.view.IInputMethodManager"); } return sClass; } public static Object asInterface( IBinder binder) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { Class clazz = Class.forName("com.android.internal.view.IInputMethodManager$Stub"); return MethodUtils.invokeStaticMethod(clazz, "asInterface", binder); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/compat/ILocationManagerCompat.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.helper.compat; import android.os.IBinder; import com.morgoo.droidplugin.reflect.MethodUtils; import java.lang.reflect.InvocationTargetException; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2016/2/25. */ public class ILocationManagerCompat { private static Class sClass; public static Class Class() throws ClassNotFoundException { if (sClass == null) { sClass = Class.forName("android.location.ILocationManager"); } return sClass; } public static Object asInterface( IBinder binder) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { Class clazz = Class.forName("android.location.ILocationManager$Stub"); return MethodUtils.invokeStaticMethod(clazz, "asInterface", binder); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/compat/IMediaRouterServiceCompat.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.helper.compat; import android.os.IBinder; import com.morgoo.droidplugin.reflect.MethodUtils; import java.lang.reflect.InvocationTargetException; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/5/4. */ public class IMediaRouterServiceCompat { private static Class sClass; public static Class Class() throws ClassNotFoundException { if (sClass == null) { sClass = Class.forName("android.media.IMediaRouterService"); } return sClass; } public static Object asInterface( IBinder binder) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { Class clazz = Class.forName("android.media.IMediaRouterService$Stub"); return MethodUtils.invokeStaticMethod(clazz, "asInterface", binder); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/compat/IMmsCompat.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.helper.compat; import android.os.IBinder; import com.morgoo.droidplugin.reflect.MethodUtils; import java.lang.reflect.InvocationTargetException; /** * Created by zhangyong8 on 16/5/9. */ public class IMmsCompat { private static Class sClass; public static Class Class() throws ClassNotFoundException { if (sClass == null) { sClass = Class.forName("com.android.internal.telephony.IMms"); } return sClass; } public static Object asInterface(IBinder binder) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { Class clazz = Class.forName("com.android.internal.telephony.IMms$Stub"); return MethodUtils.invokeStaticMethod(clazz, "asInterface", binder); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/compat/IMountServiceCompat.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.helper.compat; import android.os.IBinder; import com.morgoo.droidplugin.reflect.MethodUtils; import java.lang.reflect.InvocationTargetException; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/5/4. */ public class IMountServiceCompat { private static Class sClass; public static Class Class() throws ClassNotFoundException { if (sClass == null) { sClass = Class.forName("android.os.storage.IMountService"); } return sClass; } public static Object asInterface( IBinder binder) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { Class clazz = Class.forName("android.os.storage.IMountService$Stub"); return MethodUtils.invokeStaticMethod(clazz, "asInterface", binder); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/compat/INotificationManagerCompat.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.helper.compat; import android.os.IBinder; import com.morgoo.droidplugin.reflect.MethodUtils; import java.lang.reflect.InvocationTargetException; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/5/4. */ public class INotificationManagerCompat { private static Class sClass; public static Class Class() throws ClassNotFoundException { if (sClass == null) { sClass = Class.forName("android.app.INotificationManager"); } return sClass; } public static Object asInterface( IBinder binder) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { Class clazz = Class.forName("android.app.INotificationManager$Stub"); return MethodUtils.invokeStaticMethod(clazz, "asInterface", binder); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/compat/IPackageDataObserverCompat.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.helper.compat; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/5/4. */ public class IPackageDataObserverCompat { private static Class sClass; public static Class Class() throws ClassNotFoundException { if (sClass == null) { sClass = Class.forName("android.content.pm.IPackageDataObserver"); } return sClass; } public static boolean isIPackageDataObserver(Object obj) throws ClassNotFoundException { if (obj == null) { return false; } else { Class clazz = Class(); return clazz.isInstance(obj); } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/compat/IPhoneSubInfoCompat.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.helper.compat; import android.os.IBinder; import com.morgoo.droidplugin.reflect.MethodUtils; import java.lang.reflect.InvocationTargetException; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2016/5/6. */ public class IPhoneSubInfoCompat { private static Class sClass; public static Class Class() throws ClassNotFoundException { if (sClass == null) { sClass = Class.forName("com.android.internal.telephony.IPhoneSubInfo"); } return sClass; } public static Object asInterface(IBinder binder) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { Class clazz = Class.forName("com.android.internal.telephony.IPhoneSubInfo$Stub"); return MethodUtils.invokeStaticMethod(clazz, "asInterface", binder); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/compat/ISearchManagerCompat.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.helper.compat; import android.os.IBinder; import com.morgoo.droidplugin.reflect.MethodUtils; import java.lang.reflect.InvocationTargetException; /** * Created by wyw on 15-9-18. */ public class ISearchManagerCompat { private static Class sClass; public static Class Class() throws ClassNotFoundException { if (sClass == null) { sClass = Class.forName("android.app.ISearchManager"); } return sClass; } public static Object asInterface(IBinder binder) throws ClassNotFoundException , NoSuchMethodException, IllegalAccessException, InvocationTargetException { Class clazz = Class.forName("android.app.ISearchManager$Stub"); return MethodUtils.invokeStaticMethod(clazz, "asInterface", binder); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/compat/ISessionManagerCompat.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.helper.compat; import android.os.IBinder; import com.morgoo.droidplugin.reflect.MethodUtils; import java.lang.reflect.InvocationTargetException; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/5/1. */ public class ISessionManagerCompat { private static Class sClass; public static Class Class() throws ClassNotFoundException { if (sClass == null) { sClass = Class.forName("android.media.session.ISessionManager"); } return sClass; } public static Object asInterface(IBinder binder) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { Class clazz = Class.forName("android.media.session.ISessionManager$Stub"); return MethodUtils.invokeStaticMethod(clazz, "asInterface", binder); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/compat/ISmsCompat.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.helper.compat; import android.os.IBinder; import com.morgoo.droidplugin.reflect.MethodUtils; import java.lang.reflect.InvocationTargetException; /** * Created by zhangyong8 on 16/5/9. */ public class ISmsCompat { private static Class sClass; public static Class Class() throws ClassNotFoundException { if (sClass == null) { sClass = Class.forName("com.android.internal.telephony.ISms"); } return sClass; } public static Object asInterface(IBinder binder) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { Class clazz = Class.forName("com.android.internal.telephony.ISms$Stub"); return MethodUtils.invokeStaticMethod(clazz, "asInterface", binder); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/compat/ISubCompat.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.helper.compat; import android.os.IBinder; import com.morgoo.droidplugin.reflect.MethodUtils; import java.lang.reflect.InvocationTargetException; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2016/5/6. */ public class ISubCompat { private static Class sClass; public static Class Class() throws ClassNotFoundException { if (sClass == null) { sClass = Class.forName("com.android.internal.telephony.ISub"); } return sClass; } public static Object asInterface(IBinder binder) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { Class clazz = Class.forName("com.android.internal.telephony.ISub$Stub"); return MethodUtils.invokeStaticMethod(clazz, "asInterface", binder); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/compat/ITelephonyCompat.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.helper.compat; import android.os.IBinder; import com.morgoo.droidplugin.reflect.MethodUtils; import java.lang.reflect.InvocationTargetException; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2016/5/6. */ public class ITelephonyCompat { private static Class sClass; public static Class Class() throws ClassNotFoundException { if (sClass == null) { sClass = Class.forName("com.android.internal.telephony.ITelephony"); } return sClass; } public static Object asInterface(IBinder binder) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { Class clazz = Class.forName("com.android.internal.telephony.ITelephony$Stub"); return MethodUtils.invokeStaticMethod(clazz, "asInterface", binder); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/compat/ITelephonyRegistryCompat.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.helper.compat; import android.os.IBinder; import com.morgoo.droidplugin.reflect.MethodUtils; import java.lang.reflect.InvocationTargetException; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2016/5/6. */ public class ITelephonyRegistryCompat { private static Class sClass; public static Class Class() throws ClassNotFoundException { if (sClass == null) { sClass = Class.forName("com.android.internal.telephony.ITelephonyRegistry"); } return sClass; } public static Object asInterface(IBinder binder) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { Class clazz = Class.forName("com.android.internal.telephony.ITelephonyRegistry$Stub"); return MethodUtils.invokeStaticMethod(clazz, "asInterface", binder); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/compat/IWifiManagerCompat.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.helper.compat; import android.os.IBinder; import com.morgoo.droidplugin.reflect.MethodUtils; import java.lang.reflect.InvocationTargetException; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/6/1. */ public class IWifiManagerCompat { private static Class sClass; public static Class Class() throws ClassNotFoundException { if (sClass == null) { sClass = Class.forName("android.net.wifi.IWifiManager"); } return sClass; } public static Object asInterface(IBinder binder) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { Class clazz = Class.forName("android.net.wifi.IWifiManager$Stub"); return MethodUtils.invokeStaticMethod(clazz, "asInterface", binder); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/compat/IWindowManagerCompat.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.helper.compat; import android.os.IBinder; import com.morgoo.droidplugin.reflect.MethodUtils; import java.lang.reflect.InvocationTargetException; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/6/17. */ public class IWindowManagerCompat { private static Class sClass; public static Class Class() throws ClassNotFoundException { if (sClass == null) { sClass = Class.forName("android.view.IWindowManager"); } return sClass; } public static Object asInterface( IBinder binder) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { Class clazz = Class.forName("android.view.IWindowManager$Stub"); return MethodUtils.invokeStaticMethod(clazz, "asInterface", binder); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/compat/NativeLibraryHelperCompat.java ================================================ package com.morgoo.helper.compat; import android.annotation.TargetApi; import android.os.Build; import com.morgoo.droidplugin.pm.PluginManager; import com.morgoo.droidplugin.reflect.MethodUtils; import com.morgoo.helper.Log; import java.io.File; import java.lang.reflect.InvocationTargetException; import java.util.Enumeration; import java.util.HashSet; import java.util.Set; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; public class NativeLibraryHelperCompat { private static final String TAG = NativeLibraryHelperCompat.class.getSimpleName(); private static final Class nativeLibraryHelperClass() throws ClassNotFoundException { return Class.forName("com.android.internal.content.NativeLibraryHelper"); } private static final Class handleClass() throws ClassNotFoundException { return Class.forName("com.android.internal.content.NativeLibraryHelper$Handle"); } public static final int copyNativeBinaries(File apkFile, File sharedLibraryDir) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { return copyNativeBinariesAfterL(apkFile, sharedLibraryDir); } else { return copyNativeBinariesBeforeL(apkFile, sharedLibraryDir); } } private static int copyNativeBinariesBeforeL(File apkFile, File sharedLibraryDir) { try { Object[] args = new Object[2]; args[0] = apkFile; args[1] = sharedLibraryDir; return (int) MethodUtils.invokeStaticMethod(nativeLibraryHelperClass(), "copyNativeBinariesIfNeededLI", args); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } return -1; } @TargetApi(Build.VERSION_CODES.LOLLIPOP) private static int copyNativeBinariesAfterL(File apkFile, File sharedLibraryDir) { try { Object handleInstance = MethodUtils.invokeStaticMethod(handleClass(), "create", apkFile); if (handleInstance == null) { return -1; } String abi = null; //在64位处理器中,如果导入的so库未包含64位的,比如只导入了armeabi,此时就会找不到该abi。 //应该在32位abi中再次寻找。 if (isVM64()) { if (Build.SUPPORTED_64_BIT_ABIS.length > 0) { Set abis = getAbisFromApk(apkFile.getAbsolutePath()); if (abis == null || abis.isEmpty()) { return 0; } int abiIndex = (int) MethodUtils.invokeStaticMethod(nativeLibraryHelperClass(), "findSupportedAbi", handleInstance, Build.SUPPORTED_64_BIT_ABIS); if (abiIndex >= 0) { abi = Build.SUPPORTED_64_BIT_ABIS[abiIndex]; } } } //else { //如果abi为空,再次查找。 if(abi == null){ if (Build.SUPPORTED_32_BIT_ABIS.length > 0) { Set abis = getAbisFromApk(apkFile.getAbsolutePath()); if (abis == null || abis.isEmpty()) { return 0; } int abiIndex = (int) MethodUtils.invokeStaticMethod(nativeLibraryHelperClass(), "findSupportedAbi", handleInstance, Build.SUPPORTED_32_BIT_ABIS); if (abiIndex >= 0) { abi = Build.SUPPORTED_32_BIT_ABIS[abiIndex]; } } } if (abi == null) { return -1; } Object[] args = new Object[3]; args[0] = handleInstance; args[1] = sharedLibraryDir; args[2] = abi; return (int) MethodUtils.invokeStaticMethod(nativeLibraryHelperClass(), "copyNativeBinaries", args); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } return -1; } @TargetApi(Build.VERSION_CODES.LOLLIPOP) private static boolean isVM64() { Set supportedAbis = getAbisFromApk(getHostApk()); if (Build.SUPPORTED_64_BIT_ABIS.length == 0) { return false; } if (supportedAbis == null || supportedAbis.isEmpty()) { return true; } for (String supportedAbi : supportedAbis) { if ("arm64-v8a".endsWith(supportedAbi) || "x86_64".equals(supportedAbi) || "mips64".equals(supportedAbi)) { return true; } } return false; } private static Set getAbisFromApk(String apk) { try { ZipFile apkFile = new ZipFile(apk); Enumeration entries = apkFile.entries(); Set supportedAbis = new HashSet<>(); while (entries.hasMoreElements()) { ZipEntry entry = entries.nextElement(); String name = entry.getName(); if (name.contains("../")) { continue; } if (name.startsWith("lib/") && !entry.isDirectory() && name.endsWith(".so")) { String supportedAbi = name.substring(name.indexOf("/") + 1, name.lastIndexOf("/")); supportedAbis.add(supportedAbi); } } Log.d(TAG, "supportedAbis : %s", supportedAbis); return supportedAbis; } catch (Exception e) { Log.e(TAG, "get supportedAbis failure", e); } return null; } private static String getHostApk() { return PluginManager.getInstance().getHostContext().getApplicationInfo().sourceDir; } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/compat/PackageManagerCompat.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.helper.compat; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/4/13. */ public class PackageManagerCompat { public static final int DELETE_FAILED_INTERNAL_ERROR = -1; public static final int DELETE_SUCCEEDED = 1; public static final int INSTALL_SUCCEEDED = 1; public static final int INSTALL_FAILED_INTERNAL_ERROR = -110; public static final int INSTALL_FAILED_INVALID_APK = -2; public static final int INSTALL_REPLACE_EXISTING = 0x00000002; public static final int INSTALL_FAILED_ALREADY_EXISTS = -1; public static final int INSTALL_FAILED_NOT_SUPPORT_ABI = -3;} ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/compat/ParceledListSliceCompat.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.helper.compat; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/5/4. */ public class ParceledListSliceCompat { private static Class sClass; public static Class Class() throws ClassNotFoundException { if (sClass == null) { sClass = Class.forName("android.content.pm.ParceledListSlice"); } return sClass; } public static boolean isParceledListSlice(Object obj){ if (obj == null) { return false; } else { try { Class clazz = Class(); return clazz.isInstance(obj); } catch (ClassNotFoundException e) { return false; } } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/compat/ProcessCompat.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.helper.compat; import com.morgoo.droidplugin.reflect.MethodUtils; import java.lang.reflect.InvocationTargetException; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/3/14. */ public class ProcessCompat { public static final void setArgV0(String name) { try { MethodUtils.invokeStaticMethod(Class.forName("android.os.Process"), "setArgV0", name); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } // android.os.Process.setArgV0(name); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/compat/QueuedWorkCompat.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.helper.compat; import com.morgoo.droidplugin.reflect.MethodUtils; import java.lang.reflect.InvocationTargetException; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/5/1. */ public class QueuedWorkCompat { private static Class sClass; private static Class Class() throws ClassNotFoundException { if (sClass == null) { sClass = Class.forName("android.app.QueuedWork"); } return sClass; } public static void waitToFinish() { try { MethodUtils.invokeStaticMethod(Class(), "waitToFinish"); } catch (Exception e) { e.printStackTrace(); } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/compat/ServiceManagerCompat.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.helper.compat; import android.os.IBinder; import com.morgoo.droidplugin.reflect.MethodUtils; import java.lang.reflect.InvocationTargetException; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/4/13. */ public class ServiceManagerCompat { private static Class sClass = null; public static final Class Class() throws ClassNotFoundException { if (sClass == null) { sClass = Class.forName("android.os.ServiceManager"); } return sClass; } public static IBinder getService(String name) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { return (IBinder) MethodUtils.invokeStaticMethod(Class(), "getService", name); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/compat/SingletonCompat.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.helper.compat; import com.morgoo.droidplugin.reflect.MethodUtils; import java.lang.reflect.InvocationTargetException; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/5/4. */ public class SingletonCompat { private static Class sClass; public static Class Class() throws ClassNotFoundException { if (sClass == null) { sClass = Class.forName("android.util.Singleton"); } return sClass; } public static boolean isSingleton(Object obj) { if (obj == null) { return false; } else { try { Class clazz = Class(); return clazz.isInstance(obj); } catch (ClassNotFoundException e) { return false; } } } public static Object get(Object targetSingleton) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { return MethodUtils.invokeMethod(targetSingleton, "get"); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/compat/SystemPropertiesCompat.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.helper.compat; import com.morgoo.droidplugin.reflect.MethodUtils; import java.lang.reflect.InvocationTargetException; /** * Created by zhangyong on 15/5/1. */ public class SystemPropertiesCompat { private static Class sClass; private static Class getMyClass() throws ClassNotFoundException { if (sClass == null) { sClass = Class.forName("android.os.SystemProperties"); } return sClass; } private static String getInner(String key, String defaultValue) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, ClassNotFoundException { Class clazz = getMyClass(); return (String) MethodUtils.invokeStaticMethod(clazz, "get", key, defaultValue); } public static String get(String key, String defaultValue) { try { return getInner(key, defaultValue); } catch (Exception e) { e.printStackTrace(); return defaultValue; } } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/compat/UserHandleCompat.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.helper.compat; import android.os.UserHandle; import com.morgoo.droidplugin.reflect.MethodUtils; import java.lang.reflect.InvocationTargetException; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/4/13. */ public class UserHandleCompat { // UserHandle.getCallingUserId() public static int getCallingUserId() { try { return (int) MethodUtils.invokeStaticMethod(UserHandle.class, "getCallingUserId"); } catch (Exception e) { e.printStackTrace(); } return 0; } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/compat/VMRuntimeCompat.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.helper.compat; import android.os.Build; import com.morgoo.droidplugin.reflect.MethodUtils; import com.morgoo.helper.Log; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2016/2/4. */ public class VMRuntimeCompat { private static final String TAG = VMRuntimeCompat.class.getSimpleName(); public final static boolean is64Bit() { // dalvik.system.VMRuntime.getRuntime().is64Bit(); try { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { return false; } Class VMRuntime = Class.forName("dalvik.system.VMRuntime"); Object VMRuntimeObj = MethodUtils.invokeStaticMethod(VMRuntime, "getRuntime"); Object is64Bit = MethodUtils.invokeMethod(VMRuntimeObj, "is64Bit"); if (is64Bit instanceof Boolean) { return ((Boolean) is64Bit); } } catch (Throwable e) { Log.w(TAG, "is64Bit", e); } return false; } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/compat/WebViewFactoryCompat.java ================================================ /* ** DroidPlugin Project ** ** Copyright(c) 2015 Andy Zhang ** ** This file is part of DroidPlugin. ** ** DroidPlugin is free software: you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation, either ** version 3 of the License, or (at your option) any later version. ** ** DroidPlugin is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with DroidPlugin. If not, see ** **/ package com.morgoo.helper.compat; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * Created by Andy Zhang(zhangyong232@gmail.com) on 2014/10/10. */ public class WebViewFactoryCompat { private static Class sClass; public static Class Class() throws ClassNotFoundException { if (sClass == null) { sClass = Class.forName("android.webkit.WebViewFactory"); } return sClass; } public static Object getProvider() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, ClassNotFoundException { Method getProvider = Class().getDeclaredMethod("getProvider"); if (!getProvider.isAccessible()) { getProvider.setAccessible(true); } return getProvider.invoke(null); } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/java/com/morgoo/helper/utils/ProcessUtils.java ================================================ package com.morgoo.helper.utils; import android.app.ActivityManager; import android.content.Context; import java.lang.reflect.Method; /** * Created by ifunbow-k on 2017/8/2. */ public class ProcessUtils { private static IProcessChecker sIProcessChecker; static { sIProcessChecker = new IProcessChecker() { @Override public boolean isPluginProcess(String processName) { return processName != null && processName.contains(":Plugin"); } @Override public boolean isManagerProcess(String processName) { return processName != null && processName.contains(":CoreManager"); } }; } public static void setProcessChecker(IProcessChecker sIProcessChecker) { ProcessUtils.sIProcessChecker = sIProcessChecker; } public static IProcessChecker getProcessChecker() { return sIProcessChecker; } public interface IProcessChecker { boolean isPluginProcess(String processName); boolean isManagerProcess(String processName); } public static boolean isPluginProcess(Context context) { String processName = getProcessName(context); return getProcessChecker().isPluginProcess(processName); } public static boolean isManagerProcess(Context context) { String processName = getProcessName(context); return getProcessChecker().isManagerProcess(processName); } public static boolean isMainProcess(Context context) { String processName = getProcessName(context); return !getProcessChecker().isManagerProcess(processName) && !getProcessChecker().isPluginProcess(processName); } private static String getProcessName(Context context) { String processName = null; try { Class ActivityThread = Class.forName("android.app.ActivityThread"); Method currentActivityThread = ActivityThread.getDeclaredMethod("currentActivityThread"); currentActivityThread.setAccessible(true); Object am = currentActivityThread.invoke(null); Method getProcessName = ActivityThread.getDeclaredMethod("getProcessName"); getProcessName.setAccessible(true); processName = (String) getProcessName.invoke(am); } catch (Exception e) { int pid = android.os.Process.myPid(); ActivityManager manager = (ActivityManager) context.getSystemService(Context .ACTIVITY_SERVICE); for (ActivityManager.RunningAppProcessInfo process : manager.getRunningAppProcesses ()) { if (process.pid == pid) { processName = process.processName; break; } } } return processName; } } ================================================ FILE: project/Libraries/DroidPlugin/src/main/res/drawable-xxhdpi/plugin_activity_loading.xml ================================================ ================================================ FILE: project/Libraries/DroidPlugin/src/main/res/values/strings.xml ================================================ 启动插件ing... 插件代理服务 插件代理服务 插件管理服务 ================================================ FILE: project/Libraries/DroidPlugin/src/main/res/values/styles.xml ================================================ ================================================ FILE: project/Libraries/DroidPlugin/src/main/res/values-v11/styles.xml ================================================ ================================================ FILE: project/Libraries/DroidPlugin/src/main/res/values-v14/styles.xml ================================================ ================================================ FILE: project/Test/ApiTest/build.gradle ================================================ apply plugin: 'com.android.application' dependencies { implementation fileTree(include: '*.jar', dir: 'libs') implementation 'com.android.support:support-v4:23.1.1' implementation 'com.android.support:appcompat-v7:23.1.1' implementation 'com.android.support:design:23.1.1' } android { compileSdkVersion rootProject.ext.compileSdkVersion buildToolsVersion rootProject.ext.buildToolsVersion defaultConfig { ndk { moduleName 'Test' stl 'stlport_static' abiFilters 'armeabi', 'armeabi-v7a', 'x86' ldLibs 'log' cFlags '-DMY_LOG_LEVEL=LOG_VERBOSE' } } lintOptions { abortOnError false } } ================================================ FILE: project/Test/ApiTest/proguard-project.txt ================================================ # To enable ProGuard in your project, edit project.properties # to define the proguard.config property as described in that file. # # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in ${sdk.dir}/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the ProGuard # include property in project.properties. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} ================================================ FILE: project/Test/ApiTest/project.properties ================================================ # This file is automatically generated by Android Tools. # Do not modify this file -- YOUR CHANGES WILL BE ERASED! # # This file must be checked in Version Control Systems. # # To customize properties used by the Ant build system edit # "ant.properties", and override values to adapt the script to your # project structure. # # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt # Project target. target=android-21 android.library.reference.1=../DroidPlugin manifestmerger.enabled=true ================================================ FILE: project/Test/ApiTest/src/main/AndroidManifest.xml ================================================ ================================================ FILE: project/Test/ApiTest/src/main/cpp/Core.cpp ================================================ #include #include #include #include #include #include "help/nativehelper/JNIHelp.h" #include "help/log.h" #include "HelperJni/HelperJni.h" #define NATIVE_CLASS "com/morgoo/nativec/NativeCHelper" int registerNativeMethodsAndSetup(JNIEnv* env) { jclass nativeClass = env->FindClass(NATIVE_CLASS); if (clearJniExpcetion(env, TAG) || !nativeClass) { LOGE(TAG, "Can not found %s", NATIVE_CLASS); return 0; } env->UnregisterNatives(nativeClass); tryRegisterNativeMethodsHelperJni(env, NATIVE_CLASS); return 0; } __BEGIN_DECLS jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env; if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) { LOGE(TAG, "JavaVM::GetEnv() failed"); abort(); } registerNativeMethodsAndSetup(env); LOGE(TAG, "JNI_OnLoad OKAY(pid=%d,uid=%d) jvm_addr[0x%x],JNIEnv_addr[0x%x]", getpid(), getuid(), (uint32_t ) vm, (uint32_t ) env); return JNI_VERSION_1_6; } __END_DECLS ================================================ FILE: project/Test/ApiTest/src/main/cpp/HelperJni/HelperJni.cpp ================================================ /* * HelperJni.cpp * * Created on: 2014年5月28日 * Author: zhangyong6 */ #include #include #include #include #include #include #include #include #include #include "help/nativehelper/JNIHelp.h" #include "help/log.h" #include "HelperJni/HelperJni.h" int nativePing(JNIEnv* env, jobject obj) { LOGE(TAG, "nativePing"); return 1986; } static JNINativeMethod gMethods[] = { NATIVE_METHOD((void*) nativePing,"nativePing", "()I" )}; int tryRegisterNativeMethodsHelperJni(JNIEnv* env, const char* clazzname) { if (!clazzname) { LOGW(TAG, "Can not fund class skip register native method"); return JNI_ERR; } return jniRegisterNativeMethods2(env, clazzname, gMethods, NELEM(gMethods)); } ================================================ FILE: project/Test/ApiTest/src/main/cpp/HelperJni/HelperJni.h ================================================ /* * HelperJni.h * * Created on: 2014年5月28日 * Author: zhangyong6 */ #ifndef HELPERJNI_H_ #define HELPERJNI_H_ #include int tryRegisterNativeMethodsHelperJni(JNIEnv* env, const char* clazz); inline bool clearJniExpcetion(JNIEnv* env, const char *tag, const char* func, int line) { if (env->ExceptionCheck()) { jthrowable exception = env->ExceptionOccurred(); if (exception) { jniLogException(env, ANDROID_LOG_ERROR, tag, exception); } env->ExceptionClear(); return true; } return false; } #endif /* HELPERJNI_H_ */ ================================================ FILE: project/Test/ApiTest/src/main/cpp/help/log.h ================================================ /* * log.h * * Created on: 2013年12月4日 * Author: zhangyong232@gmail.com */ #ifndef __LOG_H__ #define __LOG_H__ #include #include #include #include __BEGIN_DECLS #define LOG_VERBOSE 1 #define LOG_DEBUG 2 #define LOG_INFO 3 #define LOG_WARNING 4 #define LOG_ERROR 5 #define LOG_FATAL 6 #define LOG_SILENT 7 #ifndef MY_LOG_LEVEL #define MY_LOG_LEVEL LOG_SILENT #endif ///是否打开日志开关 #ifndef DEBUG #define DEBUG MY_LOG_LEVEL #endif #ifndef CURRENT_FILENAME #define CURRENT_FILENAME (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) #endif //默认的日志tag "NdkNative[Test.cpp(main):25]" #ifndef TAG #define TAG \ CURRENT_FILENAME,__FUNCTION__,__LINE__ #endif inline void android_log_print(int prio, const char *file, const char* func, int line, const char *fmt, ...) { va_list args; va_start(args, fmt); char buf[1024]; snprintf(buf, sizeof(buf), "Ndk[%s(%s()):%d]", file, func, line); __android_log_vprint(prio, buf, fmt, args); va_end(args); } #if(DEBUG <= LOG_VERBOSE) #define LOGV(tag,...) android_log_print(ANDROID_LOG_VERBOSE,tag,##__VA_ARGS__) #define LOGVA(...) LOGV(TAG,##__VA_ARGS__) #else #define LOGV(tag,...) #define LOGVA(...) #endif #if(DEBUG <= LOG_DEBUG) #define LOGD(tag,...) android_log_print(ANDROID_LOG_DEBUG,tag,##__VA_ARGS__) #define LOGDA(...) LOGD(TAG,##__VA_ARGS__) #else #define LOGD(tag,...) #define LOGDA(...) #endif #if(DEBUG <= LOG_INFO) #define LOGI(tag,...) android_log_print(ANDROID_LOG_INFO,tag,##__VA_ARGS__) #define LOGIA(...) LOGI(TAG,##__VA_ARGS__) #else #define LOGI(tag,...) #define LOGIA(...) #endif #if(DEBUG <= LOG_WARN) #define LOGW(tag,...) android_log_print(ANDROID_LOG_WARN,tag,##__VA_ARGS__) #define LOGWA(...) LOGW(TAG,##__VA_ARGS__) #else #define LOGW(tag,...) #define LOGWA(...) #endif #if(DEBUG <= LOG_ERROR) #define LOGE(tag,...) android_log_print(ANDROID_LOG_ERROR,tag,##__VA_ARGS__) #define LOGEA(...) LOGE(TAG, ##__VA_ARGS__) #else #define LOGE(tag,...) #define LOGEA(...) #endif #if(DEBUG <= LOG_FATAL) #define LOGF(tag,...) android_log_print(ANDROID_LOG_FATAL,tag,##__VA_ARGS__) #define LOGFA(...) LOGF(TAG,##__VA_ARGS__) #else #define LOGF(tag,...) #define LOGFA(...) #endif __END_DECLS #endif /* LOG_H_ */ ================================================ FILE: project/Test/ApiTest/src/main/cpp/help/nativehelper/JNIHelp.cpp ================================================ /* * Copyright (C) 2006 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "help/log.h" #include "help/nativehelper/JNIHelp.h" #include #include #include #include #include /** * Equivalent to ScopedLocalRef, but for C_JNIEnv instead. (And slightly more powerful.) */ template class scoped_local_ref { public: scoped_local_ref(C_JNIEnv* env, T localRef = NULL) : mEnv(env), mLocalRef(localRef) { } ~scoped_local_ref() { reset(); } void reset(T localRef = NULL) { if (mLocalRef != NULL) { (*mEnv)->DeleteLocalRef(reinterpret_cast(mEnv), mLocalRef); mLocalRef = localRef; } } T get() const { return mLocalRef; } private: C_JNIEnv* mEnv; T mLocalRef; // Disallow copy and assignment. scoped_local_ref(const scoped_local_ref&); void operator=(const scoped_local_ref&); }; static jclass findClass(C_JNIEnv* env, const char* className) { JNIEnv* e = reinterpret_cast(env); return (*env)->FindClass(e, className); } int jniRegisterNativeMethods(C_JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods) { JNIEnv* e = reinterpret_cast(env); LOGI(TAG, "Registering %s's %d native methods...", className, numMethods); scoped_local_ref c(env, findClass(env, className)); if (c.get() == NULL) { char* msg; asprintf(&msg, "Native registration unable to find class '%s'; aborting...", className); e->FatalError(msg); } if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) { char* msg; asprintf(&msg, "RegisterNatives failed for '%s'; aborting...", className); e->FatalError(msg); } return 0; } int jniRegisterNativeMethods2(C_JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods) { JNIEnv* e = reinterpret_cast(env); LOGI(TAG, "Registering %s's %d native methods...", className, numMethods); scoped_local_ref c(env, findClass(env, className)); if (c.get() == NULL) { LOGW(TAG,"Native registration unable to find class '%s'; aborting...", className); return JNI_ERR; } int reCount = 0; for (int i = 0; i < numMethods; i++) { JNINativeMethod method = gMethods[i]; jmethodID mid = (*env)->GetStaticMethodID(e, c.get(), method.name, method.signature); if ((*env)->ExceptionCheck(e)) { (*env)->ExceptionClear(e); } if (mid && (*env)->RegisterNatives(e, c.get(), &method, 1) >= 0) { reCount++; LOGI(TAG, "Native registration method OKAY...'%s.%s(%s)' ", className, method.name, method.signature); } else { LOGE(TAG, "Native registration unable to find method '%s.%s(%s)', skip", className, method.name, method.signature); continue; } } return reCount; } /* * Returns a human-readable summary of an exception object. The buffer will * be populated with the "binary" class name and, if present, the * exception message. */ static bool getExceptionSummary(C_JNIEnv* env, jthrowable exception, std::string& result) { JNIEnv* e = reinterpret_cast(env); /* get the name of the exception's class */ scoped_local_ref exceptionClass(env, (*env)->GetObjectClass(e, exception)); // can't fail scoped_local_ref classClass(env, (*env)->GetObjectClass(e, exceptionClass.get())); // java.lang.Class, can't fail jmethodID classGetNameMethod = (*env)->GetMethodID(e, classClass.get(), "getName", "()Ljava/lang/String;"); scoped_local_ref classNameStr(env, (jstring) (*env)->CallObjectMethod(e, exceptionClass.get(), classGetNameMethod)); if (classNameStr.get() == NULL) { (*env)->ExceptionClear(e); result = ""; return false; } const char* classNameChars = (*env)->GetStringUTFChars(e, classNameStr.get(), NULL); if (classNameChars == NULL) { (*env)->ExceptionClear(e); result = ""; return false; } result += classNameChars; (*env)->ReleaseStringUTFChars(e, classNameStr.get(), classNameChars); /* if the exception has a detail message, get that */ jmethodID getMessage = (*env)->GetMethodID(e, exceptionClass.get(), "getMessage", "()Ljava/lang/String;"); scoped_local_ref messageStr(env, (jstring) (*env)->CallObjectMethod(e, exception, getMessage)); if (messageStr.get() == NULL) { return true; } result += ": "; const char* messageChars = (*env)->GetStringUTFChars(e, messageStr.get(), NULL); if (messageChars != NULL) { result += messageChars; (*env)->ReleaseStringUTFChars(e, messageStr.get(), messageChars); } else { result += ""; (*env)->ExceptionClear(e); // clear OOM } return true; } /* * Returns an exception (with stack trace) as a string. */ static bool getStackTrace(C_JNIEnv* env, jthrowable exception, std::string& result) { JNIEnv* e = reinterpret_cast(env); scoped_local_ref stringWriterClass(env, findClass(env, "java/io/StringWriter")); if (stringWriterClass.get() == NULL) { return false; } jmethodID stringWriterCtor = (*env)->GetMethodID(e, stringWriterClass.get(), "", "()V"); jmethodID stringWriterToStringMethod = (*env)->GetMethodID(e, stringWriterClass.get(), "toString", "()Ljava/lang/String;"); scoped_local_ref printWriterClass(env, findClass(env, "java/io/PrintWriter")); if (printWriterClass.get() == NULL) { return false; } jmethodID printWriterCtor = (*env)->GetMethodID(e, printWriterClass.get(), "", "(Ljava/io/Writer;)V"); scoped_local_ref stringWriter(env, (*env)->NewObject(e, stringWriterClass.get(), stringWriterCtor)); if (stringWriter.get() == NULL) { return false; } jobject printWriter = (*env)->NewObject(e, printWriterClass.get(), printWriterCtor, stringWriter.get()); if (printWriter == NULL) { return false; } scoped_local_ref exceptionClass(env, (*env)->GetObjectClass(e, exception)); // can't fail jmethodID printStackTraceMethod = (*env)->GetMethodID(e, exceptionClass.get(), "printStackTrace", "(Ljava/io/PrintWriter;)V"); (*env)->CallVoidMethod(e, exception, printStackTraceMethod, printWriter); if ((*env)->ExceptionCheck(e)) { return false; } scoped_local_ref messageStr(env, (jstring) (*env)->CallObjectMethod(e, stringWriter.get(), stringWriterToStringMethod)); if (messageStr.get() == NULL) { return false; } const char* utfChars = (*env)->GetStringUTFChars(e, messageStr.get(), NULL); if (utfChars == NULL) { return false; } result = utfChars; (*env)->ReleaseStringUTFChars(e, messageStr.get(), utfChars); return true; } int jniThrowException(C_JNIEnv* env, const char* className, const char* msg) { JNIEnv* e = reinterpret_cast(env); if ((*env)->ExceptionCheck(e)) { /* TODO: consider creating the new exception with this as "cause" */ scoped_local_ref exception(env, (*env)->ExceptionOccurred(e)); (*env)->ExceptionClear(e); if (exception.get() != NULL) { std::string text; getExceptionSummary(env, exception.get(), text); LOGW(TAG,"Discarding pending exception (%s) to throw %s", text.c_str(), className); } } scoped_local_ref exceptionClass(env, findClass(env, className)); if (exceptionClass.get() == NULL) { LOGE(TAG, "Unable to find exception class %s", className); /* ClassNotFoundException now pending */ return -1; } if ((*env)->ThrowNew(e, exceptionClass.get(), msg) != JNI_OK) { LOGE(TAG, "Failed throwing '%s' '%s'", className, msg); /* an exception, most likely OOM, will now be pending */ return -1; } return 0; } int jniThrowExceptionFmt(C_JNIEnv* env, const char* className, const char* fmt, va_list args) { char msgBuf[512]; vsnprintf(msgBuf, sizeof(msgBuf), fmt, args); return jniThrowException(env, className, msgBuf); } int jniThrowNullPointerException(C_JNIEnv* env, const char* msg) { return jniThrowException(env, "java/lang/NullPointerException", msg); } int jniThrowRuntimeException(C_JNIEnv* env, const char* msg) { return jniThrowException(env, "java/lang/RuntimeException", msg); } int jniThrowIOException(C_JNIEnv* env, int errnum) { char buffer[80]; const char* message = jniStrError(errnum, buffer, sizeof(buffer)); return jniThrowException(env, "java/io/IOException", message); } static std::string jniGetStackTrace(C_JNIEnv* env, jthrowable exception) { JNIEnv* e = reinterpret_cast(env); scoped_local_ref currentException(env, (*env)->ExceptionOccurred(e)); if (exception == NULL) { exception = currentException.get(); if (exception == NULL) { return ""; } } if (currentException.get() != NULL) { (*env)->ExceptionClear(e); } std::string trace; if (!getStackTrace(env, exception, trace)) { (*env)->ExceptionClear(e); getExceptionSummary(env, exception, trace); } if (currentException.get() != NULL) { (*env)->Throw(e, currentException.get()); // rethrow } return trace; } void jniLogException(C_JNIEnv* env, int priority, const char* tag, jthrowable exception) { std::string trace(jniGetStackTrace(env, exception)); __android_log_write(priority, tag, trace.c_str()); } const char* jniStrError(int errnum, char* buf, size_t buflen) { #if __GLIBC__ // Note: glibc has a nonstandard strerror_r that returns char* rather than POSIX's int. // char *strerror_r(int errnum, char *buf, size_t n); return strerror_r(errnum, buf, buflen); #else int rc = strerror_r(errnum, buf, buflen); if (rc != 0) { // (POSIX only guarantees a value other than 0. The safest // way to implement this function is to use C++ and overload on the // type of strerror_r to accurately distinguish GNU from POSIX.) snprintf(buf, buflen, "errno %d", errnum); } return buf; #endif } jobject jniCreateFileDescriptor(C_JNIEnv* env, int fd) { JNIEnv* e = reinterpret_cast(env); jclass javaIoFileDescriptor = findClass(env, "java/io/FileDescriptor"); static jmethodID ctor = e->GetMethodID(javaIoFileDescriptor, "", "()V"); jobject fileDescriptor = (*env)->NewObject(e, javaIoFileDescriptor, ctor); // NOTE: NewObject ensures that an OutOfMemoryError will be seen by the Java // caller if the alloc fails, so we just return NULL when that happens. if (fileDescriptor != NULL) { jniSetFileDescriptorOfFD(env, fileDescriptor, fd); } return fileDescriptor; } int jniGetFDFromFileDescriptor(C_JNIEnv* env, jobject fileDescriptor) { JNIEnv* e = reinterpret_cast(env); static jfieldID fid = e->GetFieldID( findClass(env, "java/io/FileDescriptor"), "descriptor", "I"); if (fileDescriptor != NULL) { return (*env)->GetIntField(e, fileDescriptor, fid); } else { return -1; } } void jniSetFileDescriptorOfFD(C_JNIEnv* env, jobject fileDescriptor, int value) { JNIEnv* e = reinterpret_cast(env); static jfieldID fid = e->GetFieldID( findClass(env, "java/io/FileDescriptor"), "descriptor", "I"); (*env)->SetIntField(e, fileDescriptor, fid, value); } jobject jniGetReferent(C_JNIEnv* env, jobject ref) { JNIEnv* e = reinterpret_cast(env); static jmethodID get = e->GetMethodID( findClass(env, "java/lang/ref/Reference"), "get", "()Ljava/lang/Object;"); return (*env)->CallObjectMethod(e, ref, get); } ================================================ FILE: project/Test/ApiTest/src/main/cpp/help/nativehelper/JNIHelp.h ================================================ /* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * JNI helper functions. * * This file may be included by C or C++ code, which is trouble because jni.h * uses different typedefs for JNIEnv in each language. * * TODO: remove C support. */ #ifndef NATIVEHELPER_JNIHELP_H_ #define NATIVEHELPER_JNIHELP_H_ #include "jni.h" #include #ifndef NELEM # define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0]))) #endif #ifndef NATIVE_METHOD #define NATIVE_METHOD(function,functionName, signature) \ { functionName, signature, reinterpret_cast(function) } #endif #ifdef __cplusplus extern "C" { #endif /* * Register one or more native methods with a particular class. * "className" looks like "java/lang/String". Aborts on failure. * TODO: fix all callers and change the return type to void. */ int jniRegisterNativeMethods(C_JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods); int jniRegisterNativeMethods2(C_JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods); /* * Throw an exception with the specified class and an optional message. * * The "className" argument will be passed directly to FindClass, which * takes strings with slashes (e.g. "java/lang/Object"). * * If an exception is currently pending, we log a warning message and * clear it. * * Returns 0 on success, nonzero if something failed (e.g. the exception * class couldn't be found, so *an* exception will still be pending). * * Currently aborts the VM if it can't throw the exception. */ int jniThrowException(C_JNIEnv* env, const char* className, const char* msg); /* * Throw a java.lang.NullPointerException, with an optional message. */ int jniThrowNullPointerException(C_JNIEnv* env, const char* msg); /* * Throw a java.lang.RuntimeException, with an optional message. */ int jniThrowRuntimeException(C_JNIEnv* env, const char* msg); /* * Throw a java.io.IOException, generating the message from errno. */ int jniThrowIOException(C_JNIEnv* env, int errnum); /* * Return a pointer to a locale-dependent error string explaining errno * value 'errnum'. The returned pointer may or may not be equal to 'buf'. * This function is thread-safe (unlike strerror) and portable (unlike * strerror_r). */ const char* jniStrError(int errnum, char* buf, size_t buflen); /* * Returns a new java.io.FileDescriptor for the given int fd. */ jobject jniCreateFileDescriptor(C_JNIEnv* env, int fd); /* * Returns the int fd from a java.io.FileDescriptor. */ int jniGetFDFromFileDescriptor(C_JNIEnv* env, jobject fileDescriptor); /* * Sets the int fd in a java.io.FileDescriptor. */ void jniSetFileDescriptorOfFD(C_JNIEnv* env, jobject fileDescriptor, int value); /* * Returns the reference from a java.lang.ref.Reference. */ jobject jniGetReferent(C_JNIEnv* env, jobject ref); /* * Log a message and an exception. * If exception is NULL, logs the current exception in the JNI environment. */ void jniLogException(C_JNIEnv* env, int priority, const char* tag, jthrowable exception); #ifdef __cplusplus } #endif /* * For C++ code, we provide inlines that map to the C functions. g++ always * inlines these, even on non-optimized builds. */ #if defined(__cplusplus) inline int jniRegisterNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods) { return jniRegisterNativeMethods(&env->functions, className, gMethods, numMethods); } inline int jniRegisterNativeMethods2(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods) { return jniRegisterNativeMethods2(&env->functions, className, gMethods, numMethods); } inline int jniThrowException(JNIEnv* env, const char* className, const char* msg) { return jniThrowException(&env->functions, className, msg); } int jniThrowExceptionFmt(C_JNIEnv* env, const char* className, const char* fmt, va_list args); /* * Equivalent to jniThrowException but with a printf-like format string and * variable-length argument list. This is only available in C++. */ inline int jniThrowExceptionFmt(JNIEnv* env, const char* className, const char* fmt, ...) { va_list args; va_start(args, fmt); int re = jniThrowExceptionFmt(&env->functions, className, fmt, args); va_end(args); return re; } inline int jniThrowNullPointerException(JNIEnv* env, const char* msg) { return jniThrowNullPointerException(&env->functions, msg); } inline int jniThrowRuntimeException(JNIEnv* env, const char* msg) { return jniThrowRuntimeException(&env->functions, msg); } inline int jniThrowIOException(JNIEnv* env, int errnum) { return jniThrowIOException(&env->functions, errnum); } inline jobject jniCreateFileDescriptor(JNIEnv* env, int fd) { return jniCreateFileDescriptor(&env->functions, fd); } inline int jniGetFDFromFileDescriptor(JNIEnv* env, jobject fileDescriptor) { return jniGetFDFromFileDescriptor(&env->functions, fileDescriptor); } inline void jniSetFileDescriptorOfFD(JNIEnv* env, jobject fileDescriptor, int value) { jniSetFileDescriptorOfFD(&env->functions, fileDescriptor, value); } inline jobject jniGetReferent(JNIEnv* env, jobject ref) { return jniGetReferent(&env->functions, ref); } inline void jniLogException(JNIEnv* env, int priority, const char* tag, jthrowable exception = NULL) { jniLogException(&env->functions, priority, tag, exception); } #endif #endif /* NATIVEHELPER_JNIHELP_H_ */ ================================================ FILE: project/Test/ApiTest/src/main/java/com/example/ApiTest/ActivityTestActivity.java ================================================ package com.example.ApiTest; import android.content.Intent; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; public class ActivityTestActivity extends AppCompatActivity implements View.OnClickListener { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_activity_test); findViewById(R.id.button1).setOnClickListener(this); findViewById(R.id.button2).setOnClickListener(this); findViewById(R.id.button3).setOnClickListener(this); findViewById(R.id.button4).setOnClickListener(this); } @Override public void onClick(View v) { int id = v.getId(); if (id == R.id.button1) { startActivityForResult(new Intent(this, StandardActivity.class), 1986); } else if (id == R.id.button2) { startActivity(new Intent(this, SingleTopActivity.class)); } else if (id == R.id.button3) { startActivity(new Intent(this, SingleTaskActivity.class)); } else if (id == R.id.button4) { startActivity(new Intent(this, SingleInstanceActivity.class)); } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); Log.e("ActivityTestActivity", String.format("onActivityResult,requestCode=%s,resultCode=%s", requestCode, resultCode)); } } ================================================ FILE: project/Test/ApiTest/src/main/java/com/example/ApiTest/BaseService.java ================================================ package com.example.ApiTest; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.util.Log; import android.widget.Toast; /** * Created by zhangyong6 on 2015/3/11. */ public abstract class BaseService extends Service { protected String tag = "haha"; abstract String getTag(); @Override public IBinder onBind(Intent intent) { String msg = String.format(">>服务%s:onBind,intent=%s", tag, intent); Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); Log.e(tag, msg); return null; } @Override public boolean onUnbind(Intent intent) { String msg = String.format(">>服务%s:onUnbind,intent=%s", tag, intent); Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); Log.e(tag, msg); return super.onUnbind(intent); } @Override public void onCreate() { super.onCreate(); String msg = String.format(">>服务%s:onCreate", tag); Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); Log.e(tag, msg); } @Override public void onStart(Intent intent, int startId) { super.onStart(intent, startId); String msg = String.format(">>服务%s:onStart,intent=%s,startId=%s", tag, intent, startId); Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); Log.e(tag, msg); } @Override public int onStartCommand(Intent intent, int flags, int startId) { String msg = String.format(">>服务%s:onStartCommand,intent=%s,flags=%s,startId=%s", tag, intent, flags, startId); Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); Log.e(tag, msg); return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { String msg = String.format(">>服务%s:onDestroy", tag); Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); Log.e(tag, msg); super.onDestroy(); } @Override public void onRebind(Intent intent) { String msg = String.format(">>服务%s:onRebind,intent=%s", tag, intent); Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); Log.e(tag, msg); super.onRebind(intent); } } ================================================ FILE: project/Test/ApiTest/src/main/java/com/example/ApiTest/Binder1.aidl ================================================ package com.example.ApiTest; /** * Created by zhangyong6 on 2016/1/25. */ interface Binder1 { int ping(in int inValue); String pingStr(in String inValue); } ================================================ FILE: project/Test/ApiTest/src/main/java/com/example/ApiTest/Binder2.aidl ================================================ package com.example.ApiTest; /** * Created by zhangyong6 on 2016/1/25. */ interface Binder2 { int ping(in int inValue); String pingStr(in String inValue); } ================================================ FILE: project/Test/ApiTest/src/main/java/com/example/ApiTest/BroadcastReceiverTest.java ================================================ package com.example.ApiTest; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Toast; /** * Created by zhangyong6 on 2015/3/2. */ public class BroadcastReceiverTest extends AppCompatActivity implements OnClickListener { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.broadcast_receiver); findViewById(R.id.button1).setOnClickListener(this); findViewById(R.id.button2).setOnClickListener(this); findViewById(R.id.button3).setOnClickListener(this); findViewById(R.id.button4).setOnClickListener(this); } private static class MyBroadcastReceiver extends BroadcastReceiver { public static final String ACTION = "com.example.ApiTest.MyBroadcastReceiver"; private static final String TAG = "MyBroadcastReceiver"; public static void send(Context c) { Intent intent = new Intent(ACTION); c.sendBroadcast(intent); } public static void reg(Context c, MyBroadcastReceiver re) { IntentFilter filter = new IntentFilter(); filter.addAction(ACTION); c.registerReceiver(re, filter); } public static void unreg(Context c, MyBroadcastReceiver re) { c.unregisterReceiver(re); } @Override public void onReceive(Context context, Intent intent) { String msg = String.format(">>>>%s onReceive:context=%s,intent=%s", TAG, context, intent); Toast.makeText(context, msg, Toast.LENGTH_SHORT).show(); Log.e(TAG, msg); } } private MyBroadcastReceiver re = new MyBroadcastReceiver(); @Override public void onClick(View v) { int id = v.getId(); if (id == R.id.button1) { MyBroadcastReceiver.reg(this, re); } else if (id == R.id.button2) { MyBroadcastReceiver.unreg(this, re); } else if (id == R.id.button3) { MyBroadcastReceiver.send(this); } else if (id == R.id.button4) { StaticBroadcastReceiver.send(this); } } } ================================================ FILE: project/Test/ApiTest/src/main/java/com/example/ApiTest/ContentProviderTest.java ================================================ package com.example.ApiTest; import android.content.ContentValues; import android.database.Cursor; import android.database.DatabaseUtils; import android.net.Uri; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Toast; /** * Created by zhangyong6 on 2015/3/2. */ public class ContentProviderTest extends AppCompatActivity implements OnClickListener { protected static String TAG = "ContentProviderTest1"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.content_provider); findViewById(R.id.button1).setOnClickListener(this); findViewById(R.id.button2).setOnClickListener(this); findViewById(R.id.button3).setOnClickListener(this); findViewById(R.id.button4).setOnClickListener(this); findViewById(R.id.button5).setOnClickListener(this); findViewById(R.id.button6).setOnClickListener(this); TAG = "ContentProviderTest1"; } @Override public void onClick(View v) { int id = v.getId(); Uri sUri = getsUri(); if (id == R.id.button1) { Cursor cursor = getContentResolver().query(sUri, new String[]{"name", "sex"}, "a=? and b=?", new String[]{"11", "22"}, "id acs"); String msg = "查询失败"; if (cursor != null) { msg = String.format("query结果:%s", DatabaseUtils.dumpCursorToString(cursor)); cursor.close(); } Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); Log.e(TAG, msg); } else if (id == R.id.button2) { ContentValues value = new ContentValues(); value.put("name", "哈哈"); int re = getContentResolver().update(sUri, value, "a=? and b=?", new String[]{"11", "22"}); String msg = String.format("update结果:%s", re); Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); Log.e(TAG, msg); } else if (id == R.id.button3) { int re = getContentResolver().delete(sUri, "a=? and b=?", new String[]{"11", "22"}); String msg = String.format("delete结果:%s", re); Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); Log.e(TAG, msg); } else if (id == R.id.button4) { Bundle ex = new Bundle(); ex.putString("name", "哈哈"); Bundle re = getContentResolver().call(sUri, "method1", "arg1", ex); String msg = String.format("call结果:%s", re); Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); Log.e(TAG, msg); } else if (id == R.id.button5) { String re = getContentResolver().getType(sUri); String msg = String.format("getType结果:%s", re); Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); Log.e(TAG, msg); } else if (id == R.id.button6) { ContentValues value = new ContentValues(); value.put("name", "哈哈"); Uri re = getContentResolver().insert(sUri, value); String msg = String.format("insert结果:%s", re); Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); Log.e(TAG, msg); } } protected Uri getsUri() { return MyContentProvider1.sUri; } } ================================================ FILE: project/Test/ApiTest/src/main/java/com/example/ApiTest/ContentProviderTest2.java ================================================ package com.example.ApiTest; import android.net.Uri; import android.os.Bundle; /** * Created by zhangyong6 on 2015/3/11. */ public class ContentProviderTest2 extends ContentProviderTest { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); TAG = "ContentProviderTest2"; } protected Uri getsUri() { return MyContentProvider2.sUri; } } ================================================ FILE: project/Test/ApiTest/src/main/java/com/example/ApiTest/LaunchModeTestActivity.java ================================================ package com.example.ApiTest; import android.app.ActivityManager; import android.content.ComponentName; import android.content.Intent; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.text.Html; import android.view.View; import android.widget.TextView; import java.util.List; /** * Created by zhangyong6 on 2015/10/22. */ public abstract class LaunchModeTestActivity extends AppCompatActivity implements View.OnClickListener { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_launchmode); TextView textView = (TextView) findViewById(R.id.textView); textView.setText("当前实例:" + this); TextView textView2 = (TextView) findViewById(R.id.textView2); String text = "当前Activity栈:
" + getCurrentStack(); textView2.setText(Html.fromHtml(text)); findViewById(R.id.button1).setOnClickListener(this); findViewById(R.id.button2).setOnClickListener(this); findViewById(R.id.button3).setOnClickListener(this); findViewById(R.id.button4).setOnClickListener(this); findViewById(R.id.button5).setOnClickListener(this); findViewById(R.id.button6).setOnClickListener(this); findViewById(R.id.button7).setOnClickListener(this); findViewById(R.id.button8).setOnClickListener(this); findViewById(R.id.button9).setOnClickListener(this); findViewById(R.id.button10).setOnClickListener(this); findViewById(R.id.button11).setOnClickListener(this); findViewById(R.id.button12).setOnClickListener(this); findViewById(R.id.button13).setOnClickListener(this); findViewById(R.id.button14).setOnClickListener(this); findViewById(R.id.button15).setOnClickListener(this); findViewById(R.id.button16).setOnClickListener(this); findViewById(R.id.button17).setOnClickListener(this); findViewById(R.id.button18).setOnClickListener(this); findViewById(R.id.button19).setOnClickListener(this); } @Override public void onClick(View v) { int id = v.getId(); if (id == R.id.button1) { startActivity(new Intent(this, StandardActivity.class)); } else if (id == R.id.button2) { startActivity(new Intent(this, SingleTopActivity.class)); } else if (id == R.id.button3) { startActivity(new Intent(this, SingleTaskActivity.class)); } else if (id == R.id.button4) { startActivity(new Intent(this, SingleInstanceActivity.class)); } else if (id == R.id.button4) { startActivity(new Intent(this, SingleInstanceActivity.class)); } else if (id == R.id.button5) { startActivity(new Intent(this, SingleTopActivity.SingleTopActivity1.class)); } else if (id == R.id.button6) { startActivity(new Intent(this, SingleTopActivity.SingleTopActivity2.class)); } else if (id == R.id.button7) { startActivity(new Intent(this, SingleTopActivity.SingleTopActivity3.class)); } else if (id == R.id.button8) { startActivity(new Intent(this, SingleTopActivity.SingleTopActivity4.class)); } else if (id == R.id.button9) { startActivity(new Intent(this, SingleTopActivity.SingleTopActivity5.class)); } else if (id == R.id.button10) { startActivity(new Intent(this, SingleTaskActivity.SingleTaskActivity1.class)); } else if (id == R.id.button11) { startActivity(new Intent(this, SingleTaskActivity.SingleTaskActivity2.class)); } else if (id == R.id.button12) { startActivity(new Intent(this, SingleTaskActivity.SingleTaskActivity3.class)); } else if (id == R.id.button13) { startActivity(new Intent(this, SingleTaskActivity.SingleTaskActivity4.class)); } else if (id == R.id.button14) { startActivity(new Intent(this, SingleTaskActivity.SingleTaskActivity5.class)); } else if (id == R.id.button15) { startActivity(new Intent(this, SingleInstanceActivity.SingleInstanceActivity1.class)); } else if (id == R.id.button16) { startActivity(new Intent(this, SingleInstanceActivity.SingleInstanceActivity2.class)); } else if (id == R.id.button17) { startActivity(new Intent(this, SingleInstanceActivity.SingleInstanceActivity3.class)); } else if (id == R.id.button18) { startActivity(new Intent(this, SingleInstanceActivity.SingleInstanceActivity4.class)); } else if (id == R.id.button19) { startActivity(new Intent(this, SingleInstanceActivity.SingleInstanceActivity5.class)); } } public String getCurrentStack() { StringBuilder sb = new StringBuilder(); ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE); List infos = am.getRunningTasks(Integer.MAX_VALUE); for (int i = 0; i < infos.size(); i++) { ActivityManager.RunningTaskInfo info = infos.get(i); sb.append("Stack" + i + "").append("
"); sb.append("\t ID:" + info.id).append("
"); sb.append("\t Num Running:" + info.numRunning).append("
"); sb.append("\t Num Activities:" + info.numActivities).append("
"); sb.append("\t Description:" + info.description).append("
"); sb.append("\t Top Activity:" + toComponentName(info.topActivity)).append("
"); sb.append("\t Base Activity:" + toComponentName(info.baseActivity)).append("
"); } return sb.toString(); } private String toComponentName(ComponentName cn) { if (cn == null) { return null; } else { return cn.getPackageName() + "/" + cn.getShortClassName(); } } } ================================================ FILE: project/Test/ApiTest/src/main/java/com/example/ApiTest/MyActivity.java ================================================ package com.example.ApiTest; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Toast; import java.util.List; public class MyActivity extends AppCompatActivity implements OnClickListener { private static final String TAG = "MyActivity"; private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); findViewById(R.id.button1).setOnClickListener(this); findViewById(R.id.button2).setOnClickListener(this); findViewById(R.id.button3).setOnClickListener(this); findViewById(R.id.button4).setOnClickListener(this); findViewById(R.id.button5).setOnClickListener(this); findViewById(R.id.button6).setOnClickListener(this); findViewById(R.id.button7).setOnClickListener(this); findViewById(R.id.button8).setOnClickListener(this); findViewById(R.id.button9).setOnClickListener(this); findViewById(R.id.button10).setOnClickListener(this); findViewById(R.id.button11).setOnClickListener(this); } @Override public void onClick(View v) { int id = v.getId(); if (id == R.id.button1) { startActivity(new Intent(this, ServiceTest1.class)); } else if (id == R.id.button2) { startActivity(new Intent(this, ContentProviderTest.class)); } else if (id == R.id.button3) { startActivity(new Intent(this, BroadcastReceiverTest.class)); } else if (id == R.id.button4) { Toast.makeText(this, "哈哈Tost成功显示", Toast.LENGTH_SHORT).show(); } else if (id == R.id.button5) { startActivity(new Intent(this, NotificationTest.class)); } else if (id == R.id.button6) { startActivity(new Intent(this, ServiceTest2.class)); } else if (id == R.id.button7) { startActivity(new Intent(this, ContentProviderTest2.class)); } else if (id == R.id.button8) { try { PackageManager pm = getPackageManager(); List infos = pm.getInstalledApplications(0); if (infos != null && infos.size() > 0) { Log.e(TAG, "infos.size=" + infos.size()); for (ApplicationInfo info : infos) { Log.e(TAG, "info.pkg=" + info.loadLabel(pm) + ",pkg:" + info.packageName); } } } catch (Exception e) { Log.e(TAG, "e", e); } } else if (id == R.id.button9) { startActivity(new Intent(this, ActivityTestActivity.class)); } else if (id == R.id.button10) { startActivity(new Intent(this, NativeTestActivity.class)); } else if (id == R.id.button11) { startActivity(new Intent(this, WebViewTestActivity.class)); } } } ================================================ FILE: project/Test/ApiTest/src/main/java/com/example/ApiTest/MyContentProvider1.java ================================================ package com.example.ApiTest; import android.content.ContentProvider; import android.content.ContentValues; import android.database.Cursor; import android.database.MatrixCursor; import android.net.Uri; import android.os.Handler; import android.os.Looper; import android.util.Log; import android.widget.Toast; import java.util.Arrays; /** * Created by zhangyong6 on 2015/3/3. */ public class MyContentProvider1 extends ContentProvider { public static final String name = "com.example.ApiTest.MyContentProvider1"; public static final Uri sUri = Uri.parse("content://" + name); private static final String TAG = MyContentProvider1.class.getSimpleName(); @Override public boolean onCreate() { String msg = String.format(">>>>%s onCreate", TAG); showMsg(msg); return true; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { String msg = String.format(">>>>%s query:uri=%s, projection=%s, selection=%s,selectionArgs=%s, sortOrder=%s", TAG, uri, projection, selection, Arrays.toString(selectionArgs), sortOrder); showMsg(msg); MatrixCursor c = new MatrixCursor(new String[]{"name", "sex"}); c.addRow(new String[]{"张三", "男"}); c.addRow(new String[]{"李四", "女"}); c.addRow(new String[]{"王武", "不男不女"}); return c; } private void showMsg(final String msg) { Handler handler = new Handler(Looper.getMainLooper()); handler.post(new Runnable() { @Override public void run() { Toast.makeText(getContext(), msg, Toast.LENGTH_SHORT).show(); } }); Log.e(TAG, msg); } @Override public String getType(Uri uri) { String msg = String.format(">>>>%s getType:uri=%s", TAG, uri); showMsg(msg); return "text/x-zhangyong"; } @Override public Uri insert(Uri uri, ContentValues values) { String msg = String.format(">>>>%s insert:uri=%s, values=%s", TAG, uri, values); showMsg(msg); return Uri.parse("content://" + name + "/12"); } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { String msg = String.format(">>>>%s delete:uri=%s, selection=%s, selectionArgs=%s", TAG, uri, selection, Arrays.toString(selectionArgs)); showMsg(msg); return 66; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { String msg = String.format(">>>>%s update:uri=%s, values=%s, selection=%s, selectionArgs=%s", TAG, uri, values, selection, Arrays.toString(selectionArgs)); showMsg(msg); return 77; } } ================================================ FILE: project/Test/ApiTest/src/main/java/com/example/ApiTest/MyContentProvider2.java ================================================ package com.example.ApiTest; import android.content.ContentProvider; import android.content.ContentValues; import android.database.Cursor; import android.database.MatrixCursor; import android.net.Uri; import android.os.Handler; import android.os.Looper; import android.util.Log; import android.widget.Toast; import java.util.Arrays; /** * Created by zhangyong6 on 2015/3/3. */ public class MyContentProvider2 extends ContentProvider { public static final String name = "com.example.ApiTest.MyContentProvider2"; public static final Uri sUri = Uri.parse("content://" + name); private static final String TAG = MyContentProvider2.class.getSimpleName(); @Override public boolean onCreate() { String msg = String.format(">>>>%s onCreate", TAG); showMsg(msg); return true; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { String msg = String.format(">>>>%s query:uri=%s, projection=%s, selection=%s,selectionArgs=%s, sortOrder=%s", TAG, uri, projection, selection, Arrays.toString(selectionArgs), sortOrder); showMsg(msg); MatrixCursor c = new MatrixCursor(new String[]{"name", "sex"}); c.addRow(new String[]{"张三", "男"}); c.addRow(new String[]{"李四", "女"}); c.addRow(new String[]{"王武", "不男不女"}); return c; } private void showMsg(final String msg) { Handler handler = new Handler(Looper.getMainLooper()); handler.post(new Runnable() { @Override public void run() { Toast.makeText(getContext(), msg, Toast.LENGTH_SHORT).show(); } }); Log.e(TAG, msg); } @Override public String getType(Uri uri) { String msg = String.format(">>>>%s getType:uri=%s", TAG, uri); showMsg(msg); return "text/x-zhangyong"; } @Override public Uri insert(Uri uri, ContentValues values) { String msg = String.format(">>>>%s insert:uri=%s, values=%s", TAG, uri, values); showMsg(msg); return Uri.parse("content://" + name + "/12"); } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { String msg = String.format(">>>>%s delete:uri=%s, selection=%s, selectionArgs=%s", TAG, uri, selection, Arrays.toString(selectionArgs)); showMsg(msg); return 66; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { String msg = String.format(">>>>%s update:uri=%s, values=%s, selection=%s, selectionArgs=%s", TAG, uri, values, selection, Arrays.toString(selectionArgs)); showMsg(msg); return 77; } } ================================================ FILE: project/Test/ApiTest/src/main/java/com/example/ApiTest/NativeTestActivity.java ================================================ package com.example.ApiTest; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.util.Log; import com.morgoo.nativec.NativeCHelper; public class NativeTestActivity extends AppCompatActivity { private static final String TAG = NativeTestActivity.class.getSimpleName(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_native_test); Log.e(TAG, " NativeCHelper.ping()=" + NativeCHelper.ping()); } } ================================================ FILE: project/Test/ApiTest/src/main/java/com/example/ApiTest/NotificationTest.java ================================================ package com.example.ApiTest; import android.app.PendingIntent; import android.content.Intent; import android.os.Bundle; import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationManagerCompat; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Toast; /** * Created by zhangyong6 on 2015/3/3. */ public class NotificationTest extends AppCompatActivity implements OnClickListener { private static final String TAG = NotificationTest.class.getSimpleName(); private static final int ID = 0x1986; private static final int ID2 = 0x1988; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.notification_test); findViewById(R.id.button1).setOnClickListener(this); findViewById(R.id.button2).setOnClickListener(this); findViewById(R.id.button3).setOnClickListener(this); findViewById(R.id.button4).setOnClickListener(this); } @Override public void onClick(View v) { NotificationManagerCompat nm = NotificationManagerCompat.from(this); int id = v.getId(); if (id == R.id.button1) { NotificationCompat.Builder builder = new NotificationCompat.Builder(this); builder.setSmallIcon(R.drawable.ic_launcher); builder.setAutoCancel(true); builder.setContentInfo("ContentInfo1"). setContentText("ContentText1"). setContentTitle("ContentTitle1") .setTicker("Ticker1"); PendingIntent pd = PendingIntent.getActivity(getApplicationContext(), 0, new Intent(this, MyActivity.class), 0); builder.setContentIntent(pd); builder.setWhen(System.currentTimeMillis()); nm.notify(ID, builder.build()); String msg = "发送通知1成功"; Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); Log.e(TAG, msg); } else if (id == R.id.button2) { nm.cancel(ID); String msg = "取消通知1成功"; Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); Log.e(TAG, msg); } else if (id == R.id.button3) { NotificationCompat.Builder builder = new NotificationCompat.Builder(this); builder.setSmallIcon(R.drawable.ic_launcher); builder.setAutoCancel(true); builder.setContentInfo("ContentInfo2"). setContentText("ContentText2"). setContentTitle("ContentTitle2") .setTicker("Ticker2"); PendingIntent pd = PendingIntent.getActivity(getApplicationContext(), 0, new Intent(this, MyActivity.class), 0); builder.setContentIntent(pd); builder.setWhen(System.currentTimeMillis()); nm.notify("tag1", ID2, builder.build()); String msg = "发送通知2成功"; Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); Log.e(TAG, msg); } else if (id == R.id.button4) { nm.cancel("tag1", ID2); String msg = "取消通知2成功"; Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); Log.e(TAG, msg); } } } ================================================ FILE: project/Test/ApiTest/src/main/java/com/example/ApiTest/Service1.java ================================================ package com.example.ApiTest; import android.content.Intent; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; import android.util.Log; import android.widget.Toast; /** * Created by zhangyong6 on 2015/3/2. */ public class Service1 extends BaseService { @Override String getTag() { return Service1.class.getSimpleName(); } public Service1() { tag = getTag(); } @Override public IBinder onBind(final Intent intent) { super.onBind(intent); Binder1.Stub stub = new Binder1.Stub() { private Handler handler = new Handler(Looper.getMainLooper()); @Override public int ping(int inValue) throws RemoteException { int i = inValue + 10; final String msg = String.format(">>服务%s:onBind,intent=%s ping=%s,value=%s", tag, intent, inValue, i); handler.post(new Runnable() { @Override public void run() { Toast.makeText(Service1.this, msg, Toast.LENGTH_SHORT).show(); } }); Log.e(tag, msg); return i; } @Override public String pingStr(String inValue) throws RemoteException { String i = inValue + ",Yes"; final String msg = String.format(">>服务%s:onBind,intent=%s ping=%s,value=%s", tag, intent, inValue, i); handler.post(new Runnable() { @Override public void run() { Toast.makeText(Service1.this, msg, Toast.LENGTH_SHORT).show(); } }); Log.e(tag, msg); return i; } }; return stub; } } ================================================ FILE: project/Test/ApiTest/src/main/java/com/example/ApiTest/Service2.java ================================================ package com.example.ApiTest; import android.content.Intent; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; import android.util.Log; import android.widget.Toast; /** * Created by zhangyong6 on 2015/3/2. */ public class Service2 extends BaseService { @Override String getTag() { return Service2.class.getSimpleName(); } public Service2() { tag = getTag(); } @Override public IBinder onBind(final Intent intent) { super.onBind(intent); return new Binder2.Stub() { private Handler handler = new Handler(Looper.getMainLooper()); @Override public int ping(int inValue) throws RemoteException { int i = inValue + 15; final String msg = String.format(">>服务%s:onBind,intent=%s ping=%s,value=%s", tag, intent, inValue, i); handler.post(new Runnable() { @Override public void run() { Toast.makeText(Service2.this, msg, Toast.LENGTH_SHORT).show(); } }); Log.e(tag, msg); return i; } @Override public String pingStr(String inValue) throws RemoteException { String i = inValue + ",Yes,very much!"; final String msg = String.format(">>服务%s:onBind,intent=%s ping=%s,value=%s", tag, intent, inValue, i); handler.post(new Runnable() { @Override public void run() { Toast.makeText(Service2.this, msg, Toast.LENGTH_SHORT).show(); } }); Log.e(tag, msg); return i; } }; } } ================================================ FILE: project/Test/ApiTest/src/main/java/com/example/ApiTest/Service3.java ================================================ package com.example.ApiTest; /** * Created by zhangyong6 on 2015/3/2. */ public class Service3 extends Service1 { @Override String getTag() { return Service3.class.getSimpleName(); } public Service3() { tag = getTag(); } } ================================================ FILE: project/Test/ApiTest/src/main/java/com/example/ApiTest/Service4.java ================================================ package com.example.ApiTest; /** * Created by zhangyong6 on 2015/3/2. */ public class Service4 extends Service2 { @Override String getTag() { return Service4.class.getSimpleName(); } public Service4() { tag = getTag(); } } ================================================ FILE: project/Test/ApiTest/src/main/java/com/example/ApiTest/ServiceTest1.java ================================================ package com.example.ApiTest; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Toast; /** * Created by zhangyong6 on 2015/3/2. */ public class ServiceTest1 extends AppCompatActivity implements OnClickListener { private static final String TAG = "ServiceTest"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.service); findViewById(R.id.button1).setOnClickListener(this); findViewById(R.id.button2).setOnClickListener(this); findViewById(R.id.button3).setOnClickListener(this); findViewById(R.id.button4).setOnClickListener(this); findViewById(R.id.button5).setOnClickListener(this); findViewById(R.id.button6).setOnClickListener(this); findViewById(R.id.button7).setOnClickListener(this); findViewById(R.id.button8).setOnClickListener(this); service1 = new Intent(this, Service1.class); service2 = new Intent(this, Service2.class); } protected Intent service1, service2; private ServiceConnection sc1 = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { String msg = String.format("服务%s,onServiceConnected:name=%s,service=%s", "service1", name, service); Toast.makeText(ServiceTest1.this, msg, Toast.LENGTH_SHORT).show(); Log.e(TAG, msg); try { Binder1 binder1 = Binder1.Stub.asInterface(service); msg = String.format("onServiceConnected,binder1=%s,pind(2016)=%s,pingStr(Is Andy Zhang handsome?)=%s", binder1, binder1.ping(2016), binder1.pingStr("Is Andy Zhang handsome?")); Log.e(TAG, msg); } catch (RemoteException e) { Log.e(TAG, "", e); } } @Override public void onServiceDisconnected(ComponentName name) { String msg = String.format("服务%s,onServiceDisconnected:name=%s", "service1", name); Toast.makeText(ServiceTest1.this, msg, Toast.LENGTH_SHORT).show(); Log.e(TAG, msg); } }; private ServiceConnection sc2 = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { String msg = String.format("服务%s,onServiceConnected:name=%s,service=%s", "service2", name, service); Toast.makeText(ServiceTest1.this, msg, Toast.LENGTH_SHORT).show(); Log.e(TAG, msg); try { Binder2 binder1 = Binder2.Stub.asInterface(service); msg = String.format("onServiceConnected,Binder2=%s,pind(2016)=%s,pingStr(Is Andy Zhang handsome?)=%s", binder1, binder1.ping(2016), binder1.pingStr("Is Andy Zhang handsome?")); Log.e(TAG, msg); } catch (RemoteException e) { Log.e(TAG, "", e); } } @Override public void onServiceDisconnected(ComponentName name) { String msg = String.format("服务%s,onServiceDisconnected:name=%s", "service2", name); Toast.makeText(ServiceTest1.this, msg, Toast.LENGTH_SHORT).show(); Log.e(TAG, msg); } }; @Override public void onClick(View v) { Context context = getApplicationContext(); int id = v.getId(); if (id == R.id.button1) { boolean re = context.bindService(service1, sc1, Context.BIND_AUTO_CREATE); String msg = String.format("绑定服务%s,结果:%s", "service1", re); Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); Log.e(TAG, msg); } else if (id == R.id.button2) { context.unbindService(sc1); String msg = String.format("解绑服务%s", "service1"); Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); Log.e(TAG, msg); } else if (id == R.id.button3) { boolean re = context.bindService(service2, sc2, Context.BIND_AUTO_CREATE); String msg = String.format("绑定服务%s,结果:%s", "service2", re); Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); Log.e(TAG, msg); } else if (id == R.id.button4) { context.unbindService(sc2); String msg = String.format("解绑服务%s", "service2"); Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); Log.e(TAG, msg); } else if (id == R.id.button5) { ComponentName cn = context.startService(service1); String msg = String.format("启动服务%s,结果:%s", "service1", cn); Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); Log.e(TAG, msg); } else if (id == R.id.button6) { boolean re = context.stopService(service1); String msg = String.format("停止服务%s,结果:%s", "service1", re); Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); Log.e(TAG, msg); } else if (id == R.id.button7) { ComponentName cn = context.startService(service2); String msg = String.format("启动服务%s,结果:%s", "service2", cn); Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); Log.e(TAG, msg); } else if (id == R.id.button8) { boolean re = context.stopService(service2); String msg = String.format("停止服务%s,结果:%s", "service2", re); Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); Log.e(TAG, msg); } } } ================================================ FILE: project/Test/ApiTest/src/main/java/com/example/ApiTest/ServiceTest2.java ================================================ package com.example.ApiTest; import android.content.Intent; import android.os.Bundle; /** * Created by zhangyong6 on 2015/3/11. */ public class ServiceTest2 extends ServiceTest1 { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); service1 = new Intent(this, Service3.class); service2 = new Intent(this, Service4.class); } } ================================================ FILE: project/Test/ApiTest/src/main/java/com/example/ApiTest/SingleInstanceActivity.java ================================================ package com.example.ApiTest; public class SingleInstanceActivity extends LaunchModeTestActivity { public static class SingleInstanceActivity1 extends SingleInstanceActivity { } public static class SingleInstanceActivity2 extends SingleInstanceActivity { } public static class SingleInstanceActivity3 extends SingleInstanceActivity { } public static class SingleInstanceActivity4 extends SingleInstanceActivity { } public static class SingleInstanceActivity5 extends SingleInstanceActivity { } } ================================================ FILE: project/Test/ApiTest/src/main/java/com/example/ApiTest/SingleTaskActivity.java ================================================ package com.example.ApiTest; public class SingleTaskActivity extends LaunchModeTestActivity{ public static class SingleTaskActivity1 extends SingleTaskActivity{} public static class SingleTaskActivity2 extends SingleTaskActivity{} public static class SingleTaskActivity3 extends SingleTaskActivity{} public static class SingleTaskActivity4 extends SingleTaskActivity{} public static class SingleTaskActivity5 extends SingleTaskActivity{} } ================================================ FILE: project/Test/ApiTest/src/main/java/com/example/ApiTest/SingleTopActivity.java ================================================ package com.example.ApiTest; public class SingleTopActivity extends LaunchModeTestActivity { public static class SingleTopActivity1 extends SingleTopActivity{} public static class SingleTopActivity2 extends SingleTopActivity{} public static class SingleTopActivity3 extends SingleTopActivity{} public static class SingleTopActivity4 extends SingleTopActivity{} public static class SingleTopActivity5 extends SingleTopActivity{} } ================================================ FILE: project/Test/ApiTest/src/main/java/com/example/ApiTest/StandardActivity.java ================================================ package com.example.ApiTest; public class StandardActivity extends LaunchModeTestActivity { } ================================================ FILE: project/Test/ApiTest/src/main/java/com/example/ApiTest/StaticBroadcastReceiver.java ================================================ package com.example.ApiTest; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.util.Log; import android.widget.Toast; /** * Created by zhangyong6 on 2015/3/3. */ public class StaticBroadcastReceiver extends BroadcastReceiver { public static final String ACTION = "com.example.ApiTest.StaticBroadcastReceiver"; private static final String TAG = "StaticBroadcastReceiver"; public static void send(Context c) { Intent intent = new Intent(ACTION); c.sendBroadcast(intent); } @Override public void onReceive(Context context, Intent intent) { String msg = String.format(">>>>%s onReceive:context=%s,intent=%s", TAG, context, intent); Toast.makeText(context, msg, Toast.LENGTH_SHORT).show(); Log.e(TAG, msg); } } ================================================ FILE: project/Test/ApiTest/src/main/java/com/example/ApiTest/WebViewTestActivity.java ================================================ package com.example.ApiTest; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.webkit.WebView; import android.webkit.WebViewClient; public class WebViewTestActivity extends AppCompatActivity { private WebView mWebView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_web_view_test); mWebView = (WebView) findViewById(R.id.webview); mWebView.getSettings().setJavaScriptEnabled(true); mWebView.setWebViewClient(new WebViewClient() { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { return true; } }); mWebView.loadUrl("http://www.baidu.com"); } } ================================================ FILE: project/Test/ApiTest/src/main/java/com/morgoo/nativec/NativeCHelper.java ================================================ /** * */ package com.morgoo.nativec; /** * @author zhangyong232@gmail.com */ public class NativeCHelper { private static boolean sSoLoaded = false; private static Throwable sThrowable; static { tryLoadLibraryByName("Test"); } public static void tryLoadLibraryByName(String name) { try { System.loadLibrary(name); sSoLoaded = true; } catch (Throwable e) { sThrowable = e; sSoLoaded = false; } } public static void tryLoadLibraryByPath(String pathName) { try { System.load(pathName); sSoLoaded = true; } catch (Throwable e) { sThrowable = e; sSoLoaded = false; } } public static boolean isSoLoaded() { return sSoLoaded; } // ************************ Helper Start *******************************// private final native static int nativePing(); public final static int ping() { if (sSoLoaded) { return nativePing(); } else { if (sThrowable != null) { String msg = sThrowable.getMessage(); UnsatisfiedLinkError error = new UnsatisfiedLinkError( msg != null ? msg : "Can not lazy init zhook"); error.initCause(sThrowable); throw error; } else { throw new UnsatisfiedLinkError( "We can not load so,please see logcat"); } } } // ************************ Helper End *******************************// } ================================================ FILE: project/Test/ApiTest/src/main/res/layout/activity_activity_test.xml ================================================