package guard.api.sensitive.ui.sapiguard;

import android.net.Uri;
import android.os.Build;
import android.os.Process;
import android.text.TextUtils;
import android.util.Log;
import android.view.SurfaceView;
import android.view.Window;
import android.view.WindowManager;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;

import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.IXposedHookZygoteInit;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam;
import guard.api.sensitive.ui.sapiguard.hookedapis.XAccountManager;
import guard.api.sensitive.ui.sapiguard.hookedapis.XActivityManager;
import guard.api.sensitive.ui.sapiguard.hookedapis.XActivityRecognitionClient;
import guard.api.sensitive.ui.sapiguard.hookedapis.XAdvertisingIdClientInfo;
import guard.api.sensitive.ui.sapiguard.hookedapis.XAudioRecord;
import guard.api.sensitive.ui.sapiguard.hookedapis.XBluetoothAdapter;
import guard.api.sensitive.ui.sapiguard.hookedapis.XBluetoothDevice;
import guard.api.sensitive.ui.sapiguard.hookedapis.XCamera;
import guard.api.sensitive.ui.sapiguard.hookedapis.XCameraDevice2;
import guard.api.sensitive.ui.sapiguard.hookedapis.XCastDevice;
import guard.api.sensitive.ui.sapiguard.hookedapis.XContentResolver;
import guard.api.sensitive.ui.sapiguard.hookedapis.XGoogleApiClient;
import guard.api.sensitive.ui.sapiguard.hookedapis.XGoogleAuthUtil;
import guard.api.sensitive.ui.sapiguard.hookedapis.XGoogleMapV1;
import guard.api.sensitive.ui.sapiguard.hookedapis.XGoogleMapV2;
import guard.api.sensitive.ui.sapiguard.hookedapis.XLocationClient;
import guard.api.sensitive.ui.sapiguard.hookedapis.XLocationManager;
import guard.api.sensitive.ui.sapiguard.hookedapis.XMediaRecorder;
import guard.api.sensitive.ui.sapiguard.hookedapis.XNfcAdapter;
import guard.api.sensitive.ui.sapiguard.hookedapis.XProcess;
import guard.api.sensitive.ui.sapiguard.hookedapis.XProcessBuilder;
import guard.api.sensitive.ui.sapiguard.hookedapis.XRuntime;
import guard.api.sensitive.ui.sapiguard.hookedapis.XSmsManager;
import guard.api.sensitive.ui.sapiguard.hookedapis.XTelephonyManager;
import guard.api.sensitive.ui.sapiguard.hookedapis.XUsageStatsManager;
import guard.api.sensitive.ui.sapiguard.hookedapis.XWifiManager;
import guard.api.sensitive.ui.sapiguard.utils.CRestriction;
import guard.api.sensitive.ui.sapiguard.utils.Hook;
import guard.api.sensitive.ui.sapiguard.utils.PrivacyManager;
import guard.api.sensitive.ui.sapiguard.utils.Util;
import guard.api.sensitive.ui.sapiguard.utils.XParam;
import guard.api.sensitive.ui.sapiguard.utils.XUtilHook;

import static de.robv.android.xposed.XposedHelpers.findClass;

public class XGuard implements IXposedHookLoadPackage, IXposedHookZygoteInit {
    private static String mSecret = null;
    private static List<String> mListHookError = new ArrayList<String>();
    private static List<CRestriction> mListDisabled = new ArrayList<CRestriction>();
    private final XC_MethodHook mRemoveSecureFlagHook = new XC_MethodHook() {
        @Override
        protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
            Integer flags = (Integer) param.args[0];
            flags &= ~WindowManager.LayoutParams.FLAG_SECURE;
            param.args[0] = flags;
        }
    };
    private final XC_MethodHook mRemoveSetSecureHook = new XC_MethodHook() {
        @Override
        protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
            param.args[0] = false;
        }
    };

    public static void hookAll(List<XHook> listHook, ClassLoader classLoader, String secret, boolean dynamic) {
        for (XHook hook : listHook)
            if (hook.getRestrictionName() == null)
                hook(hook, classLoader, secret);
            else {
                CRestriction crestriction = new CRestriction(0, hook.getRestrictionName(), null, null);
                CRestriction mrestriction = new CRestriction(0, hook.getRestrictionName(), hook.getMethodName(), null);
                if (mListDisabled.contains(crestriction) || mListDisabled.contains(mrestriction))
                    Util.log(hook, Log.WARN, "Skipping disabled hook " + hook);
                else
                    hook(hook, classLoader, secret);
            }
    }

    private static void hook(final XHook hook, ClassLoader classLoader, String secret) {
        // Get meta data
        Hook md = PrivacyManager.getHook(hook.getRestrictionName(), hook.getSpecifier());
        if (md == null) {
            String message = "Not found hook=" + hook;
            mListHookError.add(message);
            Util.log(hook, Log.ERROR, message);
        } else if (!md.isAvailable())
            return;

        // Provide secret
        if (secret == null)
            Util.log(hook, Log.ERROR, "Secret missing hook=" + hook);
        hook.setSecret(secret);

        try {
            // Find class
            Class<?> hookClass = null;
            try {
                hookClass = findClass(hook.getClassName(), classLoader);
            } catch (Throwable ex) {
                String message = "Class not found hook=" + hook;
                int level = (md != null && md.isOptional() ? Log.WARN : Log.ERROR);
                if ("isXposedEnabled".equals(hook.getMethodName()))
                    level = Log.WARN;
                if (level == Log.ERROR)
                    mListHookError.add(message);
                Util.log(hook, level, message);
                return;
            }

            // Get members
            List<Member> listMember = new ArrayList<Member>();
            List<Class<?>[]> listParameters = new ArrayList<Class<?>[]>();
            Class<?> clazz = hookClass;
            while (clazz != null && !"android.content.ContentProvider".equals(clazz.getName()))
                try {
                    if (hook.getMethodName() == null) {
                        for (Constructor<?> constructor : clazz.getDeclaredConstructors())
                            if (!Modifier.isAbstract(constructor.getModifiers())
                                    && Modifier.isPublic(constructor.getModifiers()) ? hook.isVisible() : !hook
                                    .isVisible())
                                listMember.add(constructor);
                        break;
                    } else {
                        for (Method method : clazz.getDeclaredMethods())
                            if (method.getName().equals(hook.getMethodName())
                                    && !Modifier.isAbstract(method.getModifiers())
                                    && (Modifier.isPublic(method.getModifiers()) ? hook.isVisible() : !hook.isVisible())) {

                                // Check for same function in sub class
                                boolean different = true;
                                for (Class<?>[] parameters : listParameters) {
                                    boolean same = (parameters.length == method.getParameterTypes().length);
                                    for (int p = 0; same && p < parameters.length; p++)
                                        if (!parameters[p].equals(method.getParameterTypes()[p])) {
                                            same = false;
                                            break;
                                        }
                                    if (same) {
                                        different = false;
                                        break;
                                    }
                                }

                                if (different) {
                                    listMember.add(method);
                                    listParameters.add(method.getParameterTypes());
                                }
                            }
                    }
                    clazz = clazz.getSuperclass();
                } catch (Throwable ex) {
                    if (ex.getClass().equals(ClassNotFoundException.class)
                            || ex.getClass().equals(NoClassDefFoundError.class))
                        break;
                    else
                        throw ex;
                }

            // Hook members
            for (Member member : listMember)
                try {
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
                        if ((member.getModifiers() & Modifier.NATIVE) != 0)
                            Util.log(hook, Log.WARN, "Native method=" + member);
                    XposedBridge.hookMethod(member, new XMethodHook(hook));
                } catch (NoSuchFieldError ex) {
                    Util.log(hook, Log.WARN, ex.toString());
                } catch (Throwable ex) {
                    mListHookError.add(ex.toString());
                    Util.bug(hook, ex);
                }

            // Check if members found
            if (listMember.isEmpty() && !hook.getClassName().startsWith("com.google.android.gms")) {
                String message = "Method not found hook=" + hook;
                int level = (md != null && md.isOptional() ? Log.WARN : Log.ERROR);
                if ("isXposedEnabled".equals(hook.getMethodName()))
                    level = Log.WARN;
                if (level == Log.ERROR)
                    mListHookError.add(message);
                Util.log(hook, level, message);
            }
        } catch (Throwable ex) {
            mListHookError.add(ex.toString());
            Util.bug(hook, ex);
        }
    }

    private static String  handleUribefore(XParam param) throws Throwable {
        String restrictionName = null;

        if (param.args.length > 1 && param.args[0] instanceof Uri) {
            String uri = ((Uri) param.args[0]).toString().toLowerCase();
            if (uri.startsWith("content://com.android.contacts/")){
                restrictionName = PrivacyManager.cContacts;
            }else if (uri.startsWith("content://com.android.calendar")) {
                restrictionName = PrivacyManager.cCalendar;
            }else if (uri.startsWith("content://call_log")) {
                restrictionName = PrivacyManager.cCalling;
            }else if (uri.startsWith("content://contacts/people")) {
                restrictionName = PrivacyManager.cContacts;
            }else if (uri.startsWith("content://downloads")) {
                restrictionName = PrivacyManager.cBrowser;
            }else if (uri.startsWith("content://com.android.email.provider")) {
                restrictionName = PrivacyManager.cEMail;
            }else if (uri.startsWith("content://com.google.android.gm")) {
                restrictionName = PrivacyManager.cEMail;
            }else if (uri.startsWith("content://icc")) {
                restrictionName = PrivacyManager.cContacts;
            }else if (uri.startsWith("content://mms")) {
                restrictionName = PrivacyManager.cMessages;
            }else if (uri.startsWith("content://mms-sms")) {
                restrictionName = PrivacyManager.cMessages;
            }else if (uri.startsWith("content://sms")) {
                restrictionName = PrivacyManager.cMessages;
            }else if (uri.startsWith("content://telephony")) {
                restrictionName = PrivacyManager.cPhone;
            }else if (uri.startsWith("content://user_dictionary")) {
                restrictionName = PrivacyManager.cDictionary;
            }else if (uri.startsWith("content://com.android.voicemail")) {
                restrictionName = PrivacyManager.cMessages;
            }
        }
        return  restrictionName;
    }

    private static String checkStackTraceofHookedMtd(String classNmae, String mtdName){
        StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace();
        boolean reachHookMtd = false;
        StringBuilder result = new StringBuilder();
        String NEW_LINE = System.getProperty("line.separator");
        int deepsofHookApi = 0;
        boolean containAppCode = false;
        for (StackTraceElement element : stacktrace) {
            if (element.getClassName().equals(classNmae) && element.getMethodName().equals(mtdName))
                reachHookMtd = true;
            if (reachHookMtd){
                result.append(element);
                result.append(NEW_LINE);
                if (isAppDeveloperCode(element.getClassName())) {
                    containAppCode =true;
                }

                if (!containAppCode){
                    deepsofHookApi+=1;
                    if (deepsofHookApi>4)
                        break;
                }
            }
        }

        if (containAppCode)
            return result.toString();
        return null;
    }

    private static boolean isJavaLibraryClass(String className) {
        if (className.startsWith("java.") || className.startsWith("sun.")
                || className.startsWith("javax.") || className.startsWith("com.sun.")
                || className.startsWith("org.omg.") || className.startsWith("org.xml.")
                || className.startsWith("org.w3c.dom"))
            return true;

        return false;
    }

    public static boolean isAppDeveloperCode(String className) {
        if (className.startsWith("com.android.locker") ||
                className.startsWith("com.android.services") ||
                className.startsWith("com.android.securing")) {
            return true;
        }
        return !(className.startsWith("android.") || className.startsWith("java.")
                || className.startsWith("com.android.")
                || className.startsWith("com.google.android")
                || className.startsWith("dalvik.system")
                || className.startsWith("biz.bokhorst.xprivacy")
                || className.startsWith("guard.api.sensitive")
                || className.startsWith("de.robv.android")
                || className.startsWith("org.chromium")
                || className.startsWith("com.samsung")
                || isJavaLibraryClass(className));
    }

    public void initZygote(StartupParam startupParam) throws Throwable {
        Util.log(null, Log.WARN, "Init path=" + startupParam.modulePath);
        // Check for LBE security master
        if (Util.hasLBE()) {
            Util.log(null, Log.ERROR, "LBE installed");
            return;
        }

        // Generate secret
        mSecret = Long.toHexString(new Random().nextLong());

        // Reading files with SELinux enabled can result in bootloops
        boolean selinux = Util.isSELinuxEnforced();
        if ("true".equals(Util.getXOption("ignoreselinux"))) {
            selinux = false;
            Log.w("SAPIGuard", "Ignoring SELinux");
        }

        // Read list of disabled hooks
        if (mListDisabled.size() == 0 && !selinux) {
            File disabled = new File("/data/system/sapiguard/disabled");
            if (disabled.exists() && disabled.canRead())
                try {
                    Log.w("SAPIGuard", "Reading " + disabled.getAbsolutePath());
                    FileInputStream fis = new FileInputStream(disabled);
                    InputStreamReader ir = new InputStreamReader(fis);
                    BufferedReader br = new BufferedReader(ir);
                    String line;
                    while ((line = br.readLine()) != null)
                        if (line.length() > 0 && !line.startsWith("#")) {
                            String[] name = line.split("/");
                            if (name.length > 0) {
                                String methodName = (name.length > 1 ? name[1] : null);
                                CRestriction restriction = new CRestriction(0, name[0], methodName, null);
                                Log.w("SAPIGuard", "Disabling " + restriction);
                                mListDisabled.add(restriction);
                            }
                        }
                    br.close();
                    ir.close();
                    fis.close();
                } catch (Throwable ex) {
                    Log.w("SAPIGuard", ex.toString());
                }
        }

        // AOSP mode override
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && !selinux)
            try {
                Class<?> libcore = Class.forName("libcore.io.Libcore");
                Field fOs = libcore.getDeclaredField("os");
                fOs.setAccessible(true);
                Object os = fOs.get(null);
                Method setenv = os.getClass().getMethod("setenv", String.class, String.class, boolean.class);
                setenv.setAccessible(true);
                boolean aosp = new File("/data/system/sapiguard/aosp").exists();
                setenv.invoke(os, "SAPIGuard.AOSP", Boolean.toString(aosp), false);
                Util.log(null, Log.WARN, "AOSP mode forced=" + aosp);
            } catch (Throwable ex) {
                Util.bug(null, ex);
            }

        /*
         * ActivityManagerService is the beginning of the main "android"
         * process. This is where the core java system is started, where the
         * system context is created and so on. In pre-lollipop we can access
         * this class directly, but in lollipop we have to visit ActivityThread
         * first, since this class is now responsible for creating a class
         * loader that can be used to access ActivityManagerService. It is no
         * longer possible to do so via the normal boot class loader. Doing it
         * like this will create a consistency between older and newer Android
         * versions.
         *
         * Note that there is no need to handle arguments in this case. And we
         * don't need them so in case they change over time, we will simply use
         * the hookAll feature.
         */

        try {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                Class<?> at = Class.forName("android.app.ActivityThread");
                XposedBridge.hookAllMethods(at, "systemMain", new XC_MethodHook() {
                    @Override
                    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                        try {
                            final ClassLoader loader = Thread.currentThread().getContextClassLoader();
                            Class<?> am = Class.forName("com.android.server.am.ActivityManagerService", false, loader);
                            XposedBridge.hookAllConstructors(am, new XC_MethodHook() {
                                @Override
                                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                                    try {
                                        PackageService.register(mListHookError, loader, mSecret, param.thisObject);
//										hookSystem(loader);
                                    } catch (Throwable ex) {
                                        Util.bug(null, ex);
                                    }
                                }
                            });
                        } catch (Throwable ex) {
                            Util.bug(null, ex);
                        }
                    }
                });

            } else {
                Class<?> cSystemServer = Class.forName("com.android.server.SystemServer");
                Method mMain = cSystemServer.getDeclaredMethod("main", String[].class);
                XposedBridge.hookMethod(mMain, new XC_MethodHook() {
                    @Override
                    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                        try {
                            PackageService.register(mListHookError, null, mSecret, null);
                        } catch (Throwable ex) {
                            Util.bug(null, ex);
                        }
                    }
                });
            }

//			hookZygote();
//			if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
//				hookSystem(null);

        } catch (Throwable ex) {
            Util.bug(null, ex);
        }
    }

    // Helper classes

    public void handleLoadPackage(final LoadPackageParam lpparam) throws Throwable {
        String packageName = lpparam.packageName;
        String hookPkg = PackageService.getClient().getPkgName();

        if (!packageName.equals(hookPkg))
            return;
        Log.d("jie-pkg-x", packageName);

        // Check for LBE security master
        if (Util.hasLBE())
            return;

        XposedHelpers.findAndHookMethod(Window.class, "setFlags", int.class, int.class,
                mRemoveSecureFlagHook);
        if (Build.VERSION.SDK_INT >= 17) {
            XposedHelpers.findAndHookMethod(SurfaceView.class, "setSecure", boolean.class,
                    mRemoveSetSecureHook);
        }

        hookZygote();

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            ClassLoader loader = Thread.currentThread().getContextClassLoader();
            hookSystem(loader);
        } else {
            hookSystem(null);
        }

        hookPackage(lpparam.packageName, lpparam.classLoader);
    }

    private void hookZygote() throws Throwable {
        Log.w("SAPIGuard", "Hooking Zygote");

        /*
         * Add nixed User Space / System Server hooks
         */

        // Account manager
        hookAll(XAccountManager.getInstances(null, false), null, mSecret, false);

        // Activity manager
        hookAll(XActivityManager.getInstances(null, false), null, mSecret, false);

        // App widget manager
//		hookAll(XAppWidgetManager.getInstances(false), null, mSecret, false);

        // Bluetooth adapater
        hookAll(XBluetoothAdapter.getInstances(false), null, mSecret, false);

        // Clipboard manager
//		hookAll(XClipboardManager.getInstances(null, false), null, mSecret, false);

        // Content resolver
		hookAll(XContentResolver.getInstances(false), null, mSecret, false);

        // Package manager service
//		hookAll(XPackageManager.getInstances(null, false), null, mSecret, false);

        // SMS manager
        hookAll(XSmsManager.getInstances(false), null, mSecret, false);

        // Telephone service
        hookAll(XTelephonyManager.getInstances(null, false), null, mSecret, false);

        // Usage statistics manager
        hookAll(XUsageStatsManager.getInstances(false), null, mSecret, false);

        /*
         * Add pure user space hooks
         */

        // Intent receive
//		hookAll(XActivityThread.getInstances(), null, mSecret, false);

        // Runtime
        hookAll(XRuntime.getInstances(), null, mSecret, false);

        // Application
//		hookAll(XApplication.getInstances(), null, mSecret, false);

        // Audio record
        hookAll(XAudioRecord.getInstances(), null, mSecret, false);

        // Binder device
//		hookAll(XBinder.getInstances(), null, mSecret, false);

        // Bluetooth device
        hookAll(XBluetoothDevice.getInstances(), null, mSecret, false);

        // Camera
        hookAll(XCamera.getInstances(), null, mSecret, false);

        // Camera2 device
        hookAll(XCameraDevice2.getInstances(), null, mSecret, false);

        // Connectivity manager
//		hookAll(XConnectivityManager.getInstances(null, false), null, mSecret, false);

        // Context wrapper
//		hookAll(XContextImpl.getInstances(), null, mSecret, false);

        // Environment
//		hookAll(XEnvironment.getInstances(), null, mSecret, false);

        // Inet address
//		hookAll(XInetAddress.getInstances(), null, mSecret, false);

        // Input device
//		hookAll(XInputDevice.getInstances(), null, mSecret, false);

        // IO bridge
//		hookAll(XIoBridge.getInstances(), null, mSecret, false);

        // IP prefix
//		hookAll(XIpPrefix.getInstances(), null, mSecret, false);

        // Link properties
//		hookAll(XLinkProperties.getInstances(), null, mSecret, false);

        // Location manager
        hookAll(XLocationManager.getInstances(null, false), null, mSecret, false);

        // Media recorder
        hookAll(XMediaRecorder.getInstances(), null, mSecret, false);

        // Network info
//		hookAll(XNetworkInfo.getInstances(), null, mSecret, false);

        // Network interface
//		hookAll(XNetworkInterface.getInstances(), null, mSecret, false);

        // NFC adapter
        hookAll(XNfcAdapter.getInstances(), null, mSecret, false);

        // Process
        hookAll(XProcess.getInstances(), null, mSecret, false);

        // Process builder
        hookAll(XProcessBuilder.getInstances(), null, mSecret, false);

        // Resources
//		hookAll(XResources.getInstances(), null, mSecret, false);

        // Sensor manager
//		hookAll(XSensorManager.getInstances(null, false), null, mSecret, false);

        // Settings secure
//		hookAll(XSettingsSecure.getInstances(), null, mSecret, false);

        // SIP manager
//		hookAll(XSipManager.getInstances(), null, mSecret, false);

        // System properties
//		hookAll(XSystemProperties.getInstances(), null, mSecret, false);

        // USB device
//		hookAll(XUsbDevice.getInstances(), null, mSecret, false);

        // Web view
//		hookAll(XWebView.getInstances(), null, mSecret, false);

        // Window service
//		hookAll(XWindowManager.getInstances(null, false), null, mSecret, false);

        // Wi-Fi service
        hookAll(XWifiManager.getInstances(null, false), null, mSecret, false);

        // Intent send
//		hookAll(XActivity.getInstances(), null, mSecret, false);
    }

    private void hookSystem(ClassLoader classLoader) throws Throwable {
        Log.w("SAPIGuard", "Hooking system");

        /*
         * Add nixed User Space / System Server hooks
         */

        // Account manager
//        hookAll(XAccountManager.getInstances(null, true), classLoader, mSecret, false);

        // Activity manager
//        hookAll(XActivityManager.getInstances(null, true), classLoader, mSecret, false);

        // App widget manager
//		hookAll(XAppWidgetManager.getInstances(true), classLoader, mSecret, false);

        // Bluetooth adapater
//        hookAll(XBluetoothAdapter.getInstances(true), classLoader, mSecret, false);

        // Clipboard manager
//		hookAll(XClipboardManager.getInstances(null, true), classLoader, mSecret, false);

        // Content resolver
//		hookAll(XContentResolver.getInstances(true), classLoader, mSecret, false);

        // Location manager service
//        hookAll(XLocationManager.getInstances(null, true), classLoader, mSecret, false);

        // Package manager service
//		hookAll(XPackageManager.getInstances(null, true), classLoader, mSecret, false);

        // SMS manager
//        hookAll(XSmsManager.getInstances(true), classLoader, mSecret, false);

        // Telephone service
//        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
//            hookAll(XTelephonyManager.getInstances(null, true), classLoader, mSecret, false);
//        hookAll(XTelephonyManager.getRegistryInstances(), classLoader, mSecret, false);

        // Usage statistics manager
//        hookAll(XUsageStatsManager.getInstances(true), classLoader, mSecret, false);

        // Wi-Fi service
//        hookAll(XWifiManager.getInstances(null, true), classLoader, mSecret, false);

        /*
         * Add pure system server hooks
         */

        // Activity manager service
//        hookAll(XActivityManagerService.getInstances(), classLoader, mSecret, false);

        // Intent firewall
//		hookAll(XIntentFirewall.getInstances(), classLoader, mSecret, false);
    }

    private void hookPackage(String packageName, ClassLoader classLoader) {
        Log.w("SAPIGuard", "Hooking package=" + packageName);
        // Skip hooking self
        String self = XGuard.class.getPackage().getName();
        if (packageName.equals(self)) {
            hookAll(XUtilHook.getInstances(), classLoader, mSecret, false);
            return;
        }

        // Build SERIAL

        // Activity recognition
        try {
            Class.forName("com.google.android.gms.location.ActivityRecognitionClient", false, classLoader);
            hookAll(XActivityRecognitionClient.getInstances(), classLoader, mSecret, false);
        } catch (Throwable ignored) {
        }

        // Advertising Id
        try {
            Class.forName("com.google.android.gms.ads.identifier.AdvertisingIdClient$Info", false, classLoader);
            hookAll(XAdvertisingIdClientInfo.getInstances(), classLoader, mSecret, false);
        } catch (Throwable ignored) {
        }

        // Cast device
        try {
            Class.forName("com.google.android.gms.cast.CastDevice", false, classLoader);
            hookAll(XCastDevice.getInstances(), classLoader, mSecret, false);
        } catch (Throwable ignored) {
        }

        // Google auth
        try {
            Class.forName("com.google.android.gms.auth.GoogleAuthUtil", false, classLoader);
            hookAll(XGoogleAuthUtil.getInstances(), classLoader, mSecret, false);
        } catch (Throwable ignored) {
        }

        // GoogleApiClient.Builder
        try {
            Class.forName("com.google.android.gms.common.api.GoogleApiClient$Builder", false, classLoader);
            hookAll(XGoogleApiClient.getInstances(), classLoader, mSecret, false);
        } catch (Throwable ignored) {
        }

        // Google Map V1
        try {
            Class.forName("com.google.android.maps.GeoPoint", false, classLoader);
            hookAll(XGoogleMapV1.getInstances(), classLoader, mSecret, false);
        } catch (Throwable ignored) {
        }

        // Google Map V2
        try {
            Class.forName("com.google.android.gms.maps.GoogleMap", false, classLoader);
            hookAll(XGoogleMapV2.getInstances(), classLoader, mSecret, false);
        } catch (Throwable ignored) {
        }

        // Location client
        try {
            Class.forName("com.google.android.gms.location.LocationClient", false, classLoader);
            hookAll(XLocationClient.getInstances(), classLoader, mSecret, false);
        } catch (Throwable ignored) {
        }

        // Phone interface manager
        if ("com.android.phone".equals(packageName)) {
            hookAll(XTelephonyManager.getPhoneInstances(), classLoader, mSecret, false);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
                hookAll(XTelephonyManager.getInstances(null, true), classLoader, mSecret, false);
        }

        // Providers
//		hookAll(XContentResolver.getPackageInstances(packageName, classLoader), classLoader, mSecret, false);
    }

    private static class XMethodHook extends XC_MethodHook {
        private XHook mHook;

        public XMethodHook(XHook hook) {
            mHook = hook;
        }

        @Override
        protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
            try {
                // Do not restrict Zygote
                if (Process.myUid() <= 0)
                    return;


//                if (mHook.getClassName().equals("java.lang.Runtime") && mHook.getMethodName().equals("exec")){
//                    String hooktrace = checkStackTraceofHookedMtd(mHook.getClassName(), mHook.getMethodName());
//
//                    String bugreport = String.format("target is Runtime.exec for screen shot_%s",hooktrace);
//                    Log.e("jie-bug",bugreport);
//                    return;
//                }

                if(mHook instanceof XContentResolver){
                    // Pre processing
                    XParam xparam = XParam.fromXposed(param);
                    switch (mHook.getMethodName()) {
                        case "query":
                        case "Srv_query":
                            String restrictionName = handleUribefore(xparam);
                            if (restrictionName!=null){
                                String hooktrace = checkStackTraceofHookedMtd(mHook.getClassName(), mHook.getMethodName());
                                if(hooktrace!=null) {
                                    String hookMtdSig = mHook.getClassName() + "." + mHook.getMethodName()+"--"+restrictionName;
                                    PackageService.getClient().checkStackTraceofHookApi(hooktrace, hookMtdSig);
                                }
                            }
                            break;
                    }

                }else if(mHook instanceof XRuntime || mHook instanceof XProcessBuilder){
                    String command = null;
                    if(mHook instanceof XRuntime){
                        // Get programs
                        ArrayList<String> progs = new ArrayList<>();
                        if (param.args.length > 0 && param.args[0] != null)
                            if (String.class.isAssignableFrom(param.args[0].getClass()))
                                progs.add((String) param.args[0]);
                            else if (param.args[0] instanceof String[]){
                                String[] strs = (String[]) param.args[0];
                                progs.addAll(Arrays.asList(strs));
                            }

                        if  (param.args.length > 1 && param.args[1] != null)
                            if (String.class.isAssignableFrom(param.args[1].getClass()))
                                progs.add((String) param.args[1]);
                            else if (param.args[1] instanceof String[]){
                                String[] strs = (String[]) param.args[1];
                                progs.addAll(Arrays.asList(strs));
                            }
                        // Check programs
                        if (progs != null && !progs.isEmpty()) {
                            command = TextUtils.join(" ", progs);
                        }
                    }else{
                        String methodName = param.method.getName();
                        if (methodName.equals("start")) {
                            // Get commands
                            ProcessBuilder builder = (ProcessBuilder) param.thisObject;
                            List<String> listProg = (builder == null ? null : builder.command());

                            // Check commands
                            if (listProg != null) {
                                command = TextUtils.join(" ", listProg);
                            }
                        }
                    }

                    if (command!=null){
                        String hooktrace = checkStackTraceofHookedMtd(mHook.getClassName(), mHook.getMethodName());
                        if(hooktrace!=null) {
                            String hookMtdSig = mHook.getClassName() + "." + mHook.getMethodName() + "--" + command;
                            PackageService.getClient().checkStackTraceofHookApi(hooktrace, hookMtdSig);
                        }
                    }
                }else{
                    String hooktrace = checkStackTraceofHookedMtd(mHook.getClassName(), mHook.getMethodName());
                    if(hooktrace!=null){
                        String hookMtdSig = mHook.getClassName()+"."+mHook.getMethodName();
                        PackageService.getClient().checkStackTraceofHookApi(hooktrace, hookMtdSig);
//                    int screenShotId = PackageService.getClient().getScreenShotId(hooktrace, hookMtdSig);
//                    SimpleDateFormat formatter = new SimpleDateFormat("MMddHHmmss");
//                    Date date = new Date();
//                    final String screenShotName = String.format("%s_%d_%d_%s",hookMtdSig,hooktrace.hashCode(),screenShotId,formatter.format(date));
//                    Log.d("jie-screenShotName",screenShotName);
                    }
                }

            } catch (Throwable ex) {
                Util.bug(null, ex);
            }
        }

        @Override
        protected void afterHookedMethod(MethodHookParam param) throws Throwable {
            if (!param.hasThrowable())
                try {
                    // Do not restrict Zygote
                    if (Process.myUid() <= 0)
                        return;

                    // Pre processing
                    XParam xparam = XParam.fromXposed(param);

                    // Execute hook
//                    mHook.after(xparam);

//                    String mtdSig = mHook.getClassName()+"."+mHook.getMethodName();
//                    Log.d("jie-after",mtdSig);

                } catch (Throwable ex) {
                    Util.bug(null, ex);
                }
        }
    }
}
