add virtual controller element abstraction class

This commit is contained in:
Karim Mreisi
2015-01-28 07:12:20 +01:00
39 changed files with 1468 additions and 1087 deletions

View File

@@ -11,8 +11,8 @@ android {
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 21 targetSdkVersion 21
versionName "3.0.3" versionName "3.1-beta1"
versionCode = 49 versionCode = 50
} }
productFlavors { productFlavors {

Binary file not shown.

View File

@@ -68,7 +68,7 @@
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection" > android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection" >
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value="com.limelight.Connection" /> android:value="com.limelight.AppView" />
</activity> </activity>
<activity <activity
android:name=".binding.input.virtual_controller.VirtualControllerConfiguration" android:name=".binding.input.virtual_controller.VirtualControllerConfiguration"
@@ -79,7 +79,7 @@
<activity <activity
android:name=".binding.input.virtual_controller.VirtualControllerSettings" android:name=".binding.input.virtual_controller.VirtualControllerSettings"
android:screenOrientation="landscape" android:screenOrientation="landscape"
android:theme="@style/StreamTheme" android:theme="@android:style/Theme.Holo.Dialog"
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection" > android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection" >
</activity> </activity>
<service <service

View File

@@ -5,6 +5,7 @@ import java.io.IOException;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.List; import java.util.List;
import java.util.Locale;
import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserException;
@@ -13,27 +14,34 @@ import com.limelight.grid.AppGridAdapter;
import com.limelight.nvstream.http.GfeHttpResponseException; import com.limelight.nvstream.http.GfeHttpResponseException;
import com.limelight.nvstream.http.NvApp; import com.limelight.nvstream.http.NvApp;
import com.limelight.nvstream.http.NvHTTP; import com.limelight.nvstream.http.NvHTTP;
import com.limelight.preferences.PreferenceConfiguration;
import com.limelight.ui.AdapterFragment;
import com.limelight.ui.AdapterFragmentCallbacks;
import com.limelight.utils.Dialog; import com.limelight.utils.Dialog;
import com.limelight.utils.SpinnerDialog; import com.limelight.utils.SpinnerDialog;
import com.limelight.utils.UiHelper; import com.limelight.utils.UiHelper;
import android.app.Activity; import android.app.Activity;
import android.app.FragmentManager;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.os.Bundle; import android.os.Bundle;
import android.preference.Preference;
import android.view.ContextMenu; import android.view.ContextMenu;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ContextMenu.ContextMenuInfo; import android.view.ContextMenu.ContextMenuInfo;
import android.widget.AbsListView;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemClickListener;
import android.widget.GridView; import android.widget.GridView;
import android.widget.ListView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.AdapterView.AdapterContextMenuInfo;
public class AppView extends Activity { public class AppView extends Activity implements AdapterFragmentCallbacks {
private AppGridAdapter appGridAdapter; private AppGridAdapter appGridAdapter;
private InetAddress ipAddress; private InetAddress ipAddress;
private String uniqueId; private String uniqueId;
@@ -52,6 +60,14 @@ public class AppView extends Activity {
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
String locale = PreferenceConfiguration.readPreferences(this).language;
if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
Configuration config = new Configuration(getResources().getConfiguration());
config.locale = new Locale(locale);
getResources().updateConfiguration(config, getResources().getDisplayMetrics());
}
setContentView(R.layout.activity_app_view); setContentView(R.layout.activity_app_view);
UiHelper.notifyNewRootView(this); UiHelper.notifyNewRootView(this);
@@ -76,35 +92,19 @@ public class AppView extends Activity {
finish(); finish();
return; return;
} }
// Setup the list view
GridView appGrid = (GridView) findViewById(R.id.appGridView);
try { try {
appGridAdapter = new AppGridAdapter(this, ipAddress, uniqueId); appGridAdapter = new AppGridAdapter(this,
PreferenceConfiguration.readPreferences(this).listMode,
ipAddress, uniqueId);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
finish(); finish();
return; return;
} }
appGrid.setAdapter(appGridAdapter);
appGrid.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> arg0, View arg1, int pos,
long id) {
AppObject app = (AppObject) appGridAdapter.getItem(pos);
if (app == null || app.app == null) {
return;
}
// Only open the context menu if something is running, otherwise start it getFragmentManager().beginTransaction()
if (getRunningAppId() != -1) { .add(R.id.appFragmentContainer, new AdapterFragment()).commitAllowingStateLoss();
openContextMenu(arg1);
} else {
doStart(app.app);
}
}
});
registerForContextMenu(appGrid);
} }
@Override @Override
@@ -283,8 +283,37 @@ public class AppView extends Activity {
} }
}).start(); }).start();
} }
public class AppObject { @Override
public int getAdapterFragmentLayoutId() {
return PreferenceConfiguration.readPreferences(this).listMode ?
R.layout.list_view : R.layout.app_grid_view;
}
@Override
public void receiveAbsListView(AbsListView listView) {
listView.setAdapter(appGridAdapter);
listView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> arg0, View arg1, int pos,
long id) {
AppObject app = (AppObject) appGridAdapter.getItem(pos);
if (app == null || app.app == null) {
return;
}
// Only open the context menu if something is running, otherwise start it
if (getRunningAppId() != -1) {
openContextMenu(arg1);
} else {
doStart(app.app);
}
}
});
registerForContextMenu(listView);
}
public class AppObject {
public NvApp app; public NvApp app;
public AppObject(NvApp app) { public AppObject(NvApp app) {

View File

@@ -17,18 +17,23 @@ import com.limelight.nvstream.av.video.VideoDecoderRenderer;
import com.limelight.nvstream.input.KeyboardPacket; import com.limelight.nvstream.input.KeyboardPacket;
import com.limelight.nvstream.input.MouseButtonPacket; import com.limelight.nvstream.input.MouseButtonPacket;
import com.limelight.preferences.PreferenceConfiguration; import com.limelight.preferences.PreferenceConfiguration;
import com.limelight.ui.GameGestures;
import com.limelight.utils.Dialog; import com.limelight.utils.Dialog;
import com.limelight.utils.SpinnerDialog; import com.limelight.utils.SpinnerDialog;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.graphics.Point; import android.graphics.Point;
import android.media.AudioManager; import android.media.AudioManager;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import android.net.wifi.WifiManager; import android.net.wifi.WifiManager;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.view.Display; import android.view.Display;
import android.view.InputDevice; import android.view.InputDevice;
import android.view.KeyEvent; import android.view.KeyEvent;
@@ -43,12 +48,15 @@ import android.view.ViewGroup;
import android.view.Window; import android.view.Window;
import android.view.WindowManager; import android.view.WindowManager;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.view.inputmethod.InputMethodManager;
import android.widget.Toast; import android.widget.Toast;
import java.util.Locale;
public class Game extends Activity implements SurfaceHolder.Callback, public class Game extends Activity implements SurfaceHolder.Callback,
OnGenericMotionListener, OnTouchListener, NvConnectionListener, EvdevListener, OnGenericMotionListener, OnTouchListener, NvConnectionListener, EvdevListener,
OnSystemUiVisibilityChangeListener OnSystemUiVisibilityChangeListener, GameGestures
{ {
private int lastMouseX = Integer.MIN_VALUE; private int lastMouseX = Integer.MIN_VALUE;
private int lastMouseY = Integer.MIN_VALUE; private int lastMouseY = Integer.MIN_VALUE;
@@ -56,6 +64,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
// Only 2 touches are supported // Only 2 touches are supported
private TouchContext[] touchContextMap = new TouchContext[2]; private TouchContext[] touchContextMap = new TouchContext[2];
private long threeFingerDownTime = 0;
private static final int THREE_FINGER_TAP_THRESHOLD = 300;
private ControllerHandler controllerHandler; private ControllerHandler controllerHandler;
private VirtualController virtualController; private VirtualController virtualController;
@@ -89,6 +100,13 @@ public class Game extends Activity implements SurfaceHolder.Callback,
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
String locale = PreferenceConfiguration.readPreferences(this).language;
if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
Configuration config = new Configuration(getResources().getConfiguration());
config.locale = new Locale(locale);
getResources().updateConfiguration(config, getResources().getDisplayMetrics());
}
// We don't want a title bar // We don't want a title bar
requestWindowFeature(Window.FEATURE_NO_TITLE); requestWindowFeature(Window.FEATURE_NO_TITLE);
@@ -180,7 +198,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
// Initialize the connection // Initialize the connection
conn = new NvConnection(host, uniqueId, Game.this, config, PlatformBinding.getCryptoProvider(this)); conn = new NvConnection(host, uniqueId, Game.this, config, PlatformBinding.getCryptoProvider(this));
keybTranslator = new KeyboardTranslator(conn); keybTranslator = new KeyboardTranslator(conn);
controllerHandler = new ControllerHandler(conn, prefConfig.deadzonePercentage); controllerHandler = new ControllerHandler(conn, this, prefConfig.deadzonePercentage);
SurfaceHolder sh = sv.getHolder(); SurfaceHolder sh = sv.getHolder();
if (prefConfig.stretchVideo || !decoderRenderer.isHardwareAccelerated()) { if (prefConfig.stretchVideo || !decoderRenderer.isHardwareAccelerated()) {
@@ -478,6 +496,13 @@ public class Game extends Activity implements SurfaceHolder.Callback,
} }
} }
@Override
public void showKeyboard() {
LimeLog.info("Showing keyboard overlay");
InputMethodManager inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
inputManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY);
}
// Returns true if the event was consumed // Returns true if the event was consumed
private boolean handleMotionEvent(MotionEvent event) { private boolean handleMotionEvent(MotionEvent event) {
// Pass through keyboard input if we're not grabbing // Pass through keyboard input if we're not grabbing
@@ -499,7 +524,22 @@ public class Game extends Activity implements SurfaceHolder.Callback,
int actionIndex = event.getActionIndex(); int actionIndex = event.getActionIndex();
int eventX = (int)event.getX(actionIndex); int eventX = (int)event.getX(actionIndex);
int eventY = (int)event.getY(actionIndex); int eventY = (int)event.getY(actionIndex);
// Special handling for 3 finger gesture
if (event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN &&
event.getPointerCount() == 3) {
// Three fingers down
threeFingerDownTime = SystemClock.uptimeMillis();
// Cancel the first and second touches to avoid
// erroneous events
for (TouchContext aTouchContext : touchContextMap) {
aTouchContext.cancelTouch();
}
return true;
}
TouchContext context = getTouchContext(actionIndex); TouchContext context = getTouchContext(actionIndex);
if (context == null) { if (context == null) {
@@ -514,8 +554,16 @@ public class Game extends Activity implements SurfaceHolder.Callback,
break; break;
case MotionEvent.ACTION_POINTER_UP: case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_UP: case MotionEvent.ACTION_UP:
if (event.getPointerCount() == 1) {
// All fingers up
if (SystemClock.uptimeMillis() - threeFingerDownTime < THREE_FINGER_TAP_THRESHOLD) {
// This is a 3 finger tap to bring up the keyboard
showKeyboard();
return true;
}
}
context.touchUpEvent(eventX, eventY); context.touchUpEvent(eventX, eventY);
if (actionIndex == 0 && event.getPointerCount() > 1) { if (actionIndex == 0 && event.getPointerCount() > 1 && !context.isCancelled()) {
// The original secondary touch now becomes primary // The original secondary touch now becomes primary
context.touchDownEvent((int)event.getX(1), (int)event.getY(1)); context.touchDownEvent((int)event.getX(1), (int)event.getY(1));
} }

View File

@@ -4,6 +4,7 @@ import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.Locale;
import com.limelight.binding.PlatformBinding; import com.limelight.binding.PlatformBinding;
import com.limelight.binding.crypto.AndroidCryptoProvider; import com.limelight.binding.crypto.AndroidCryptoProvider;
@@ -16,15 +17,20 @@ import com.limelight.nvstream.http.PairingManager;
import com.limelight.nvstream.http.PairingManager.PairState; import com.limelight.nvstream.http.PairingManager.PairState;
import com.limelight.nvstream.wol.WakeOnLanSender; import com.limelight.nvstream.wol.WakeOnLanSender;
import com.limelight.preferences.AddComputerManually; import com.limelight.preferences.AddComputerManually;
import com.limelight.preferences.PreferenceConfiguration;
import com.limelight.preferences.StreamSettings; import com.limelight.preferences.StreamSettings;
import com.limelight.ui.AdapterFragment;
import com.limelight.ui.AdapterFragmentCallbacks;
import com.limelight.utils.Dialog; import com.limelight.utils.Dialog;
import com.limelight.utils.UiHelper; import com.limelight.utils.UiHelper;
import android.app.Activity; import android.app.Activity;
import android.app.FragmentTransaction;
import android.app.Service; import android.app.Service;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Intent; import android.content.Intent;
import android.content.ServiceConnection; import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
@@ -35,15 +41,18 @@ import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ContextMenu.ContextMenuInfo; import android.view.ContextMenu.ContextMenuInfo;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.widget.AbsListView;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemClickListener;
import android.widget.GridView; import android.widget.GridView;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.ListView;
import android.widget.RelativeLayout; import android.widget.RelativeLayout;
import android.widget.Toast; import android.widget.Toast;
import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.AdapterView.AdapterContextMenuInfo;
public class PcView extends Activity { public class PcView extends Activity implements AdapterFragmentCallbacks {
private AdapterFragment adapterFragment;
private RelativeLayout noPcFoundLayout; private RelativeLayout noPcFoundLayout;
private PcGridAdapter pcGridAdapter; private PcGridAdapter pcGridAdapter;
private ComputerManagerService.ComputerManagerBinder managerBinder; private ComputerManagerService.ComputerManagerBinder managerBinder;
@@ -102,27 +111,6 @@ public class PcView extends Activity {
ImageButton settingsButton = (ImageButton) findViewById(R.id.settingsButton); ImageButton settingsButton = (ImageButton) findViewById(R.id.settingsButton);
ImageButton addComputerButton = (ImageButton) findViewById(R.id.manuallyAddPc); ImageButton addComputerButton = (ImageButton) findViewById(R.id.manuallyAddPc);
GridView pcGrid = (GridView) findViewById(R.id.pcGridView);
pcGrid.setAdapter(pcGridAdapter);
pcGrid.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> arg0, View arg1, int pos,
long id) {
ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(pos);
if (computer.details.reachability == ComputerDetails.Reachability.UNKNOWN) {
// Do nothing
} else if (computer.details.reachability == ComputerDetails.Reachability.OFFLINE) {
// Open the context menu if a PC is offline
openContextMenu(arg1);
} else if (computer.details.pairState != PairState.PAIRED) {
// Pair an unpaired machine by default
doPair(computer.details);
} else {
doAppList(computer.details);
}
}
});
registerForContextMenu(pcGrid);
settingsButton.setOnClickListener(new OnClickListener() { settingsButton.setOnClickListener(new OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@@ -137,6 +125,15 @@ public class PcView extends Activity {
} }
}); });
FragmentTransaction transaction = getFragmentManager().beginTransaction();
if (adapterFragment != null) {
// Remove the old fragment
transaction.remove(adapterFragment);
}
adapterFragment = new AdapterFragment();
transaction.add(R.id.pcFragmentContainer, adapterFragment);
transaction.commitAllowingStateLoss();
noPcFoundLayout = (RelativeLayout) findViewById(R.id.no_pc_found_layout); noPcFoundLayout = (RelativeLayout) findViewById(R.id.no_pc_found_layout);
if (pcGridAdapter.getCount() == 0) { if (pcGridAdapter.getCount() == 0) {
noPcFoundLayout.setVisibility(View.VISIBLE); noPcFoundLayout.setVisibility(View.VISIBLE);
@@ -150,12 +147,20 @@ public class PcView extends Activity {
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
String locale = PreferenceConfiguration.readPreferences(this).language;
if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
Configuration config = new Configuration(getResources().getConfiguration());
config.locale = new Locale(locale);
getResources().updateConfiguration(config, getResources().getDisplayMetrics());
}
// Bind to the computer manager service // Bind to the computer manager service
bindService(new Intent(PcView.this, ComputerManagerService.class), serviceConnection, bindService(new Intent(PcView.this, ComputerManagerService.class), serviceConnection,
Service.BIND_AUTO_CREATE); Service.BIND_AUTO_CREATE);
pcGridAdapter = new PcGridAdapter(this); pcGridAdapter = new PcGridAdapter(this,
PreferenceConfiguration.readPreferences(this).listMode);
initializeViews(); initializeViews();
} }
@@ -561,8 +566,38 @@ public class PcView extends Activity {
// Notify the view that the data has changed // Notify the view that the data has changed
pcGridAdapter.notifyDataSetChanged(); pcGridAdapter.notifyDataSetChanged();
} }
public class ComputerObject { @Override
public int getAdapterFragmentLayoutId() {
return PreferenceConfiguration.readPreferences(this).listMode ?
R.layout.list_view : R.layout.pc_grid_view;
}
@Override
public void receiveAbsListView(AbsListView listView) {
listView.setAdapter(pcGridAdapter);
listView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> arg0, View arg1, int pos,
long id) {
ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(pos);
if (computer.details.reachability == ComputerDetails.Reachability.UNKNOWN) {
// Do nothing
} else if (computer.details.reachability == ComputerDetails.Reachability.OFFLINE) {
// Open the context menu if a PC is offline
openContextMenu(arg1);
} else if (computer.details.pairState != PairState.PAIRED) {
// Pair an unpaired machine by default
doPair(computer.details);
} else {
doAppList(computer.details);
}
}
});
registerForContextMenu(listView);
}
public class ComputerObject {
public ComputerDetails details; public ComputerDetails details;
public ComputerObject(ComputerDetails details) { public ComputerObject(ComputerDetails details) {

View File

@@ -10,6 +10,7 @@ import android.view.MotionEvent;
import com.limelight.LimeLog; import com.limelight.LimeLog;
import com.limelight.nvstream.NvConnection; import com.limelight.nvstream.NvConnection;
import com.limelight.nvstream.input.ControllerPacket; import com.limelight.nvstream.input.ControllerPacket;
import com.limelight.ui.GameGestures;
import com.limelight.utils.Vector2d; import com.limelight.utils.Vector2d;
public class ControllerHandler { public class ControllerHandler {
@@ -30,6 +31,9 @@ public class ControllerHandler {
private long lastLbUpTime = 0; private long lastLbUpTime = 0;
private long lastRbUpTime = 0; private long lastRbUpTime = 0;
private static final int MAXIMUM_BUMPER_UP_DELAY_MS = 100; private static final int MAXIMUM_BUMPER_UP_DELAY_MS = 100;
private long startDownTime = 0;
private static final int START_DOWN_TIME_KEYB_MS = 750;
private static final int MINIMUM_BUTTON_DOWN_TIME_MS = 25; private static final int MINIMUM_BUTTON_DOWN_TIME_MS = 25;
@@ -46,10 +50,12 @@ public class ControllerHandler {
private NvConnection conn; private NvConnection conn;
private double stickDeadzone; private double stickDeadzone;
private final ControllerMapping defaultMapping = new ControllerMapping(); private final ControllerMapping defaultMapping = new ControllerMapping();
private GameGestures gestures;
private boolean hasGameController; private boolean hasGameController;
public ControllerHandler(NvConnection conn, int deadzonePercentage) { public ControllerHandler(NvConnection conn, GameGestures gestures, int deadzonePercentage) {
this.conn = conn; this.conn = conn;
this.gestures = gestures;
// HACK: For now we're hardcoding a 10% deadzone. Some deadzone // HACK: For now we're hardcoding a 10% deadzone. Some deadzone
// is required for controller batching support to work. // is required for controller batching support to work.
@@ -513,6 +519,9 @@ public class ControllerHandler {
break; break;
case KeyEvent.KEYCODE_BUTTON_START: case KeyEvent.KEYCODE_BUTTON_START:
case KeyEvent.KEYCODE_MENU: case KeyEvent.KEYCODE_MENU:
if (SystemClock.uptimeMillis() - startDownTime > ControllerHandler.START_DOWN_TIME_KEYB_MS) {
gestures.showKeyboard();
}
inputMap &= ~ControllerPacket.PLAY_FLAG; inputMap &= ~ControllerPacket.PLAY_FLAG;
break; break;
case KeyEvent.KEYCODE_BACK: case KeyEvent.KEYCODE_BACK:
@@ -621,6 +630,9 @@ public class ControllerHandler {
break; break;
case KeyEvent.KEYCODE_BUTTON_START: case KeyEvent.KEYCODE_BUTTON_START:
case KeyEvent.KEYCODE_MENU: case KeyEvent.KEYCODE_MENU:
if (event.getRepeatCount() == 0) {
startDownTime = SystemClock.uptimeMillis();
}
inputMap |= ControllerPacket.PLAY_FLAG; inputMap |= ControllerPacket.PLAY_FLAG;
break; break;
case KeyEvent.KEYCODE_BACK: case KeyEvent.KEYCODE_BACK:

View File

@@ -9,6 +9,7 @@ public class TouchContext {
private int originalTouchX = 0; private int originalTouchX = 0;
private int originalTouchY = 0; private int originalTouchY = 0;
private long originalTouchTime = 0; private long originalTouchTime = 0;
private boolean cancelled;
private NvConnection conn; private NvConnection conn;
private int actionIndex; private int actionIndex;
@@ -56,12 +57,17 @@ public class TouchContext {
originalTouchX = lastTouchX = eventX; originalTouchX = lastTouchX = eventX;
originalTouchY = lastTouchY = eventY; originalTouchY = lastTouchY = eventY;
originalTouchTime = System.currentTimeMillis(); originalTouchTime = System.currentTimeMillis();
cancelled = false;
return true;
return true;
} }
public void touchUpEvent(int eventX, int eventY) public void touchUpEvent(int eventX, int eventY)
{ {
if (cancelled) {
return;
}
if (isTap()) if (isTap())
{ {
byte buttonIndex = getMouseButtonIndex(); byte buttonIndex = getMouseButtonIndex();
@@ -81,8 +87,8 @@ public class TouchContext {
} }
public boolean touchMoveEvent(int eventX, int eventY) public boolean touchMoveEvent(int eventX, int eventY)
{ {
if (eventX != lastTouchX || eventY != lastTouchY) if (eventX != lastTouchX || eventY != lastTouchY)
{ {
// We only send moves for the primary touch point // We only send moves for the primary touch point
if (actionIndex == 0) { if (actionIndex == 0) {
@@ -102,4 +108,12 @@ public class TouchContext {
return true; return true;
} }
public void cancelTouch() {
cancelled = true;
}
public boolean isCancelled() {
return cancelled;
}
} }

View File

@@ -26,7 +26,7 @@ public class EvdevReader {
// 4.4 and later to do live SELinux policy changes. // 4.4 and later to do live SELinux policy changes.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
EvdevShell shell = EvdevShell.getInstance(); EvdevShell shell = EvdevShell.getInstance();
shell.runCommand("supolicy --live \"allow untrusted_app input_device dir getattr\" " + shell.runCommand("supolicy --live \"allow untrusted_app input_device dir { getattr read search }\" " +
"\"allow untrusted_app input_device chr_file { open read write ioctl }\""); "\"allow untrusted_app input_device chr_file { open read write ioctl }\"");
} }
} }

View File

@@ -5,106 +5,56 @@ import android.graphics.Canvas;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Paint; import android.graphics.Paint;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date;
import java.util.List; import java.util.List;
/** /**
* Created by Karim Mreisi on 30.11.2014. * Created by Karim Mreisi on 30.11.2014.
*/ */
public class AnalogStick extends View public class AnalogStick extends VirtualControllerElement
{ {
private enum _STICK_STATE protected static boolean _PRINT_DEBUG_INFORMATION = true;
{
NO_MOVEMENT,
MOVED
}
private enum _CLICK_STATE float radius_complete = 0;
{ float radius_dead_zone = 0;
SINGLE, float radius_analog_stick = 0;
DOUBLE float position_stick_x = 0;
} float position_stick_y = 0;
private static final boolean _PRINT_DEBUG_INFORMATION = false; boolean viewPressed = false;
boolean analogStickActive = false;
public interface AnalogStickListener _STICK_STATE stick_state = _STICK_STATE.NO_MOVEMENT;
{ _CLICK_STATE click_state = _CLICK_STATE.SINGLE;
void onMovement(float x, float y);
void onClick();
void onRevoke();
void onDoubleClick();
}
public void addAnalogStickListener (AnalogStickListener listener) List<AnalogStickListener> listeners = new ArrayList<AnalogStickListener>();
{ OnTouchListener onTouchListener = null;
listeners.add(listener); private long timeoutDoubleClick = 250;
} private long timeLastClick = 0;
public void setOnTouchListener(OnTouchListener listener)
{
onTouchListener = listener;
}
private static final void _DBG(String text)
{
if (_PRINT_DEBUG_INFORMATION)
{
System.out.println("AnalogStick: " + text);
}
}
private int normalColor = 0xF0888888;
private int pressedColor = 0xF00000FF;
private long timeoutDoubleClick = 250;
private long timeLastClick = 0;
float radius_complete = 0;
float radius_dead_zone = 0;
float radius_analog_stick = 0;
float position_stick_x = 0;
float position_stick_y = 0;
boolean viewPressed = false;
boolean analogStickActive = false;
_STICK_STATE stick_state = _STICK_STATE.NO_MOVEMENT;
_CLICK_STATE click_state = _CLICK_STATE.SINGLE;
List<AnalogStickListener> listeners = new ArrayList<AnalogStickListener>();
OnTouchListener onTouchListener = null;
public AnalogStick(Context context) public AnalogStick(Context context)
{ {
super(context); super(context);
position_stick_x = getWidth() / 2; position_stick_x = getWidth() / 2;
position_stick_y = getHeight() / 2; position_stick_y = getHeight() / 2;
stick_state = _STICK_STATE.NO_MOVEMENT;
click_state = _CLICK_STATE.SINGLE;
viewPressed = false;
analogStickActive = false;
} }
public void setColors(int normalColor, int pressedColor) public void addAnalogStickListener(AnalogStickListener listener)
{
this.normalColor = normalColor;
this.pressedColor = pressedColor;
}
private float getPercent(float value, int percent)
{ {
return value / 100 * percent; listeners.add(listener);
} }
private int getCorrectWidth() public void setOnTouchListener(OnTouchListener listener)
{ {
return getWidth() > getHeight() ? getHeight() : getWidth(); onTouchListener = listener;
}
public void setColors(int normalColor, int pressedColor)
{
this.normalColor = normalColor;
this.pressedColor = pressedColor;
} }
private double getMovementRadius(float x, float y) private double getMovementRadius(float x, float y)
@@ -119,15 +69,15 @@ public class AnalogStick extends View
return x > 0 ? x : -x; return x > 0 ? x : -x;
} }
return Math.sqrt(x * x + y * y); return Math.sqrt(x * x + y * y);
} }
@Override @Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) protected void onSizeChanged(int w, int h, int oldw, int oldh)
{ {
radius_complete = getPercent(getCorrectWidth() / 2, 95); radius_complete = getPercent(getCorrectWidth() / 2, 95);
radius_dead_zone = getPercent(getCorrectWidth() / 2, 20); radius_dead_zone = getPercent(getCorrectWidth() / 2, 20);
radius_analog_stick = getPercent(getCorrectWidth() / 2, 30); radius_analog_stick = getPercent(getCorrectWidth() / 2, 30);
super.onSizeChanged(w, h, oldw, oldh); super.onSizeChanged(w, h, oldw, oldh);
} }
@@ -135,29 +85,29 @@ public class AnalogStick extends View
@Override @Override
protected void onDraw(Canvas canvas) protected void onDraw(Canvas canvas)
{ {
// set transparent background // set transparent background
canvas.drawColor(Color.TRANSPARENT); canvas.drawColor(Color.TRANSPARENT);
Paint paint = new Paint(); Paint paint = new Paint();
paint.setStyle(Paint.Style.STROKE); paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(getPercent(getCorrectWidth() / 2, 2)); paint.setStrokeWidth(getPercent(getCorrectWidth() / 2, 2));
// draw outer circle // draw outer circle
if (!viewPressed || click_state == _CLICK_STATE.SINGLE) if (!viewPressed || click_state == _CLICK_STATE.SINGLE)
{ {
paint.setColor(normalColor); paint.setColor(normalColor);
} }
else else
{ {
paint.setColor(pressedColor); paint.setColor(pressedColor);
} }
canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius_complete, paint); canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius_complete, paint);
paint.setColor(normalColor); paint.setColor(normalColor);
// draw dead zone // draw dead zone
canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius_dead_zone, paint); canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius_dead_zone, paint);
// draw stick depending on state (no movement, moved, active(out of dead zone)) // draw stick depending on state (no movement, moved, active(out of dead zone))
if (analogStickActive) if (analogStickActive)
@@ -209,11 +159,11 @@ public class AnalogStick extends View
{ {
if (way_x > 0) if (way_x > 0)
{ {
angle = Math.PI * 3/2; angle = Math.PI * 3 / 2;
} }
else if (way_x < 0) else if (way_x < 0)
{ {
angle = Math.PI * 1/2; angle = Math.PI * 1 / 2;
} }
} }
else else
@@ -221,30 +171,31 @@ public class AnalogStick extends View
if (way_x > 0) if (way_x > 0)
{ {
if (way_y < 0) if (way_y < 0)
{ // first quadrant { // first quadrant
angle = 3 * Math.PI / 2 + Math.atan((double)(-way_y / way_x)); angle =
3 * Math.PI / 2 + Math.atan((double) (-way_y / way_x));
} }
else else
{ // second quadrant { // second quadrant
angle = Math.PI + Math.atan((double)(way_x / way_y)); angle = Math.PI + Math.atan((double) (way_x / way_y));
} }
} }
else else
{ {
if (way_y > 0) if (way_y > 0)
{ // third quadrant { // third quadrant
angle = Math.PI / 2 + Math.atan((double)(way_y / -way_x)); angle = Math.PI / 2 + Math.atan((double) (way_y / -way_x));
} }
else else
{ // fourth quadrant { // fourth quadrant
angle = 0 + Math.atan((double) (-way_x / -way_y)); angle = 0 + Math.atan((double) (-way_x / -way_y));
} }
} }
} }
_DBG("angle: " + angle + " way y: "+ way_y + " way x: " + way_x); _DBG("angle: " + angle + " way y: " + way_y + " way x: " + way_x);
return angle; return angle;
} }
private void moveActionCallback(float x, float y) private void moveActionCallback(float x, float y)
@@ -258,50 +209,49 @@ public class AnalogStick extends View
} }
} }
private void clickActionCallback() private void clickActionCallback()
{
_DBG("click");
// notify listeners
for (AnalogStickListener listener : listeners)
{
listener.onClick();
}
}
private void doubleClickActionCallback()
{
_DBG("double click");
// notify listeners
for (AnalogStickListener listener : listeners)
{
listener.onDoubleClick();
}
}
private void revokeActionCallback()
{
_DBG("revoke");
// notify listeners
for (AnalogStickListener listener : listeners)
{
listener.onRevoke();
}
}
private void updatePosition(float x, float y)
{ {
float way_x = -(getWidth() / 2 - x); _DBG("click");
float way_y = -(getHeight() / 2 - y);
float movement_x = 0; // notify listeners
float movement_y = 0; for (AnalogStickListener listener : listeners)
{
listener.onClick();
}
}
double movement_radius = getMovementRadius(way_x, way_y); private void doubleClickActionCallback()
double movement_angle = getAngle(way_x, way_y); {
_DBG("double click");
// notify listeners
for (AnalogStickListener listener : listeners)
{
listener.onDoubleClick();
}
}
private void revokeActionCallback()
{
_DBG("revoke");
// notify listeners
for (AnalogStickListener listener : listeners)
{
listener.onRevoke();
}
}
private void updatePosition(float x, float y)
{
float way_x = -(getWidth() / 2 - x);
float way_y = -(getHeight() / 2 - y);
float movement_x = 0;
float movement_y = 0;
double movement_radius = getMovementRadius(way_x, way_y);
double movement_angle = getAngle(way_x, way_y);
// chop radius if out of outer circle // chop radius if out of outer circle
if (movement_radius > (radius_complete - radius_analog_stick)) if (movement_radius > (radius_complete - radius_analog_stick))
@@ -309,8 +259,10 @@ public class AnalogStick extends View
movement_radius = radius_complete - radius_analog_stick; movement_radius = radius_complete - radius_analog_stick;
} }
float correlated_y = (float)(Math.sin(Math.PI / 2 - movement_angle) * (movement_radius)); float correlated_y =
float correlated_x = (float)(Math.cos(Math.PI / 2 - movement_angle) * (movement_radius)); (float) (Math.sin(Math.PI / 2 - movement_angle) * (movement_radius));
float correlated_x =
(float) (Math.cos(Math.PI / 2 - movement_angle) * (movement_radius));
float complete = (radius_complete - radius_analog_stick); float complete = (radius_complete - radius_analog_stick);
@@ -336,71 +288,69 @@ public class AnalogStick extends View
@Override @Override
public boolean onTouchEvent(MotionEvent event) public boolean onTouchEvent(MotionEvent event)
{ {
if (onTouchListener != null) if (onTouchListener != null)
{ {
return onTouchListener.onTouch(this, event); return onTouchListener.onTouch(this, event);
} }
// get masked (not specific to a pointer) action // get masked (not specific to a pointer) action
int action = event.getActionMasked(); int action = event.getActionMasked();
_CLICK_STATE lastClickState = click_state; _CLICK_STATE lastClickState = click_state;
boolean wasPressed = analogStickActive; boolean wasPressed = analogStickActive;
switch (action) switch (action)
{ {
case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN: case MotionEvent.ACTION_POINTER_DOWN:
{ {
viewPressed = true; viewPressed = true;
// check for double click // check for double click
if (lastClickState == _CLICK_STATE.SINGLE && timeLastClick + timeoutDoubleClick > System.currentTimeMillis()) if (lastClickState == _CLICK_STATE.SINGLE && timeLastClick + timeoutDoubleClick > System.currentTimeMillis())
{ {
click_state = _CLICK_STATE.DOUBLE; click_state = _CLICK_STATE.DOUBLE;
doubleClickActionCallback(); doubleClickActionCallback();
} }
else else
{ {
click_state = _CLICK_STATE.SINGLE; click_state = _CLICK_STATE.SINGLE;
clickActionCallback(); clickActionCallback();
} }
timeLastClick = System.currentTimeMillis(); timeLastClick = System.currentTimeMillis();
break;
}
case MotionEvent.ACTION_MOVE:
{
if (analogStickActive || timeLastClick + timeoutDoubleClick < System.currentTimeMillis())
{
analogStickActive = true;
}
break; break;
} }
case MotionEvent.ACTION_UP: case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_POINTER_UP:
{ {
analogStickActive = false; if (analogStickActive || timeLastClick + timeoutDoubleClick < System.currentTimeMillis())
viewPressed = false; {
analogStickActive = true;
}
revokeActionCallback(); break;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
{
analogStickActive = false;
viewPressed = false;
revokeActionCallback();
break; break;
} }
} }
// no longer pressed reset movement
if (analogStickActive) if (analogStickActive)
{ // when is pressed calculate new positions (will trigger movement if necessary) { // when is pressed calculate new positions (will trigger movement if necessary)
updatePosition(event.getX(), event.getY()); updatePosition(event.getX(), event.getY());
} }
else else if (wasPressed)
{ // no longer pressed reset movement {
if (wasPressed) moveActionCallback(0, 0);
{
moveActionCallback(0, 0);
}
} }
// to get view refreshed // to get view refreshed
@@ -408,4 +358,28 @@ public class AnalogStick extends View
return true; return true;
} }
private enum _STICK_STATE
{
NO_MOVEMENT,
MOVED
}
private enum _CLICK_STATE
{
SINGLE,
DOUBLE
}
public interface AnalogStickListener
{
void onMovement(float x, float y);
void onClick();
void onRevoke();
void onDoubleClick();
}
} }

View File

@@ -6,7 +6,6 @@ import android.graphics.Color;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -16,214 +15,181 @@ import java.util.TimerTask;
/** /**
* Created by Karim on 24.01.2015. * Created by Karim on 24.01.2015.
*/ */
public class DigitalButton extends View public class DigitalButton extends VirtualControllerElement
{ {
private class TimerLongClickTimerTask extends TimerTask List<DigitalButtonListener> listeners = new ArrayList<DigitalButtonListener>();
{ OnTouchListener onTouchListener = null;
@Override boolean clicked;
public void run() private String text = "";
{ private int icon = -1;
onLongClickCallback(); private long timerLongClickTimeout = 3000;
} private Timer timerLongClick = null;
} private TimerLongClickTimerTask longClickTimerTask = null;
private static final boolean _PRINT_DEBUG_INFORMATION = false;
private int normalColor = 0xF0888888; public DigitalButton(Context context)
private int pressedColor = 0xF00000FF; {
private String text = ""; super(context);
private int icon = -1; clicked = false;
}
private long timerLongClickTimeout = 3000; public void addDigitalButtonListener(DigitalButtonListener listener)
private Timer timerLongClick = null; {
private TimerLongClickTimerTask longClickTimerTask = null; listeners.add(listener);
}
public void setOnTouchListener(OnTouchListener listener)
{
onTouchListener = listener;
}
public interface DigitalButtonListener public void setText(String text)
{ {
void onClick(); this.text = text;
void onLongClick(); invalidate();
void onRelease(); }
}
public void addDigitalButtonListener(DigitalButtonListener listener) public void setIcon(int id)
{ {
listeners.add(listener); this.icon = id;
} invalidate();
}
public void setColors(int normalColor, int pressedColor) @Override
{ protected void onDraw(Canvas canvas)
this.normalColor = normalColor; {
this.pressedColor = pressedColor; // set transparent background
} canvas.drawColor(Color.TRANSPARENT);
public void setOnTouchListener(OnTouchListener listener) Paint paint = new Paint();
{
onTouchListener = listener;
}
private static final void _DBG(String text) paint.setTextSize(getPercent(getCorrectWidth(), 50));
{ paint.setTextAlign(Paint.Align.CENTER);
if (_PRINT_DEBUG_INFORMATION) paint.setStrokeWidth(3);
{
System.out.println("DigitalButton: " + text);
}
}
List<DigitalButtonListener> listeners = new ArrayList<DigitalButtonListener>(); paint.setColor(clicked ? pressedColor : normalColor);
OnTouchListener onTouchListener = null; paint.setStyle(Paint.Style.STROKE);
canvas.drawRect(
1, 1,
getWidth() - 1, getHeight() - 1,
paint
);
boolean clicked; if (icon != -1)
{
Drawable d = getResources().getDrawable(icon);
d.setBounds(5, 5, getWidth() - 5, getHeight() - 5);
d.draw(canvas);
}
else
{
paint.setStyle(Paint.Style.FILL_AND_STROKE);
canvas.drawText(text,
getPercent(getWidth(), 50), getPercent(getHeight(), 73),
paint);
}
public DigitalButton(Context context) super.onDraw(canvas);
{ }
super(context);
clicked = false; private void onClickCallback()
} {
_DBG("clicked");
public void setText(String text) // notify listeners
{ for (DigitalButtonListener listener : listeners)
this.text = text; {
listener.onClick();
}
invalidate(); timerLongClick = new Timer();
} longClickTimerTask = new TimerLongClickTimerTask();
public void setIcon(int id) timerLongClick.schedule(longClickTimerTask, timerLongClickTimeout);
{ }
this.icon = id;
invalidate(); private void onLongClickCallback()
} {
_DBG("long click");
private float getPercent(float value, float percent) // notify listeners
{ for (DigitalButtonListener listener : listeners)
return value / 100 * percent; {
} listener.onLongClick();
}
}
private int getCorrectWidth() private void onReleaseCallback()
{ {
return getWidth() > getHeight() ? getHeight() : getWidth(); _DBG("released");
}
@Override // notify listeners
protected void onDraw(Canvas canvas) for (DigitalButtonListener listener : listeners)
{ {
// set transparent background listener.onRelease();
canvas.drawColor(Color.TRANSPARENT); }
Paint paint = new Paint(); timerLongClick.cancel();
longClickTimerTask.cancel();
}
paint.setTextSize(getPercent(getCorrectWidth(), 50)); @Override
paint.setTextAlign(Paint.Align.CENTER); public boolean onTouchEvent(MotionEvent event)
paint.setStrokeWidth(3); {
/*
if (onTouchListener != null)
{
return onTouchListener.onTouch(this, event);
}
*/
// get masked (not specific to a pointer) action
int action = event.getActionMasked();
paint.setColor(clicked ? pressedColor : normalColor); switch (action)
paint.setStyle(Paint.Style.STROKE); {
canvas.drawRect( case MotionEvent.ACTION_DOWN:
1, 1, case MotionEvent.ACTION_POINTER_DOWN:
getWidth() - 1, getHeight() - 1, {
paint clicked = true;
); onClickCallback();
if (icon != -1) invalidate();
{
Drawable d = getResources().getDrawable(icon);
d.setBounds(5, 5, getWidth() - 5, getHeight() - 5);
d.draw(canvas);
}
else
{
paint.setStyle(Paint.Style.FILL_AND_STROKE);
canvas.drawText(text,
getPercent(getWidth(), 50), getPercent(getHeight(), 73),
paint);
}
super.onDraw(canvas); return true;
} }
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
{
clicked = false;
onReleaseCallback();
private void onClickCallback() invalidate();
{
_DBG("clicked");
// notify listeners return true;
for (DigitalButtonListener listener : listeners) }
{ default:
listener.onClick(); {
} }
}
timerLongClick = new Timer(); return true;
longClickTimerTask = new TimerLongClickTimerTask(); }
timerLongClick.schedule(longClickTimerTask, timerLongClickTimeout); public interface DigitalButtonListener
} {
void onClick();
private void onLongClickCallback() void onLongClick();
{
_DBG("long click");
// notify listeners void onRelease();
for (DigitalButtonListener listener : listeners) }
{
listener.onLongClick();
}
}
private void onReleaseCallback() private class TimerLongClickTimerTask extends TimerTask
{ {
_DBG("released"); @Override
public void run()
// notify listeners {
for (DigitalButtonListener listener : listeners) onLongClickCallback();
{ }
listener.onRelease(); }
}
timerLongClick.cancel();
longClickTimerTask.cancel();
}
@Override
public boolean onTouchEvent(MotionEvent event)
{
/*
if (onTouchListener != null)
{
return onTouchListener.onTouch(this, event);
}
*/
// get masked (not specific to a pointer) action
int action = event.getActionMasked();
switch (action)
{
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
{
clicked = true;
onClickCallback();
invalidate();
return true;
}
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
{
clicked = false;
onReleaseCallback();
invalidate();
return true;
}
default:
{
}
}
return true;
}
} }

View File

@@ -5,7 +5,6 @@ import android.graphics.Canvas;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Paint; import android.graphics.Paint;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -13,273 +12,244 @@ import java.util.List;
/** /**
* Created by Karim Mreisi on 23.01.2015. * Created by Karim Mreisi on 23.01.2015.
*/ */
public class DigitalPad extends View public class DigitalPad extends VirtualControllerElement
{ {
public final static int DIGITAL_PAD_DIRECTION_NO_DIRECTION = 0; public final static int DIGITAL_PAD_DIRECTION_NO_DIRECTION = 0;
public final static int DIGITAL_PAD_DIRECTION_LEFT = 1; int direction = DIGITAL_PAD_DIRECTION_NO_DIRECTION;
public final static int DIGITAL_PAD_DIRECTION_UP = 2; public final static int DIGITAL_PAD_DIRECTION_LEFT = 1;
public final static int DIGITAL_PAD_DIRECTION_RIGHT = 4; public final static int DIGITAL_PAD_DIRECTION_UP = 2;
public final static int DIGITAL_PAD_DIRECTION_DOWN = 8; public final static int DIGITAL_PAD_DIRECTION_RIGHT = 4;
public final static int DIGITAL_PAD_DIRECTION_DOWN = 8;
List<DigitalPadListener> listeners = new ArrayList<DigitalPadListener>();
OnTouchListener onTouchListener = null;
private int normalColor = 0xF0888888; public DigitalPad(Context context)
private int pressedColor = 0xF00000FF; {
super(context);
}
private static final boolean _PRINT_DEBUG_INFORMATION = false; public void addDigitalPadListener(DigitalPadListener listener)
{
listeners.add(listener);
}
public interface DigitalPadListener public void setOnTouchListener(OnTouchListener listener)
{ {
void onDirectionChange(int direction); onTouchListener = listener;
} }
public void addDigitalPadListener (DigitalPadListener listener) @Override
{ protected void onDraw(Canvas canvas)
listeners.add(listener); {
} // set transparent background
canvas.drawColor(Color.TRANSPARENT);
public void setOnTouchListener(OnTouchListener listener) Paint paint = new Paint();
{
onTouchListener = listener;
}
private static final void _DBG(String text) paint.setTextSize(getPercent(getCorrectWidth(), 20));
{ paint.setTextAlign(Paint.Align.CENTER);
if (_PRINT_DEBUG_INFORMATION) paint.setStrokeWidth(3);
{
System.out.println("DigitalPad: " + text);
}
}
List<DigitalPadListener> listeners = new ArrayList<DigitalPadListener>(); if (direction == DIGITAL_PAD_DIRECTION_NO_DIRECTION)
OnTouchListener onTouchListener = null; {
// draw no direction rect
paint.setStyle(Paint.Style.STROKE);
paint.setColor(normalColor);
canvas.drawRect(
getPercent(getWidth(), 36), getPercent(getHeight(), 36),
getPercent(getWidth(), 63), getPercent(getHeight(), 63),
paint
);
}
int direction; // draw left rect
paint.setColor(
(direction & DIGITAL_PAD_DIRECTION_LEFT) > 0 ? pressedColor : normalColor);
paint.setStyle(Paint.Style.FILL_AND_STROKE);
canvas.drawText("LF",
getPercent(getWidth(), 16.5f), getPercent(getHeight(), 56),
paint);
paint.setStyle(Paint.Style.STROKE);
canvas.drawRect(
0, getPercent(getHeight(), 33),
getPercent(getWidth(), 33), getPercent(getHeight(), 66),
paint
);
public DigitalPad(Context context) // draw left up line
{ paint.setColor((
super(context); (direction & DIGITAL_PAD_DIRECTION_LEFT) > 0 &&
(direction & DIGITAL_PAD_DIRECTION_UP) > 0
) ? pressedColor : normalColor
);
paint.setStyle(Paint.Style.STROKE);
canvas.drawLine(
0, getPercent(getWidth(), 33),
getPercent(getWidth(), 33), 0,
paint
);
direction = DIGITAL_PAD_DIRECTION_NO_DIRECTION; // draw up rect
} paint.setColor(
(direction & DIGITAL_PAD_DIRECTION_UP) > 0 ? pressedColor : normalColor);
paint.setStyle(Paint.Style.FILL_AND_STROKE);
canvas.drawText("UP",
getPercent(getWidth(), 49.5f), getPercent(getHeight(), 23),
paint);
paint.setStyle(Paint.Style.STROKE);
canvas.drawRect(
getPercent(getWidth(), 33), 0,
getPercent(getWidth(), 66), getPercent(getHeight(), 33),
paint
);
private float getPercent(float value, float percent) // draw up right line
{ paint.setColor((
return value / 100 * percent; (direction & DIGITAL_PAD_DIRECTION_UP) > 0 &&
} (direction & DIGITAL_PAD_DIRECTION_RIGHT) > 0
) ? pressedColor : normalColor
);
paint.setStyle(Paint.Style.STROKE);
canvas.drawLine(
getPercent(getWidth(), 66), 0,
getPercent(getWidth(), 100), getPercent(getHeight(), 33),
paint
);
private int getCorrectWidth() // draw right rect
{ paint.setColor(
return getWidth() > getHeight() ? getHeight() : getWidth(); (direction & DIGITAL_PAD_DIRECTION_RIGHT) > 0 ? pressedColor : normalColor);
} paint.setStyle(Paint.Style.FILL_AND_STROKE);
canvas.drawText("RI",
getPercent(getWidth(), 82.5f), getPercent(getHeight(), 56),
paint);
paint.setStyle(Paint.Style.STROKE);
canvas.drawRect(
getPercent(getWidth(), 66), getPercent(getHeight(), 33),
getPercent(getWidth(), 100), getPercent(getHeight(), 66),
paint
);
public void setColors(int normalColor, int pressedColor) // draw right down line
{ paint.setColor((
this.normalColor = normalColor; (direction & DIGITAL_PAD_DIRECTION_RIGHT) > 0 &&
this.pressedColor = pressedColor; (direction & DIGITAL_PAD_DIRECTION_DOWN) > 0
} ) ? pressedColor : normalColor
);
paint.setStyle(Paint.Style.STROKE);
canvas.drawLine(
getPercent(getWidth(), 100), getPercent(getHeight(), 66),
getPercent(getWidth(), 66), getPercent(getHeight(), 100),
paint
);
@Override // draw down rect
protected void onDraw(Canvas canvas) paint.setColor(
{ (direction & DIGITAL_PAD_DIRECTION_DOWN) > 0 ? pressedColor : normalColor);
// set transparent background paint.setStyle(Paint.Style.FILL_AND_STROKE);
canvas.drawColor(Color.TRANSPARENT); canvas.drawText("DW",
getPercent(getWidth(), 49.5f), getPercent(getHeight(), 89),
paint);
paint.setStyle(Paint.Style.STROKE);
canvas.drawRect(
getPercent(getWidth(), 33), getPercent(getHeight(), 66),
getPercent(getWidth(), 66), getPercent(getHeight(), 100),
paint
);
Paint paint = new Paint(); // draw down left line
paint.setColor((
(direction & DIGITAL_PAD_DIRECTION_DOWN) > 0 &&
(direction & DIGITAL_PAD_DIRECTION_LEFT) > 0
) ? pressedColor : normalColor
);
paint.setStyle(Paint.Style.STROKE);
canvas.drawLine(
getPercent(getWidth(), 33), getPercent(getHeight(), 100),
getPercent(getWidth(), 0), getPercent(getHeight(), 66),
paint
);
paint.setTextSize(getPercent(getCorrectWidth(), 20)); super.onDraw(canvas);
paint.setTextAlign(Paint.Align.CENTER); }
paint.setStrokeWidth(3);
if (direction == DIGITAL_PAD_DIRECTION_NO_DIRECTION) private void newDirectionCallback(int direction)
{ {
// draw no direction rect _DBG("direction: " + direction);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(normalColor);
canvas.drawRect(
getPercent(getWidth(), 36), getPercent(getHeight(), 36),
getPercent(getWidth(), 63), getPercent(getHeight(), 63),
paint
);
}
// draw left rect // notify listeners
paint.setColor((direction & DIGITAL_PAD_DIRECTION_LEFT) > 0 ? pressedColor : normalColor); for (DigitalPadListener listener : listeners)
paint.setStyle(Paint.Style.FILL_AND_STROKE); {
canvas.drawText("LF", listener.onDirectionChange(direction);
getPercent(getWidth(), 16.5f), getPercent(getHeight(), 56), }
paint); }
paint.setStyle(Paint.Style.STROKE);
canvas.drawRect(
0, getPercent(getHeight(), 33),
getPercent(getWidth(), 33), getPercent(getHeight(), 66),
paint
);
// draw left up line @Override
paint.setColor(( public boolean onTouchEvent(MotionEvent event)
(direction & DIGITAL_PAD_DIRECTION_LEFT) > 0 && {
(direction & DIGITAL_PAD_DIRECTION_UP) > 0 if (onTouchListener != null)
) ? pressedColor : normalColor {
); return onTouchListener.onTouch(this, event);
paint.setStyle(Paint.Style.STROKE); }
canvas.drawLine(
0, getPercent(getWidth(), 33),
getPercent(getWidth(), 33), 0,
paint
);
// draw up rect // get masked (not specific to a pointer) action
paint.setColor((direction & DIGITAL_PAD_DIRECTION_UP) > 0 ? pressedColor : normalColor); int action = event.getActionMasked();
paint.setStyle(Paint.Style.FILL_AND_STROKE);
canvas.drawText("UP",
getPercent(getWidth(), 49.5f), getPercent(getHeight(), 23),
paint);
paint.setStyle(Paint.Style.STROKE);
canvas.drawRect(
getPercent(getWidth(), 33), 0,
getPercent(getWidth(), 66), getPercent(getHeight(), 33),
paint
);
// draw up right line switch (action)
paint.setColor(( {
(direction & DIGITAL_PAD_DIRECTION_UP) > 0 && case MotionEvent.ACTION_DOWN:
(direction & DIGITAL_PAD_DIRECTION_RIGHT) > 0 case MotionEvent.ACTION_POINTER_DOWN:
) ? pressedColor : normalColor {
); direction = 0;
paint.setStyle(Paint.Style.STROKE);
canvas.drawLine(
getPercent(getWidth(), 66), 0,
getPercent(getWidth(), 100), getPercent(getHeight(), 33),
paint
);
// draw right rect if (event.getX() < getPercent(getWidth(), 33))
paint.setColor((direction & DIGITAL_PAD_DIRECTION_RIGHT) > 0 ? pressedColor : normalColor); {
paint.setStyle(Paint.Style.FILL_AND_STROKE); direction |= DIGITAL_PAD_DIRECTION_LEFT;
canvas.drawText("RI", }
getPercent(getWidth(), 82.5f), getPercent(getHeight(), 56),
paint);
paint.setStyle(Paint.Style.STROKE);
canvas.drawRect(
getPercent(getWidth(), 66), getPercent(getHeight(), 33),
getPercent(getWidth(), 100), getPercent(getHeight(), 66),
paint
);
// draw right down line if (event.getX() > getPercent(getWidth(), 66))
paint.setColor(( {
(direction & DIGITAL_PAD_DIRECTION_RIGHT) > 0 && direction |= DIGITAL_PAD_DIRECTION_RIGHT;
(direction & DIGITAL_PAD_DIRECTION_DOWN) > 0 }
) ? pressedColor : normalColor
);
paint.setStyle(Paint.Style.STROKE);
canvas.drawLine(
getPercent(getWidth(), 100), getPercent(getHeight(), 66),
getPercent(getWidth(), 66), getPercent(getHeight(), 100),
paint
);
// draw down rect if (event.getY() > getPercent(getHeight(), 66))
paint.setColor((direction & DIGITAL_PAD_DIRECTION_DOWN) > 0 ? pressedColor : normalColor); {
paint.setStyle(Paint.Style.FILL_AND_STROKE); direction |= DIGITAL_PAD_DIRECTION_DOWN;
canvas.drawText("DW", }
getPercent(getWidth(), 49.5f), getPercent(getHeight(), 89),
paint);
paint.setStyle(Paint.Style.STROKE);
canvas.drawRect(
getPercent(getWidth(), 33), getPercent(getHeight(), 66),
getPercent(getWidth(), 66), getPercent(getHeight(), 100),
paint
);
// draw down left line if (event.getY() < getPercent(getHeight(), 33))
paint.setColor(( {
(direction & DIGITAL_PAD_DIRECTION_DOWN) > 0 && direction |= DIGITAL_PAD_DIRECTION_UP;
(direction & DIGITAL_PAD_DIRECTION_LEFT) > 0 }
) ? pressedColor : normalColor
);
paint.setStyle(Paint.Style.STROKE);
canvas.drawLine(
getPercent(getWidth(), 33), getPercent(getHeight(), 100),
getPercent(getWidth(), 0), getPercent(getHeight(), 66),
paint
);
super.onDraw(canvas); newDirectionCallback(direction);
}
private void newDirectionCallback(int direction) invalidate();
{
_DBG("direction: " + direction);
// notify listeners return true;
for (DigitalPadListener listener : listeners) }
{ case MotionEvent.ACTION_CANCEL:
listener.onDirectionChange(direction); case MotionEvent.ACTION_UP:
} case MotionEvent.ACTION_POINTER_UP:
} {
direction = 0;
@Override newDirectionCallback(direction);
public boolean onTouchEvent(MotionEvent event)
{
if (onTouchListener != null)
{
return onTouchListener.onTouch(this, event);
}
// get masked (not specific to a pointer) action invalidate();
int action = event.getActionMasked();
switch (action) return true;
{ }
case MotionEvent.ACTION_DOWN: default:
case MotionEvent.ACTION_POINTER_DOWN: {
{ }
direction = 0; }
if (event.getX() < getPercent(getWidth(), 33)) return true;
{ }
direction |= DIGITAL_PAD_DIRECTION_LEFT;
}
if (event.getX() > getPercent(getWidth(), 66)) public interface DigitalPadListener
{ {
direction |= DIGITAL_PAD_DIRECTION_RIGHT; void onDirectionChange(int direction);
} }
if (event.getY() > getPercent(getHeight(), 66))
{
direction |= DIGITAL_PAD_DIRECTION_DOWN;
}
if (event.getY() < getPercent(getHeight(), 33))
{
direction |= DIGITAL_PAD_DIRECTION_UP;
}
newDirectionCallback(direction);
invalidate();
return true;
}
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
{
direction = 0;
newDirectionCallback(direction);
invalidate();
return true;
}
default:
{
}
}
return true;
}
} }

View File

@@ -2,13 +2,9 @@ package com.limelight.binding.input.virtual_controller;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.view.MenuInflater;
import android.view.View; import android.view.View;
import android.view.WindowManager;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.PopupMenu;
import android.widget.RelativeLayout; import android.widget.RelativeLayout;
import android.widget.Toast;
import com.limelight.R; import com.limelight.R;
import com.limelight.nvstream.NvConnection; import com.limelight.nvstream.NvConnection;
@@ -19,183 +15,63 @@ import com.limelight.nvstream.input.ControllerPacket;
*/ */
public class VirtualController public class VirtualController
{ {
private static final boolean _PRINT_DEBUG_INFORMATION = false; private static final boolean _PRINT_DEBUG_INFORMATION = false;
NvConnection connection = null;
private Context context = null;
private short inputMap = 0x0000;
private byte leftTrigger = 0x00;
private byte rightTrigger = 0x00;
private short rightStickX = 0x0000;
private short rightStickY = 0x0000;
private short leftStickX = 0x0000;
private short leftStickY = 0x0000;
private static final void _DBG(String text) private FrameLayout frame_layout = null;
{ private RelativeLayout relative_layout = null;
if (_PRINT_DEBUG_INFORMATION)
{
System.out.println("VirtualController: " + text);
}
}
private short inputMap = 0x0000; private RelativeLayout.LayoutParams layoutParamsButtonStart = null;
private byte leftTrigger = 0x00; private RelativeLayout.LayoutParams layoutParamsButtonSelect = null;
private byte rightTrigger = 0x00;
private short rightStickX = 0x0000;
private short rightStickY = 0x0000;
private short leftStickX = 0x0000;
private short leftStickY = 0x0000;
private FrameLayout frame_layout = null; private RelativeLayout.LayoutParams layoutParamsDPad = null;
private RelativeLayout relative_layout = null;
private RelativeLayout.LayoutParams layoutParamsButtonStart = null; private RelativeLayout.LayoutParams layoutParamsButtonA = null;
private RelativeLayout.LayoutParams layoutParamsButtonSelect = null; private RelativeLayout.LayoutParams layoutParamsButtonB = null;
private RelativeLayout.LayoutParams layoutParamsButtonX = null;
private RelativeLayout.LayoutParams layoutParamsButtonY = null;
private RelativeLayout.LayoutParams layoutParamsButtonLT = null;
private RelativeLayout.LayoutParams layoutParamsButtonRT = null;
private RelativeLayout.LayoutParams layoutParamsButtonLB = null;
private RelativeLayout.LayoutParams layoutParamsButtonRB = null;
private RelativeLayout.LayoutParams layoutParamsDPad = null; private RelativeLayout.LayoutParams layoutParamsStick = null;
private RelativeLayout.LayoutParams layoutParamsStick2 = null;
private RelativeLayout.LayoutParams layoutParamsButtonA = null; private RelativeLayout.LayoutParams layoutParamsButtonConfigure = null;
private RelativeLayout.LayoutParams layoutParamsButtonB = null;
private RelativeLayout.LayoutParams layoutParamsButtonX = null;
private RelativeLayout.LayoutParams layoutParamsButtonY = null;
private RelativeLayout.LayoutParams layoutParamsButtonLT = null;
private RelativeLayout.LayoutParams layoutParamsButtonRT = null;
private RelativeLayout.LayoutParams layoutParamsButtonLB = null;
private RelativeLayout.LayoutParams layoutParamsButtonRB = null;
private RelativeLayout.LayoutParams layoutParamsStick = null; private DigitalButton buttonStart = null;
private RelativeLayout.LayoutParams layoutParamsStick2 = null; private DigitalButton buttonSelect = null;
private RelativeLayout.LayoutParams layoutParamsButtonConfigure = null; private DigitalPad digitalPad = null;
private DigitalButton buttonStart = null; private DigitalButton buttonA = null;
private DigitalButton buttonSelect = null; private DigitalButton buttonB = null;
private DigitalButton buttonX = null;
private DigitalButton buttonY = null;
private DigitalButton buttonLT = null;
private DigitalButton buttonRT = null;
private DigitalButton buttonLB = null;
private DigitalButton buttonRB = null;
private DigitalPad digitalPad = null; private AnalogStick stick = null;
private AnalogStick stick2 = null;
private DigitalButton buttonA = null; private DigitalButton buttonConfigure = null;
private DigitalButton buttonB = null;
private DigitalButton buttonX = null;
private DigitalButton buttonY = null;
private DigitalButton buttonLT = null;
private DigitalButton buttonRT = null;
private DigitalButton buttonLB = null;
private DigitalButton buttonRB = null;
private AnalogStick stick = null;
private AnalogStick stick2 = null;
private DigitalButton buttonConfigure = null;
NvConnection connection = null;
private int getPercentageV(int percent)
{
return (int)(((float)frame_layout.getHeight() / (float)100) * (float)percent);
}
private int getPercentageH(int percent)
{
return (int)(((float)frame_layout.getWidth() / (float)100) * (float)percent);
}
private void setPercentilePosition(RelativeLayout.LayoutParams parm, float pos_x, float pos_y)
{
parm.setMargins(
(int)(((float)frame_layout.getWidth() / (float)100 * pos_x) - ((float)parm.width / (float)2)),
(int)(((float)frame_layout.getHeight() / (float)100 * pos_y) - ((float)parm.height / (float)2)),
0,
0
);
}
void refreshLayout()
{
relative_layout.removeAllViews();
layoutParamsDPad = new RelativeLayout.LayoutParams(getPercentageV(30), getPercentageV(30));
layoutParamsStick = new RelativeLayout.LayoutParams(getPercentageV(40), getPercentageV(40));
layoutParamsStick2 = new RelativeLayout.LayoutParams(getPercentageV(40), getPercentageV(40));
layoutParamsButtonA = new RelativeLayout.LayoutParams(getPercentageV(10), getPercentageV(10));
layoutParamsButtonB = new RelativeLayout.LayoutParams(getPercentageV(10), getPercentageV(10));
layoutParamsButtonX = new RelativeLayout.LayoutParams(getPercentageV(10), getPercentageV(10));
layoutParamsButtonY = new RelativeLayout.LayoutParams(getPercentageV(10), getPercentageV(10));
layoutParamsButtonLT = new RelativeLayout.LayoutParams(getPercentageV(10), getPercentageV(10));
layoutParamsButtonRT = new RelativeLayout.LayoutParams(getPercentageV(10), getPercentageV(10));
layoutParamsButtonLB = new RelativeLayout.LayoutParams(getPercentageV(10), getPercentageV(10));
layoutParamsButtonRB = new RelativeLayout.LayoutParams(getPercentageV(10), getPercentageV(10));
layoutParamsButtonStart = new RelativeLayout.LayoutParams(getPercentageH(12), getPercentageV(8));
layoutParamsButtonSelect = new RelativeLayout.LayoutParams(getPercentageH(12), getPercentageV(8));
layoutParamsButtonConfigure = new RelativeLayout.LayoutParams(getPercentageV(10), getPercentageV(10));
setPercentilePosition(layoutParamsDPad, 10, 35);
setPercentilePosition(layoutParamsStick, 22, 78);
setPercentilePosition(layoutParamsStick2, 78, 78);
setPercentilePosition(layoutParamsButtonA, 85, 52);
setPercentilePosition(layoutParamsButtonB, 92, 47);
setPercentilePosition(layoutParamsButtonX, 85, 40);
setPercentilePosition(layoutParamsButtonY, 92, 35);
setPercentilePosition(layoutParamsButtonLT, 95, 68);
setPercentilePosition(layoutParamsButtonRT, 95, 80);
setPercentilePosition(layoutParamsButtonLB, 85, 28);
setPercentilePosition(layoutParamsButtonRB, 92, 23);
setPercentilePosition(layoutParamsButtonSelect, 43, 94);
setPercentilePosition(layoutParamsButtonStart, 57, 94);
setPercentilePosition(layoutParamsButtonConfigure, 93, 7);
relative_layout.addView(digitalPad, layoutParamsDPad);
relative_layout.addView(stick, layoutParamsStick);
relative_layout.addView(stick2, layoutParamsStick2);
relative_layout.addView(buttonA, layoutParamsButtonA);
relative_layout.addView(buttonB, layoutParamsButtonB);
relative_layout.addView(buttonX, layoutParamsButtonX);
relative_layout.addView(buttonY, layoutParamsButtonY);
relative_layout.addView(buttonLT, layoutParamsButtonLT);
relative_layout.addView(buttonRT, layoutParamsButtonRT);
relative_layout.addView(buttonLB, layoutParamsButtonLB);
relative_layout.addView(buttonRB, layoutParamsButtonRB);
relative_layout.addView(buttonSelect, layoutParamsButtonSelect);
relative_layout.addView(buttonStart, layoutParamsButtonStart);
relative_layout.addView(buttonConfigure, layoutParamsButtonConfigure);
}
private DigitalButton createDigitalButton(String text, final int key, Context context)
{
DigitalButton button = new DigitalButton(context);
button.setText(text);
button.addDigitalButtonListener(new DigitalButton.DigitalButtonListener() {
@Override
public void onClick() {
inputMap |= key;
sendControllerInputPacket();
}
@Override
public void onLongClick()
{
}
@Override
public void onRelease() {
inputMap &= ~key;
sendControllerInputPacket();
}
});
return button;
}
public VirtualController(final NvConnection conn, FrameLayout layout, final Context context) public VirtualController(final NvConnection conn, FrameLayout layout, final Context context)
{ {
this.connection = conn; this.connection = conn;
frame_layout = layout; this.frame_layout = layout;
this.context = context;
relative_layout = new RelativeLayout(context); relative_layout = new RelativeLayout(context);
@@ -210,113 +86,113 @@ public class VirtualController
frame_layout.addView(relative_layout); frame_layout.addView(relative_layout);
digitalPad = new DigitalPad(context); digitalPad = new DigitalPad(context);
digitalPad.addDigitalPadListener(new DigitalPad.DigitalPadListener() digitalPad.addDigitalPadListener(new DigitalPad.DigitalPadListener()
{ {
@Override @Override
public void onDirectionChange(int direction) public void onDirectionChange(int direction)
{ {
do do
{ {
if (direction == DigitalPad.DIGITAL_PAD_DIRECTION_NO_DIRECTION) if (direction == DigitalPad.DIGITAL_PAD_DIRECTION_NO_DIRECTION)
{ {
inputMap &= ~ControllerPacket.LEFT_FLAG; inputMap &= ~ControllerPacket.LEFT_FLAG;
inputMap &= ~ControllerPacket.RIGHT_FLAG; inputMap &= ~ControllerPacket.RIGHT_FLAG;
inputMap &= ~ControllerPacket.UP_FLAG; inputMap &= ~ControllerPacket.UP_FLAG;
inputMap &= ~ControllerPacket.DOWN_FLAG; inputMap &= ~ControllerPacket.DOWN_FLAG;
break; break;
} }
if ((direction & DigitalPad.DIGITAL_PAD_DIRECTION_LEFT) > 0) if ((direction & DigitalPad.DIGITAL_PAD_DIRECTION_LEFT) > 0)
{ {
inputMap |= ControllerPacket.LEFT_FLAG; inputMap |= ControllerPacket.LEFT_FLAG;
} }
if ((direction & DigitalPad.DIGITAL_PAD_DIRECTION_RIGHT) > 0) if ((direction & DigitalPad.DIGITAL_PAD_DIRECTION_RIGHT) > 0)
{ {
inputMap |= ControllerPacket.RIGHT_FLAG; inputMap |= ControllerPacket.RIGHT_FLAG;
} }
if ((direction & DigitalPad.DIGITAL_PAD_DIRECTION_UP) > 0) if ((direction & DigitalPad.DIGITAL_PAD_DIRECTION_UP) > 0)
{ {
inputMap |= ControllerPacket.UP_FLAG; inputMap |= ControllerPacket.UP_FLAG;
} }
if ((direction & DigitalPad.DIGITAL_PAD_DIRECTION_DOWN) > 0) if ((direction & DigitalPad.DIGITAL_PAD_DIRECTION_DOWN) > 0)
{ {
inputMap |= ControllerPacket.DOWN_FLAG; inputMap |= ControllerPacket.DOWN_FLAG;
} }
} }
while (false); while (false);
sendControllerInputPacket(); sendControllerInputPacket();
} }
}); });
buttonX = createDigitalButton("X", ControllerPacket.X_FLAG ,context); buttonX = createDigitalButton("X", ControllerPacket.X_FLAG, context);
buttonY = createDigitalButton("Y", ControllerPacket.Y_FLAG ,context); buttonY = createDigitalButton("Y", ControllerPacket.Y_FLAG, context);
buttonA = createDigitalButton("A", ControllerPacket.A_FLAG ,context); buttonA = createDigitalButton("A", ControllerPacket.A_FLAG, context);
buttonB = createDigitalButton("B", ControllerPacket.B_FLAG ,context); buttonB = createDigitalButton("B", ControllerPacket.B_FLAG, context);
buttonLT = new DigitalButton(context); buttonLT = new DigitalButton(context);
buttonLT.setText("LT"); buttonLT.setText("LT");
buttonLT.addDigitalButtonListener(new DigitalButton.DigitalButtonListener() buttonLT.addDigitalButtonListener(new DigitalButton.DigitalButtonListener()
{ {
@Override @Override
public void onClick() public void onClick()
{ {
leftTrigger = (byte) (1 * 0xFF); leftTrigger = (byte) (1 * 0xFF);
sendControllerInputPacket(); sendControllerInputPacket();
} }
@Override @Override
public void onLongClick() public void onLongClick()
{ {
} }
@Override @Override
public void onRelease() public void onRelease()
{ {
leftTrigger = (byte) (0 * 0xFF); leftTrigger = (byte) (0 * 0xFF);
sendControllerInputPacket(); sendControllerInputPacket();
} }
}); });
buttonRT = new DigitalButton(context); buttonRT = new DigitalButton(context);
buttonRT.setText("RT"); buttonRT.setText("RT");
buttonRT.addDigitalButtonListener(new DigitalButton.DigitalButtonListener() buttonRT.addDigitalButtonListener(new DigitalButton.DigitalButtonListener()
{ {
@Override @Override
public void onClick() public void onClick()
{ {
rightTrigger = (byte) (0xFF); rightTrigger = (byte) (0xFF);
sendControllerInputPacket(); sendControllerInputPacket();
} }
@Override @Override
public void onLongClick() public void onLongClick()
{ {
} }
@Override @Override
public void onRelease() public void onRelease()
{ {
rightTrigger = (byte) (0); rightTrigger = (byte) (0);
sendControllerInputPacket(); sendControllerInputPacket();
} }
}); });
buttonLB = createDigitalButton("LB", ControllerPacket.LB_FLAG ,context); buttonLB = createDigitalButton("LB", ControllerPacket.LB_FLAG, context);
buttonRB = createDigitalButton("RB", ControllerPacket.RB_FLAG ,context); buttonRB = createDigitalButton("RB", ControllerPacket.RB_FLAG, context);
stick = new AnalogStick(context); stick = new AnalogStick(context);
stick.addAnalogStickListener(new AnalogStick.AnalogStickListener() stick.addAnalogStickListener(new AnalogStick.AnalogStickListener()
{ {
@@ -326,30 +202,30 @@ public class VirtualController
leftStickX = (short) (x * 0x7FFE); leftStickX = (short) (x * 0x7FFE);
leftStickY = (short) (y * 0x7FFE); leftStickY = (short) (y * 0x7FFE);
_DBG("LEFT STICK MOVEMENT X: "+ leftStickX + " Y: " + leftStickY); _DBG("LEFT STICK MOVEMENT X: " + leftStickX + " Y: " + leftStickY);
sendControllerInputPacket(); sendControllerInputPacket();
} }
@Override @Override
public void onClick() public void onClick()
{ {
} }
@Override @Override
public void onDoubleClick() public void onDoubleClick()
{ {
inputMap |= ControllerPacket.LS_CLK_FLAG; inputMap |= ControllerPacket.LS_CLK_FLAG;
sendControllerInputPacket(); sendControllerInputPacket();
} }
@Override @Override
public void onRevoke() public void onRevoke()
{ {
inputMap &= ~ControllerPacket.LS_CLK_FLAG; inputMap &= ~ControllerPacket.LS_CLK_FLAG;
sendControllerInputPacket(); sendControllerInputPacket();
} }
}); });
stick2 = new AnalogStick(context); stick2 = new AnalogStick(context);
@@ -361,83 +237,229 @@ public class VirtualController
rightStickX = (short) (x * 0x7FFE); rightStickX = (short) (x * 0x7FFE);
rightStickY = (short) (y * 0x7FFE); rightStickY = (short) (y * 0x7FFE);
_DBG("RIGHT STICK MOVEMENT X: "+ rightStickX + " Y: " + rightStickY); _DBG("RIGHT STICK MOVEMENT X: " + rightStickX + " Y: " + rightStickY);
sendControllerInputPacket(); sendControllerInputPacket();
} }
@Override @Override
public void onClick() public void onClick()
{ {
} }
@Override @Override
public void onDoubleClick() public void onDoubleClick()
{ {
inputMap |= ControllerPacket.RS_CLK_FLAG; inputMap |= ControllerPacket.RS_CLK_FLAG;
sendControllerInputPacket(); sendControllerInputPacket();
} }
@Override @Override
public void onRevoke() public void onRevoke()
{ {
inputMap &= ~ControllerPacket.RS_CLK_FLAG; inputMap &= ~ControllerPacket.RS_CLK_FLAG;
sendControllerInputPacket(); sendControllerInputPacket();
} }
}); });
buttonStart = createDigitalButton("START", ControllerPacket.PLAY_FLAG, context); buttonStart = createDigitalButton("START", ControllerPacket.PLAY_FLAG, context);
buttonSelect = createDigitalButton("SELECT", ControllerPacket.SPECIAL_BUTTON_FLAG, context); buttonSelect =
createDigitalButton("SELECT", ControllerPacket.SPECIAL_BUTTON_FLAG, context);
buttonConfigure = new DigitalButton(context); buttonConfigure = new DigitalButton(context);
buttonConfigure.setIcon(R.drawable.settings); buttonConfigure.setIcon(R.drawable.settings);
buttonConfigure.addDigitalButtonListener(new DigitalButton.DigitalButtonListener() buttonConfigure.addDigitalButtonListener(new DigitalButton.DigitalButtonListener()
{ {
@Override @Override
public void onClick() { public void onClick()
{
} }
@Override @Override
public void onLongClick() public void onLongClick()
{ {
Intent virtualControllerConfiguration = new Intent(context,VirtualControllerSettings.class); openSettingsDialog();
}
virtualControllerConfiguration.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); @Override
public void onRelease()
{
context.startActivity(virtualControllerConfiguration); }
});
}
@Override
public void onRelease() {
}
});
refreshLayout(); refreshLayout();
} }
private static final void _DBG(String text)
{
if (_PRINT_DEBUG_INFORMATION)
{
System.out.println("VirtualController: " + text);
}
}
private int getPercentageV(int percent)
{
return (int) (((float) frame_layout.getHeight() / (float) 100) * (float) percent);
}
private int getPercentageH(int percent)
{
return (int) (((float) frame_layout.getWidth() / (float) 100) * (float) percent);
}
private void setPercentilePosition(RelativeLayout.LayoutParams parm, float pos_x, float pos_y)
{
parm.setMargins(
(int) (((float) frame_layout.getWidth() / (float) 100 * pos_x) - ((float) parm.width / (float) 2)),
(int) (((float) frame_layout.getHeight() / (float) 100 * pos_y) - ((float) parm.height / (float) 2)),
0,
0
);
}
public void openSettingsDialog()
{
Intent virtualControllerConfiguration =
new Intent(context, VirtualControllerSettings.class);
virtualControllerConfiguration.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(virtualControllerConfiguration);
}
void refreshLayout()
{
relative_layout.removeAllViews();
layoutParamsDPad =
new RelativeLayout.LayoutParams(getPercentageV(30), getPercentageV(30));
layoutParamsStick =
new RelativeLayout.LayoutParams(getPercentageV(40), getPercentageV(40));
layoutParamsStick2 =
new RelativeLayout.LayoutParams(getPercentageV(40), getPercentageV(40));
layoutParamsButtonA =
new RelativeLayout.LayoutParams(getPercentageV(10), getPercentageV(10));
layoutParamsButtonB =
new RelativeLayout.LayoutParams(getPercentageV(10), getPercentageV(10));
layoutParamsButtonX =
new RelativeLayout.LayoutParams(getPercentageV(10), getPercentageV(10));
layoutParamsButtonY =
new RelativeLayout.LayoutParams(getPercentageV(10), getPercentageV(10));
layoutParamsButtonLT =
new RelativeLayout.LayoutParams(getPercentageV(10), getPercentageV(10));
layoutParamsButtonRT =
new RelativeLayout.LayoutParams(getPercentageV(10), getPercentageV(10));
layoutParamsButtonLB =
new RelativeLayout.LayoutParams(getPercentageV(10), getPercentageV(10));
layoutParamsButtonRB =
new RelativeLayout.LayoutParams(getPercentageV(10), getPercentageV(10));
layoutParamsButtonStart =
new RelativeLayout.LayoutParams(getPercentageH(12), getPercentageV(8));
layoutParamsButtonSelect =
new RelativeLayout.LayoutParams(getPercentageH(12), getPercentageV(8));
layoutParamsButtonConfigure =
new RelativeLayout.LayoutParams(getPercentageV(10), getPercentageV(10));
setPercentilePosition(layoutParamsDPad, 10, 35);
setPercentilePosition(layoutParamsStick, 22, 78);
setPercentilePosition(layoutParamsStick2, 78, 78);
setPercentilePosition(layoutParamsButtonA, 85, 52);
setPercentilePosition(layoutParamsButtonB, 92, 47);
setPercentilePosition(layoutParamsButtonX, 85, 40);
setPercentilePosition(layoutParamsButtonY, 92, 35);
setPercentilePosition(layoutParamsButtonLT, 95, 68);
setPercentilePosition(layoutParamsButtonRT, 95, 80);
setPercentilePosition(layoutParamsButtonLB, 85, 28);
setPercentilePosition(layoutParamsButtonRB, 92, 23);
setPercentilePosition(layoutParamsButtonSelect, 43, 94);
setPercentilePosition(layoutParamsButtonStart, 57, 94);
setPercentilePosition(layoutParamsButtonConfigure, 93, 7);
relative_layout.addView(digitalPad, layoutParamsDPad);
relative_layout.addView(stick, layoutParamsStick);
relative_layout.addView(stick2, layoutParamsStick2);
relative_layout.addView(buttonA, layoutParamsButtonA);
relative_layout.addView(buttonB, layoutParamsButtonB);
relative_layout.addView(buttonX, layoutParamsButtonX);
relative_layout.addView(buttonY, layoutParamsButtonY);
relative_layout.addView(buttonLT, layoutParamsButtonLT);
relative_layout.addView(buttonRT, layoutParamsButtonRT);
relative_layout.addView(buttonLB, layoutParamsButtonLB);
relative_layout.addView(buttonRB, layoutParamsButtonRB);
relative_layout.addView(buttonSelect, layoutParamsButtonSelect);
relative_layout.addView(buttonStart, layoutParamsButtonStart);
relative_layout.addView(buttonConfigure, layoutParamsButtonConfigure);
}
private DigitalButton createDigitalButton(String text, final int key, Context context)
{
DigitalButton button = new DigitalButton(context);
button.setText(text);
button.addDigitalButtonListener(new DigitalButton.DigitalButtonListener()
{
@Override
public void onClick()
{
inputMap |= key;
sendControllerInputPacket();
}
@Override
public void onLongClick()
{
}
@Override
public void onRelease()
{
inputMap &= ~key;
sendControllerInputPacket();
}
});
return button;
}
private void sendControllerInputPacket() private void sendControllerInputPacket()
{ {
try { try
_DBG("INPUT_MAP + " + inputMap); {
_DBG("LEFT_TRIGGER " + leftTrigger); _DBG("INPUT_MAP + " + inputMap);
_DBG("RIGHT_TRIGGER " + rightTrigger); _DBG("LEFT_TRIGGER " + leftTrigger);
_DBG("LEFT STICK X: " + leftStickX + " Y: " + leftStickY); _DBG("RIGHT_TRIGGER " + rightTrigger);
_DBG("RIGHT STICK X: " + rightStickX + " Y: " + rightStickY); _DBG("LEFT STICK X: " + leftStickX + " Y: " + leftStickY);
_DBG("RIGHT STICK X: " + rightStickX + " Y: " + rightStickY); _DBG("RIGHT STICK X: " + rightStickX + " Y: " + rightStickY);
_DBG("RIGHT STICK X: " + rightStickX + " Y: " + rightStickY);
if (connection != null) if (connection != null)
{ {
connection.sendControllerInput(inputMap, leftTrigger, rightTrigger, connection.sendControllerInput(inputMap, leftTrigger, rightTrigger,
leftStickX, leftStickY, rightStickX, rightStickY); leftStickX, leftStickY, rightStickX, rightStickY);
} }
} }
catch (Exception e) catch (Exception e)
{ {
e.printStackTrace(); e.printStackTrace();
} }
} }
} }

View File

@@ -1,9 +1,7 @@
package com.limelight.binding.input.virtual_controller; package com.limelight.binding.input.virtual_controller;
import android.app.Activity; import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle; import android.os.Bundle;
import android.os.PersistableBundle;
import android.view.Window; import android.view.Window;
import android.view.WindowManager; import android.view.WindowManager;
import android.widget.FrameLayout; import android.widget.FrameLayout;
@@ -16,29 +14,30 @@ import com.limelight.R;
*/ */
public class VirtualControllerConfiguration extends Activity public class VirtualControllerConfiguration extends Activity
{ {
VirtualController virtualController; VirtualController virtualController;
@Override @Override
protected void onCreate(Bundle savedInstanceState) protected void onCreate(Bundle savedInstanceState)
{ {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
// We don't want a title bar // We don't want a title bar
requestWindowFeature(Window.FEATURE_NO_TITLE); requestWindowFeature(Window.FEATURE_NO_TITLE);
// Full-screen and don't let the display go off // Full-screen and don't let the display go off
getWindow().addFlags( getWindow().addFlags(
WindowManager.LayoutParams.FLAG_FULLSCREEN | WindowManager.LayoutParams.FLAG_FULLSCREEN |
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
// Inflate the content // Inflate the content
setContentView(R.layout.activity_configure_virtual_controller); setContentView(R.layout.activity_configure_virtual_controller);
FrameLayout frameLayout = (FrameLayout) findViewById(R.id.configure_virtual_controller_frameLayout); FrameLayout frameLayout =
(FrameLayout) findViewById(R.id.configure_virtual_controller_frameLayout);
// start with configuration constructor // start with configuration constructor
virtualController = new VirtualController(null, frameLayout, this); virtualController = new VirtualController(null, frameLayout, this);
Toast.makeText(getApplicationContext(), "Not implemented yet!", Toast.LENGTH_SHORT).show(); Toast.makeText(getApplicationContext(), "Not implemented yet!", Toast.LENGTH_SHORT).show();
} }
} }

View File

@@ -0,0 +1,45 @@
package com.limelight.binding.input.virtual_controller;
import android.content.Context;
import android.view.View;
/**
* Created by Karim on 27.01.2015.
*/
public abstract class VirtualControllerElement extends View
{
protected static boolean _PRINT_DEBUG_INFORMATION = false;
protected int normalColor = 0xF0888888;
protected int pressedColor = 0xF00000FF;
protected VirtualControllerElement(Context context)
{
super(context);
}
protected static final void _DBG(String text)
{
if (_PRINT_DEBUG_INFORMATION)
{
System.out.println("DigitalButton: " + text);
}
}
public void setColors(int normalColor, int pressedColor)
{
this.normalColor = normalColor;
this.pressedColor = pressedColor;
invalidate();
}
protected final float getPercent(float value, float percent)
{
return value / 100 * percent;
}
protected final int getCorrectWidth()
{
return getWidth() > getHeight() ? getHeight() : getWidth();
}
}

View File

@@ -2,46 +2,28 @@ package com.limelight.binding.input.virtual_controller;
import android.app.Activity; import android.app.Activity;
import android.os.Bundle; import android.os.Bundle;
import android.view.MenuInflater;
import android.view.View;
import android.view.Window; import android.view.Window;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.PopupMenu;
import android.widget.Toast; import android.widget.Toast;
import com.limelight.R; import com.limelight.R;
import org.apache.http.util.VersionInfo;
/** /**
* Created by Karim on 26.01.2015. * Created by Karim on 26.01.2015.
*/ */
public class VirtualControllerSettings extends Activity public class VirtualControllerSettings extends Activity
{ {
private static VirtualController controller = null; private VirtualController controller = null;
private static View view = null;
static void setController(VirtualController value) @Override
{ protected void onCreate(Bundle savedInstanceState)
controller = value; {
} super.onCreate(savedInstanceState);
// We don't want a title bar
requestWindowFeature(Window.FEATURE_NO_TITLE);
static void setView(View value) // Inflate the content
{ setContentView(R.layout.activity_virtual_controller_settings);
view = value;
}
@Override Toast.makeText(getApplicationContext(), "Not implemented yet!", Toast.LENGTH_SHORT).show();
protected void onCreate(Bundle savedInstanceState) }
{
super.onCreate(savedInstanceState);
// We don't want a title bar
requestWindowFeature(Window.FEATURE_NO_TITLE);
// Inflate the content
setContentView(R.layout.activity_virtual_controller_settings);
Toast.makeText(getApplicationContext(), "Not implemented yet!", Toast.LENGTH_SHORT).show();
}
} }

View File

@@ -251,6 +251,11 @@ public class ComputerManagerService extends Service {
((!details.name.isEmpty() && !tuple.computer.name.isEmpty()) && ((!details.name.isEmpty() && !tuple.computer.name.isEmpty()) &&
tuple.computer.name.equals(details.name))) { tuple.computer.name.equals(details.name))) {
// Update details anyway in case this machine has been re-added by IP
// after not being reachable by our existing information
tuple.computer.localIp = details.localIp;
tuple.computer.remoteIp = details.remoteIp;
// Start a polling thread if polling is active // Start a polling thread if polling is active
if (pollingActive && tuple.thread == null) { if (pollingActive && tuple.thread == null) {
tuple.thread = createPollingThread(details); tuple.thread = createPollingThread(details);

View File

@@ -1,16 +1,30 @@
package com.limelight.grid; package com.limelight.grid;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import com.koushikdutta.async.future.FutureCallback; import com.koushikdutta.async.future.FutureCallback;
import com.koushikdutta.ion.ImageViewBitmapInfo;
import com.koushikdutta.ion.Ion; import com.koushikdutta.ion.Ion;
import com.koushikdutta.ion.bitmap.BitmapInfo;
import com.limelight.AppView; import com.limelight.AppView;
import com.limelight.LimeLog;
import com.limelight.R; import com.limelight.R;
import com.limelight.binding.PlatformBinding; import com.limelight.binding.PlatformBinding;
import com.limelight.nvstream.http.LimelightCryptoProvider; import com.limelight.nvstream.http.LimelightCryptoProvider;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.Socket; import java.net.Socket;
import java.security.KeyManagementException; import java.security.KeyManagementException;
@@ -32,14 +46,15 @@ import java.security.cert.X509Certificate;
public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> { public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
private boolean listMode;
private InetAddress address; private InetAddress address;
private String uniqueId; private String uniqueId;
private LimelightCryptoProvider cryptoProvider; private LimelightCryptoProvider cryptoProvider;
private SSLContext sslContext; private SSLContext sslContext;
private final HashMap<ImageView, Future> pendingRequests = new HashMap<ImageView, Future>(); private final HashMap<ImageView, Future> pendingRequests = new HashMap<ImageView, Future>();
public AppGridAdapter(Context context, InetAddress address, String uniqueId) throws NoSuchAlgorithmException, KeyManagementException { public AppGridAdapter(Context context, boolean listMode, InetAddress address, String uniqueId) throws NoSuchAlgorithmException, KeyManagementException {
super(context, R.layout.app_grid_item, R.drawable.image_loading); super(context, listMode ? R.layout.simple_row : R.layout.app_grid_item, R.drawable.image_loading);
this.address = address; this.address = address;
this.uniqueId = uniqueId; this.uniqueId = uniqueId;
@@ -107,7 +122,9 @@ public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
} }
for (Future f : tempMap.values()) { for (Future f : tempMap.values()) {
f.cancel(true); if (!f.isCancelled() && !f.isDone()) {
f.cancel(true);
}
} }
synchronized (pendingRequests) { synchronized (pendingRequests) {
@@ -118,28 +135,92 @@ public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
} }
} }
private Bitmap checkBitmapCache(String addrStr, int appId) {
File addrFolder = new File(context.getCacheDir(), addrStr);
if (addrFolder.isDirectory()) {
File bitmapFile = new File(addrFolder, appId+".png");
if (bitmapFile.exists()) {
InputStream fileIn = null;
try {
fileIn = new BufferedInputStream(new FileInputStream(bitmapFile));
Bitmap bm = BitmapFactory.decodeStream(fileIn);
if (bm == null) {
// The image seems corrupt
bitmapFile.delete();
}
return bm;
} catch (IOException e) {
e.printStackTrace();
bitmapFile.delete();
} finally {
if (fileIn != null) {
try {
fileIn.close();
} catch (IOException ignored) {}
}
}
}
}
return null;
}
// TODO: Handle pruning of bitmap cache
private void populateBitmapCache(String addrStr, int appId, Bitmap bitmap) {
File addrFolder = new File(context.getCacheDir(), addrStr);
addrFolder.mkdirs();
File bitmapFile = new File(addrFolder, appId+".png");
try {
// PNG ignores quality setting
bitmap.compress(Bitmap.CompressFormat.PNG, 0, new FileOutputStream(bitmapFile));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
@Override @Override
public boolean populateImageView(final ImageView imgView, AppView.AppObject obj) { public boolean populateImageView(final ImageView imgView, final AppView.AppObject obj) {
// Set SSL contexts correctly to allow us to authenticate // Set SSL contexts correctly to allow us to authenticate
Ion.getDefault(imgView.getContext()).getHttpClient().getSSLSocketMiddleware().setTrustManagers(trustAllCerts); Ion.getDefault(imgView.getContext()).getHttpClient().getSSLSocketMiddleware().setTrustManagers(trustAllCerts);
Ion.getDefault(imgView.getContext()).getHttpClient().getSSLSocketMiddleware().setSSLContext(sslContext); Ion.getDefault(imgView.getContext()).getHttpClient().getSSLSocketMiddleware().setSSLContext(sslContext);
// Set off the deferred image load // Check the on-disk cache
Bitmap cachedBitmap = checkBitmapCache(address.getHostAddress(), obj.app.getAppId());
if (cachedBitmap != null) {
// Cache hit; we're done
LimeLog.info("Image cache hit for ("+address.getHostAddress()+", "+obj.app.getAppId()+")");
imgView.setImageBitmap(cachedBitmap);
return true;
}
// Kick off the deferred image load
synchronized (pendingRequests) { synchronized (pendingRequests) {
Future f = Ion.with(imgView) Future<ImageViewBitmapInfo> f = Ion.with(imgView)
.placeholder(defaultImageRes) .placeholder(defaultImageRes)
.error(defaultImageRes) .error(defaultImageRes)
.load("https://" + address.getHostAddress() + ":47984/appasset?uniqueid=" + uniqueId + "&appid=" + .load("https://" + address.getHostAddress() + ":47984/appasset?uniqueid=" + uniqueId + "&appid=" +
obj.app.getAppId() + "&AssetType=2&AssetIdx=0") obj.app.getAppId() + "&AssetType=2&AssetIdx=0")
.setCallback(new FutureCallback<ImageView>() { .withBitmapInfo()
@Override .setCallback(
public void onCompleted(Exception e, ImageView result) { new FutureCallback<ImageViewBitmapInfo>() {
synchronized (pendingRequests) { @Override
pendingRequests.remove(imgView); public void onCompleted(Exception e, ImageViewBitmapInfo result) {
} synchronized (pendingRequests) {
} pendingRequests.remove(imgView);
}); }
// Populate the cache if we got an image back
if (result != null &&
result.getBitmapInfo() != null &&
result.getBitmapInfo().bitmap != null) {
populateBitmapCache(address.getHostAddress(), obj.app.getAppId(),
result.getBitmapInfo().bitmap);
}
}
});
pendingRequests.put(imgView, f); pendingRequests.put(imgView, f);
} }

View File

@@ -60,17 +60,21 @@ public abstract class GenericGridAdapter<T> extends BaseAdapter {
ImageView overlayView = (ImageView) convertView.findViewById(R.id.grid_overlay); ImageView overlayView = (ImageView) convertView.findViewById(R.id.grid_overlay);
TextView txtView = (TextView) convertView.findViewById(R.id.grid_text); TextView txtView = (TextView) convertView.findViewById(R.id.grid_text);
if (!populateImageView(imgView, itemList.get(i))) { if (imgView != null) {
imgView.setImageResource(defaultImageRes); if (!populateImageView(imgView, itemList.get(i))) {
imgView.setImageResource(defaultImageRes);
}
} }
if (!populateTextView(txtView, itemList.get(i))) { if (!populateTextView(txtView, itemList.get(i))) {
txtView.setText(itemList.get(i).toString()); txtView.setText(itemList.get(i).toString());
} }
if (!populateOverlayView(overlayView, itemList.get(i))) { if (overlayView != null) {
overlayView.setVisibility(View.INVISIBLE); if (!populateOverlayView(overlayView, itemList.get(i))) {
} overlayView.setVisibility(View.INVISIBLE);
else { }
overlayView.setVisibility(View.VISIBLE); else {
overlayView.setVisibility(View.VISIBLE);
}
} }
return convertView; return convertView;

View File

@@ -8,14 +8,27 @@ import com.limelight.PcView;
import com.limelight.R; import com.limelight.R;
import com.limelight.nvstream.http.ComputerDetails; import com.limelight.nvstream.http.ComputerDetails;
import java.util.Collections;
import java.util.Comparator;
public class PcGridAdapter extends GenericGridAdapter<PcView.ComputerObject> { public class PcGridAdapter extends GenericGridAdapter<PcView.ComputerObject> {
public PcGridAdapter(Context context) { public PcGridAdapter(Context context, boolean listMode) {
super(context, R.layout.pc_grid_item, R.drawable.computer); super(context, listMode ? R.layout.simple_row : R.layout.pc_grid_item, R.drawable.computer);
} }
public void addComputer(PcView.ComputerObject computer) { public void addComputer(PcView.ComputerObject computer) {
itemList.add(computer); itemList.add(computer);
sortList();
}
private void sortList() {
Collections.sort(itemList, new Comparator<PcView.ComputerObject>() {
@Override
public int compare(PcView.ComputerObject lhs, PcView.ComputerObject rhs) {
return lhs.details.name.compareTo(rhs.details.name);
}
});
} }
public boolean removeComputer(PcView.ComputerObject computer) { public boolean removeComputer(PcView.ComputerObject computer) {

View File

@@ -2,6 +2,7 @@ package com.limelight.preferences;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.Locale;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import com.limelight.computers.ComputerManagerService; import com.limelight.computers.ComputerManagerService;
@@ -15,8 +16,10 @@ import android.app.Service;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Intent; import android.content.Intent;
import android.content.ServiceConnection; import android.content.ServiceConnection;
import android.content.res.Configuration;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
import android.preference.Preference;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.inputmethod.EditorInfo; import android.view.inputmethod.EditorInfo;
import android.widget.TextView; import android.widget.TextView;
@@ -132,6 +135,13 @@ public class AddComputerManually extends Activity {
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
String locale = PreferenceConfiguration.readPreferences(this).language;
if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
Configuration config = new Configuration(getResources().getConfiguration());
config.locale = new Locale(locale);
getResources().updateConfiguration(config, getResources().getDisplayMetrics());
}
setContentView(R.layout.activity_add_computer_manually); setContentView(R.layout.activity_add_computer_manually);
UiHelper.notifyNewRootView(this); UiHelper.notifyNewRootView(this);

View File

@@ -13,6 +13,8 @@ public class PreferenceConfiguration {
private static final String DISABLE_TOASTS_PREF_STRING = "checkbox_disable_warnings"; private static final String DISABLE_TOASTS_PREF_STRING = "checkbox_disable_warnings";
private static final String HOST_AUDIO_PREF_STRING = "checkbox_host_audio"; private static final String HOST_AUDIO_PREF_STRING = "checkbox_host_audio";
private static final String DEADZONE_PREF_STRING = "seekbar_deadzone"; private static final String DEADZONE_PREF_STRING = "seekbar_deadzone";
private static final String LANGUAGE_PREF_STRING = "list_languages";
private static final String LIST_MODE_PREF_STRING = "checkbox_list_mode";
private static final String VIRTUAL_CONTROLLER_ENABLE = "virtual_controller_checkbox_enable"; private static final String VIRTUAL_CONTROLLER_ENABLE = "virtual_controller_checkbox_enable";
private static final Boolean VIRTUAL_CONTROLLER_ENABLE_DEFAULT = true; private static final Boolean VIRTUAL_CONTROLLER_ENABLE_DEFAULT = true;
@@ -30,6 +32,8 @@ public class PreferenceConfiguration {
private static final boolean DEFAULT_DISABLE_TOASTS = false; private static final boolean DEFAULT_DISABLE_TOASTS = false;
private static final boolean DEFAULT_HOST_AUDIO = false; private static final boolean DEFAULT_HOST_AUDIO = false;
private static final int DEFAULT_DEADZONE = 15; private static final int DEFAULT_DEADZONE = 15;
public static final String DEFAULT_LANGUAGE = "default";
private static final boolean DEFAULT_LIST_MODE = false;
public static final int FORCE_HARDWARE_DECODER = -1; public static final int FORCE_HARDWARE_DECODER = -1;
public static final int AUTOSELECT_DECODER = 0; public static final int AUTOSELECT_DECODER = 0;
@@ -40,6 +44,8 @@ public class PreferenceConfiguration {
public int decoder; public int decoder;
public int deadzonePercentage; public int deadzonePercentage;
public boolean stretchVideo, enableSops, playHostAudio, disableWarnings; public boolean stretchVideo, enableSops, playHostAudio, disableWarnings;
public String language;
public boolean listMode;
public boolean virtualController_enable; public boolean virtualController_enable;
@@ -140,11 +146,14 @@ public class PreferenceConfiguration {
config.deadzonePercentage = prefs.getInt(DEADZONE_PREF_STRING, DEFAULT_DEADZONE); config.deadzonePercentage = prefs.getInt(DEADZONE_PREF_STRING, DEFAULT_DEADZONE);
config.language = prefs.getString(LANGUAGE_PREF_STRING, DEFAULT_LANGUAGE);
// Checkbox preferences // Checkbox preferences
config.disableWarnings = prefs.getBoolean(DISABLE_TOASTS_PREF_STRING, DEFAULT_DISABLE_TOASTS); config.disableWarnings = prefs.getBoolean(DISABLE_TOASTS_PREF_STRING, DEFAULT_DISABLE_TOASTS);
config.enableSops = prefs.getBoolean(SOPS_PREF_STRING, DEFAULT_SOPS); config.enableSops = prefs.getBoolean(SOPS_PREF_STRING, DEFAULT_SOPS);
config.stretchVideo = prefs.getBoolean(STRETCH_PREF_STRING, DEFAULT_STRETCH); config.stretchVideo = prefs.getBoolean(STRETCH_PREF_STRING, DEFAULT_STRETCH);
config.playHostAudio = prefs.getBoolean(HOST_AUDIO_PREF_STRING, DEFAULT_HOST_AUDIO); config.playHostAudio = prefs.getBoolean(HOST_AUDIO_PREF_STRING, DEFAULT_HOST_AUDIO);
config.listMode = prefs.getBoolean(LIST_MODE_PREF_STRING, DEFAULT_LIST_MODE);
config.virtualController_enable = prefs.getBoolean(VIRTUAL_CONTROLLER_ENABLE, VIRTUAL_CONTROLLER_ENABLE_DEFAULT); config.virtualController_enable = prefs.getBoolean(VIRTUAL_CONTROLLER_ENABLE, VIRTUAL_CONTROLLER_ENABLE_DEFAULT);

View File

@@ -2,22 +2,33 @@ package com.limelight.preferences;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.os.Bundle; import android.os.Bundle;
import android.app.Activity; import android.app.Activity;
import android.preference.Preference; import android.preference.Preference;
import android.preference.PreferenceFragment; import android.preference.PreferenceFragment;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import com.limelight.PcView;
import com.limelight.R; import com.limelight.R;
import com.limelight.binding.input.virtual_controller.VirtualController; import com.limelight.binding.input.virtual_controller.VirtualController;
import com.limelight.binding.input.virtual_controller.VirtualControllerConfiguration; import com.limelight.binding.input.virtual_controller.VirtualControllerConfiguration;
import com.limelight.utils.UiHelper; import com.limelight.utils.UiHelper;
import java.util.Locale;
public class StreamSettings extends Activity { public class StreamSettings extends Activity {
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
String locale = PreferenceConfiguration.readPreferences(this).language;
if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
Configuration config = new Configuration(getResources().getConfiguration());
config.locale = new Locale(locale);
getResources().updateConfiguration(config, getResources().getDisplayMetrics());
}
setContentView(R.layout.activity_stream_settings); setContentView(R.layout.activity_stream_settings);
getFragmentManager().beginTransaction().replace( getFragmentManager().beginTransaction().replace(
R.id.stream_settings, new SettingsFragment() R.id.stream_settings, new SettingsFragment()
@@ -26,6 +37,16 @@ public class StreamSettings extends Activity {
UiHelper.notifyNewRootView(this); UiHelper.notifyNewRootView(this);
} }
@Override
public void onBackPressed() {
finish();
// Restart the PC view to apply UI changes
Intent intent = new Intent(this, PcView.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent, null);
}
public static class SettingsFragment extends PreferenceFragment { public static class SettingsFragment extends PreferenceFragment {
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {

View File

@@ -0,0 +1,35 @@
package com.limelight.ui;
import android.app.Activity;
import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import com.limelight.R;
public class AdapterFragment extends Fragment {
private AdapterFragmentCallbacks callbacks;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
callbacks = (AdapterFragmentCallbacks) activity;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(callbacks.getAdapterFragmentLayoutId(), container, false);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
callbacks.receiveAbsListView((AbsListView) getView().findViewById(R.id.fragmentView));
}
}

View File

@@ -0,0 +1,8 @@
package com.limelight.ui;
import android.widget.AbsListView;
public interface AdapterFragmentCallbacks {
public int getAdapterFragmentLayoutId();
public void receiveAbsListView(AbsListView gridView);
}

View File

@@ -0,0 +1,5 @@
package com.limelight.ui;
public interface GameGestures {
public void showKeyboard();
}

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<stroke android:width="1dip" android:color="#ffffff"/>
</shape>

View File

@@ -33,12 +33,10 @@
android:text="@string/searching_pc"/> android:text="@string/searching_pc"/>
</RelativeLayout> </RelativeLayout>
<GridView <FrameLayout
android:id="@+id/pcGridView" android:id="@+id/pcFragmentContainer"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:numColumns="auto_fit"
android:columnWidth="160dp"
android:gravity="center" android:gravity="center"
android:layout_alignParentBottom="true" android:layout_alignParentBottom="true"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"

View File

@@ -33,12 +33,10 @@
android:text="@string/searching_pc"/> android:text="@string/searching_pc"/>
</RelativeLayout> </RelativeLayout>
<GridView <FrameLayout
android:id="@+id/pcGridView" android:id="@+id/pcFragmentContainer"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:numColumns="auto_fit"
android:columnWidth="160dp"
android:gravity="center" android:gravity="center"
android:layout_toLeftOf="@+id/manuallyAddPc" android:layout_toLeftOf="@+id/manuallyAddPc"
android:layout_toStartOf="@+id/manuallyAddPc" android:layout_toStartOf="@+id/manuallyAddPc"

View File

@@ -8,21 +8,17 @@
android:paddingTop="@dimen/activity_vertical_margin" android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".AppView" > tools:context=".AppView" >
<GridView <FrameLayout
android:id="@+id/appGridView" android:id="@+id/appFragmentContainer"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:numColumns="auto_fit"
android:columnWidth="160dp"
android:stretchMode="spacingWidth"
android:gravity="center" android:gravity="center"
android:layout_alignParentLeft="true" android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" android:layout_alignParentStart="true"
android:layout_alignParentRight="true" android:layout_alignParentRight="true"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true" android:layout_alignParentBottom="true"
android:layout_below="@+id/appListText"> android:layout_below="@+id/appListText"/>
</GridView>
<TextView <TextView
android:id="@+id/appListText" android:id="@+id/appListText"

View File

@@ -61,7 +61,7 @@
android:text="Color 1"/> android:text="Color 1"/>
<ImageView <ImageView
android:layout_width="100dp" android:layout_width="200dp"
android:layout_height="100dp" android:layout_height="100dp"
android:layout_gravity="center_horizontal"/> android:layout_gravity="center_horizontal"/>
@@ -93,7 +93,8 @@
android:text="Color 2"/> android:text="Color 2"/>
<ImageView <ImageView
android:layout_width="100dp" android:layout_width="200dp"
android:layout_height="100dp" android:layout_height="100dp"
android:layout_gravity="center_horizontal"/> android:layout_gravity="center_horizontal"/>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<GridView
android:id="@+id/fragmentView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:numColumns="auto_fit"
android:columnWidth="160dp"
android:stretchMode="spacingWidth"
android:gravity="center"/>
</LinearLayout>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/fragmentView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/list_view_unselected"
android:fastScrollEnabled="true"
android:longClickable="false"
android:stackFromBottom="false" >
</ListView>
</LinearLayout>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<GridView
android:id="@+id/fragmentView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:numColumns="auto_fit"
android:columnWidth="160dp"
android:gravity="center"/>
</LinearLayout>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:padding="10dp"
android:layout_height="wrap_content">
<TextView
android:id="@+id/grid_text"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textIsSelectable="false"
android:textSize="16sp" >
</TextView>
</LinearLayout>

View File

@@ -94,11 +94,17 @@
<string name="title_seekbar_deadzone">Aggiusta deadzone degli stick analogici</string> <string name="title_seekbar_deadzone">Aggiusta deadzone degli stick analogici</string>
<string name="suffix_seekbar_deadzone">%</string> <string name="suffix_seekbar_deadzone">%</string>
<string name="category_ui_settings">UI Settings</string>
<string name="title_language_list">Lingua</string>
<string name="summary_language_list">Lingua da usare in Limelight</string>
<string name="title_checkbox_list_mode">Use lists instead of grids</string>
<string name="summary_checkbox_list_mode">Display apps and PCs in lists instead of grids</string>
<string name="category_host_settings">Impostazioni Host</string> <string name="category_host_settings">Impostazioni Host</string>
<string name="title_checkbox_enable_sops">Ottimizza le impostazioni dei giochi</string> <string name="title_checkbox_enable_sops">Ottimizza le impostazioni dei giochi</string>
<string name="summary_checkbox_enable_sops">Permetti a GFE di modificare le impostazioni dei giochi per uno streaming ottimale</string> <string name="summary_checkbox_enable_sops">Permetti a GFE di modificare le impostazioni dei giochi per uno streaming ottimale</string>
<string name="title_checkbox_host_audio">Riproduci audio sul PC</string> <string name="title_checkbox_host_audio">Riproduci audio sul PC</string>
<string name="summary_checkbox_host_audio">Riproduci l\'audio sul computer e su questo dispositivo. Richiede GFE 2.1.2+</string> <string name="summary_checkbox_host_audio">Riproduci l\'audio sul computer e su questo dispositivo</string>
<string name="category_advanced_settings">Impostazioni Avanzate</string> <string name="category_advanced_settings">Impostazioni Avanzate</string>
<string name="title_decoder_list">Cambia decoder</string> <string name="title_decoder_list">Cambia decoder</string>

View File

@@ -13,6 +13,17 @@
<item>1080p60</item> <item>1080p60</item>
</string-array> </string-array>
<string-array name="language_names" translatable="false">
<item>Default</item>
<item>English</item>
<item>Italiano</item>
</string-array>
<string-array name="language_values" translatable="false">
<item>default</item>
<item>en</item>
<item>it</item>
</string-array>
<string-array name="decoder_names"> <string-array name="decoder_names">
<item>Auto-select Decoder</item> <item>Auto-select Decoder</item>
<item>Force Software Decoding</item> <item>Force Software Decoding</item>

View File

@@ -94,11 +94,17 @@
<string name="title_seekbar_deadzone">Adjust analog stick deadzone</string> <string name="title_seekbar_deadzone">Adjust analog stick deadzone</string>
<string name="suffix_seekbar_deadzone">%</string> <string name="suffix_seekbar_deadzone">%</string>
<string name="category_ui_settings">UI Settings</string>
<string name="title_language_list">Language</string>
<string name="summary_language_list">Language to use for Limelight</string>
<string name="title_checkbox_list_mode">Use lists instead of grids</string>
<string name="summary_checkbox_list_mode">Display apps and PCs in lists instead of grids</string>
<string name="category_host_settings">Host Settings</string> <string name="category_host_settings">Host Settings</string>
<string name="title_checkbox_enable_sops">Optimize game settings</string> <string name="title_checkbox_enable_sops">Optimize game settings</string>
<string name="summary_checkbox_enable_sops">Allow GFE to modify game settings for optimal streaming</string> <string name="summary_checkbox_enable_sops">Allow GFE to modify game settings for optimal streaming</string>
<string name="title_checkbox_host_audio">Play audio on PC</string> <string name="title_checkbox_host_audio">Play audio on PC</string>
<string name="summary_checkbox_host_audio">Play audio from the computer and this device. Requires GFE 2.1.2+</string> <string name="summary_checkbox_host_audio">Play audio from the computer and this device</string>
<string name="category_advanced_settings">Advanced Settings</string> <string name="category_advanced_settings">Advanced Settings</string>
<string name="title_decoder_list">Change decoder</string> <string name="title_decoder_list">Change decoder</string>

View File

@@ -46,6 +46,20 @@
android:summary="@string/summary_checkbox_host_audio" android:summary="@string/summary_checkbox_host_audio"
android:defaultValue="false" /> android:defaultValue="false" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory android:title="@string/category_ui_settings">
<ListPreference
android:key="list_languages"
android:title="@string/title_language_list"
android:entries="@array/language_names"
android:entryValues="@array/language_values"
android:summary="@string/summary_language_list"
android:defaultValue="default" />
<CheckBoxPreference
android:key="checkbox_list_mode"
android:title="@string/title_checkbox_list_mode"
android:summary="@string/summary_checkbox_list_mode"
android:defaultValue="false" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/category_advanced_settings"> <PreferenceCategory android:title="@string/category_advanced_settings">
<ListPreference <ListPreference
android:key="list_decoders" android:key="list_decoders"
@@ -66,4 +80,4 @@
android:summary="tbd"/> android:summary="tbd"/>
</PreferenceCategory> </PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>