// Path: app/src/main/java/me/yifeiyuan/climb/tools/utils/ChannelUtil.java
// public class ChannelUtil {
// 
//     private static final String TAG = "ChannelUtil";
//     private static final String CHANNEL_KEY = "channel";
// 
//     private static String sChannel;
// 
//     /**
//      * @param context
//      *
//      * @return 渠道 默认为空
//      */
//     public static String getChannel(Context context) {
//         return getChannel(context, "");
//     }
// 
//     /**
//      * @param context
//      * @param defaultChannel 默认值
//      *
//      * @return 渠道
//      */
//     public static String getChannel(Context context, String defaultChannel) {
//         //内存中获取
//         if (!TextUtils.isEmpty(sChannel)) {
//             return sChannel;
//         }
//         //从apk中获取
//         sChannel = getChannelFromApk(context, CHANNEL_KEY);
//         //全部获取失败
//         return defaultChannel;
//     }
// 
//     /**
//      * 从apk中获取版本信息
//      *
//      * @param context
//      * @param channelKey
//      *
//      * @return
//      */
//     private static String getChannelFromApk(Context context, String channelKey) {
//         //从apk包中获取
//         ApplicationInfo appinfo = context.getApplicationInfo();
//         String sourceDir = appinfo.sourceDir;
//         //默认放在meta-inf/里
//         String key = "META-INF/" + channelKey;
//         String ret = "";
//         ZipFile zipfile = null;
//         try {
//             zipfile = new ZipFile(sourceDir);
//             Enumeration<?> entries = zipfile.entries();
//             while (entries.hasMoreElements()) {
//                 ZipEntry entry = ((ZipEntry) entries.nextElement());
//                 String entryName = entry.getName();
//                 if (entryName.startsWith(key)) {
//                     ret = entryName;
//                     break;
//                 }
//             }
//         } catch (IOException e) {
//             e.printStackTrace();
//         } finally {
//             if (zipfile != null) {
//                 try {
//                     zipfile.close();
//                 } catch (IOException e) {
//                     e.printStackTrace();
//                 }
//             }
//         }
//         String[] split = ret.split("_");
//         String channel = "";
//         if (split.length >= 2) {
//             channel = ret.substring(split[0].length() + 1);
//         }
//         Log.i(TAG, "getChannelFromApk: " + channel);
//         return channel;
//     }
// }


import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.text.TextUtils;
import android.util.Log;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import me.yifeiyuan.climb.tools.utils.ChannelUtil;
/*
 * Copyright (C) 2016, 程序亦非猿
 *
 * 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 me.yifeiyuan.climb.hook;




/**
 * Created by 程序亦非猿 on 16/8/22.
 * hook ActivityThread.sPackageManager
 * @see #hook
 *
 */
public class PackageManagerHook  {

    private static String CHANNEL_KEY = "UMENG_CHANNEL";

    /**
     * 越早 hook 越好，推荐在 attachBaseContext 调用
     * @param context base
     */
    public static void hook(Context context) {

        try {

            // 获取 activityThread
            Class<?> activityThreadClazz = Class.forName("android.app.ActivityThread", false, context.getClassLoader());
            Method currentActivityThread = activityThreadClazz.getDeclaredMethod("currentActivityThread");
            currentActivityThread.setAccessible(true);
            Object activityThread = currentActivityThread.invoke(null);

            // 获取 activityThread 的 packageManager
            Method getPackageManager = activityThreadClazz.getDeclaredMethod("getPackageManager");
            getPackageManager.setAccessible(true);
            Object pkgManager = getPackageManager.invoke(activityThread);//IPackageManager$Stub$Proxy

            // 动态代理
            Class<?> packageManagerClazz = Class.forName("android.content.pm.IPackageManager", false, context.getClassLoader());

            Object pmProxy = Proxy.newProxyInstance(context.getClassLoader(), new Class[]{packageManagerClazz}, new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                    Object result = method.invoke(pkgManager, args);

                    //拦截getApplicationInfo方法 umeng 获取 CHANNEL_KEY 就是通过它
                    //其它的方法就让它自己过去吧
                    if ("getApplicationInfo".equals(method.getName())) {
                        int mask = (int) args[1];
                        if (mask == PackageManager.GET_META_DATA) {
                            ApplicationInfo appInfo = (ApplicationInfo) result;
                            if (null != appInfo.metaData&&appInfo.metaData.containsKey(CHANNEL_KEY)) {
                                String channel = String.valueOf(appInfo.metaData.get(CHANNEL_KEY));
                                Log.d("Channel before", "："+channel);
// Path: app/src/main/java/me/yifeiyuan/climb/tools/utils/ChannelUtil.java
// public class ChannelUtil {
// 
//     private static final String TAG = "ChannelUtil";
//     private static final String CHANNEL_KEY = "channel";
// 
//     private static String sChannel;
// 
//     /**
//      * @param context
//      *
//      * @return 渠道 默认为空
//      */
//     public static String getChannel(Context context) {
//         return getChannel(context, "");
//     }
// 
//     /**
//      * @param context
//      * @param defaultChannel 默认值
//      *
//      * @return 渠道
//      */
//     public static String getChannel(Context context, String defaultChannel) {
//         //内存中获取
//         if (!TextUtils.isEmpty(sChannel)) {
//             return sChannel;
//         }
//         //从apk中获取
//         sChannel = getChannelFromApk(context, CHANNEL_KEY);
//         //全部获取失败
//         return defaultChannel;
//     }
// 
//     /**
//      * 从apk中获取版本信息
//      *
//      * @param context
//      * @param channelKey
//      *
//      * @return
//      */
//     private static String getChannelFromApk(Context context, String channelKey) {
//         //从apk包中获取
//         ApplicationInfo appinfo = context.getApplicationInfo();
//         String sourceDir = appinfo.sourceDir;
//         //默认放在meta-inf/里
//         String key = "META-INF/" + channelKey;
//         String ret = "";
//         ZipFile zipfile = null;
//         try {
//             zipfile = new ZipFile(sourceDir);
//             Enumeration<?> entries = zipfile.entries();
//             while (entries.hasMoreElements()) {
//                 ZipEntry entry = ((ZipEntry) entries.nextElement());
//                 String entryName = entry.getName();
//                 if (entryName.startsWith(key)) {
//                     ret = entryName;
//                     break;
//                 }
//             }
//         } catch (IOException e) {
//             e.printStackTrace();
//         } finally {
//             if (zipfile != null) {
//                 try {
//                     zipfile.close();
//                 } catch (IOException e) {
//                     e.printStackTrace();
//                 }
//             }
//         }
//         String[] split = ret.split("_");
//         String channel = "";
//         if (split.length >= 2) {
//             channel = ret.substring(split[0].length() + 1);
//         }
//         Log.i(TAG, "getChannelFromApk: " + channel);
//         return channel;
//     }
// }


// Path: app/src/main/java/me/yifeiyuan/climb/hook/PackageManagerHook.java
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.text.TextUtils;
import android.util.Log;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import me.yifeiyuan.climb.tools.utils.ChannelUtil;

/*
 * Copyright (C) 2016, 程序亦非猿
 *
 * 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 me.yifeiyuan.climb.hook;




/**
 * Created by 程序亦非猿 on 16/8/22.
 * hook ActivityThread.sPackageManager
 * @see #hook
 *
 */
public class PackageManagerHook  {

    private static String CHANNEL_KEY = "UMENG_CHANNEL";

    /**
     * 越早 hook 越好，推荐在 attachBaseContext 调用
     * @param context base
     */
    public static void hook(Context context) {

        try {

            // 获取 activityThread
            Class<?> activityThreadClazz = Class.forName("android.app.ActivityThread", false, context.getClassLoader());
            Method currentActivityThread = activityThreadClazz.getDeclaredMethod("currentActivityThread");
            currentActivityThread.setAccessible(true);
            Object activityThread = currentActivityThread.invoke(null);

            // 获取 activityThread 的 packageManager
            Method getPackageManager = activityThreadClazz.getDeclaredMethod("getPackageManager");
            getPackageManager.setAccessible(true);
            Object pkgManager = getPackageManager.invoke(activityThread);//IPackageManager$Stub$Proxy

            // 动态代理
            Class<?> packageManagerClazz = Class.forName("android.content.pm.IPackageManager", false, context.getClassLoader());

            Object pmProxy = Proxy.newProxyInstance(context.getClassLoader(), new Class[]{packageManagerClazz}, new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                    Object result = method.invoke(pkgManager, args);

                    //拦截getApplicationInfo方法 umeng 获取 CHANNEL_KEY 就是通过它
                    //其它的方法就让它自己过去吧
                    if ("getApplicationInfo".equals(method.getName())) {
                        int mask = (int) args[1];
                        if (mask == PackageManager.GET_META_DATA) {
                            ApplicationInfo appInfo = (ApplicationInfo) result;
                            if (null != appInfo.metaData&&appInfo.metaData.containsKey(CHANNEL_KEY)) {
                                String channel = String.valueOf(appInfo.metaData.get(CHANNEL_KEY));
                                Log.d("Channel before", "："+channel);
                                String newChannel = ChannelUtil.getChannel(context);