/*
**        DroidPlugin Project
**
** Copyright(c) 2015 Andy Zhang <[email protected]>
**
** 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 <http://www.gnu.org/licenses/lgpl.txt>
**
**/

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([email protected]) 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);
                    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:onActivityDestory", 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);
        }
    }


}