From a1c57de466855bf826262a1613699ce9c468cadc Mon Sep 17 00:00:00 2001
From: Charles Lombardo <clombardo169@gmail.com>
Date: Wed, 8 Mar 2023 17:12:47 -0500
Subject: [PATCH] android: Convert InputOverlay to Kotlin

---
 .../yuzu/yuzu_emu/overlay/InputOverlay.java   | 656 -------------
 .../org/yuzu/yuzu_emu/overlay/InputOverlay.kt | 886 ++++++++++++++++++
 2 files changed, 886 insertions(+), 656 deletions(-)
 delete mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.java
 create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt

diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.java
deleted file mode 100644
index 74119c398..000000000
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.java
+++ /dev/null
@@ -1,656 +0,0 @@
-/**
- * Copyright 2013 Dolphin Emulator Project
- * Licensed under GPLv2+
- * Refer to the license.txt file included.
- */
-
-package org.yuzu.yuzu_emu.overlay;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.VectorDrawable;
-import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
-import android.preference.PreferenceManager;
-import android.util.AttributeSet;
-import android.util.DisplayMetrics;
-import android.view.Display;
-import android.view.MotionEvent;
-import android.view.SurfaceView;
-import android.view.View;
-import android.view.View.OnTouchListener;
-
-import androidx.core.content.ContextCompat;
-
-import org.yuzu.yuzu_emu.NativeLibrary;
-import org.yuzu.yuzu_emu.NativeLibrary.ButtonType;
-import org.yuzu.yuzu_emu.NativeLibrary.StickType;
-import org.yuzu.yuzu_emu.R;
-import org.yuzu.yuzu_emu.utils.EmulationMenuSettings;
-
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Draws the interactive input overlay on top of the
- * {@link SurfaceView} that is rendering emulation.
- */
-public final class InputOverlay extends SurfaceView implements OnTouchListener, SensorEventListener {
-    private final Set<InputOverlayDrawableButton> overlayButtons = new HashSet<>();
-    private final Set<InputOverlayDrawableDpad> overlayDpads = new HashSet<>();
-    private final Set<InputOverlayDrawableJoystick> overlayJoysticks = new HashSet<>();
-
-    private boolean mIsInEditMode = false;
-
-    private SharedPreferences mPreferences;
-
-    private float[] gyro = new float[3];
-    private float[] accel = new float[3];
-
-    private long motionTimestamp;
-
-    /**
-     * Constructor
-     *
-     * @param context The current {@link Context}.
-     * @param attrs   {@link AttributeSet} for parsing XML attributes.
-     */
-    public InputOverlay(Context context, AttributeSet attrs) {
-        super(context, attrs);
-
-        mPreferences = PreferenceManager.getDefaultSharedPreferences(getContext());
-        if (!mPreferences.getBoolean("OverlayInit", false)) {
-            defaultOverlay();
-        }
-
-        // Load the controls.
-        refreshControls();
-
-        // Set the on motion sensor listener.
-        setMotionSensorListener(context);
-
-        // Set the on touch listener.
-        setOnTouchListener(this);
-
-        // Force draw
-        setWillNotDraw(false);
-
-        // Request focus for the overlay so it has priority on presses.
-        requestFocus();
-    }
-
-    private void setMotionSensorListener(Context context) {
-        SensorManager sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
-        Sensor gyro_sensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
-        Sensor accel_sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
-
-        if (gyro_sensor != null) {
-            sensorManager.registerListener(this, gyro_sensor, SensorManager.SENSOR_DELAY_GAME);
-        }
-        if (accel_sensor != null) {
-            sensorManager.registerListener(this, accel_sensor, SensorManager.SENSOR_DELAY_GAME);
-        }
-    }
-
-
-    /**
-     * Resizes a {@link Bitmap} by a given scale factor
-     *
-     * @param vectorDrawable The {@link Bitmap} to scale.
-     * @param scale          The scale factor for the bitmap.
-     * @return The scaled {@link Bitmap}
-     */
-    private static Bitmap getBitmap(VectorDrawable vectorDrawable, float scale) {
-        Bitmap bitmap = Bitmap.createBitmap((int) (vectorDrawable.getIntrinsicWidth() * scale),
-                (int) (vectorDrawable.getIntrinsicHeight() * scale), Bitmap.Config.ARGB_8888);
-        Canvas canvas = new Canvas(bitmap);
-        vectorDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
-        vectorDrawable.draw(canvas);
-        return bitmap;
-    }
-
-    private static Bitmap getBitmap(Context context, int drawableId, float scale) {
-        Drawable drawable = ContextCompat.getDrawable(context, drawableId);
-        if (drawable instanceof BitmapDrawable) {
-            return BitmapFactory.decodeResource(context.getResources(), drawableId);
-        } else if (drawable instanceof VectorDrawable) {
-            return getBitmap((VectorDrawable) drawable, scale);
-        } else {
-            throw new IllegalArgumentException("unsupported drawable type");
-        }
-    }
-
-    /**
-     * Initializes an InputOverlayDrawableButton, given by resId, with all of the
-     * parameters set for it to be properly shown on the InputOverlay.
-     * <p>
-     * This works due to the way the X and Y coordinates are stored within
-     * the {@link SharedPreferences}.
-     * <p>
-     * In the input overlay configuration menu,
-     * once a touch event begins and then ends (ie. Organizing the buttons to one's own liking for the overlay).
-     * the X and Y coordinates of the button at the END of its touch event
-     * (when you remove your finger/stylus from the touchscreen) are then stored
-     * within a SharedPreferences instance so that those values can be retrieved here.
-     * <p>
-     * This has a few benefits over the conventional way of storing the values
-     * (ie. within the yuzu ini file).
-     * <ul>
-     * <li>No native calls</li>
-     * <li>Keeps Android-only values inside the Android environment</li>
-     * </ul>
-     * <p>
-     * Technically no modifications should need to be performed on the returned
-     * InputOverlayDrawableButton. Simply add it to the HashSet of overlay items and wait
-     * for Android to call the onDraw method.
-     *
-     * @param context      The current {@link Context}.
-     * @param defaultResId The resource ID of the {@link Drawable} to get the {@link Bitmap} of (Default State).
-     * @param pressedResId The resource ID of the {@link Drawable} to get the {@link Bitmap} of (Pressed State).
-     * @param buttonId     Identifier for determining what type of button the initialized InputOverlayDrawableButton represents.
-     * @return An {@link InputOverlayDrawableButton} with the correct drawing bounds set.
-     */
-    private static InputOverlayDrawableButton initializeOverlayButton(Context context,
-                                                                      int defaultResId, int pressedResId, int buttonId, String orientation) {
-        // Resources handle for fetching the initial Drawable resource.
-        final Resources res = context.getResources();
-
-        // SharedPreference to retrieve the X and Y coordinates for the InputOverlayDrawableButton.
-        final SharedPreferences sPrefs = PreferenceManager.getDefaultSharedPreferences(context);
-
-        // Decide scale based on button ID and user preference
-        float scale;
-
-        switch (buttonId) {
-            case ButtonType.BUTTON_HOME:
-            case ButtonType.BUTTON_CAPTURE:
-            case ButtonType.BUTTON_PLUS:
-            case ButtonType.BUTTON_MINUS:
-                scale = 0.35f;
-                break;
-            case ButtonType.TRIGGER_L:
-            case ButtonType.TRIGGER_R:
-            case ButtonType.TRIGGER_ZL:
-            case ButtonType.TRIGGER_ZR:
-                scale = 0.38f;
-                break;
-            default:
-                scale = 0.43f;
-                break;
-        }
-
-        scale *= (sPrefs.getInt("controlScale", 50) + 50);
-        scale /= 100;
-
-        // Initialize the InputOverlayDrawableButton.
-        final Bitmap defaultStateBitmap = getBitmap(context, defaultResId, scale);
-        final Bitmap pressedStateBitmap = getBitmap(context, pressedResId, scale);
-        final InputOverlayDrawableButton overlayDrawable =
-                new InputOverlayDrawableButton(res, defaultStateBitmap, pressedStateBitmap, buttonId);
-
-        // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay.
-        // These were set in the input overlay configuration menu.
-        String xKey;
-        String yKey;
-
-        xKey = buttonId + orientation + "-X";
-        yKey = buttonId + orientation + "-Y";
-
-        int drawableX = (int) sPrefs.getFloat(xKey, 0f);
-        int drawableY = (int) sPrefs.getFloat(yKey, 0f);
-
-        int width = overlayDrawable.getWidth();
-        int height = overlayDrawable.getHeight();
-
-        // Now set the bounds for the InputOverlayDrawableButton.
-        // This will dictate where on the screen (and the what the size) the InputOverlayDrawableButton will be.
-        overlayDrawable.setBounds(drawableX - (width / 2), drawableY - (height / 2), drawableX + (width / 2), drawableY + (height / 2));
-
-        // Need to set the image's position
-        overlayDrawable.setPosition(drawableX - (width / 2), drawableY - (height / 2));
-
-        return overlayDrawable;
-    }
-
-    /**
-     * Initializes an {@link InputOverlayDrawableDpad}
-     *
-     * @param context                   The current {@link Context}.
-     * @param defaultResId              The {@link Bitmap} resource ID of the default sate.
-     * @param pressedOneDirectionResId  The {@link Bitmap} resource ID of the pressed sate in one direction.
-     * @param pressedTwoDirectionsResId The {@link Bitmap} resource ID of the pressed sate in two directions.
-     * @param buttonUp                  Identifier for the up button.
-     * @param buttonDown                Identifier for the down button.
-     * @param buttonLeft                Identifier for the left button.
-     * @param buttonRight               Identifier for the right button.
-     * @return the initialized {@link InputOverlayDrawableDpad}
-     */
-    private static InputOverlayDrawableDpad initializeOverlayDpad(Context context,
-                                                                  int defaultResId,
-                                                                  int pressedOneDirectionResId,
-                                                                  int pressedTwoDirectionsResId,
-                                                                  int buttonUp,
-                                                                  int buttonDown,
-                                                                  int buttonLeft,
-                                                                  int buttonRight,
-                                                                  String orientation) {
-        // Resources handle for fetching the initial Drawable resource.
-        final Resources res = context.getResources();
-
-        // SharedPreference to retrieve the X and Y coordinates for the InputOverlayDrawableDpad.
-        final SharedPreferences sPrefs = PreferenceManager.getDefaultSharedPreferences(context);
-
-        // Decide scale based on button ID and user preference
-        float scale = 0.40f;
-
-        scale *= (sPrefs.getInt("controlScale", 50) + 50);
-        scale /= 100;
-
-        // Initialize the InputOverlayDrawableDpad.
-        final Bitmap defaultStateBitmap = getBitmap(context, defaultResId, scale);
-        final Bitmap pressedOneDirectionStateBitmap = getBitmap(context, pressedOneDirectionResId,
-                scale);
-        final Bitmap pressedTwoDirectionsStateBitmap = getBitmap(context, pressedTwoDirectionsResId,
-                scale);
-        final InputOverlayDrawableDpad overlayDrawable =
-                new InputOverlayDrawableDpad(res, defaultStateBitmap,
-                        pressedOneDirectionStateBitmap, pressedTwoDirectionsStateBitmap,
-                        buttonUp, buttonDown, buttonLeft, buttonRight);
-
-        // The X and Y coordinates of the InputOverlayDrawableDpad on the InputOverlay.
-        // These were set in the input overlay configuration menu.
-        int drawableX = (int) sPrefs.getFloat(buttonUp + orientation + "-X", 0f);
-        int drawableY = (int) sPrefs.getFloat(buttonUp + orientation + "-Y", 0f);
-
-        int width = overlayDrawable.getWidth();
-        int height = overlayDrawable.getHeight();
-
-        // Now set the bounds for the InputOverlayDrawableDpad.
-        // This will dictate where on the screen (and the what the size) the InputOverlayDrawableDpad will be.
-        overlayDrawable.setBounds(drawableX - (width / 2), drawableY - (height / 2), drawableX + (width / 2), drawableY + (height / 2));
-
-        // Need to set the image's position
-        overlayDrawable.setPosition(drawableX - (width / 2), drawableY - (height / 2));
-
-        return overlayDrawable;
-    }
-
-    /**
-     * Initializes an {@link InputOverlayDrawableJoystick}
-     *
-     * @param context         The current {@link Context}
-     * @param resOuter        Resource ID for the outer image of the joystick (the static image that shows the circular bounds).
-     * @param defaultResInner Resource ID for the default inner image of the joystick (the one you actually move around).
-     * @param pressedResInner Resource ID for the pressed inner image of the joystick.
-     * @param joystick        Identifier for which joystick this is.
-     * @param button          Identifier for which joystick button this is.
-     * @return the initialized {@link InputOverlayDrawableJoystick}.
-     */
-    private static InputOverlayDrawableJoystick initializeOverlayJoystick(Context context,
-                                                                          int resOuter, int defaultResInner, int pressedResInner, int joystick, int button, String orientation) {
-        // Resources handle for fetching the initial Drawable resource.
-        final Resources res = context.getResources();
-
-        // SharedPreference to retrieve the X and Y coordinates for the InputOverlayDrawableJoystick.
-        final SharedPreferences sPrefs = PreferenceManager.getDefaultSharedPreferences(context);
-
-        // Decide scale based on user preference
-        float scale = 0.40f;
-        scale *= (sPrefs.getInt("controlScale", 50) + 50);
-        scale /= 100;
-
-        // Initialize the InputOverlayDrawableJoystick.
-        final Bitmap bitmapOuter = getBitmap(context, resOuter, scale);
-        final Bitmap bitmapInnerDefault = getBitmap(context, defaultResInner, 1.0f);
-        final Bitmap bitmapInnerPressed = getBitmap(context, pressedResInner, 1.0f);
-
-        // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay.
-        // These were set in the input overlay configuration menu.
-        int drawableX = (int) sPrefs.getFloat(button + orientation + "-X", 0f);
-        int drawableY = (int) sPrefs.getFloat(button + orientation + "-Y", 0f);
-
-        float outerScale = 1.66f;
-
-        // Now set the bounds for the InputOverlayDrawableJoystick.
-        // This will dictate where on the screen (and the what the size) the InputOverlayDrawableJoystick will be.
-        int outerSize = bitmapOuter.getWidth();
-        Rect outerRect = new Rect(drawableX - (outerSize / 2), drawableY - (outerSize / 2), drawableX + (outerSize / 2), drawableY + (outerSize / 2));
-        Rect innerRect = new Rect(0, 0, (int) (outerSize / outerScale), (int) (outerSize / outerScale));
-
-        // Send the drawableId to the joystick so it can be referenced when saving control position.
-        final InputOverlayDrawableJoystick overlayDrawable
-                = new InputOverlayDrawableJoystick(res, bitmapOuter,
-                bitmapInnerDefault, bitmapInnerPressed,
-                outerRect, innerRect, joystick, button);
-
-        // Need to set the image's position
-        overlayDrawable.setPosition(drawableX, drawableY);
-
-        return overlayDrawable;
-    }
-
-    @Override
-    public void draw(Canvas canvas) {
-        super.draw(canvas);
-
-        for (InputOverlayDrawableButton button : overlayButtons) {
-            button.draw(canvas);
-        }
-
-        for (InputOverlayDrawableDpad dpad : overlayDpads) {
-            dpad.draw(canvas);
-        }
-
-        for (InputOverlayDrawableJoystick joystick : overlayJoysticks) {
-            joystick.draw(canvas);
-        }
-    }
-
-    @Override
-    public boolean onTouch(View v, MotionEvent event) {
-        if (isInEditMode()) {
-            return onTouchWhileEditing(event);
-        }
-        boolean should_update_view = false;
-        for (InputOverlayDrawableButton button : overlayButtons) {
-            if (!button.updateStatus(event)) {
-                continue;
-            }
-            NativeLibrary.onGamePadButtonEvent(NativeLibrary.Player1Device, button.getId(), button.getStatus());
-            should_update_view = true;
-        }
-
-        for (InputOverlayDrawableDpad dpad : overlayDpads) {
-            if (!dpad.updateStatus(event, EmulationMenuSettings.getDpadSlideEnable())) {
-                continue;
-            }
-            NativeLibrary.onGamePadButtonEvent(NativeLibrary.Player1Device, dpad.getUpId(), dpad.getUpStatus());
-            NativeLibrary.onGamePadButtonEvent(NativeLibrary.Player1Device, dpad.getDownId(), dpad.getDownStatus());
-            NativeLibrary.onGamePadButtonEvent(NativeLibrary.Player1Device, dpad.getLeftId(), dpad.getLeftStatus());
-            NativeLibrary.onGamePadButtonEvent(NativeLibrary.Player1Device, dpad.getRightId(), dpad.getRightStatus());
-            should_update_view = true;
-        }
-
-        for (InputOverlayDrawableJoystick joystick : overlayJoysticks) {
-            if (!joystick.updateStatus(event)) {
-                continue;
-            }
-            int axisID = joystick.getJoystickId();
-            NativeLibrary.onGamePadJoystickEvent(NativeLibrary.Player1Device, axisID, joystick.getXAxis(), joystick.getYAxis());
-            NativeLibrary.onGamePadButtonEvent(NativeLibrary.Player1Device, joystick.getButtonId(), joystick.getButtonStatus());
-            should_update_view = true;
-        }
-
-        if (should_update_view) {
-            invalidate();
-        }
-
-        if (!mPreferences.getBoolean("isTouchEnabled", true)) {
-            return true;
-        }
-
-        int pointerIndex = event.getActionIndex();
-        int xPosition = (int) event.getX(pointerIndex);
-        int yPosition = (int) event.getY(pointerIndex);
-        int pointerId = event.getPointerId(pointerIndex);
-        int motion_event = event.getAction() & MotionEvent.ACTION_MASK;
-        boolean isActionDown = motion_event == MotionEvent.ACTION_DOWN || motion_event == MotionEvent.ACTION_POINTER_DOWN;
-        boolean isActionMove = motion_event == MotionEvent.ACTION_MOVE;
-        boolean isActionUp = motion_event == MotionEvent.ACTION_UP || motion_event == MotionEvent.ACTION_POINTER_UP;
-
-        if (isActionDown && !isTouchInputConsumed(pointerId)) {
-            NativeLibrary.onTouchPressed(pointerId, xPosition, yPosition);
-        }
-
-        if (isActionMove) {
-            for (int i = 0; i < event.getPointerCount(); i++) {
-                int fingerId = event.getPointerId(i);
-                if (isTouchInputConsumed(fingerId)) {
-                    continue;
-                }
-                NativeLibrary.onTouchMoved(fingerId, event.getX(i), event.getY(i));
-            }
-        }
-
-        if (isActionUp && !isTouchInputConsumed(pointerId)) {
-            NativeLibrary.onTouchReleased(pointerId);
-        }
-
-        return true;
-    }
-
-    private boolean isTouchInputConsumed(int track_id) {
-        for (InputOverlayDrawableButton button : overlayButtons) {
-            if (button.getTrackId() == track_id) {
-                return true;
-            }
-        }
-        for (InputOverlayDrawableDpad dpad : overlayDpads) {
-            if (dpad.getTrackId() == track_id) {
-                return true;
-            }
-        }
-        for (InputOverlayDrawableJoystick joystick : overlayJoysticks) {
-            if (joystick.getTrackId() == track_id) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    public boolean onTouchWhileEditing(MotionEvent event) {
-        // TODO: Reimplement this
-        return true;
-    }
-
-    @Override
-    public void onSensorChanged(SensorEvent event) {
-        if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
-            accel[0] = -event.values[1] / SensorManager.GRAVITY_EARTH;
-            accel[1] = event.values[0] / SensorManager.GRAVITY_EARTH;
-            accel[2] = -event.values[2] / SensorManager.GRAVITY_EARTH;
-        }
-
-        if (event.sensor.getType() == Sensor.TYPE_GYROSCOPE) {
-            // Investigate why sensor value is off by 12x
-            gyro[0] = event.values[1] / 12.0f;
-            gyro[1] = -event.values[0] / 12.0f;
-            gyro[2] = event.values[2] / 12.0f;
-        }
-
-        // Only update state on accelerometer data
-        if (event.sensor.getType() != Sensor.TYPE_ACCELEROMETER) {
-            return;
-        }
-
-        long delta_timestamp = (event.timestamp - motionTimestamp) / 1000;
-        motionTimestamp = event.timestamp;
-        NativeLibrary.onGamePadMotionEvent(NativeLibrary.Player1Device, delta_timestamp, gyro[0], gyro[1], gyro[2], accel[0], accel[1], accel[2]);
-        NativeLibrary.onGamePadMotionEvent(NativeLibrary.ConsoleDevice, delta_timestamp, gyro[0], gyro[1], gyro[2], accel[0], accel[1], accel[2]);
-    }
-
-    @Override
-    public void onAccuracyChanged(Sensor sensor, int i) {
-    }
-
-    private void addOverlayControls(String orientation) {
-        if (mPreferences.getBoolean("buttonToggle0", true)) {
-            overlayButtons.add(initializeOverlayButton(getContext(), R.drawable.facebutton_a,
-                    R.drawable.facebutton_a_depressed, ButtonType.BUTTON_A, orientation));
-        }
-        if (mPreferences.getBoolean("buttonToggle1", true)) {
-            overlayButtons.add(initializeOverlayButton(getContext(), R.drawable.facebutton_b,
-                    R.drawable.facebutton_b_depressed, ButtonType.BUTTON_B, orientation));
-        }
-        if (mPreferences.getBoolean("buttonToggle2", true)) {
-            overlayButtons.add(initializeOverlayButton(getContext(), R.drawable.facebutton_x,
-                    R.drawable.facebutton_x_depressed, ButtonType.BUTTON_X, orientation));
-        }
-        if (mPreferences.getBoolean("buttonToggle3", true)) {
-            overlayButtons.add(initializeOverlayButton(getContext(), R.drawable.facebutton_y,
-                    R.drawable.facebutton_y_depressed, ButtonType.BUTTON_Y, orientation));
-        }
-        if (mPreferences.getBoolean("buttonToggle4", true)) {
-            overlayButtons.add(initializeOverlayButton(getContext(), R.drawable.l_shoulder,
-                    R.drawable.l_shoulder_depressed, ButtonType.TRIGGER_L, orientation));
-        }
-        if (mPreferences.getBoolean("buttonToggle5", true)) {
-            overlayButtons.add(initializeOverlayButton(getContext(), R.drawable.r_shoulder,
-                    R.drawable.r_shoulder_depressed, ButtonType.TRIGGER_R, orientation));
-        }
-        if (mPreferences.getBoolean("buttonToggle6", true)) {
-            overlayButtons.add(initializeOverlayButton(getContext(), R.drawable.zl_trigger,
-                    R.drawable.zl_trigger_depressed, ButtonType.TRIGGER_ZL, orientation));
-        }
-        if (mPreferences.getBoolean("buttonToggle7", true)) {
-            overlayButtons.add(initializeOverlayButton(getContext(), R.drawable.zr_trigger,
-                    R.drawable.zr_trigger_depressed, ButtonType.TRIGGER_ZR, orientation));
-        }
-        if (mPreferences.getBoolean("buttonToggle8", true)) {
-            overlayButtons.add(initializeOverlayButton(getContext(), R.drawable.facebutton_plus,
-                    R.drawable.facebutton_plus_depressed, ButtonType.BUTTON_PLUS, orientation));
-        }
-        if (mPreferences.getBoolean("buttonToggle9", true)) {
-            overlayButtons.add(initializeOverlayButton(getContext(), R.drawable.facebutton_minus,
-                    R.drawable.facebutton_minus_depressed, ButtonType.BUTTON_MINUS, orientation));
-        }
-        if (mPreferences.getBoolean("buttonToggle10", true)) {
-            overlayDpads.add(initializeOverlayDpad(getContext(), R.drawable.dpad_standard,
-                    R.drawable.dpad_standard_cardinal_depressed,
-                    R.drawable.dpad_standard_diagonal_depressed,
-                    ButtonType.DPAD_UP, ButtonType.DPAD_DOWN,
-                    ButtonType.DPAD_LEFT, ButtonType.DPAD_RIGHT, orientation));
-        }
-        if (mPreferences.getBoolean("buttonToggle11", true)) {
-            overlayJoysticks.add(initializeOverlayJoystick(getContext(), R.drawable.joystick_range,
-                    R.drawable.joystick, R.drawable.joystick_depressed,
-                    StickType.STICK_L, ButtonType.STICK_L, orientation));
-        }
-        if (mPreferences.getBoolean("buttonToggle12", true)) {
-            overlayJoysticks.add(initializeOverlayJoystick(getContext(), R.drawable.joystick_range,
-                    R.drawable.joystick, R.drawable.joystick_depressed, StickType.STICK_R, ButtonType.STICK_R, orientation));
-        }
-        if (mPreferences.getBoolean("buttonToggle13", false)) {
-            overlayButtons.add(initializeOverlayButton(getContext(), R.drawable.facebutton_home,
-                    R.drawable.facebutton_home_depressed, ButtonType.BUTTON_HOME, orientation));
-        }
-        if (mPreferences.getBoolean("buttonToggle14", false)) {
-            overlayButtons.add(initializeOverlayButton(getContext(), R.drawable.facebutton_screenshot,
-                    R.drawable.facebutton_screenshot_depressed, ButtonType.BUTTON_CAPTURE, orientation));
-        }
-    }
-
-    public void refreshControls() {
-        // Remove all the overlay buttons from the HashSet.
-        overlayButtons.clear();
-        overlayDpads.clear();
-        overlayJoysticks.clear();
-
-        String orientation =
-                getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT ?
-                        "-Portrait" : "";
-
-        // Add all the enabled overlay items back to the HashSet.
-        if (EmulationMenuSettings.getShowOverlay()) {
-            addOverlayControls(orientation);
-        }
-
-        invalidate();
-    }
-
-    private void saveControlPosition(int sharedPrefsId, int x, int y, String orientation) {
-        final SharedPreferences sPrefs = PreferenceManager.getDefaultSharedPreferences(getContext());
-        SharedPreferences.Editor sPrefsEditor = sPrefs.edit();
-        sPrefsEditor.putFloat(sharedPrefsId + orientation + "-X", x);
-        sPrefsEditor.putFloat(sharedPrefsId + orientation + "-Y", y);
-        sPrefsEditor.apply();
-    }
-
-    public void setIsInEditMode(boolean isInEditMode) {
-        mIsInEditMode = isInEditMode;
-    }
-
-    private void defaultOverlay() {
-        if (!mPreferences.getBoolean("OverlayInit", false)) {
-            defaultOverlayLandscape();
-        }
-        resetButtonPlacement();
-        SharedPreferences.Editor sPrefsEditor = mPreferences.edit();
-        sPrefsEditor.putBoolean("OverlayInit", true);
-        sPrefsEditor.apply();
-    }
-
-    public void resetButtonPlacement() {
-        defaultOverlayLandscape();
-        refreshControls();
-    }
-
-    private void defaultOverlayLandscape() {
-        SharedPreferences.Editor sPrefsEditor = mPreferences.edit();
-        // Get screen size
-        Display display = ((Activity) getContext()).getWindowManager().getDefaultDisplay();
-        DisplayMetrics outMetrics = new DisplayMetrics();
-        display.getRealMetrics(outMetrics);
-        float maxX = outMetrics.heightPixels;
-        float maxY = outMetrics.widthPixels;
-        // Height and width changes depending on orientation. Use the larger value for height.
-        if (maxY > maxX) {
-            float tmp = maxX;
-            maxX = maxY;
-            maxY = tmp;
-        }
-
-        Resources res = getResources();
-
-        // Each value is a percent from max X/Y stored as an int. Have to bring that value down
-        // to a decimal before multiplying by MAX X/Y.
-        sPrefsEditor.putFloat(ButtonType.BUTTON_A + "-X", (((float) res.getInteger(R.integer.SWITCH_BUTTON_A_X) / 1000) * maxX));
-        sPrefsEditor.putFloat(ButtonType.BUTTON_A + "-Y", (((float) res.getInteger(R.integer.SWITCH_BUTTON_A_Y) / 1000) * maxY));
-        sPrefsEditor.putFloat(ButtonType.BUTTON_B + "-X", (((float) res.getInteger(R.integer.SWITCH_BUTTON_B_X) / 1000) * maxX));
-        sPrefsEditor.putFloat(ButtonType.BUTTON_B + "-Y", (((float) res.getInteger(R.integer.SWITCH_BUTTON_B_Y) / 1000) * maxY));
-        sPrefsEditor.putFloat(ButtonType.BUTTON_X + "-X", (((float) res.getInteger(R.integer.SWITCH_BUTTON_X_X) / 1000) * maxX));
-        sPrefsEditor.putFloat(ButtonType.BUTTON_X + "-Y", (((float) res.getInteger(R.integer.SWITCH_BUTTON_X_Y) / 1000) * maxY));
-        sPrefsEditor.putFloat(ButtonType.BUTTON_Y + "-X", (((float) res.getInteger(R.integer.SWITCH_BUTTON_Y_X) / 1000) * maxX));
-        sPrefsEditor.putFloat(ButtonType.BUTTON_Y + "-Y", (((float) res.getInteger(R.integer.SWITCH_BUTTON_Y_Y) / 1000) * maxY));
-        sPrefsEditor.putFloat(ButtonType.TRIGGER_ZL + "-X", (((float) res.getInteger(R.integer.SWITCH_TRIGGER_ZL_X) / 1000) * maxX));
-        sPrefsEditor.putFloat(ButtonType.TRIGGER_ZL + "-Y", (((float) res.getInteger(R.integer.SWITCH_TRIGGER_ZL_Y) / 1000) * maxY));
-        sPrefsEditor.putFloat(ButtonType.TRIGGER_ZR + "-X", (((float) res.getInteger(R.integer.SWITCH_TRIGGER_ZR_X) / 1000) * maxX));
-        sPrefsEditor.putFloat(ButtonType.TRIGGER_ZR + "-Y", (((float) res.getInteger(R.integer.SWITCH_TRIGGER_ZR_Y) / 1000) * maxY));
-        sPrefsEditor.putFloat(ButtonType.DPAD_UP + "-X", (((float) res.getInteger(R.integer.SWITCH_BUTTON_DPAD_X) / 1000) * maxX));
-        sPrefsEditor.putFloat(ButtonType.DPAD_UP + "-Y", (((float) res.getInteger(R.integer.SWITCH_BUTTON_DPAD_Y) / 1000) * maxY));
-        sPrefsEditor.putFloat(ButtonType.TRIGGER_L + "-X", (((float) res.getInteger(R.integer.SWITCH_TRIGGER_L_X) / 1000) * maxX));
-        sPrefsEditor.putFloat(ButtonType.TRIGGER_L + "-Y", (((float) res.getInteger(R.integer.SWITCH_TRIGGER_L_Y) / 1000) * maxY));
-        sPrefsEditor.putFloat(ButtonType.TRIGGER_R + "-X", (((float) res.getInteger(R.integer.SWITCH_TRIGGER_R_X) / 1000) * maxX));
-        sPrefsEditor.putFloat(ButtonType.TRIGGER_R + "-Y", (((float) res.getInteger(R.integer.SWITCH_TRIGGER_R_Y) / 1000) * maxY));
-        sPrefsEditor.putFloat(ButtonType.BUTTON_PLUS + "-X", (((float) res.getInteger(R.integer.SWITCH_BUTTON_PLUS_X) / 1000) * maxX));
-        sPrefsEditor.putFloat(ButtonType.BUTTON_PLUS + "-Y", (((float) res.getInteger(R.integer.SWITCH_BUTTON_PLUS_Y) / 1000) * maxY));
-        sPrefsEditor.putFloat(ButtonType.BUTTON_MINUS + "-X", (((float) res.getInteger(R.integer.SWITCH_BUTTON_MINUS_X) / 1000) * maxX));
-        sPrefsEditor.putFloat(ButtonType.BUTTON_MINUS + "-Y", (((float) res.getInteger(R.integer.SWITCH_BUTTON_MINUS_Y) / 1000) * maxY));
-        sPrefsEditor.putFloat(ButtonType.BUTTON_HOME + "-X", (((float) res.getInteger(R.integer.SWITCH_BUTTON_HOME_X) / 1000) * maxX));
-        sPrefsEditor.putFloat(ButtonType.BUTTON_HOME + "-Y", (((float) res.getInteger(R.integer.SWITCH_BUTTON_HOME_Y) / 1000) * maxY));
-        sPrefsEditor.putFloat(ButtonType.BUTTON_CAPTURE + "-X", (((float) res.getInteger(R.integer.SWITCH_BUTTON_CAPTURE_X) / 1000) * maxX));
-        sPrefsEditor.putFloat(ButtonType.BUTTON_CAPTURE + "-Y", (((float) res.getInteger(R.integer.SWITCH_BUTTON_CAPTURE_Y) / 1000) * maxY));
-        sPrefsEditor.putFloat(ButtonType.STICK_R + "-X", (((float) res.getInteger(R.integer.SWITCH_STICK_R_X) / 1000) * maxX));
-        sPrefsEditor.putFloat(ButtonType.STICK_R + "-Y", (((float) res.getInteger(R.integer.SWITCH_STICK_R_Y) / 1000) * maxY));
-        sPrefsEditor.putFloat(ButtonType.STICK_L + "-X", (((float) res.getInteger(R.integer.SWITCH_STICK_L_X) / 1000) * maxX));
-        sPrefsEditor.putFloat(ButtonType.STICK_L + "-Y", (((float) res.getInteger(R.integer.SWITCH_STICK_L_Y) / 1000) * maxY));
-
-        // We want to commit right away, otherwise the overlay could load before this is saved.
-        sPrefsEditor.commit();
-    }
-
-    public boolean isInEditMode() {
-        return mIsInEditMode;
-    }
-}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt
new file mode 100644
index 000000000..a964b6257
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt
@@ -0,0 +1,886 @@
+package org.yuzu.yuzu_emu.overlay
+
+import android.app.Activity
+import android.content.Context
+import android.content.SharedPreferences
+import android.content.res.Configuration
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.graphics.Canvas
+import android.graphics.Rect
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.VectorDrawable
+import android.hardware.Sensor
+import android.hardware.SensorEvent
+import android.hardware.SensorEventListener
+import android.hardware.SensorManager
+import android.util.AttributeSet
+import android.util.DisplayMetrics
+import android.view.MotionEvent
+import android.view.SurfaceView
+import android.view.View
+import android.view.View.OnTouchListener
+import androidx.core.content.ContextCompat
+import androidx.preference.PreferenceManager
+import org.yuzu.yuzu_emu.NativeLibrary
+import org.yuzu.yuzu_emu.NativeLibrary.ButtonType
+import org.yuzu.yuzu_emu.NativeLibrary.StickType
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.YuzuApplication
+import org.yuzu.yuzu_emu.features.settings.model.Settings
+import org.yuzu.yuzu_emu.utils.EmulationMenuSettings
+
+
+/**
+ * Draws the interactive input overlay on top of the
+ * [SurfaceView] that is rendering emulation.
+ */
+class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context, attrs),
+    OnTouchListener, SensorEventListener {
+    private val overlayButtons: MutableSet<InputOverlayDrawableButton> = HashSet()
+    private val overlayDpads: MutableSet<InputOverlayDrawableDpad> = HashSet()
+    private val overlayJoysticks: MutableSet<InputOverlayDrawableJoystick> = HashSet()
+    private var inEditMode = false
+    private val preferences: SharedPreferences =
+        PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
+    private val gyro = FloatArray(3)
+    private val accel = FloatArray(3)
+    private var motionTimestamp: Long = 0
+
+    init {
+        if (!preferences.getBoolean(Settings.PREF_OVERLAY_INIT, false)) {
+            defaultOverlay()
+        }
+
+        // Load the controls.
+        refreshControls()
+
+        // Set the on motion sensor listener.
+        setMotionSensorListener(context)
+
+        // Set the on touch listener.
+        setOnTouchListener(this)
+
+        // Force draw
+        setWillNotDraw(false)
+
+        // Request focus for the overlay so it has priority on presses.
+        requestFocus()
+    }
+
+    private fun setMotionSensorListener(context: Context) {
+        val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
+        val gyroSensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
+        val accelSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
+        if (gyroSensor != null) {
+            sensorManager.registerListener(this, gyroSensor, SensorManager.SENSOR_DELAY_GAME)
+        }
+        if (accelSensor != null) {
+            sensorManager.registerListener(this, accelSensor, SensorManager.SENSOR_DELAY_GAME)
+        }
+    }
+
+    override fun draw(canvas: Canvas) {
+        super.draw(canvas)
+        for (button in overlayButtons) {
+            button.draw(canvas)
+        }
+        for (dpad in overlayDpads) {
+            dpad.draw(canvas)
+        }
+        for (joystick in overlayJoysticks) {
+            joystick.draw(canvas)
+        }
+    }
+
+    override fun onTouch(v: View, event: MotionEvent): Boolean {
+        if (inEditMode) {
+            return onTouchWhileEditing(event)
+        }
+
+        var shouldUpdateView = false
+
+        for (button in overlayButtons) {
+            if (!button.updateStatus(event)) {
+                continue
+            }
+            NativeLibrary.onGamePadButtonEvent(
+                NativeLibrary.Player1Device,
+                button.id,
+                button.status
+            )
+            shouldUpdateView = true
+        }
+
+        for (dpad in overlayDpads) {
+            if (!dpad.updateStatus(event, EmulationMenuSettings.dpadSlideEnable)) {
+                continue
+            }
+            NativeLibrary.onGamePadButtonEvent(
+                NativeLibrary.Player1Device,
+                dpad.upId,
+                dpad.upStatus
+            )
+            NativeLibrary.onGamePadButtonEvent(
+                NativeLibrary.Player1Device,
+                dpad.downId,
+                dpad.downStatus
+            )
+            NativeLibrary.onGamePadButtonEvent(
+                NativeLibrary.Player1Device,
+                dpad.leftId,
+                dpad.leftStatus
+            )
+            NativeLibrary.onGamePadButtonEvent(
+                NativeLibrary.Player1Device,
+                dpad.rightId,
+                dpad.rightStatus
+            )
+            shouldUpdateView = true
+        }
+
+        for (joystick in overlayJoysticks) {
+            if (!joystick.updateStatus(event)) {
+                continue
+            }
+            val axisID = joystick.joystickId
+            NativeLibrary.onGamePadJoystickEvent(
+                NativeLibrary.Player1Device,
+                axisID,
+                joystick.xAxis,
+                joystick.realYAxis
+            )
+            NativeLibrary.onGamePadButtonEvent(
+                NativeLibrary.Player1Device,
+                joystick.buttonId,
+                joystick.buttonStatus
+            )
+            shouldUpdateView = true
+        }
+
+        if (shouldUpdateView)
+            invalidate()
+
+        if (!preferences.getBoolean(Settings.PREF_TOUCH_ENABLED, true)) {
+            return true
+        }
+
+        val pointerIndex = event.actionIndex
+        val xPosition = event.getX(pointerIndex).toInt()
+        val yPosition = event.getY(pointerIndex).toInt()
+        val pointerId = event.getPointerId(pointerIndex)
+        val motionEvent = event.action and MotionEvent.ACTION_MASK
+        val isActionDown =
+            motionEvent == MotionEvent.ACTION_DOWN || motionEvent == MotionEvent.ACTION_POINTER_DOWN
+        val isActionMove = motionEvent == MotionEvent.ACTION_MOVE
+        val isActionUp =
+            motionEvent == MotionEvent.ACTION_UP || motionEvent == MotionEvent.ACTION_POINTER_UP
+
+        if (isActionDown && !isTouchInputConsumed(pointerId)) {
+            NativeLibrary.onTouchPressed(pointerId, xPosition.toFloat(), yPosition.toFloat())
+        }
+
+        if (isActionMove) {
+            for (i in 0 until event.pointerCount) {
+                val fingerId = event.getPointerId(i)
+                if (isTouchInputConsumed(fingerId)) {
+                    continue
+                }
+                NativeLibrary.onTouchMoved(fingerId, event.getX(i), event.getY(i))
+            }
+        }
+
+        if (isActionUp && !isTouchInputConsumed(pointerId)) {
+            NativeLibrary.onTouchReleased(pointerId)
+        }
+
+        return true
+    }
+
+    private fun isTouchInputConsumed(track_id: Int): Boolean {
+        for (button in overlayButtons) {
+            if (button.trackId == track_id) {
+                return true
+            }
+        }
+        for (dpad in overlayDpads) {
+            if (dpad.trackId == track_id) {
+                return true
+            }
+        }
+        for (joystick in overlayJoysticks) {
+            if (joystick.trackId == track_id) {
+                return true
+            }
+        }
+        return false
+    }
+
+    private fun onTouchWhileEditing(event: MotionEvent?): Boolean {
+        // TODO: Reimplement this
+        return true
+    }
+
+    override fun onSensorChanged(event: SensorEvent) {
+        if (event.sensor.type == Sensor.TYPE_ACCELEROMETER) {
+            accel[0] = -event.values[1] / SensorManager.GRAVITY_EARTH
+            accel[1] = event.values[0] / SensorManager.GRAVITY_EARTH
+            accel[2] = -event.values[2] / SensorManager.GRAVITY_EARTH
+        }
+        if (event.sensor.type == Sensor.TYPE_GYROSCOPE) {
+            // Investigate why sensor value is off by 12x
+            gyro[0] = event.values[1] / 12.0f
+            gyro[1] = -event.values[0] / 12.0f
+            gyro[2] = event.values[2] / 12.0f
+        }
+
+        // Only update state on accelerometer data
+        if (event.sensor.type != Sensor.TYPE_ACCELEROMETER) {
+            return
+        }
+        val deltaTimestamp = (event.timestamp - motionTimestamp) / 1000
+        motionTimestamp = event.timestamp
+        NativeLibrary.onGamePadMotionEvent(
+            NativeLibrary.Player1Device,
+            deltaTimestamp,
+            gyro[0],
+            gyro[1],
+            gyro[2],
+            accel[0],
+            accel[1],
+            accel[2]
+        )
+        NativeLibrary.onGamePadMotionEvent(
+            NativeLibrary.ConsoleDevice,
+            deltaTimestamp,
+            gyro[0],
+            gyro[1],
+            gyro[2],
+            accel[0],
+            accel[1],
+            accel[2]
+        )
+    }
+
+    override fun onAccuracyChanged(sensor: Sensor, i: Int) {}
+    private fun addOverlayControls(orientation: String) {
+        if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_0, true)) {
+            overlayButtons.add(
+                initializeOverlayButton(
+                    context,
+                    R.drawable.facebutton_a,
+                    R.drawable.facebutton_a_depressed,
+                    ButtonType.BUTTON_A,
+                    orientation
+                )
+            )
+        }
+        if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_1, true)) {
+            overlayButtons.add(
+                initializeOverlayButton(
+                    context,
+                    R.drawable.facebutton_b,
+                    R.drawable.facebutton_b_depressed,
+                    ButtonType.BUTTON_B,
+                    orientation
+                )
+            )
+        }
+        if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_2, true)) {
+            overlayButtons.add(
+                initializeOverlayButton(
+                    context,
+                    R.drawable.facebutton_x,
+                    R.drawable.facebutton_x_depressed,
+                    ButtonType.BUTTON_X,
+                    orientation
+                )
+            )
+        }
+        if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_3, true)) {
+            overlayButtons.add(
+                initializeOverlayButton(
+                    context,
+                    R.drawable.facebutton_y,
+                    R.drawable.facebutton_y_depressed,
+                    ButtonType.BUTTON_Y,
+                    orientation
+                )
+            )
+        }
+        if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_4, true)) {
+            overlayButtons.add(
+                initializeOverlayButton(
+                    context,
+                    R.drawable.l_shoulder,
+                    R.drawable.l_shoulder_depressed,
+                    ButtonType.TRIGGER_L,
+                    orientation
+                )
+            )
+        }
+        if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_5, true)) {
+            overlayButtons.add(
+                initializeOverlayButton(
+                    context,
+                    R.drawable.r_shoulder,
+                    R.drawable.r_shoulder_depressed,
+                    ButtonType.TRIGGER_R,
+                    orientation
+                )
+            )
+        }
+        if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_6, true)) {
+            overlayButtons.add(
+                initializeOverlayButton(
+                    context,
+                    R.drawable.zl_trigger,
+                    R.drawable.zl_trigger_depressed,
+                    ButtonType.TRIGGER_ZL,
+                    orientation
+                )
+            )
+        }
+        if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_7, true)) {
+            overlayButtons.add(
+                initializeOverlayButton(
+                    context,
+                    R.drawable.zr_trigger,
+                    R.drawable.zr_trigger_depressed,
+                    ButtonType.TRIGGER_ZR,
+                    orientation
+                )
+            )
+        }
+        if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_8, true)) {
+            overlayButtons.add(
+                initializeOverlayButton(
+                    context,
+                    R.drawable.facebutton_plus,
+                    R.drawable.facebutton_plus_depressed,
+                    ButtonType.BUTTON_PLUS,
+                    orientation
+                )
+            )
+        }
+        if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_9, true)) {
+            overlayButtons.add(
+                initializeOverlayButton(
+                    context,
+                    R.drawable.facebutton_minus,
+                    R.drawable.facebutton_minus_depressed,
+                    ButtonType.BUTTON_MINUS,
+                    orientation
+                )
+            )
+        }
+        if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_10, true)) {
+            overlayDpads.add(
+                initializeOverlayDpad(
+                    context,
+                    R.drawable.dpad_standard,
+                    R.drawable.dpad_standard_cardinal_depressed,
+                    R.drawable.dpad_standard_diagonal_depressed,
+                    orientation
+                )
+            )
+        }
+        if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_11, true)) {
+            overlayJoysticks.add(
+                initializeOverlayJoystick(
+                    context,
+                    R.drawable.joystick_range,
+                    R.drawable.joystick,
+                    R.drawable.joystick_depressed,
+                    StickType.STICK_L,
+                    ButtonType.STICK_L,
+                    orientation
+                )
+            )
+        }
+        if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_12, true)) {
+            overlayJoysticks.add(
+                initializeOverlayJoystick(
+                    context,
+                    R.drawable.joystick_range,
+                    R.drawable.joystick,
+                    R.drawable.joystick_depressed,
+                    StickType.STICK_R,
+                    ButtonType.STICK_R,
+                    orientation
+                )
+            )
+        }
+        if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_13, false)) {
+            overlayButtons.add(
+                initializeOverlayButton(
+                    context,
+                    R.drawable.facebutton_home,
+                    R.drawable.facebutton_home_depressed,
+                    ButtonType.BUTTON_HOME,
+                    orientation
+                )
+            )
+        }
+        if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_14, false)) {
+            overlayButtons.add(
+                initializeOverlayButton(
+                    context,
+                    R.drawable.facebutton_screenshot,
+                    R.drawable.facebutton_screenshot_depressed,
+                    ButtonType.BUTTON_CAPTURE,
+                    orientation
+                )
+            )
+        }
+    }
+
+    fun refreshControls() {
+        // Remove all the overlay buttons from the HashSet.
+        overlayButtons.clear()
+        overlayDpads.clear()
+        overlayJoysticks.clear()
+        val orientation =
+            if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) "-Portrait" else ""
+
+        // Add all the enabled overlay items back to the HashSet.
+        if (EmulationMenuSettings.showOverlay) {
+            addOverlayControls(orientation)
+        }
+        invalidate()
+    }
+
+    private fun saveControlPosition(sharedPrefsId: Int, x: Int, y: Int, orientation: String) {
+        PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit()
+            .putFloat("$sharedPrefsId$orientation-X", x.toFloat())
+            .putFloat("$sharedPrefsId$orientation-Y", y.toFloat())
+            .apply()
+    }
+
+    fun setIsInEditMode(editMode: Boolean) {
+        inEditMode = editMode
+    }
+
+    private fun defaultOverlay() {
+        if (!preferences.getBoolean(Settings.PREF_OVERLAY_INIT, false)) {
+            defaultOverlayLandscape()
+        }
+
+        resetButtonPlacement()
+        preferences.edit()
+            .putBoolean(Settings.PREF_OVERLAY_INIT, true)
+            .apply()
+    }
+
+    fun resetButtonPlacement() {
+        defaultOverlayLandscape()
+        refreshControls()
+    }
+
+    private fun defaultOverlayLandscape() {
+        // Get screen size
+        val display = (context as Activity).windowManager.defaultDisplay
+        val outMetrics = DisplayMetrics()
+        display.getRealMetrics(outMetrics)
+        var maxX = outMetrics.heightPixels.toFloat()
+        var maxY = outMetrics.widthPixels.toFloat()
+        // Height and width changes depending on orientation. Use the larger value for height.
+        if (maxY > maxX) {
+            val tmp = maxX
+            maxX = maxY
+            maxY = tmp
+        }
+        val res = resources
+
+        // Each value is a percent from max X/Y stored as an int. Have to bring that value down
+        // to a decimal before multiplying by MAX X/Y.
+        preferences.edit()
+            .putFloat(
+                ButtonType.BUTTON_A.toString() + "-X",
+                res.getInteger(R.integer.SWITCH_BUTTON_A_X).toFloat() / 1000 * maxX
+            )
+            .putFloat(
+                ButtonType.BUTTON_A.toString() + "-Y",
+                res.getInteger(R.integer.SWITCH_BUTTON_A_Y).toFloat() / 1000 * maxY
+            )
+            .putFloat(
+                ButtonType.BUTTON_B.toString() + "-X",
+                res.getInteger(R.integer.SWITCH_BUTTON_B_X).toFloat() / 1000 * maxX
+            )
+            .putFloat(
+                ButtonType.BUTTON_B.toString() + "-Y",
+                res.getInteger(R.integer.SWITCH_BUTTON_B_Y).toFloat() / 1000 * maxY
+            )
+            .putFloat(
+                ButtonType.BUTTON_X.toString() + "-X",
+                res.getInteger(R.integer.SWITCH_BUTTON_X_X).toFloat() / 1000 * maxX
+            )
+            .putFloat(
+                ButtonType.BUTTON_X.toString() + "-Y",
+                res.getInteger(R.integer.SWITCH_BUTTON_X_Y).toFloat() / 1000 * maxY
+            )
+            .putFloat(
+                ButtonType.BUTTON_Y.toString() + "-X",
+                res.getInteger(R.integer.SWITCH_BUTTON_Y_X).toFloat() / 1000 * maxX
+            )
+            .putFloat(
+                ButtonType.BUTTON_Y.toString() + "-Y",
+                res.getInteger(R.integer.SWITCH_BUTTON_Y_Y).toFloat() / 1000 * maxY
+            )
+            .putFloat(
+                ButtonType.TRIGGER_ZL.toString() + "-X",
+                res.getInteger(R.integer.SWITCH_TRIGGER_ZL_X).toFloat() / 1000 * maxX
+            )
+            .putFloat(
+                ButtonType.TRIGGER_ZL.toString() + "-Y",
+                res.getInteger(R.integer.SWITCH_TRIGGER_ZL_Y).toFloat() / 1000 * maxY
+            )
+            .putFloat(
+                ButtonType.TRIGGER_ZR.toString() + "-X",
+                res.getInteger(R.integer.SWITCH_TRIGGER_ZR_X).toFloat() / 1000 * maxX
+            )
+            .putFloat(
+                ButtonType.TRIGGER_ZR.toString() + "-Y",
+                res.getInteger(R.integer.SWITCH_TRIGGER_ZR_Y).toFloat() / 1000 * maxY
+            )
+            .putFloat(
+                ButtonType.DPAD_UP.toString() + "-X",
+                res.getInteger(R.integer.SWITCH_BUTTON_DPAD_X).toFloat() / 1000 * maxX
+            )
+            .putFloat(
+                ButtonType.DPAD_UP.toString() + "-Y",
+                res.getInteger(R.integer.SWITCH_BUTTON_DPAD_Y).toFloat() / 1000 * maxY
+            )
+            .putFloat(
+                ButtonType.TRIGGER_L.toString() + "-X",
+                res.getInteger(R.integer.SWITCH_TRIGGER_L_X).toFloat() / 1000 * maxX
+            )
+            .putFloat(
+                ButtonType.TRIGGER_L.toString() + "-Y",
+                res.getInteger(R.integer.SWITCH_TRIGGER_L_Y).toFloat() / 1000 * maxY
+            )
+            .putFloat(
+                ButtonType.TRIGGER_R.toString() + "-X",
+                res.getInteger(R.integer.SWITCH_TRIGGER_R_X).toFloat() / 1000 * maxX
+            )
+            .putFloat(
+                ButtonType.TRIGGER_R.toString() + "-Y",
+                res.getInteger(R.integer.SWITCH_TRIGGER_R_Y).toFloat() / 1000 * maxY
+            )
+            .putFloat(
+                ButtonType.BUTTON_PLUS.toString() + "-X",
+                res.getInteger(R.integer.SWITCH_BUTTON_PLUS_X).toFloat() / 1000 * maxX
+            )
+            .putFloat(
+                ButtonType.BUTTON_PLUS.toString() + "-Y",
+                res.getInteger(R.integer.SWITCH_BUTTON_PLUS_Y).toFloat() / 1000 * maxY
+            )
+            .putFloat(
+                ButtonType.BUTTON_MINUS.toString() + "-X",
+                res.getInteger(R.integer.SWITCH_BUTTON_MINUS_X).toFloat() / 1000 * maxX
+            )
+            .putFloat(
+                ButtonType.BUTTON_MINUS.toString() + "-Y",
+                res.getInteger(R.integer.SWITCH_BUTTON_MINUS_Y).toFloat() / 1000 * maxY
+            )
+            .putFloat(
+                ButtonType.BUTTON_HOME.toString() + "-X",
+                res.getInteger(R.integer.SWITCH_BUTTON_HOME_X).toFloat() / 1000 * maxX
+            )
+            .putFloat(
+                ButtonType.BUTTON_HOME.toString() + "-Y",
+                res.getInteger(R.integer.SWITCH_BUTTON_HOME_Y).toFloat() / 1000 * maxY
+            )
+            .putFloat(
+                ButtonType.BUTTON_CAPTURE.toString() + "-X",
+                res.getInteger(R.integer.SWITCH_BUTTON_CAPTURE_X).toFloat() / 1000 * maxX
+            )
+            .putFloat(
+                ButtonType.BUTTON_CAPTURE.toString() + "-Y",
+                res.getInteger(R.integer.SWITCH_BUTTON_CAPTURE_Y).toFloat() / 1000 * maxY
+            )
+            .putFloat(
+                ButtonType.STICK_R.toString() + "-X",
+                res.getInteger(R.integer.SWITCH_STICK_R_X).toFloat() / 1000 * maxX
+            )
+            .putFloat(
+                ButtonType.STICK_R.toString() + "-Y",
+                res.getInteger(R.integer.SWITCH_STICK_R_Y).toFloat() / 1000 * maxY
+            )
+            .putFloat(
+                ButtonType.STICK_L.toString() + "-X",
+                res.getInteger(R.integer.SWITCH_STICK_L_X).toFloat() / 1000 * maxX
+            )
+            .putFloat(
+                ButtonType.STICK_L.toString() + "-Y",
+                res.getInteger(R.integer.SWITCH_STICK_L_Y).toFloat() / 1000 * maxY
+            )
+            .commit()
+        // We want to commit right away, otherwise the overlay could load before this is saved.
+    }
+
+    override fun isInEditMode(): Boolean {
+        return inEditMode
+    }
+
+    companion object {
+        /**
+         * Resizes a [Bitmap] by a given scale factor
+         *
+         * @param vectorDrawable The {@link Bitmap} to scale.
+         * @param scale          The scale factor for the bitmap.
+         * @return The scaled [Bitmap]
+         */
+        private fun getBitmap(vectorDrawable: VectorDrawable, scale: Float): Bitmap {
+            val bitmap = Bitmap.createBitmap(
+                (vectorDrawable.intrinsicWidth * scale).toInt(),
+                (vectorDrawable.intrinsicHeight * scale).toInt(),
+                Bitmap.Config.ARGB_8888
+            )
+            val canvas = Canvas(bitmap)
+            vectorDrawable.setBounds(0, 0, canvas.width, canvas.height)
+            vectorDrawable.draw(canvas)
+            return bitmap
+        }
+
+        private fun getBitmap(context: Context, drawableId: Int, scale: Float): Bitmap {
+            return when (val drawable = ContextCompat.getDrawable(context, drawableId)) {
+                is BitmapDrawable -> BitmapFactory.decodeResource(context.resources, drawableId)
+                is VectorDrawable -> getBitmap(drawable, scale)
+                else -> throw IllegalArgumentException("Unsupported drawable type")
+            }
+        }
+
+        /**
+         * Initializes an InputOverlayDrawableButton, given by resId, with all of the
+         * parameters set for it to be properly shown on the InputOverlay.
+         *
+         *
+         * This works due to the way the X and Y coordinates are stored within
+         * the [SharedPreferences].
+         *
+         *
+         * In the input overlay configuration menu,
+         * once a touch event begins and then ends (ie. Organizing the buttons to one's own liking for the overlay).
+         * the X and Y coordinates of the button at the END of its touch event
+         * (when you remove your finger/stylus from the touchscreen) are then stored
+         * within a SharedPreferences instance so that those values can be retrieved here.
+         *
+         *
+         * This has a few benefits over the conventional way of storing the values
+         * (ie. within the yuzu ini file).
+         *
+         *  * No native calls
+         *  * Keeps Android-only values inside the Android environment
+         *
+         *
+         *
+         * Technically no modifications should need to be performed on the returned
+         * InputOverlayDrawableButton. Simply add it to the HashSet of overlay items and wait
+         * for Android to call the onDraw method.
+         *
+         * @param context      The current [Context].
+         * @param defaultResId The resource ID of the [Drawable] to get the [Bitmap] of (Default State).
+         * @param pressedResId The resource ID of the [Drawable] to get the [Bitmap] of (Pressed State).
+         * @param buttonId     Identifier for determining what type of button the initialized InputOverlayDrawableButton represents.
+         * @return An [InputOverlayDrawableButton] with the correct drawing bounds set.
+         */
+        private fun initializeOverlayButton(
+            context: Context,
+            defaultResId: Int,
+            pressedResId: Int,
+            buttonId: Int,
+            orientation: String
+        ): InputOverlayDrawableButton {
+            // Resources handle for fetching the initial Drawable resource.
+            val res = context.resources
+
+            // SharedPreference to retrieve the X and Y coordinates for the InputOverlayDrawableButton.
+            val sPrefs = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
+
+            // Decide scale based on button ID and user preference
+            var scale: Float = when (buttonId) {
+                ButtonType.BUTTON_HOME,
+                ButtonType.BUTTON_CAPTURE,
+                ButtonType.BUTTON_PLUS,
+                ButtonType.BUTTON_MINUS -> 0.35f
+                ButtonType.TRIGGER_L,
+                ButtonType.TRIGGER_R,
+                ButtonType.TRIGGER_ZL,
+                ButtonType.TRIGGER_ZR -> 0.38f
+                else -> 0.43f
+            }
+            scale *= (sPrefs.getInt(Settings.PREF_CONTROL_SCALE, 50) + 50).toFloat()
+            scale /= 100f
+
+            // Initialize the InputOverlayDrawableButton.
+            val defaultStateBitmap = getBitmap(context, defaultResId, scale)
+            val pressedStateBitmap = getBitmap(context, pressedResId, scale)
+            val overlayDrawable =
+                InputOverlayDrawableButton(res, defaultStateBitmap, pressedStateBitmap, buttonId)
+
+            // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay.
+            // These were set in the input overlay configuration menu.
+            val xKey = "$buttonId$orientation-X"
+            val yKey = "$buttonId$orientation-Y"
+            val drawableX = sPrefs.getFloat(xKey, 0f).toInt()
+            val drawableY = sPrefs.getFloat(yKey, 0f).toInt()
+            val width = overlayDrawable.width
+            val height = overlayDrawable.height
+
+            // Now set the bounds for the InputOverlayDrawableButton.
+            // This will dictate where on the screen (and the what the size) the InputOverlayDrawableButton will be.
+            overlayDrawable.setBounds(
+                drawableX - (width / 2),
+                drawableY - (height / 2),
+                drawableX + (width / 2),
+                drawableY + (height / 2)
+            )
+
+            // Need to set the image's position
+            overlayDrawable.setPosition(
+                drawableX - (width / 2),
+                drawableY - (height / 2)
+            )
+            return overlayDrawable
+        }
+
+        /**
+         * Initializes an [InputOverlayDrawableDpad]
+         *
+         * @param context                   The current [Context].
+         * @param defaultResId              The [Bitmap] resource ID of the default sate.
+         * @param pressedOneDirectionResId  The [Bitmap] resource ID of the pressed sate in one direction.
+         * @param pressedTwoDirectionsResId The [Bitmap] resource ID of the pressed sate in two directions.
+         * @return the initialized [InputOverlayDrawableDpad]
+         */
+        private fun initializeOverlayDpad(
+            context: Context,
+            defaultResId: Int,
+            pressedOneDirectionResId: Int,
+            pressedTwoDirectionsResId: Int,
+            orientation: String
+        ): InputOverlayDrawableDpad {
+            // Resources handle for fetching the initial Drawable resource.
+            val res = context.resources
+
+            // SharedPreference to retrieve the X and Y coordinates for the InputOverlayDrawableDpad.
+            val sPrefs = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
+
+            // Decide scale based on button ID and user preference
+            var scale = 0.40f
+            scale *= (sPrefs.getInt(Settings.PREF_CONTROL_SCALE, 50) + 50).toFloat()
+            scale /= 100f
+
+            // Initialize the InputOverlayDrawableDpad.
+            val defaultStateBitmap =
+                getBitmap(context, defaultResId, scale)
+            val pressedOneDirectionStateBitmap = getBitmap(context, pressedOneDirectionResId, scale)
+            val pressedTwoDirectionsStateBitmap =
+                getBitmap(context, pressedTwoDirectionsResId, scale)
+
+            val overlayDrawable = InputOverlayDrawableDpad(
+                res,
+                defaultStateBitmap,
+                pressedOneDirectionStateBitmap,
+                pressedTwoDirectionsStateBitmap,
+                ButtonType.DPAD_UP,
+                ButtonType.DPAD_DOWN,
+                ButtonType.DPAD_LEFT,
+                ButtonType.DPAD_RIGHT
+            )
+
+            // The X and Y coordinates of the InputOverlayDrawableDpad on the InputOverlay.
+            // These were set in the input overlay configuration menu.
+            val drawableX = sPrefs.getFloat("${ButtonType.DPAD_UP}$orientation-X", 0f).toInt()
+            val drawableY = sPrefs.getFloat("${ButtonType.DPAD_UP}$orientation-Y", 0f).toInt()
+            val width = overlayDrawable.width
+            val height = overlayDrawable.height
+
+            // Now set the bounds for the InputOverlayDrawableDpad.
+            // This will dictate where on the screen (and the what the size) the InputOverlayDrawableDpad will be.
+            overlayDrawable.setBounds(
+                drawableX - (width / 2),
+                drawableY - (height / 2),
+                drawableX + (width / 2),
+                drawableY + (height / 2)
+            )
+
+            // Need to set the image's position
+            overlayDrawable.setPosition(drawableX - (width / 2), drawableY - (height / 2))
+            return overlayDrawable
+        }
+
+        /**
+         * Initializes an [InputOverlayDrawableJoystick]
+         *
+         * @param context         The current [Context]
+         * @param resOuter        Resource ID for the outer image of the joystick (the static image that shows the circular bounds).
+         * @param defaultResInner Resource ID for the default inner image of the joystick (the one you actually move around).
+         * @param pressedResInner Resource ID for the pressed inner image of the joystick.
+         * @param joystick        Identifier for which joystick this is.
+         * @param button          Identifier for which joystick button this is.
+         * @return the initialized [InputOverlayDrawableJoystick].
+         */
+        private fun initializeOverlayJoystick(
+            context: Context,
+            resOuter: Int,
+            defaultResInner: Int,
+            pressedResInner: Int,
+            joystick: Int,
+            button: Int,
+            orientation: String
+        ): InputOverlayDrawableJoystick {
+            // Resources handle for fetching the initial Drawable resource.
+            val res = context.resources
+
+            // SharedPreference to retrieve the X and Y coordinates for the InputOverlayDrawableJoystick.
+            val sPrefs = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
+
+            // Decide scale based on user preference
+            var scale = 0.40f
+            scale *= (sPrefs.getInt(Settings.PREF_CONTROL_SCALE, 50) + 50).toFloat()
+            scale /= 100f
+
+            // Initialize the InputOverlayDrawableJoystick.
+            val bitmapOuter = getBitmap(context, resOuter, scale)
+            val bitmapInnerDefault = getBitmap(context, defaultResInner, 1.0f)
+            val bitmapInnerPressed = getBitmap(context, pressedResInner, 1.0f)
+
+            // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay.
+            // These were set in the input overlay configuration menu.
+            val drawableX = sPrefs.getFloat("$button$orientation-X", 0f).toInt()
+            val drawableY = sPrefs.getFloat("$button$orientation-Y", 0f).toInt()
+            val outerScale = 1.66f
+
+            // Now set the bounds for the InputOverlayDrawableJoystick.
+            // This will dictate where on the screen (and the what the size) the InputOverlayDrawableJoystick will be.
+            val outerSize = bitmapOuter.width
+            val outerRect = Rect(
+                drawableX - (outerSize / 2),
+                drawableY - (outerSize / 2),
+                drawableX + (outerSize / 2),
+                drawableY + (outerSize / 2)
+            )
+            val innerRect =
+                Rect(0, 0, (outerSize / outerScale).toInt(), (outerSize / outerScale).toInt())
+
+            // Send the drawableId to the joystick so it can be referenced when saving control position.
+            val overlayDrawable = InputOverlayDrawableJoystick(
+                res,
+                bitmapOuter,
+                bitmapInnerDefault,
+                bitmapInnerPressed,
+                outerRect,
+                innerRect,
+                joystick,
+                button
+            )
+
+            // Need to set the image's position
+            overlayDrawable.setPosition(drawableX, drawableY)
+            return overlayDrawable
+        }
+    }
+}