Android: Basic touchscreen controller implementation

This commit is contained in:
Connor McLaughlin 2019-12-09 01:43:37 +10:00
parent 89e9373037
commit 5d91c011a6
25 changed files with 625 additions and 2 deletions

View File

@ -4,6 +4,7 @@
#include "YBaseLib/String.h" #include "YBaseLib/String.h"
#include "android_audio_stream.h" #include "android_audio_stream.h"
#include "android_gles_host_display.h" #include "android_gles_host_display.h"
#include "core/controller.h"
#include "core/gpu.h" #include "core/gpu.h"
#include "core/game_list.h" #include "core/game_list.h"
#include "core/host_display.h" #include "core/host_display.h"
@ -330,6 +331,51 @@ void AndroidHostInterface::SurfaceChanged(ANativeWindow* window, int format, int
m_display->ChangeRenderWindow(window); m_display->ChangeRenderWindow(window);
} }
void AndroidHostInterface::SetControllerType(u32 index, std::string_view type_name)
{
if (!IsEmulationThreadRunning())
{
m_controller_type_names[index] = type_name;
return;
}
std::string type_name_copy(type_name);
RunOnEmulationThread([this, index, type_name_copy]() {
Log_InfoPrintf("Changing controller slot %d to %s", index, type_name_copy.c_str());
m_controller_type_names[index] = std::move(type_name_copy);
m_controllers[index] = Controller::Create(m_controller_type_names[index]);
m_system->SetController(index, m_controllers[index]);
}, false);
}
void AndroidHostInterface::SetControllerButtonState(u32 index, s32 button_code, bool pressed)
{
if (!IsEmulationThreadRunning())
return;
RunOnEmulationThread([this, index, button_code, pressed]() {
if (!m_controllers[index])
return;
m_controllers[index]->SetButtonState(button_code, pressed);
}, false);
}
void AndroidHostInterface::ConnectControllers()
{
for (u32 i = 0; i < NUM_CONTROLLERS; i++)
{
if (m_controller_type_names[i].empty())
continue;
Log_InfoPrintf("Connecting controller '%s' to port %u", m_controller_type_names[i].c_str(), i);
m_controllers[i] = Controller::Create(m_controller_type_names[i]);
if (m_controllers[i])
m_system->SetController(i, m_controllers[i]);
}
}
extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved) extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
{ {
Log::GetInstance().SetDebugOutputParams(true, nullptr, LOGLEVEL_DEV); Log::GetInstance().SetDebugOutputParams(true, nullptr, LOGLEVEL_DEV);
@ -433,6 +479,22 @@ DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_surfaceChanged, jobject obj, j
[hi, native_surface, format, width, height]() { hi->SurfaceChanged(native_surface, format, width, height); }, true); [hi, native_surface, format, width, height]() { hi->SurfaceChanged(native_surface, format, width, height); }, true);
} }
DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_setControllerType, jobject obj, jint index, jstring controller_type)
{
GetNativeClass(env, obj)->SetControllerType(index, JStringToString(env, controller_type));
}
DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_setControllerButtonState, jobject obj, jint index, jint button_code, jboolean pressed)
{
GetNativeClass(env, obj)->SetControllerButtonState(index, button_code, pressed);
}
DEFINE_JNI_ARGS_METHOD(jint, AndroidHostInterface_getControllerButtonCode, jobject unused, jstring controller_type, jstring button_name)
{
std::optional<s32> code = Controller::GetButtonCodeByName(JStringToString(env, controller_type), JStringToString(env, button_name));
return code.value_or(-1);
}
DEFINE_JNI_ARGS_METHOD(jarray, GameList_getEntries, jobject unused, jstring j_cache_path, jstring j_redump_dat_path, jarray j_search_directories, jboolean search_recursively) DEFINE_JNI_ARGS_METHOD(jarray, GameList_getEntries, jobject unused, jstring j_cache_path, jstring j_redump_dat_path, jarray j_search_directories, jboolean search_recursively)
{ {
const std::string cache_path = JStringToString(env, j_cache_path); const std::string cache_path = JStringToString(env, j_cache_path);

View File

@ -2,13 +2,17 @@
#include "YBaseLib/Event.h" #include "YBaseLib/Event.h"
#include "YBaseLib/Timer.h" #include "YBaseLib/Timer.h"
#include "core/host_interface.h" #include "core/host_interface.h"
#include <array>
#include <atomic> #include <atomic>
#include <functional> #include <functional>
#include <jni.h> #include <jni.h>
#include <memory>
#include <thread> #include <thread>
struct ANativeWindow; struct ANativeWindow;
class Controller;
class AndroidHostInterface final : public HostInterface class AndroidHostInterface final : public HostInterface
{ {
public: public:
@ -26,7 +30,15 @@ public:
void SurfaceChanged(ANativeWindow* window, int format, int width, int height); void SurfaceChanged(ANativeWindow* window, int format, int width, int height);
void SetControllerType(u32 index, std::string_view type_name);
void SetControllerButtonState(u32 index, s32 button_code, bool pressed);
private: private:
enum : u32
{
NUM_CONTROLLERS = 2
};
void EmulationThreadEntryPoint(ANativeWindow* initial_surface, std::string initial_filename, void EmulationThreadEntryPoint(ANativeWindow* initial_surface, std::string initial_filename,
std::string initial_state_filename); std::string initial_state_filename);
@ -36,6 +48,8 @@ private:
void DrawFPSWindow(); void DrawFPSWindow();
void ConnectControllers() override;
jobject m_java_object = {}; jobject m_java_object = {};
std::mutex m_callback_mutex; std::mutex m_callback_mutex;
@ -45,4 +59,7 @@ private:
std::atomic_bool m_emulation_thread_stop_request{false}; std::atomic_bool m_emulation_thread_stop_request{false};
std::atomic_bool m_emulation_thread_start_result{false}; std::atomic_bool m_emulation_thread_start_result{false};
Event m_emulation_thread_started; Event m_emulation_thread_started;
std::array<std::string, NUM_CONTROLLERS> m_controller_type_names;
std::array<std::shared_ptr<Controller>, NUM_CONTROLLERS> m_controllers;
}; };

View File

@ -9,10 +9,10 @@
android:allowBackup="true" android:allowBackup="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:requestLegacyExternalStorage="true"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme" android:theme="@style/AppTheme">
android:requestLegacyExternalStorage="true">
<activity <activity
android:name=".EmulationActivity" android:name=".EmulationActivity"
android:configChanges="orientation|keyboardHidden|screenSize" android:configChanges="orientation|keyboardHidden|screenSize"

View File

@ -22,4 +22,9 @@ public class AndroidHostInterface
public native void stopEmulationThread(); public native void stopEmulationThread();
public native void surfaceChanged(Surface surface, int format, int width, int height); public native void surfaceChanged(Surface surface, int format, int width, int height);
// TODO: Find a better place for this.
public native void setControllerType(int index, String typeName);
public native void setControllerButtonState(int index, int buttonCode, boolean pressed);
public static native int getControllerButtonCode(String controllerType, String buttonName);
} }

View File

@ -13,6 +13,7 @@ import android.view.SurfaceHolder;
import android.view.SurfaceView; import android.view.SurfaceView;
import android.view.View; import android.view.View;
import android.view.MenuItem; import android.view.MenuItem;
import android.widget.FrameLayout;
import androidx.core.app.NavUtils; import androidx.core.app.NavUtils;
@ -26,6 +27,11 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
*/ */
AndroidHostInterface mHostInterface; AndroidHostInterface mHostInterface;
/**
* Touchscreen controller overlay
*/
TouchscreenControllerView mTouchscreenController;
/** /**
* Whether or not the system UI should be auto-hidden after * Whether or not the system UI should be auto-hidden after
* {@link #AUTO_HIDE_DELAY_MILLIS} milliseconds. * {@link #AUTO_HIDE_DELAY_MILLIS} milliseconds.
@ -153,6 +159,12 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
mHostInterface = AndroidHostInterface.create(); mHostInterface = AndroidHostInterface.create();
if (mHostInterface == null) if (mHostInterface == null)
throw new InstantiationError("Failed to create host interface"); throw new InstantiationError("Failed to create host interface");
// Create touchscreen controller.
FrameLayout activityLayout = findViewById(R.id.frameLayout);
mTouchscreenController = new TouchscreenControllerView(this);
activityLayout.addView(mTouchscreenController);
mTouchscreenController.init(0, "DigitalController", mHostInterface);
} }
@Override @Override

View File

@ -0,0 +1,160 @@
package com.github.stenzek.duckstation;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
/**
* TODO: document your custom view class.
*/
public class TouchscreenControllerButtonView extends View {
private Drawable mUnpressedDrawable;
private Drawable mPressedDrawable;
private boolean mPressed = false;
private int mButtonCode = -1;
private String mButtonName = "";
private ButtonStateChangedListener mListener;
public interface ButtonStateChangedListener
{
void onButtonStateChanged(TouchscreenControllerButtonView view, boolean pressed);
}
public TouchscreenControllerButtonView(Context context) {
super(context);
init(context,null, 0);
}
public TouchscreenControllerButtonView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context,attrs, 0);
}
public TouchscreenControllerButtonView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context,attrs, defStyle);
}
private void init(Context context, AttributeSet attrs, int defStyle) {
// Load attributes
final TypedArray a = getContext().obtainStyledAttributes(
attrs, R.styleable.TouchscreenControllerButtonView, defStyle, 0);
if (a.hasValue(R.styleable.TouchscreenControllerButtonView_unpressedDrawable)) {
mUnpressedDrawable = a.getDrawable(R.styleable.TouchscreenControllerButtonView_unpressedDrawable);
mUnpressedDrawable.setCallback(this);
}
if (a.hasValue(R.styleable.TouchscreenControllerButtonView_pressedDrawable)) {
mPressedDrawable = a.getDrawable(R.styleable.TouchscreenControllerButtonView_pressedDrawable);
mPressedDrawable.setCallback(this);
}
a.recycle();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int paddingLeft = getPaddingLeft();
int paddingTop = getPaddingTop();
int paddingRight = getPaddingRight();
int paddingBottom = getPaddingBottom();
int contentWidth = getWidth() - paddingLeft - paddingRight;
int contentHeight = getHeight() - paddingTop - paddingBottom;
// Draw the example drawable on top of the text.
Drawable drawable = mPressed ? mPressedDrawable : mUnpressedDrawable;
if (drawable != null) {
drawable.setBounds(paddingLeft, paddingTop,
paddingLeft + contentWidth, paddingTop + contentHeight);
drawable.draw(canvas);
}
}
@Override
public boolean onTouchEvent(MotionEvent event)
{
final boolean oldState = mPressed;
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
{
mPressed = true;
invalidate();
if (mListener != null && !oldState)
mListener.onButtonStateChanged(this, true);
return true;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
{
mPressed = false;
invalidate();
if (mListener != null && oldState)
mListener.onButtonStateChanged(this, false);
return true;
}
}
return super.onTouchEvent(event);
}
public boolean isPressed()
{
return mPressed;
}
public String getButtonName() {
return mButtonName;
}
public void setButtonName(String buttonName) {
mButtonName = buttonName;
}
public int getButtonCode()
{
return mButtonCode;
}
public void setButtonCode(int code)
{
mButtonCode = code;
}
public Drawable getPressedDrawable() {
return mPressedDrawable;
}
public void setPressedDrawable(Drawable pressedDrawable) {
mPressedDrawable = pressedDrawable;
}
public Drawable getUnpressedDrawable() {
return mUnpressedDrawable;
}
public void setUnpressedDrawable(Drawable unpressedDrawable) {
mUnpressedDrawable = unpressedDrawable;
}
public void setButtonStateChangedListener(ButtonStateChangedListener listener)
{
mListener = listener;
}
}

View File

@ -0,0 +1,80 @@
package com.github.stenzek.duckstation;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
/**
* TODO: document your custom view class.
*/
public class TouchscreenControllerView extends FrameLayout implements TouchscreenControllerButtonView.ButtonStateChangedListener {
private int mControllerIndex;
private String mControllerType;
private AndroidHostInterface mHostInterface;
public TouchscreenControllerView(Context context) {
super(context);
}
public TouchscreenControllerView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public TouchscreenControllerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void init(int controllerIndex, String controllerType,
AndroidHostInterface hostInterface) {
mControllerIndex = controllerIndex;
mControllerType = controllerType;
mHostInterface = hostInterface;
if (mHostInterface != null)
mHostInterface.setControllerType(controllerIndex, controllerType);
LayoutInflater inflater = LayoutInflater.from(getContext());
View view = inflater.inflate(R.layout.layout_touchscreen_controller, this, true);
// TODO: Make dynamic, editable.
linkButton(view, R.id.controller_button_up, "Up");
linkButton(view, R.id.controller_button_right, "Right");
linkButton(view, R.id.controller_button_down, "Down");
linkButton(view, R.id.controller_button_left, "Left");
linkButton(view, R.id.controller_button_triangle, "Triangle");
linkButton(view, R.id.controller_button_circle, "Circle");
linkButton(view, R.id.controller_button_cross, "Cross");
linkButton(view, R.id.controller_button_square, "Square");
}
private void linkButton(View view, int id, String buttonName)
{
TouchscreenControllerButtonView buttonView = (TouchscreenControllerButtonView)view.findViewById(id);
buttonView.setButtonName(buttonName);
buttonView.setButtonStateChangedListener(this);
if (mHostInterface != null)
{
int code = mHostInterface.getControllerButtonCode(mControllerType, buttonName);
buttonView.setButtonCode(code);
Log.i("TouchscreenController", String.format("%s -> %d", buttonName, code));
if (code < 0) {
Log.e("TouchscreenController", String.format("Unknown button name '%s' " +
"for '%s'", buttonName, mControllerType));
}
}
}
@Override
public void onButtonStateChanged(TouchscreenControllerButtonView view, boolean pressed) {
if (mHostInterface == null || view.getButtonCode() < 0)
return;
mHostInterface.setControllerButtonState(mControllerIndex, view.getButtonCode(), pressed);
}
}

View File

@ -0,0 +1,19 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="100dp"
android:height="100dp"
android:viewportWidth="26.458332"
android:viewportHeight="26.45833">
<path
android:pathData="M13.2292,13.2292m-12.5677,0a12.5677,12.5677 0,1 1,25.1354 0a12.5677,12.5677 0,1 1,-25.1354 0"
android:strokeWidth="0.26458332"
android:fillColor="#00000000"
android:strokeColor="#ffffff"/>
<path
android:pathData="M13.2292,13.2292m-10.5833,0a10.5833,10.5833 0,1 1,21.1667 0a10.5833,10.5833 0,1 1,-21.1667 0"
android:strokeLineJoin="round"
android:strokeWidth="1.05833328"
android:fillColor="#00000000"
android:strokeColor="#ff0000"
android:fillAlpha="1"
android:strokeLineCap="butt"/>
</vector>

View File

@ -0,0 +1,19 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="100dp"
android:height="100dp"
android:viewportWidth="26.458332"
android:viewportHeight="26.45833">
<path
android:pathData="M13.2292,13.2292m-12.5677,0a12.5677,12.5677 0,1 1,25.1354 0a12.5677,12.5677 0,1 1,-25.1354 0"
android:strokeWidth="0.26458332"
android:fillColor="#4d4d4d"
android:strokeColor="#ffffff"/>
<path
android:pathData="M13.2292,13.2292m-10.5833,0a10.5833,10.5833 0,1 1,21.1667 0a10.5833,10.5833 0,1 1,-21.1667 0"
android:strokeLineJoin="round"
android:strokeWidth="1.05833328"
android:fillColor="#00000000"
android:strokeColor="#ff0000"
android:fillAlpha="1"
android:strokeLineCap="butt"/>
</vector>

View File

@ -0,0 +1,27 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="100dp"
android:height="100dp"
android:viewportWidth="26.458332"
android:viewportHeight="26.45833">
<path
android:pathData="M13.2292,13.2292m-12.5677,0a12.5677,12.5677 0,1 1,25.1354 0a12.5677,12.5677 0,1 1,-25.1354 0"
android:strokeWidth="0.26458332"
android:fillColor="#00000000"
android:strokeColor="#ffffff"/>
<path
android:pathData="m5.2917,5.2917 l15.875,15.875"
android:strokeAlpha="1"
android:strokeLineJoin="miter"
android:strokeWidth="1.05833333"
android:fillColor="#ffffff"
android:strokeColor="#008080"
android:strokeLineCap="butt"/>
<path
android:pathData="m5.2917,21.1667 l15.875,-15.875"
android:strokeAlpha="1"
android:strokeLineJoin="miter"
android:strokeWidth="1.05833333"
android:fillColor="#00000000"
android:strokeColor="#008080"
android:strokeLineCap="butt"/>
</vector>

View File

@ -0,0 +1,27 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="100dp"
android:height="100dp"
android:viewportWidth="26.458332"
android:viewportHeight="26.45833">
<path
android:pathData="M13.2292,13.2292m-12.5677,0a12.5677,12.5677 0,1 1,25.1354 0a12.5677,12.5677 0,1 1,-25.1354 0"
android:strokeWidth="0.26458332"
android:fillColor="#4d4d4d"
android:strokeColor="#ffffff"/>
<path
android:pathData="m5.2917,5.2917 l15.875,15.875"
android:strokeAlpha="1"
android:strokeLineJoin="miter"
android:strokeWidth="1.05833333"
android:fillColor="#ffffff"
android:strokeColor="#008080"
android:strokeLineCap="butt"/>
<path
android:pathData="m5.2917,21.1667 l15.875,-15.875"
android:strokeAlpha="1"
android:strokeLineJoin="miter"
android:strokeWidth="1.05833333"
android:fillColor="#00000000"
android:strokeColor="#008080"
android:strokeLineCap="butt"/>
</vector>

View File

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="100dp"
android:height="100dp"
android:viewportWidth="26.458332"
android:viewportHeight="26.458332">
<path
android:pathData="m5.2917,10.5833 l7.9375,-7.9375 7.9375,7.9375v14.5521L5.2917,25.1354Z"
android:strokeAlpha="1"
android:strokeLineJoin="miter"
android:strokeWidth="0.26458332"
android:fillColor="#00000000"
android:strokeColor="#e6e6e6"
android:strokeLineCap="butt"/>
</vector>

View File

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="100dp"
android:height="100dp"
android:viewportWidth="26.458332"
android:viewportHeight="26.458332">
<path
android:pathData="m5.2917,10.5833 l7.9375,-7.9375 7.9375,7.9375v14.5521L5.2917,25.1354Z"
android:strokeAlpha="1"
android:strokeLineJoin="miter"
android:strokeWidth="0.26458332"
android:fillColor="#4d4d4d"
android:strokeColor="#e6e6e6"
android:strokeLineCap="butt"/>
</vector>

View File

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="100dp"
android:height="100dp"
android:viewportWidth="26.458332"
android:viewportHeight="26.458332">
<path
android:pathData="m16.5365,5.9531 l7.9375,7.9375 -7.9375,7.9375L1.9844,21.8281v-15.875z"
android:strokeAlpha="1"
android:strokeLineJoin="miter"
android:strokeWidth="0.26458332"
android:fillColor="#00000000"
android:strokeColor="#e6e6e6"
android:strokeLineCap="butt"/>
</vector>

View File

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="100dp"
android:height="100dp"
android:viewportWidth="26.458332"
android:viewportHeight="26.458332">
<path
android:pathData="m16.5365,5.9531 l7.9375,7.9375 -7.9375,7.9375L1.9844,21.8281v-15.875z"
android:strokeAlpha="1"
android:strokeLineJoin="miter"
android:strokeWidth="0.26458332"
android:fillColor="#4d4d4d"
android:strokeColor="#e6e6e6"
android:strokeLineCap="butt"/>
</vector>

View File

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="100dp"
android:height="100dp"
android:viewportWidth="26.458332"
android:viewportHeight="26.458332">
<path
android:pathData="m9.9219,21.8281 l-7.9375,-7.9375 7.9375,-7.9375L24.474,5.9531v15.875z"
android:strokeAlpha="1"
android:strokeLineJoin="miter"
android:strokeWidth="0.26458332"
android:fillColor="#00000000"
android:strokeColor="#e6e6e6"
android:strokeLineCap="butt"/>
</vector>

View File

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="100dp"
android:height="100dp"
android:viewportWidth="26.458332"
android:viewportHeight="26.458332">
<path
android:pathData="m9.9219,21.8281 l-7.9375,-7.9375 7.9375,-7.9375L24.474,5.9531v15.875z"
android:strokeAlpha="1"
android:strokeLineJoin="miter"
android:strokeWidth="0.26458332"
android:fillColor="#4d4d4d"
android:strokeColor="#e6e6e6"
android:strokeLineCap="butt"/>
</vector>

View File

@ -0,0 +1,20 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="100dp"
android:height="100dp"
android:viewportWidth="26.458332"
android:viewportHeight="26.45833">
<path
android:pathData="M13.2292,13.2292m-12.5677,0a12.5677,12.5677 0,1 1,25.1354 0a12.5677,12.5677 0,1 1,-25.1354 0"
android:strokeWidth="0.26458332"
android:fillColor="#00000000"
android:strokeColor="#ffffff"/>
<path
android:pathData="M5.2917,5.2917L21.1667,5.2917v15.875L5.2917,21.1667v-15.875"
android:strokeAlpha="1"
android:strokeLineJoin="miter"
android:strokeWidth="1.05833333"
android:fillColor="#00000000"
android:fillAlpha="1"
android:strokeColor="#ff00ff"
android:strokeLineCap="square"/>
</vector>

View File

@ -0,0 +1,20 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="100dp"
android:height="100dp"
android:viewportWidth="26.458332"
android:viewportHeight="26.45833">
<path
android:pathData="M13.2292,13.2292m-12.5677,0a12.5677,12.5677 0,1 1,25.1354 0a12.5677,12.5677 0,1 1,-25.1354 0"
android:strokeWidth="0.26458332"
android:fillColor="#4d4d4d"
android:strokeColor="#ffffff"/>
<path
android:pathData="M5.2917,5.2917L21.1667,5.2917v15.875L5.2917,21.1667v-15.875"
android:strokeAlpha="1"
android:strokeLineJoin="miter"
android:strokeWidth="1.05833333"
android:fillColor="#00000000"
android:fillAlpha="1"
android:strokeColor="#ff00ff"
android:strokeLineCap="square"/>
</vector>

View File

@ -0,0 +1,19 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="100dp"
android:height="100dp"
android:viewportWidth="26.458332"
android:viewportHeight="26.45833">
<path
android:pathData="M13.2292,13.2292m-12.5677,0a12.5677,12.5677 0,1 1,25.1354 0a12.5677,12.5677 0,1 1,-25.1354 0"
android:strokeWidth="0.26458332"
android:fillColor="#00000000"
android:strokeColor="#ffffff"/>
<path
android:pathData="M5.2917,19.0398L21.1667,19.0398l-7.9375,-13.7481 -7.8548,13.6049"
android:strokeAlpha="1"
android:strokeLineJoin="round"
android:strokeWidth="1.05833328"
android:fillColor="#00000000"
android:strokeColor="#00ff00"
android:strokeLineCap="round"/>
</vector>

View File

@ -0,0 +1,19 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="100dp"
android:height="100dp"
android:viewportWidth="26.458332"
android:viewportHeight="26.45833">
<path
android:pathData="M13.2292,13.2292m-12.5677,0a12.5677,12.5677 0,1 1,25.1354 0a12.5677,12.5677 0,1 1,-25.1354 0"
android:strokeWidth="0.26458332"
android:fillColor="#4d4d4d"
android:strokeColor="#ffffff"/>
<path
android:pathData="M5.2917,19.0398L21.1667,19.0398l-7.9375,-13.7481 -7.8548,13.6049"
android:strokeAlpha="1"
android:strokeLineJoin="round"
android:strokeWidth="1.05833328"
android:fillColor="#00000000"
android:strokeColor="#00ff00"
android:strokeLineCap="round"/>
</vector>

View File

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="100dp"
android:height="100dp"
android:viewportWidth="26.458332"
android:viewportHeight="26.458332">
<path
android:pathData="m21.1667,17.1979 l-7.9375,7.9375 -7.9375,-7.9375L5.2917,2.6458L21.1667,2.6458Z"
android:strokeAlpha="1"
android:strokeLineJoin="miter"
android:strokeWidth="0.26458332"
android:fillColor="#00000000"
android:strokeColor="#e6e6e6"
android:strokeLineCap="butt"/>
</vector>

View File

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="100dp"
android:height="100dp"
android:viewportWidth="26.458332"
android:viewportHeight="26.458332">
<path
android:pathData="m21.1667,17.1979 l-7.9375,7.9375 -7.9375,-7.9375L5.2917,2.6458L21.1667,2.6458Z"
android:strokeAlpha="1"
android:strokeLineJoin="miter"
android:strokeWidth="0.26458332"
android:fillColor="#4d4d4d"
android:strokeColor="#e6e6e6"
android:strokeLineCap="butt"/>
</vector>

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/frameLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="#0099cc" android:background="#0099cc"

View File

@ -39,4 +39,8 @@
<string name="title_activity_emulation">EmulationActivity</string> <string name="title_activity_emulation">EmulationActivity</string>
<string name="dummy_button">Dummy Button</string> <string name="dummy_button">Dummy Button</string>
<string name="dummy_content">DUMMY\nCONTENT</string> <string name="dummy_content">DUMMY\nCONTENT</string>
<!-- TODO: Remove or change this placeholder text -->
<string name="hello_blank_fragment">Hello blank fragment</string>
<string name="title_activity_test_controller">TestControllerActivity</string>
</resources> </resources>