/*
 * Copyright (C) 2015 Peter Gregus for GravityBox Project (C3C076@xda)
 * 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.wrbug.gravitybox.nougat;

import java.io.File;
import java.lang.reflect.Method;
import java.util.ArrayList;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Message;
import android.os.PowerManager;
import android.os.SystemClock;
import android.util.SparseArray;
import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.ImageView.ScaleType;

import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XC_MethodReplacement;
import de.robv.android.xposed.XSharedPreferences;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;

public class ModNavigationBar {
    public static final String PACKAGE_NAME = "com.android.systemui";
    private static final String TAG = "GB:ModNavigationBar";
    private static final boolean DEBUG = BuildConfig.DEBUG;

    private static final String CLASS_NAVBAR_VIEW = "com.android.systemui.statusbar.phone.NavigationBarView";
    private static final String CLASS_KEY_BUTTON_VIEW = "com.android.systemui.statusbar.policy.KeyButtonView";
    private static final String CLASS_KEY_BUTTON_RIPPLE = "com.android.systemui.statusbar.policy.KeyButtonRipple";
    private static final String CLASS_NAVBAR_TRANSITIONS =
            "com.android.systemui.statusbar.phone.NavigationBarTransitions";
    private static final String CLASS_DEADZONE = "com.android.systemui.statusbar.policy.DeadZone";
    private static final String CLASS_PHONE_STATUSBAR = "com.android.systemui.statusbar.phone.PhoneStatusBar";

    private static final int MODE_OPAQUE = 0;
    private static final int MODE_LIGHTS_OUT = 3;
    private static final int MODE_LIGHTS_OUT_TRANSPARENT = 6;
    private static final int MSG_LIGHTS_OUT = 1;

    private static final int NAVIGATION_HINT_BACK_ALT = 1 << 0;
    private static final int STATUS_BAR_DISABLE_RECENT = 0x01000000;

    private static boolean mAlwaysShowMenukey;
    private static View mNavigationBarView;
    private static Object[] mRecentsKeys;
    private static HomeKeyInfo[] mHomeKeys;
    private static ModHwKeys.HwKeyAction mRecentsSingletapActionBck = new ModHwKeys.HwKeyAction(0, null);
    private static ModHwKeys.HwKeyAction mRecentsSingletapAction = new ModHwKeys.HwKeyAction(0, null);
    private static ModHwKeys.HwKeyAction mRecentsLongpressAction = new ModHwKeys.HwKeyAction(0, null);
    private static ModHwKeys.HwKeyAction mRecentsDoubletapAction = new ModHwKeys.HwKeyAction(0, null);
    private static int mHomeLongpressAction = 0;
    private static boolean mHwKeysEnabled;
    private static boolean mCursorControlEnabled;
    private static boolean mDpadKeysVisible;
    private static boolean mNavbarVertical;
    private static boolean mNavbarLeftHanded;
    private static boolean mUseLargerIcons;
    private static boolean mHideImeSwitcher;
    private static boolean sHideNavigationBar;
    private static WindowManager sWindowManager;
    private static ViewGroup.LayoutParams sLayoutParams;
    private static PowerManager mPm;
    private static long mLastTouchMs;
    private static int mBarModeOriginal;
    private static int mAutofadeTimeoutMs;
    private static String mAutofadeShowKeysPolicy;

    // Navbar dimensions
    private static int mNavbarHeight;
    private static int mNavbarWidth;

    // Custom key
    private enum CustomKeyIconStyle {
        SIX_DOT, THREE_DOT, TRANSPARENT, CUSTOM
    }

    ;
    private static boolean mCustomKeyEnabled;
    private static Resources mResources;
    private static Context mGbContext;
    private static NavbarViewInfo[] mNavbarViewInfo = new NavbarViewInfo[2];
    private static boolean mCustomKeySwapEnabled;
    private static CustomKeyIconStyle mCustomKeyIconStyle;

    // Colors
    private static boolean mNavbarColorsEnabled;
    private static int mKeyDefaultColor = 0xe8ffffff;
    private static int mKeyDefaultGlowColor = 0x33ffffff;
    private static int mKeyColor;
    private static int mKeyGlowColor;

    private static Drawable mRecentIcon, mRecentLandIcon;
    private static Drawable mRecentAltIcon, mRecentAltLandIcon;
    private static boolean mRecentAlt = false;
    private static ImageView mRecentBtn = null;

    private static void log(String message) {
        XposedBridge.log(TAG + ": " + message);
    }

    static class HomeKeyInfo {
        public ImageView homeKey;
        public boolean supportsLongPressDefault;
    }

    static class NavbarViewInfo {
        ViewGroup navButtons;
        View originalView;
        KeyButtonContainer customKey;
        KeyButtonContainer dpadLeft;
        KeyButtonContainer dpadRight;
        int customKeyPosition;
        boolean visible;
        boolean menuCustomSwapped;
        ViewGroup menuImeGroup;
        SparseArray<ScaleType> originalScaleType = new SparseArray<ScaleType>();

        @Override
        public String toString() {
            return "NavbarViewInfo{" +
                    "navButtons=" + navButtons +
                    ", originalView=" + originalView +
                    ", customKey=" + customKey +
                    ", dpadLeft=" + dpadLeft +
                    ", dpadRight=" + dpadRight +
                    ", customKeyPosition=" + customKeyPosition +
                    ", visible=" + visible +
                    ", menuCustomSwapped=" + menuCustomSwapped +
                    ", menuImeGroup=" + menuImeGroup +
                    ", originalScaleType=" + originalScaleType +
                    '}';
        }
    }

    private static BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (DEBUG) log("Broadcast received: " + intent.toString());
            if (intent.getAction().equals(GravityBoxSettings.ACTION_PREF_NAVBAR_CHANGED)) {
                if (intent.hasExtra(GravityBoxSettings.EXTRA_NAVBAR_MENUKEY)) {
                    mAlwaysShowMenukey = intent.getBooleanExtra(
                            GravityBoxSettings.EXTRA_NAVBAR_MENUKEY, false);
                    if (DEBUG) log("mAlwaysShowMenukey = " + mAlwaysShowMenukey);
                    setMenuKeyVisibility();
                }
                if (intent.hasExtra(GravityBoxSettings.EXTRA_NAVBAR_CUSTOM_KEY_ENABLE)) {
                    mCustomKeyEnabled = intent.getBooleanExtra(
                            GravityBoxSettings.EXTRA_NAVBAR_CUSTOM_KEY_ENABLE, false);
                    setCustomKeyVisibility();
                }
                if (intent.hasExtra(GravityBoxSettings.EXTRA_NAVBAR_KEY_COLOR)) {
                    mKeyColor = intent.getIntExtra(
                            GravityBoxSettings.EXTRA_NAVBAR_KEY_COLOR, mKeyDefaultColor);
                    setKeyColor();
                }
                if (intent.hasExtra(GravityBoxSettings.EXTRA_NAVBAR_KEY_GLOW_COLOR)) {
                    mKeyGlowColor = intent.getIntExtra(
                            GravityBoxSettings.EXTRA_NAVBAR_KEY_GLOW_COLOR, mKeyDefaultGlowColor);
                    setKeyColor();
                }
                if (intent.hasExtra(GravityBoxSettings.EXTRA_NAVBAR_COLOR_ENABLE)) {
                    mNavbarColorsEnabled = intent.getBooleanExtra(
                            GravityBoxSettings.EXTRA_NAVBAR_COLOR_ENABLE, false);
                    setKeyColor();
                }
                if (intent.hasExtra(GravityBoxSettings.EXTRA_NAVBAR_CURSOR_CONTROL)) {
                    mCursorControlEnabled = intent.getBooleanExtra(
                            GravityBoxSettings.EXTRA_NAVBAR_CURSOR_CONTROL, false);
                }
                if (intent.hasExtra(GravityBoxSettings.EXTRA_NAVBAR_CUSTOM_KEY_SWAP)) {
                    mCustomKeySwapEnabled = intent.getBooleanExtra(
                            GravityBoxSettings.EXTRA_NAVBAR_CUSTOM_KEY_SWAP, false);
                    setCustomKeyVisibility();
                }
                if (intent.hasExtra(GravityBoxSettings.EXTRA_NAVBAR_CUSTOM_KEY_ICON_STYLE)) {
                    mCustomKeyIconStyle = CustomKeyIconStyle.valueOf(intent.getStringExtra(
                            GravityBoxSettings.EXTRA_NAVBAR_CUSTOM_KEY_ICON_STYLE));
                    updateCustomKeyIcon();
                }
                if (intent.hasExtra(GravityBoxSettings.EXTRA_NAVBAR_HIDE_IME)) {
                    mHideImeSwitcher = intent.getBooleanExtra(
                            GravityBoxSettings.EXTRA_NAVBAR_HIDE_IME, false);
                }
                if (intent.hasExtra(GravityBoxSettings.EXTRA_NAVBAR_HEIGHT)) {
                    mNavbarHeight = intent.getIntExtra(GravityBoxSettings.EXTRA_NAVBAR_HEIGHT, 100);
                    updateIconScaleType();
                }
                if (intent.hasExtra(GravityBoxSettings.EXTRA_NAVBAR_WIDTH)) {
                    mNavbarWidth = intent.getIntExtra(GravityBoxSettings.EXTRA_NAVBAR_WIDTH, 100);
                    updateIconScaleType();
                }
                if (intent.hasExtra(GravityBoxSettings.EXTRA_NAVBAR_AUTOFADE_KEYS)) {
                    mAutofadeTimeoutMs = intent.getIntExtra(GravityBoxSettings.EXTRA_NAVBAR_AUTOFADE_KEYS, 0) * 1000;
                    mBarModeHandler.removeMessages(MSG_LIGHTS_OUT);
                    if (mAutofadeTimeoutMs == 0) {
                        setBarMode(mBarModeOriginal);
                    } else {
                        mBarModeHandler.sendEmptyMessageDelayed(MSG_LIGHTS_OUT, mAutofadeTimeoutMs);
                    }
                }
                if (intent.hasExtra(GravityBoxSettings.EXTRA_NAVBAR_AUTOFADE_SHOW_KEYS)) {
                    mAutofadeShowKeysPolicy = intent.getStringExtra(
                            GravityBoxSettings.EXTRA_NAVBAR_AUTOFADE_SHOW_KEYS);
                }
            } else if (intent.getAction().equals(
                    GravityBoxSettings.ACTION_PREF_HWKEY_CHANGED) &&
                    GravityBoxSettings.PREF_KEY_HWKEY_RECENTS_SINGLETAP.equals(intent.getStringExtra(
                            GravityBoxSettings.EXTRA_HWKEY_KEY))) {
                mRecentsSingletapAction.actionId = intent.getIntExtra(GravityBoxSettings.EXTRA_HWKEY_VALUE, 0);
                mRecentsSingletapAction.customApp = intent.getStringExtra(GravityBoxSettings.EXTRA_HWKEY_CUSTOM_APP);
                if (mRecentsSingletapAction.actionId != GravityBoxSettings.HWKEY_ACTION_CLEAR_ALL_RECENTS_SINGLETAP) {
                    mRecentsSingletapActionBck.actionId = mRecentsSingletapAction.actionId;
                    mRecentsSingletapActionBck.customApp = mRecentsSingletapAction.customApp;
                    if (DEBUG)
                        log("mRecentsSingletapActionBck.actionId = " + mRecentsSingletapActionBck.actionId);
                }
                updateRecentsKeyCode();
            } else if (intent.getAction().equals(
                    GravityBoxSettings.ACTION_PREF_HWKEY_CHANGED) &&
                    GravityBoxSettings.PREF_KEY_HWKEY_RECENTS_LONGPRESS.equals(intent.getStringExtra(
                            GravityBoxSettings.EXTRA_HWKEY_KEY))) {
                mRecentsLongpressAction.actionId = intent.getIntExtra(GravityBoxSettings.EXTRA_HWKEY_VALUE, 0);
                mRecentsLongpressAction.customApp = intent.getStringExtra(GravityBoxSettings.EXTRA_HWKEY_CUSTOM_APP);
                updateRecentsKeyCode();
            } else if (intent.getAction().equals(
                    GravityBoxSettings.ACTION_PREF_HWKEY_CHANGED) &&
                    GravityBoxSettings.PREF_KEY_HWKEY_RECENTS_DOUBLETAP.equals(intent.getStringExtra(
                            GravityBoxSettings.EXTRA_HWKEY_KEY))) {
                mRecentsDoubletapAction.actionId = intent.getIntExtra(GravityBoxSettings.EXTRA_HWKEY_VALUE, 0);
                mRecentsDoubletapAction.customApp = intent.getStringExtra(GravityBoxSettings.EXTRA_HWKEY_CUSTOM_APP);
                updateRecentsKeyCode();
            } else if (intent.getAction().equals(
                    GravityBoxSettings.ACTION_PREF_HWKEY_CHANGED) &&
                    GravityBoxSettings.PREF_KEY_HWKEY_HOME_LONGPRESS.equals(intent.getStringExtra(
                            GravityBoxSettings.EXTRA_HWKEY_KEY))) {
                mHomeLongpressAction = intent.getIntExtra(GravityBoxSettings.EXTRA_HWKEY_VALUE, 0);
            } else if (intent.getAction().equals(GravityBoxSettings.ACTION_PREF_PIE_CHANGED) &&
                    intent.hasExtra(GravityBoxSettings.EXTRA_PIE_HWKEYS_DISABLE)) {
                mHwKeysEnabled = !intent.getBooleanExtra(GravityBoxSettings.EXTRA_PIE_HWKEYS_DISABLE, false);
                updateRecentsKeyCode();
            } else if (intent.getAction().equals(GravityBoxSettings.ACTION_PREF_NAVBAR_SWAP_KEYS)) {
                swapBackAndRecents();
            } else if (intent.getAction().equals(GravityBoxSettings.ACTION_PREF_HIDE_NAVBAR)) {
                setNavigationBarVisible(intent.getBooleanExtra(GravityBoxSettings.PREF_KEY_HIDE_NAVI_BAR, false));
            }
        }
    };


    public static void init(final XSharedPreferences prefs, final ClassLoader classLoader) {
        try {
            final Class<?> navbarViewClass = XposedHelpers.findClass(CLASS_NAVBAR_VIEW, classLoader);
            final Class<?> navbarTransitionsClass = XposedHelpers.findClass(CLASS_NAVBAR_TRANSITIONS, classLoader);

            mAlwaysShowMenukey = prefs.getBoolean(GravityBoxSettings.PREF_KEY_NAVBAR_MENUKEY, false);
            mNavbarLeftHanded = prefs.getBoolean(GravityBoxSettings.PREF_KEY_NAVBAR_ENABLE, false) &&
                    prefs.getBoolean(GravityBoxSettings.PREF_KEY_NAVBAR_LEFT_HANDED, false);
            mUseLargerIcons = prefs.getBoolean(GravityBoxSettings.PREF_KEY_NAVBAR_LARGER_ICONS, false);

            try {
                mRecentsSingletapAction = new ModHwKeys.HwKeyAction(Integer.valueOf(
                        prefs.getString(GravityBoxSettings.PREF_KEY_HWKEY_RECENTS_SINGLETAP, "0")),
                        prefs.getString(GravityBoxSettings.PREF_KEY_HWKEY_RECENTS_SINGLETAP + "_custom", null));
                sHideNavigationBar = prefs.getBoolean(GravityBoxSettings.PREF_KEY_HIDE_NAVI_BAR, false);
                mRecentsLongpressAction = new ModHwKeys.HwKeyAction(Integer.valueOf(
                        prefs.getString(GravityBoxSettings.PREF_KEY_HWKEY_RECENTS_LONGPRESS, "0")),
                        prefs.getString(GravityBoxSettings.PREF_KEY_HWKEY_RECENTS_LONGPRESS + "_custom", null));
                mRecentsDoubletapAction = new ModHwKeys.HwKeyAction(Integer.valueOf(
                        prefs.getString(GravityBoxSettings.PREF_KEY_HWKEY_RECENTS_DOUBLETAP, "0")),
                        prefs.getString(GravityBoxSettings.PREF_KEY_HWKEY_RECENTS_DOUBLETAP + "_custom", null));
                mRecentsSingletapActionBck.actionId = mRecentsSingletapAction.actionId;
                mRecentsSingletapActionBck.customApp = mRecentsSingletapAction.customApp;
                mHomeLongpressAction = Integer.valueOf(
                        prefs.getString(GravityBoxSettings.PREF_KEY_HWKEY_HOME_LONGPRESS, "0"));
            } catch (NumberFormatException nfe) {
                XposedBridge.log(nfe);
            }

            mCustomKeyEnabled = prefs.getBoolean(
                    GravityBoxSettings.PREF_KEY_NAVBAR_CUSTOM_KEY_ENABLE, false);
            mHwKeysEnabled = !prefs.getBoolean(GravityBoxSettings.PREF_KEY_HWKEYS_DISABLE, false);
            mCursorControlEnabled = prefs.getBoolean(
                    GravityBoxSettings.PREF_KEY_NAVBAR_CURSOR_CONTROL, false);
            mCustomKeySwapEnabled = prefs.getBoolean(
                    GravityBoxSettings.PREF_KEY_NAVBAR_CUSTOM_KEY_SWAP, false);
            mHideImeSwitcher = prefs.getBoolean(GravityBoxSettings.PREF_KEY_NAVBAR_HIDE_IME, false);

            mNavbarHeight = prefs.getInt(GravityBoxSettings.PREF_KEY_NAVBAR_HEIGHT, 100);
            mNavbarWidth = prefs.getInt(GravityBoxSettings.PREF_KEY_NAVBAR_WIDTH, 100);
            mAutofadeTimeoutMs = prefs.getInt(GravityBoxSettings.PREF_KEY_NAVBAR_AUTOFADE_KEYS, 0) * 1000;
            mAutofadeShowKeysPolicy = prefs.getString(GravityBoxSettings.PREF_KEY_NAVBAR_AUTOFADE_SHOW_KEYS, "NAVBAR");

            // for HTC GPE devices having capacitive keys
            if (prefs.getBoolean(GravityBoxSettings.PREF_KEY_NAVBAR_ENABLE, false)) {
                try {
                    Class<?> sbFlagClass = XposedHelpers.findClass(
                            "com.android.systemui.statusbar.StatusBarFlag", classLoader);
                    XposedHelpers.setStaticBooleanField(sbFlagClass, "supportHWNav", false);
                } catch (Throwable t) {
                }
            }

            XposedBridge.hookAllConstructors(navbarViewClass, new XC_MethodHook() {
                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    Context context = (Context) param.args[0];
                    if (context == null) return;

                    mResources = context.getResources();

                    mGbContext = Utils.getGbContext(context);
                    final Resources res = mGbContext.getResources();
                    mNavbarColorsEnabled = prefs.getBoolean(GravityBoxSettings.PREF_KEY_NAVBAR_COLOR_ENABLE, false);
                    mKeyDefaultColor = res.getColor(R.color.navbar_key_color);
                    mKeyColor = prefs.getInt(GravityBoxSettings.PREF_KEY_NAVBAR_KEY_COLOR, mKeyDefaultColor);
                    mKeyDefaultGlowColor = res.getColor(R.color.navbar_key_glow_color);
                    mKeyGlowColor = prefs.getInt(
                            GravityBoxSettings.PREF_KEY_NAVBAR_KEY_GLOW_COLOR, mKeyDefaultGlowColor);
                    mCustomKeyIconStyle = CustomKeyIconStyle.valueOf(prefs.getString(
                            GravityBoxSettings.PREF_KEY_NAVBAR_CUSTOM_KEY_ICON_STYLE, "SIX_DOT"));

                    mNavigationBarView = (View) param.thisObject;
                    IntentFilter intentFilter = new IntentFilter();
                    intentFilter.addAction(GravityBoxSettings.ACTION_PREF_NAVBAR_CHANGED);
                    intentFilter.addAction(GravityBoxSettings.ACTION_PREF_HWKEY_CHANGED);
                    intentFilter.addAction(GravityBoxSettings.ACTION_PREF_PIE_CHANGED);
                    intentFilter.addAction(GravityBoxSettings.ACTION_PREF_NAVBAR_SWAP_KEYS);
                    intentFilter.addAction(GravityBoxSettings.ACTION_PREF_HIDE_NAVBAR);
                    context.registerReceiver(mBroadcastReceiver, intentFilter);
                    if (DEBUG) log("NavigationBarView constructed; Broadcast receiver registered");
                }
            });

            XposedHelpers.findAndHookMethod(navbarViewClass, "setMenuVisibility",
                    boolean.class, boolean.class, new XC_MethodHook() {
                        @Override
                        protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                            setMenuKeyVisibility();
                        }
                    });

            XposedHelpers.findAndHookMethod(navbarViewClass, "onFinishInflate", new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    startHookNavigationBar(((ViewGroup) param.thisObject).getContext().getSystemService(Context.WINDOW_SERVICE).getClass());
                }

                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    ViewGroup navigationBarView = (ViewGroup) param.thisObject;
                    final Context context = (navigationBarView).getContext();
                    final Resources gbRes = mGbContext.getResources();
                    final int recentAppsResId = mResources.getIdentifier("recent_apps", "id", PACKAGE_NAME);
                    final int homeButtonResId = mResources.getIdentifier("home", "id", PACKAGE_NAME);
                    final View[] rotatedViews =
                            (View[]) XposedHelpers.getObjectField(param.thisObject, "mRotatedViews");
                    if (rotatedViews != null) {
                        mRecentsKeys = new Object[rotatedViews.length];
                        mHomeKeys = new HomeKeyInfo[rotatedViews.length];
                        int index = 0;
                        for (View v : rotatedViews) {
                            if (recentAppsResId != 0) {
                                ImageView recentAppsButton = (ImageView) v.findViewById(recentAppsResId);
                                mRecentsKeys[index] = recentAppsButton;
                            }
                            if (homeButtonResId != 0) {
                                HomeKeyInfo hkInfo = new HomeKeyInfo();
                                hkInfo.homeKey = (ImageView) v.findViewById(homeButtonResId);
                                if (hkInfo.homeKey != null) {
                                    hkInfo.supportsLongPressDefault =
                                            XposedHelpers.getBooleanField(hkInfo.homeKey, "mSupportsLongpress");
                                }
                                mHomeKeys[index] = hkInfo;
                            }
                            index++;
                        }
                    }

                    // prepare app, dpad left, dpad right keys
                    ViewGroup vRot, navButtons;

                    // prepare keys for rot0 view
                    vRot = (ViewGroup) (navigationBarView).findViewById(
                            mResources.getIdentifier("rot0", "id", PACKAGE_NAME));
                    if (vRot != null) {
                        ScaleType scaleType = getIconScaleType(0, View.NO_ID);
                        KeyButtonContainer appKey = new KeyButtonContainer(context);
                        appKey.setScaleType(scaleType);
                        appKey.setClickable(true);
                        appKey.setImageDrawable(getCustomKeyIconDrawable());
                        appKey.setKeyCode(KeyEvent.KEYCODE_SOFT_LEFT);

                        KeyButtonContainer dpadLeft = new KeyButtonContainer(context);
                        dpadLeft.setScaleType(scaleType);
                        dpadLeft.setClickable(true);
                        dpadLeft.setImageDrawable(gbRes.getDrawable(R.drawable.ic_sysbar_ime_left, null));
                        dpadLeft.setVisibility(View.GONE);
                        dpadLeft.setKeyCode(KeyEvent.KEYCODE_DPAD_LEFT);

                        KeyButtonContainer dpadRight = new KeyButtonContainer(context);
                        dpadRight.setScaleType(scaleType);
                        dpadRight.setClickable(true);
                        dpadRight.setImageDrawable(gbRes.getDrawable(R.drawable.ic_sysbar_ime_right, null));
                        dpadRight.setVisibility(View.GONE);
                        dpadRight.setKeyCode(KeyEvent.KEYCODE_DPAD_RIGHT);

                        navButtons = (ViewGroup) vRot.findViewById(
                                mResources.getIdentifier("nav_buttons", "id", PACKAGE_NAME));
                        prepareNavbarViewInfo(navButtons, 0, appKey, dpadLeft, dpadRight);
                    }

                    // prepare keys for rot90 view
                    vRot = (ViewGroup) (navigationBarView).findViewById(
                            mResources.getIdentifier("rot90", "id", PACKAGE_NAME));
                    if (vRot != null) {
                        ScaleType scaleType = getIconScaleType(1, View.NO_ID);
                        KeyButtonContainer appKey = new KeyButtonContainer(context);
                        appKey.setScaleType(scaleType);
                        appKey.setClickable(true);
                        appKey.setImageDrawable(getCustomKeyIconDrawable());
                        appKey.setKeyCode(KeyEvent.KEYCODE_SOFT_LEFT);

                        KeyButtonContainer dpadLeft = new KeyButtonContainer(context);
                        dpadLeft.setScaleType(scaleType);
                        dpadLeft.setClickable(true);
                        dpadLeft.setImageDrawable(gbRes.getDrawable(R.drawable.ic_sysbar_ime_left, null));
                        dpadLeft.setVisibility(View.GONE);
                        dpadLeft.setKeyCode(KeyEvent.KEYCODE_DPAD_LEFT);

                        KeyButtonContainer dpadRight = new KeyButtonContainer(context);
                        dpadRight.setScaleType(scaleType);
                        dpadRight.setClickable(true);
                        dpadRight.setImageDrawable(gbRes.getDrawable(R.drawable.ic_sysbar_ime_right, null));
                        dpadRight.setVisibility(View.GONE);
                        dpadRight.setKeyCode(KeyEvent.KEYCODE_DPAD_RIGHT);

                        navButtons = (ViewGroup) vRot.findViewById(
                                mResources.getIdentifier("nav_buttons", "id", PACKAGE_NAME));
                        prepareNavbarViewInfo(navButtons, 1, appKey, dpadLeft, dpadRight);
                    }

                    updateRecentsKeyCode();

                    if (!Utils.isOxygenOs35Rom() &&
                            prefs.getBoolean(GravityBoxSettings.PREF_KEY_NAVBAR_SWAP_KEYS, false)) {
                        new Handler().postDelayed(new Runnable() {
                            @Override
                            public void run() {
                                swapBackAndRecents();
                            }
                        }, 200);
                    }

                    updateIconScaleType();
                }
            });

            XposedHelpers.findAndHookMethod(navbarViewClass, "setDisabledFlags",
                    int.class, boolean.class, new XC_MethodHook() {
                        @Override
                        protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                            setDpadKeyVisibility();
                            setCustomKeyVisibility();
                            setMenuKeyVisibility();
                        }
                    });
            XposedHelpers.findAndHookMethod(navbarViewClass, "updateIcons", Context.class, Configuration.class, Configuration.class, new XC_MethodHook() {
                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    if (mGbContext == null) return;

                    final Resources gbRes = mGbContext.getResources();
                    try {
                        mRecentIcon = (Drawable) XposedHelpers.getObjectField(param.thisObject, "mRecentIcon");
                        mRecentLandIcon = mRecentIcon;
                        mRecentAltIcon = gbRes.getDrawable(R.drawable.ic_sysbar_recent_clear, null);
                        mRecentAltLandIcon = gbRes.getDrawable(R.drawable.ic_sysbar_recent_clear, null);
                    } catch (Throwable t) {
                        log("getIcons: system does not seem to have standard AOSP recents key? (" + t.getMessage() + ")");
                    }
                }
            });

            XposedHelpers.findAndHookMethod(navbarViewClass, "setNavigationIconHints",
                    int.class, boolean.class, new XC_MethodHook() {
                        @Override
                        protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                            if (mNavbarColorsEnabled) {
                                final int navigationIconHints = XposedHelpers.getIntField(
                                        param.thisObject, "mNavigationIconHints");
                                if ((Integer) param.args[0] != navigationIconHints || (Boolean) param.args[1]) {
                                    setKeyColor();
                                }
                            }
                            if (mHideImeSwitcher) {
                                hideImeSwitcher();
                            }
                            setDpadKeyVisibility();

                            try {
                                Method m = XposedHelpers.findMethodExact(navbarViewClass, "getRecentsButton");
                                Object buttonDispatcher = m.invoke(param.thisObject);
                                ArrayList<View> views = (ArrayList<View>) XposedHelpers.getObjectField(buttonDispatcher, "mViews");
                                if ((views != null || !views.isEmpty())) {
                                    for (View view : views) {
                                        if (view instanceof ImageView && view.getClass().getName().toLowerCase().contains("KeyButtonView".toLowerCase())) {
                                            mRecentBtn = (ImageView) view;
                                        }
                                    }
                                }
                            } catch (NoSuchMethodError nme) {
                                if (DEBUG) log("getRecentsButton method doesn't exist");
                            }
                            mNavbarVertical = XposedHelpers.getBooleanField(param.thisObject, "mVertical");
                            updateRecentAltButton();
                        }
                    });

            XposedHelpers.findAndHookMethod(navbarTransitionsClass, "applyMode",
                    int.class, boolean.class, boolean.class, new XC_MethodHook() {
                        @Override
                        protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                            int barMode = (int) param.args[0];
                            if (barMode != MODE_LIGHTS_OUT_TRANSPARENT) {
                                mBarModeOriginal = barMode;
                            }
                            if (mAutofadeTimeoutMs > 0 &&
                                    SystemClock.uptimeMillis() - mLastTouchMs >= mAutofadeTimeoutMs &&
                                    barMode != MODE_LIGHTS_OUT &&
                                    barMode != MODE_LIGHTS_OUT_TRANSPARENT) {
                                param.args[0] = MODE_LIGHTS_OUT_TRANSPARENT;
                            }
                        }

                        @Override
                        protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                            final int mode = (Integer) param.args[0];
                            final boolean animate = (Boolean) param.args[1];
                            final boolean isOpaque = mode == MODE_OPAQUE || mode == MODE_LIGHTS_OUT;
                            final float alpha = isOpaque ? KeyButtonView.DEFAULT_QUIESCENT_ALPHA : 1f;
                            for (int i = 0; i < mNavbarViewInfo.length; i++) {
                                if (mNavbarViewInfo[i] != null) {
                                    if (mNavbarViewInfo[i].customKey != null) {
                                        mNavbarViewInfo[i].customKey.setQuiescentAlpha(alpha, animate);
                                    }
                                    if (mNavbarViewInfo[i].dpadLeft != null) {
                                        mNavbarViewInfo[i].dpadLeft.setQuiescentAlpha(alpha, animate);
                                    }
                                    if (mNavbarViewInfo[i].dpadRight != null) {
                                        mNavbarViewInfo[i].dpadRight.setQuiescentAlpha(alpha, animate);
                                    }
                                }
                            }
                        }
                    });

            if (mNavbarLeftHanded) {
                XposedHelpers.findAndHookMethod(CLASS_DEADZONE, classLoader, "onTouchEvent",
                        MotionEvent.class, new XC_MethodReplacement() {
                            @Override
                            protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
                                try {
                                    View v = (View) param.thisObject;
                                    MotionEvent event = (MotionEvent) param.args[0];
                                    final int action = event.getAction();
                                    if (action == MotionEvent.ACTION_OUTSIDE) {
                                        XposedHelpers.setLongField(v, "mLastPokeTime", event.getEventTime());
                                    } else if (action == MotionEvent.ACTION_DOWN) {
                                        int size = (int) (float) (Float) (XposedHelpers.callMethod(v, "getSize", event.getEventTime()));
                                        boolean vertical = XposedHelpers.getBooleanField(v, "mVertical");
                                        boolean isCaptured;
                                        if (vertical) {
                                            float pixelsFromRight = v.getWidth() - event.getX();
                                            isCaptured = 0 <= pixelsFromRight && pixelsFromRight < size;
                                        } else {
                                            isCaptured = event.getY() < size;
                                        }
                                        if (isCaptured) {
                                            return true;
                                        }
                                    }
                                    return false;
                                } catch (Throwable t) {
                                    XposedBridge.log(t);
                                    return XposedBridge.invokeOriginalMethod(param.method, param.thisObject, param.args);
                                }
                            }
                        });
            }

            XposedHelpers.findAndHookMethod(CLASS_KEY_BUTTON_RIPPLE, classLoader,
                    "getRipplePaint", new XC_MethodHook() {
                        @Override
                        protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                            if (mNavbarColorsEnabled) {
                                ((Paint) param.getResult()).setColor(mKeyGlowColor);
                            }
                        }
                    });

            XposedHelpers.findAndHookMethod(CLASS_KEY_BUTTON_VIEW, classLoader,
                    "sendEvent", int.class, int.class, long.class, new XC_MethodHook() {
                        @Override
                        protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                            if (mPm == null) {
                                mPm = (PowerManager) ((View) param.thisObject).getContext()
                                        .getSystemService(Context.POWER_SERVICE);
                            }
                            if (mPm != null && !mPm.isInteractive()) {
                                int keyCode = XposedHelpers.getIntField(param.thisObject, "mCode");
                                if (keyCode != KeyEvent.KEYCODE_HOME) {
                                    if (DEBUG)
                                        log("key button sendEvent: ignoring since not interactive");
                                    param.setResult(null);
                                }
                            }
                        }
                    });

            XposedHelpers.findAndHookMethod(CLASS_PHONE_STATUSBAR, classLoader,
                    "shouldDisableNavbarGestures", new XC_MethodHook() {
                        @Override
                        protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                            if (mHomeLongpressAction != 0) {
                                param.setResult(true);
                            }
                        }
                    });

            XC_MethodHook touchEventHook = new XC_MethodHook() {
                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    if (mAutofadeTimeoutMs == 0) return;

                    int action = ((MotionEvent) param.args[0]).getAction();
                    if (action == MotionEvent.ACTION_DOWN ||
                            (action == MotionEvent.ACTION_OUTSIDE &&
                                    "SCREEN".equals(mAutofadeShowKeysPolicy))) {
                        mLastTouchMs = SystemClock.uptimeMillis();
                        if (mBarModeHandler.hasMessages(MSG_LIGHTS_OUT)) {
                            mBarModeHandler.removeMessages(MSG_LIGHTS_OUT);
                        } else {
                            setBarMode(mBarModeOriginal);
                        }
                        mBarModeHandler.sendEmptyMessageDelayed(MSG_LIGHTS_OUT, mAutofadeTimeoutMs);
                    }
                }
            };
            XposedHelpers.findAndHookMethod(CLASS_NAVBAR_VIEW, classLoader,
                    "onInterceptTouchEvent", MotionEvent.class, touchEventHook);
            XposedHelpers.findAndHookMethod(CLASS_NAVBAR_VIEW, classLoader,
                    "onTouchEvent", MotionEvent.class, touchEventHook);
        } catch (Throwable t) {
            XposedBridge.log(t);
        }
    }

    private static void setNavigationBarVisible(boolean hide) {
        if (hide) {
            mNavigationBarView.setTag(false);
            sWindowManager.removeViewImmediate(mNavigationBarView);
        } else {
            mNavigationBarView.setTag(true);
            sWindowManager.addView(mNavigationBarView, sLayoutParams);
        }
    }

    private static void startHookNavigationBar(final Class<?> windowManager) {
        XposedHelpers.findAndHookMethod(windowManager, "addView", View.class, ViewGroup.LayoutParams.class, new XC_MethodHook() {
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                if (param.args[0].getClass().getName().contains("NavigationBarView")) {
                    if (sWindowManager == null) {
                        sWindowManager = (WindowManager) param.thisObject;
                        sLayoutParams = (ViewGroup.LayoutParams) param.args[1];
                    }
                    if (((View) param.args[0]).getTag() == null && sHideNavigationBar) {
                        new Handler().postDelayed(new Runnable() {
                            @Override
                            public void run() {
                                setNavigationBarVisible(true);
                            }
                        }, 200);
                    }
                }
            }
        });
    }

    private static Handler mBarModeHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == MSG_LIGHTS_OUT) {
                setBarMode(MODE_LIGHTS_OUT_TRANSPARENT);
            }
        }
    };

    private static void setBarMode(int mode) {
        try {
            Object bt = XposedHelpers.callMethod(mNavigationBarView, "getBarTransitions");
            XposedHelpers.callMethod(bt, "applyMode", mode, true, true);
        } catch (Throwable t) {
            XposedBridge.log(t);
        }
    }

    private static void prepareNavbarViewInfo(ViewGroup navButtons, int index,
                                              KeyButtonContainer appView, KeyButtonContainer dpadLeft, KeyButtonContainer dpadRight) {
        try {
            mNavbarViewInfo[index] = new NavbarViewInfo();
            mNavbarViewInfo[index].navButtons = navButtons;
            mNavbarViewInfo[index].customKey = appView;
            mNavbarViewInfo[index].dpadLeft = dpadLeft;
            mNavbarViewInfo[index].dpadRight = dpadRight;
            mNavbarViewInfo[index].navButtons.addView(dpadLeft, 0);
            mNavbarViewInfo[index].navButtons.addView(dpadRight);

            int searchPosition = index == 0 ? 1 : navButtons.getChildCount() - 2;
            View v = navButtons.getChildAt(searchPosition);
            if (v.getId() == -1 && !v.getClass().getName().equals(CLASS_KEY_BUTTON_VIEW) &&
                    !(v instanceof ViewGroup)) {
                mNavbarViewInfo[index].originalView = v;
            } else {
                searchPosition = searchPosition == 1 ? navButtons.getChildCount() - 2 : 1;
                v = navButtons.getChildAt(searchPosition);
                if (v.getId() == -1 && !v.getClass().getName().equals(CLASS_KEY_BUTTON_VIEW) &&
                        !(v instanceof ViewGroup)) {
                    mNavbarViewInfo[index].originalView = v;
                }
            }
            mNavbarViewInfo[index].customKeyPosition = searchPosition;

            // find ime switcher and menu group
            int childCount = mNavbarViewInfo[index].navButtons.getChildCount();
            for (int i = 0; i < childCount; i++) {
                View child = mNavbarViewInfo[index].navButtons.getChildAt(i);
                if (child instanceof ViewGroup) {
                    mNavbarViewInfo[index].menuImeGroup = (ViewGroup) child;
                    break;
                }
            }

            // determine custom key layout
            boolean hasVerticalNavbar = mGbContext.getResources().getBoolean(R.bool.hasVerticalNavbar);
            final int sizeResId = navButtons.getResources().getIdentifier(hasVerticalNavbar ?
                    "navigation_side_padding" : "navigation_extra_key_width", "dimen", PACKAGE_NAME);
            final int size = sizeResId == 0 ?
                    (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                            50, navButtons.getResources().getDisplayMetrics()) :
                    navButtons.getResources().getDimensionPixelSize(sizeResId);
            if (DEBUG) log("App key view minimum size=" + size);
            ViewGroup.LayoutParams lp;
            int w = (index == 1 && hasVerticalNavbar) ? ViewGroup.LayoutParams.MATCH_PARENT : size;
            int h = (index == 1 && hasVerticalNavbar) ? size : ViewGroup.LayoutParams.MATCH_PARENT;
            if (navButtons instanceof RelativeLayout)
                lp = new RelativeLayout.LayoutParams(w, h);
            else if (navButtons instanceof FrameLayout)
                lp = new FrameLayout.LayoutParams(w, h);
            else
                lp = new LinearLayout.LayoutParams(w, h, 0);
            if (DEBUG) log("appView: lpWidth=" + lp.width + "; lpHeight=" + lp.height);
            mNavbarViewInfo[index].customKey.setLayoutParams(lp);
            mNavbarViewInfo[index].dpadLeft.setLayoutParams(lp);
            mNavbarViewInfo[index].dpadRight.setLayoutParams(lp);
        } catch (Throwable t) {
            log("Error preparing NavbarViewInfo: " + t.getMessage());
        }
    }

    private static void setCustomKeyVisibility() {
        try {
            final int disabledFlags = XposedHelpers.getIntField(mNavigationBarView, "mDisabledFlags");
            final boolean visible = mCustomKeyEnabled &&
                    !((disabledFlags & STATUS_BAR_DISABLE_RECENT) != 0);
            for (int i = 0; i <= 1; i++) {
                if (mNavbarViewInfo[i] == null) continue;

                if (mNavbarViewInfo[i].visible != visible) {
                    if (mNavbarViewInfo[i].originalView != null) {
                        mNavbarViewInfo[i].navButtons.removeViewAt(mNavbarViewInfo[i].customKeyPosition);
                        mNavbarViewInfo[i].navButtons.addView(visible ?
                                        mNavbarViewInfo[i].customKey : mNavbarViewInfo[i].originalView,
                                mNavbarViewInfo[i].customKeyPosition);
                    } else {
                        if (visible) {
                            mNavbarViewInfo[i].navButtons.addView(mNavbarViewInfo[i].customKey,
                                    mNavbarViewInfo[i].customKeyPosition);
                        } else {
                            mNavbarViewInfo[i].navButtons.removeView(mNavbarViewInfo[i].customKey);
                        }
                    }
                    mNavbarViewInfo[i].visible = visible;
                    if (DEBUG) log("setAppKeyVisibility: visible=" + visible);
                }

                // swap / unswap with menu key if necessary
                if ((!mCustomKeyEnabled || !mCustomKeySwapEnabled) &&
                        mNavbarViewInfo[i].menuCustomSwapped) {
                    swapMenuAndCustom(mNavbarViewInfo[i]);
                } else if (mCustomKeyEnabled && mCustomKeySwapEnabled &&
                        !mNavbarViewInfo[i].menuCustomSwapped) {
                    swapMenuAndCustom(mNavbarViewInfo[i]);
                }
            }
        } catch (Throwable t) {
            log("Error setting app key visibility: " + t.getMessage());
        }
    }

    private static void setMenuKeyVisibility() {
        try {
            final boolean showMenu = XposedHelpers.getBooleanField(mNavigationBarView, "mShowMenu");
            final int disabledFlags = XposedHelpers.getIntField(mNavigationBarView, "mDisabledFlags");
            final boolean visible = (showMenu || mAlwaysShowMenukey) &&
                    !((disabledFlags & STATUS_BAR_DISABLE_RECENT) != 0);
            int menuResId = mResources.getIdentifier("menu", "id", PACKAGE_NAME);
            int imeSwitcherResId = mResources.getIdentifier("ime_switcher", "id", PACKAGE_NAME);
            for (int i = 0; i <= 1; i++) {
                if (mNavbarViewInfo[i] == null) continue;

                boolean isImeSwitcherVisible = false;
                View v = null;
                if (imeSwitcherResId != 0) {
                    v = mNavbarViewInfo[i].navButtons.findViewById(imeSwitcherResId);
                    if (v != null) {
                        isImeSwitcherVisible = v.getVisibility() == View.VISIBLE;
                    }
                }
                v = mNavbarViewInfo[i].navButtons.findViewById(menuResId);
                if (v != null) {
                    v.setVisibility(mDpadKeysVisible || isImeSwitcherVisible ? View.GONE :
                            visible ? View.VISIBLE : View.INVISIBLE);
                }
            }
        } catch (Throwable t) {
            log("Error setting menu key visibility:" + t.getMessage());
        }

    }

    private static void hideImeSwitcher() {
        try {
            int imeSwitcherResId = mResources.getIdentifier("ime_switcher", "id", PACKAGE_NAME);
            for (int i = 0; i <= 1; i++) {
                View v = mNavbarViewInfo[i].navButtons.findViewById(imeSwitcherResId);
                if (v != null) {
                    v.setVisibility(View.GONE);
                }
            }
        } catch (Throwable t) {
            log("Error hiding IME switcher: " + t.getMessage());
        }
    }

    public static void setRecentAlt(boolean recentAlt) {
        if (mRecentBtn == null || mRecentAlt == recentAlt) return;

        mRecentAlt = recentAlt;
        if (mRecentAlt) {
            updateRecentAltButton();
            broadcastRecentsActions(mRecentBtn.getContext(),
                    new ModHwKeys.HwKeyAction(GravityBoxSettings.HWKEY_ACTION_CLEAR_ALL_RECENTS_SINGLETAP, null));
        } else {
            mRecentBtn.post(resetRecentKeyStateRunnable);
        }
    }

    private static Runnable resetRecentKeyStateRunnable = new Runnable() {
        @Override
        public void run() {
            if (mRecentBtn.isPressed()) {
                mRecentBtn.postDelayed(this, 200);
            } else {
                updateRecentAltButton();
                broadcastRecentsActions(mRecentBtn.getContext(), mRecentsSingletapActionBck);
            }
        }
    };

    private static void updateRecentAltButton() {
        if (mRecentBtn != null && mRecentIcon != null && mRecentLandIcon != null) {
            if (mRecentAlt) {
                mRecentBtn.setImageDrawable(mNavbarVertical ? mRecentAltLandIcon : mRecentAltIcon);
            } else {
                mRecentBtn.setImageDrawable(mNavbarVertical ? mRecentLandIcon : mRecentIcon);
            }
        }
    }

    private static void broadcastRecentsActions(Context context, ModHwKeys.HwKeyAction singleTapAction) {
        if (context == null) return;

        Intent intent;
        intent = new Intent();
        intent.setAction(GravityBoxSettings.ACTION_PREF_HWKEY_CHANGED);
        intent.putExtra(GravityBoxSettings.EXTRA_HWKEY_KEY, GravityBoxSettings.PREF_KEY_HWKEY_RECENTS_SINGLETAP);
        intent.putExtra(GravityBoxSettings.EXTRA_HWKEY_VALUE, singleTapAction.actionId);
        intent.putExtra(GravityBoxSettings.EXTRA_HWKEY_CUSTOM_APP, singleTapAction.customApp);
        context.sendBroadcast(intent);
    }

    private static void setDpadKeyVisibility() {
        if (!mCursorControlEnabled) return;
        try {
            final int iconHints = XposedHelpers.getIntField(mNavigationBarView, "mNavigationIconHints");
            final int disabledFlags = XposedHelpers.getIntField(mNavigationBarView, "mDisabledFlags");
            mDpadKeysVisible = !((disabledFlags & STATUS_BAR_DISABLE_RECENT) != 0) &&
                    (iconHints & NAVIGATION_HINT_BACK_ALT) != 0;

            for (int i = 0; i <= 1; i++) {
                // hide/unhide app key or whatever view at that position
                View v = mNavbarViewInfo[i].navButtons.getChildAt(mNavbarViewInfo[i].customKeyPosition);
                if (v != null) {
                    v.setVisibility(mDpadKeysVisible ? View.GONE : View.VISIBLE);
                }
                // hide/unhide menu key
                int menuResId = mResources.getIdentifier("menu", "id", PACKAGE_NAME);
                v = mNavbarViewInfo[i].navButtons.findViewById(menuResId);
                if (v != null) {
                    if (mDpadKeysVisible) {
                        v.setVisibility(View.GONE);
                    } else {
                        setMenuKeyVisibility();
                    }
                }
                // Hide view group holding menu/customkey and ime switcher if all children hidden
                if (mNavbarViewInfo[i].menuImeGroup != null) {
                    boolean allHidden = true;
                    for (int j = 0; j < mNavbarViewInfo[i].menuImeGroup.getChildCount(); j++) {
                        allHidden &= mNavbarViewInfo[i].menuImeGroup.getChildAt(j)
                                .getVisibility() != View.VISIBLE;
                    }
                    mNavbarViewInfo[i].menuImeGroup.setVisibility(
                            mDpadKeysVisible && allHidden ? View.GONE : View.VISIBLE);
                }
                mNavbarViewInfo[i].dpadLeft.setVisibility(mDpadKeysVisible ? View.VISIBLE : View.GONE);
                mNavbarViewInfo[i].dpadRight.setVisibility(mDpadKeysVisible ? View.VISIBLE : View.GONE);
                if (DEBUG) log("setDpadKeyVisibility: visible=" + mDpadKeysVisible);
            }
        } catch (Throwable t) {
            log("Error setting dpad key visibility: " + t.getMessage());
        }
    }

    private static void updateRecentsKeyCode() {
        if (mRecentsKeys == null) return;

        try {
            final boolean hasAction = recentsKeyHasAction();
            for (Object o : mRecentsKeys) {
                if (o != null) {
                    XposedHelpers.setIntField(o, "mCode", hasAction ? KeyEvent.KEYCODE_APP_SWITCH : 0);
                }
            }
        } catch (Throwable t) {
            XposedBridge.log(t);
        }
    }

    private static boolean recentsKeyHasAction() {
        return (mRecentsSingletapAction.actionId != 0 ||
                mRecentsLongpressAction.actionId != 0 ||
                mRecentsDoubletapAction.actionId != 0 ||
                !mHwKeysEnabled);
    }

    private static void setKeyColor() {
        try {
            View v = (View) XposedHelpers.getObjectField(mNavigationBarView, "mCurrentView");
            ViewGroup navButtons = (ViewGroup) v.findViewById(
                    mResources.getIdentifier("nav_buttons", "id", PACKAGE_NAME));
            setKeyColorRecursive(navButtons);
        } catch (Throwable t) {
            XposedBridge.log(t);
        }
    }

    private static void setKeyColorRecursive(ViewGroup vg) {
        if (vg == null) return;
        final int childCount = vg.getChildCount();
        for (int i = 0; i < childCount; i++) {
            View child = vg.getChildAt(i);
            if (child instanceof ViewGroup) {
                setKeyColorRecursive((ViewGroup) child);
            } else if (child instanceof ImageView) {
                ImageView imgv = (ImageView) vg.getChildAt(i);
                if (mNavbarColorsEnabled) {
                    imgv.setColorFilter(mKeyColor, PorterDuff.Mode.SRC_ATOP);
                } else {
                    imgv.clearColorFilter();
                }
                if (imgv.getClass().getName().equals(CLASS_KEY_BUTTON_VIEW) &&
                        !mNavbarColorsEnabled) {
                    Drawable ripple = imgv.getBackground();
                    if (ripple != null &&
                            ripple.getClass().getName().equals(CLASS_KEY_BUTTON_RIPPLE)) {
                        Paint paint = (Paint) XposedHelpers.getObjectField(ripple, "mRipplePaint");
                        if (paint != null) {
                            paint.setColor(0xffffffff);
                        }
                    }
                } else if (imgv instanceof KeyButtonView) {
                    ((KeyButtonView) imgv).setGlowColor(mNavbarColorsEnabled ?
                            mKeyGlowColor : mKeyDefaultGlowColor);
                }
            }
        }
    }

    private static void swapBackAndRecents() {
        try {
            final int backButtonResId = mResources.getIdentifier("back", "id", PACKAGE_NAME);
            final int recentAppsResId = mResources.getIdentifier("recent_apps", "id", PACKAGE_NAME);
            for (int i = 0; i < 2; i++) {
                if (mNavbarViewInfo[i].navButtons == null) {
                    continue;
                }
                View backKey = mNavbarViewInfo[i].navButtons.findViewById(backButtonResId);
                View recentsKey = mNavbarViewInfo[i].navButtons.findViewById(recentAppsResId);
                ViewInfo backViewInfo = removeView(mNavbarViewInfo[i].navButtons, backKey);
                ViewInfo recentsViewInfo = removeView(mNavbarViewInfo[i].navButtons, recentsKey);
                if (backViewInfo == null || recentsViewInfo == null) {
                    continue;
                }
                ViewGroup.LayoutParams params = recentsKey.getLayoutParams();
                recentsKey.setLayoutParams(backKey.getLayoutParams());
                backKey.setLayoutParams(params);
                if (backViewInfo.mViewGroup == recentsViewInfo.mViewGroup) {
                    if (backViewInfo.index <= recentsViewInfo.index) {
                        recentsViewInfo.index++;
                    }
                }
                if (backViewInfo.index <= recentsViewInfo.index) {
                    backViewInfo.mViewGroup.addView(recentsKey, backViewInfo.index);
                    recentsViewInfo.mViewGroup.addView(backKey, recentsViewInfo.index);
                } else {
                    recentsViewInfo.mViewGroup.addView(backKey, recentsViewInfo.index);
                    backViewInfo.mViewGroup.addView(recentsKey, backViewInfo.index);
                }
            }
        } catch (Throwable t) {
            t.printStackTrace();
            log("Error swapping back and recents key: " + t.getMessage());
        }
    }

    private static void log(ViewGroup b) {
        if (!DEBUG) {
            return;
        }
        log("--------------");
        log(b.toString());
        for (int i = 0; i < b.getChildCount(); i++) {
            log(b.getChildAt(i).toString());
        }
        log("--------------\n");
    }

    private static ViewInfo removeView(ViewGroup viewGroup, View view) {
        int index = viewGroup.indexOfChild(view);
        if (index != -1) {
            ViewInfo viewInfo = new ViewInfo();
            viewInfo.mViewGroup = viewGroup;
            viewInfo.index = index;
            viewGroup.removeView(view);
            return viewInfo;
        }
        for (int i = 0; i < viewGroup.getChildCount(); i++) {
            View view1 = viewGroup.getChildAt(i);
            if (view1 instanceof ViewGroup) {
                ViewInfo viewInfo = removeView((ViewGroup) view1, view);
                if (viewInfo != null) {
                    return viewInfo;
                }
            }
        }
        return null;
    }

    private static class ViewInfo {
        ViewGroup mViewGroup;
        int index;

        @Override
        public String toString() {
            return "ViewInfo{" +
                    "mViewGroup=" + mViewGroup +
                    ", index=" + index +
                    '}';
        }
    }

    private static void swapMenuAndCustom(NavbarViewInfo nvi) {
        if (!nvi.customKey.isAttachedToWindow()) return;

        try {
            final int menuButtonResId = mResources.getIdentifier("menu", "id", PACKAGE_NAME);
            View menuKey = (View) nvi.navButtons.findViewById(menuButtonResId).getParent();
            View customKey = nvi.customKey;
            int menuPos = nvi.navButtons.indexOfChild(menuKey);
            int customPos = nvi.customKeyPosition;
            nvi.navButtons.removeView(menuKey);
            nvi.navButtons.removeView(customKey);
            if (menuPos < customPos) {
                nvi.navButtons.addView(customKey, menuPos);
                nvi.navButtons.addView(menuKey, customPos);
            } else {
                nvi.navButtons.addView(menuKey, customPos);
                nvi.navButtons.addView(customKey, menuPos);
            }
            nvi.customKeyPosition = menuPos;
            nvi.menuCustomSwapped = !nvi.menuCustomSwapped;
            if (DEBUG) log("swapMenuAndCustom: swapped=" + nvi.menuCustomSwapped);
        } catch (Throwable t) {
            log("Error swapping menu and custom key: " + t.getMessage());
        }
    }

    private static void updateCustomKeyIcon() {
        try {
            for (NavbarViewInfo nvi : mNavbarViewInfo) {
                nvi.customKey.setImageDrawable(getCustomKeyIconDrawable());
            }
        } catch (Throwable t) {
            XposedBridge.log(t);
        }
    }

    private static Drawable getCustomKeyIconDrawable() {
        switch (mCustomKeyIconStyle) {
            case CUSTOM:
                File f = new File(mGbContext.getFilesDir() + "/navbar_custom_key_image");
                if (f.exists() && f.canRead()) {
                    Bitmap b = BitmapFactory.decodeFile(f.getAbsolutePath());
                    if (b != null) {
                        return new BitmapDrawable(mResources, b);
                    }
                }
                // fall through to transparent if custom not available
            case TRANSPARENT:
                Drawable d = mGbContext.getDrawable(R.drawable.ic_sysbar_apps);
                Drawable transD = new ColorDrawable(Color.TRANSPARENT);
                transD.setBounds(0, 0, d.getMinimumWidth(), d.getMinimumHeight());
                return transD;
            case THREE_DOT:
                return mGbContext.getDrawable(R.drawable.ic_sysbar_apps2);
            case SIX_DOT:
            default:
                return mGbContext.getDrawable(R.drawable.ic_sysbar_apps);
        }
    }

    private static ScaleType getIconScaleType(int index, int keyId) {
        if (mUseLargerIcons) {
            return ScaleType.FIT_CENTER;
        } else {
            ScaleType origScaleType = mNavbarViewInfo[index] == null ? ScaleType.CENTER :
                    mNavbarViewInfo[index].originalScaleType.get(keyId, ScaleType.CENTER);
            if (index == 0) {
                return (mNavbarHeight < 75 ? ScaleType.CENTER_INSIDE : origScaleType);
            } else {
                boolean hasVerticalNavbar = mGbContext.getResources().getBoolean(R.bool.hasVerticalNavbar);
                return (mNavbarWidth < 75 && hasVerticalNavbar ? ScaleType.CENTER_INSIDE :
                        origScaleType);
            }
        }
    }

    private static int[] getIconPaddingPx(int index) {
        int[] p = new int[]{0, 0, 0, 0};

        if (mUseLargerIcons) {
            int paddingPx = Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 7.5f,
                    mResources.getDisplayMetrics()));
            p[0] = p[1] = p[2] = p[3] = paddingPx;
        } else {
            int paddingPx = Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5,
                    mResources.getDisplayMetrics()));
            boolean hasVerticalNavbar = mGbContext.getResources().getBoolean(R.bool.hasVerticalNavbar);
            if (index == 0 && mNavbarHeight < 75) {
                p[1] = paddingPx;
                p[3] = paddingPx;
            }
            if (index == 1 && hasVerticalNavbar && mNavbarWidth < 75) {
                p[0] = paddingPx;
                p[2] = paddingPx;
            }
        }
        return p;
    }

    private static void updateIconScaleType() {
        try {
            for (int i = 0; i < mNavbarViewInfo.length; i++) {
                int[] paddingPx = getIconPaddingPx(i);
                ViewGroup navButtons = mNavbarViewInfo[i].navButtons;
                int childCount = navButtons.getChildCount();
                for (int j = 0; j < childCount; j++) {
                    View child = navButtons.getChildAt(j);
                    if (child.getClass().getName().equals(CLASS_KEY_BUTTON_VIEW) ||
                            child instanceof KeyButtonView) {
                        ImageView iv = (ImageView) child;
                        if (iv.getId() != View.NO_ID &&
                                mNavbarViewInfo[i].originalScaleType.get(iv.getId()) == null) {
                            mNavbarViewInfo[i].originalScaleType.put(iv.getId(),
                                    iv.getScaleType());
                        }
                        iv.setScaleType(getIconScaleType(i, iv.getId()));
                        if (!Utils.isXperiaDevice()) {
                            iv.setPadding(paddingPx[0], paddingPx[1], paddingPx[2], paddingPx[3]);
                        }
                    }
                }
                // menu/ime group
                if (mNavbarViewInfo[i].menuImeGroup != null) {
                    childCount = mNavbarViewInfo[i].menuImeGroup.getChildCount();
                    for (int j = 0; j < childCount; j++) {
                        View child = mNavbarViewInfo[i].menuImeGroup.getChildAt(j);
                        if (child.getClass().getName().equals(CLASS_KEY_BUTTON_VIEW)) {
                            KeyButtonContainer iv = (KeyButtonContainer) child;
                            if (iv.getId() != View.NO_ID &&
                                    mNavbarViewInfo[i].originalScaleType.get(iv.getId()) == null) {
                                mNavbarViewInfo[i].originalScaleType.put(iv.getId(),
                                        iv.getScaleType());
                            }
                            iv.setScaleType(getIconScaleType(i, iv.getId()));
                            if (!Utils.isXperiaDevice()) {
                                iv.setPadding(
                                        paddingPx[0], paddingPx[1], paddingPx[2], paddingPx[3]);
                            }
                        }
                    }
                }
                // do this explicitly for custom key
                KeyButtonContainer key = mNavbarViewInfo[i].customKey;
                key.setScaleType(getIconScaleType(i, key.getId()));
                if (!Utils.isXperiaDevice()) {
                    key.setPadding(paddingPx[0], paddingPx[1], paddingPx[2], paddingPx[3]);
                }
            }
        } catch (Throwable t) {

        }
    }
}