diff --git a/app/src/main/java/com/limelight/AppView.java b/app/src/main/java/com/limelight/AppView.java index 6beee6dd..baefa505 100644 --- a/app/src/main/java/com/limelight/AppView.java +++ b/app/src/main/java/com/limelight/AppView.java @@ -57,13 +57,13 @@ public class AppView extends Activity implements AdapterFragmentCallbacks { private int consecutiveAppListFailures = 0; private final static int CONSECUTIVE_FAILURE_LIMIT = 3; - private final static int START_OR_RESUME_ID = 1; - private final static int QUIT_ID = 2; - private final static int CANCEL_ID = 3; + private final static int START_OR_RESUME_ID = 1; + private final static int QUIT_ID = 2; + private final static int CANCEL_ID = 3; private final static int START_WTIH_QUIT = 4; public final static String NAME_EXTRA = "Name"; - public final static String UUID_EXTRA = "UUID"; + public final static String UUID_EXTRA = "UUID"; private ComputerManagerService.ComputerManagerBinder managerBinder; private final ServiceConnection serviceConnection = new ServiceConnection() { @@ -183,33 +183,33 @@ public class AppView extends Activity implements AdapterFragmentCallbacks { managerBinder.stopPolling(); } } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + + @Override + protected void onCreate(Bundle 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()); - } + 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); uuidString = getIntent().getStringExtra(UUID_EXTRA); - - String labelText = getResources().getString(R.string.title_applist)+" "+getIntent().getStringExtra(NAME_EXTRA); - TextView label = (TextView) findViewById(R.id.appListText); - setTitle(labelText); - label.setText(labelText); + + String labelText = getResources().getString(R.string.title_applist)+" "+getIntent().getStringExtra(NAME_EXTRA); + TextView label = (TextView) findViewById(R.id.appListText); + setTitle(labelText); + label.setText(labelText); // Bind to the computer manager service bindService(new Intent(this, ComputerManagerService.class), serviceConnection, Service.BIND_AUTO_CREATE); - } + } private void populateAppGridWithCache() { try { @@ -233,25 +233,25 @@ public class AppView extends Activity implements AdapterFragmentCallbacks { blockingLoadSpinner = SpinnerDialog.displayDialog(this, getResources().getString(R.string.applist_refresh_title), getResources().getString(R.string.applist_refresh_msg), true); } - - @Override - protected void onDestroy() { - super.onDestroy(); - - SpinnerDialog.closeDialogs(this); - Dialog.closeDialogs(); + + @Override + protected void onDestroy() { + super.onDestroy(); + + SpinnerDialog.closeDialogs(this); + Dialog.closeDialogs(); if (managerBinder != null) { unbindService(serviceConnection); } - } - - @Override - protected void onResume() { - super.onResume(); + } + + @Override + protected void onResume() { + super.onResume(); startComputerUpdates(); - } + } @Override protected void onPause() { @@ -259,49 +259,49 @@ public class AppView extends Activity implements AdapterFragmentCallbacks { stopComputerUpdates(); } - - private int getRunningAppId() { + + private int getRunningAppId() { int runningAppId = -1; for (int i = 0; i < appGridAdapter.getCount(); i++) { - AppObject app = (AppObject) appGridAdapter.getItem(i); - if (app.app == null) { - continue; - } - - if (app.app.getIsRunning()) { - runningAppId = app.app.getAppId(); - break; - } + AppObject app = (AppObject) appGridAdapter.getItem(i); + if (app.app == null) { + continue; + } + + if (app.app.getIsRunning()) { + runningAppId = app.app.getAppId(); + break; + } } return runningAppId; - } - - @Override - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { - super.onCreateContextMenu(menu, v, menuInfo); + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo; AppObject selectedApp = (AppObject) appGridAdapter.getItem(info.position); if (selectedApp == null || selectedApp.app == null) { - return; + return; } int runningAppId = getRunningAppId(); if (runningAppId != -1) { - if (runningAppId == selectedApp.app.getAppId()) { + if (runningAppId == selectedApp.app.getAppId()) { menu.add(Menu.NONE, START_OR_RESUME_ID, 1, getResources().getString(R.string.applist_menu_resume)); menu.add(Menu.NONE, QUIT_ID, 2, getResources().getString(R.string.applist_menu_quit)); - } - else { + } + else { menu.add(Menu.NONE, START_WTIH_QUIT, 1, getResources().getString(R.string.applist_menu_quit_and_start)); menu.add(Menu.NONE, CANCEL_ID, 2, getResources().getString(R.string.applist_menu_cancel)); - } + } } } - - @Override - public void onContextMenuClosed(Menu menu) { - } + + @Override + public void onContextMenuClosed(Menu menu) { + } private void displayQuitConfirmationDialog(final Runnable onYes, final Runnable onNo) { DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() { @@ -414,57 +414,57 @@ public class AppView extends Activity implements AdapterFragmentCallbacks { }); } - private void doStart(NvApp app) { - Intent intent = new Intent(this, Game.class); - intent.putExtra(Game.EXTRA_HOST, + private void doStart(NvApp app) { + Intent intent = new Intent(this, Game.class); + intent.putExtra(Game.EXTRA_HOST, computer.reachability == ComputerDetails.Reachability.LOCAL ? computer.localIp.getHostAddress() : computer.remoteIp.getHostAddress()); - intent.putExtra(Game.EXTRA_APP, app.getAppName()); - intent.putExtra(Game.EXTRA_UNIQUEID, managerBinder.getUniqueId()); - intent.putExtra(Game.EXTRA_STREAMING_REMOTE, + intent.putExtra(Game.EXTRA_APP, app.getAppName()); + intent.putExtra(Game.EXTRA_UNIQUEID, managerBinder.getUniqueId()); + intent.putExtra(Game.EXTRA_STREAMING_REMOTE, computer.reachability != ComputerDetails.Reachability.LOCAL); - startActivity(intent); - } - - private void doQuit(final NvApp app) { - Toast.makeText(AppView.this, getResources().getString(R.string.applist_quit_app)+" "+app.getAppName()+"...", Toast.LENGTH_SHORT).show(); - new Thread(new Runnable() { - @Override - public void run() { - NvHTTP httpConn; - String message; - try { - httpConn = new NvHTTP(getAddress(), + startActivity(intent); + } + + private void doQuit(final NvApp app) { + Toast.makeText(AppView.this, getResources().getString(R.string.applist_quit_app)+" "+app.getAppName()+"...", Toast.LENGTH_SHORT).show(); + new Thread(new Runnable() { + @Override + public void run() { + NvHTTP httpConn; + String message; + try { + httpConn = new NvHTTP(getAddress(), managerBinder.getUniqueId(), null, PlatformBinding.getCryptoProvider(AppView.this)); - if (httpConn.quitApp()) { - message = getResources().getString(R.string.applist_quit_success)+" "+app.getAppName(); - } - else { - message = getResources().getString(R.string.applist_quit_fail)+" "+app.getAppName(); - } - } catch (UnknownHostException e) { - message = getResources().getString(R.string.error_unknown_host); - } catch (FileNotFoundException e) { - message = getResources().getString(R.string.error_404); - } catch (Exception e) { - message = e.getMessage(); - } finally { + if (httpConn.quitApp()) { + message = getResources().getString(R.string.applist_quit_success)+" "+app.getAppName(); + } + else { + message = getResources().getString(R.string.applist_quit_fail)+" "+app.getAppName(); + } + } catch (UnknownHostException e) { + message = getResources().getString(R.string.error_unknown_host); + } catch (FileNotFoundException e) { + message = getResources().getString(R.string.error_404); + } catch (Exception e) { + message = e.getMessage(); + } finally { // Trigger a poll immediately if (poller != null) { poller.pollNow(); } } - - final String toastMessage = message; - runOnUiThread(new Runnable() { - @Override - public void run() { - Toast.makeText(AppView.this, toastMessage, Toast.LENGTH_LONG).show(); - } - }); - } - }).start(); - } + + final String toastMessage = message; + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(AppView.this, toastMessage, Toast.LENGTH_LONG).show(); + } + }); + } + }).start(); + } @Override public int getAdapterFragmentLayoutId() { @@ -497,15 +497,15 @@ public class AppView extends Activity implements AdapterFragmentCallbacks { } public class AppObject { - public final NvApp app; - - public AppObject(NvApp app) { - this.app = app; - } - - @Override - public String toString() { - return app.getAppName(); - } - } + public final NvApp app; + + public AppObject(NvApp app) { + this.app = app; + } + + @Override + public String toString() { + return app.getAppName(); + } + } } diff --git a/app/src/main/java/com/limelight/Game.java b/app/src/main/java/com/limelight/Game.java index a5bef3e1..0e327ae1 100644 --- a/app/src/main/java/com/limelight/Game.java +++ b/app/src/main/java/com/limelight/Game.java @@ -52,133 +52,133 @@ import java.util.Locale; public class Game extends Activity implements SurfaceHolder.Callback, - OnGenericMotionListener, OnTouchListener, NvConnectionListener, EvdevListener, - OnSystemUiVisibilityChangeListener, GameGestures + OnGenericMotionListener, OnTouchListener, NvConnectionListener, EvdevListener, + OnSystemUiVisibilityChangeListener, GameGestures { - private int lastMouseX = Integer.MIN_VALUE; - private int lastMouseY = Integer.MIN_VALUE; - private int lastButtonState = 0; - - // Only 2 touches are supported - private final TouchContext[] touchContextMap = new TouchContext[2]; + private int lastMouseX = Integer.MIN_VALUE; + private int lastMouseY = Integer.MIN_VALUE; + private int lastButtonState = 0; + + // Only 2 touches are supported + private final TouchContext[] touchContextMap = new TouchContext[2]; private long threeFingerDownTime = 0; private static final int THREE_FINGER_TAP_THRESHOLD = 300; - - private ControllerHandler controllerHandler; - private KeyboardTranslator keybTranslator; - - private PreferenceConfiguration prefConfig; - private final Point screenSize = new Point(0, 0); - - private NvConnection conn; - private SpinnerDialog spinner; - private boolean displayedFailureDialog = false; - private boolean connecting = false; - private boolean connected = false; - - private EvdevWatcher evdevWatcher; - private int modifierFlags = 0; - private boolean grabbedInput = true; - private boolean grabComboDown = false; - - private ConfigurableDecoderRenderer decoderRenderer; - - private WifiManager.WifiLock wifiLock; - - private int drFlags = 0; - - public static final String EXTRA_HOST = "Host"; - public static final String EXTRA_APP = "App"; - public static final String EXTRA_UNIQUEID = "UniqueId"; - public static final String EXTRA_STREAMING_REMOTE = "Remote"; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + + private ControllerHandler controllerHandler; + private KeyboardTranslator keybTranslator; + + private PreferenceConfiguration prefConfig; + private final Point screenSize = new Point(0, 0); + + private NvConnection conn; + private SpinnerDialog spinner; + private boolean displayedFailureDialog = false; + private boolean connecting = false; + private boolean connected = false; + + private EvdevWatcher evdevWatcher; + private int modifierFlags = 0; + private boolean grabbedInput = true; + private boolean grabComboDown = false; + + private ConfigurableDecoderRenderer decoderRenderer; + + private WifiManager.WifiLock wifiLock; + + private int drFlags = 0; + + public static final String EXTRA_HOST = "Host"; + public static final String EXTRA_APP = "App"; + public static final String EXTRA_UNIQUEID = "UniqueId"; + public static final String EXTRA_STREAMING_REMOTE = "Remote"; + + @Override + protected void onCreate(Bundle 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 - requestWindowFeature(Window.FEATURE_NO_TITLE); - - // Full-screen and don't let the display go off - getWindow().addFlags( - WindowManager.LayoutParams.FLAG_FULLSCREEN | - WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - - // If we're going to use immersive mode, we want to have - // the entire screen - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { - getWindow().getDecorView().setSystemUiVisibility( - View.SYSTEM_UI_FLAG_LAYOUT_STABLE | - View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | - View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); - - getWindow().addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN); - } - - // Listen for UI visibility events - getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(this); - - // Change volume button behavior - setVolumeControlStream(AudioManager.STREAM_MUSIC); - - // Inflate the content - setContentView(R.layout.activity_game); - - // Start the spinner - spinner = SpinnerDialog.displayDialog(this, getResources().getString(R.string.conn_establishing_title), - getResources().getString(R.string.conn_establishing_msg), true); - - // Read the stream preferences - prefConfig = PreferenceConfiguration.readPreferences(this); - switch (prefConfig.decoder) { - case PreferenceConfiguration.FORCE_SOFTWARE_DECODER: - drFlags |= VideoDecoderRenderer.FLAG_FORCE_SOFTWARE_DECODING; - break; - case PreferenceConfiguration.AUTOSELECT_DECODER: - break; - case PreferenceConfiguration.FORCE_HARDWARE_DECODER: - drFlags |= VideoDecoderRenderer.FLAG_FORCE_HARDWARE_DECODING; - break; - } - - if (prefConfig.stretchVideo) { - drFlags |= VideoDecoderRenderer.FLAG_FILL_SCREEN; - } - - Display display = getWindowManager().getDefaultDisplay(); - display.getSize(screenSize); - - // Listen for events on the game surface - SurfaceView sv = (SurfaceView) findViewById(R.id.surfaceView); - sv.setOnGenericMotionListener(this); - sv.setOnTouchListener(this); - - // Warn the user if they're on a metered connection - checkDataConnection(); - - // Make sure Wi-Fi is fully powered up - WifiManager wifiMgr = (WifiManager) getSystemService(Context.WIFI_SERVICE); - wifiLock = wifiMgr.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "Limelight"); - wifiLock.setReferenceCounted(false); - wifiLock.acquire(); - - String host = Game.this.getIntent().getStringExtra(EXTRA_HOST); - String app = Game.this.getIntent().getStringExtra(EXTRA_APP); - String uniqueId = Game.this.getIntent().getStringExtra(EXTRA_UNIQUEID); + Configuration config = new Configuration(getResources().getConfiguration()); + config.locale = new Locale(locale); + getResources().updateConfiguration(config, getResources().getDisplayMetrics()); + } + + // We don't want a title bar + requestWindowFeature(Window.FEATURE_NO_TITLE); + + // Full-screen and don't let the display go off + getWindow().addFlags( + WindowManager.LayoutParams.FLAG_FULLSCREEN | + WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + + // If we're going to use immersive mode, we want to have + // the entire screen + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { + getWindow().getDecorView().setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE | + View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | + View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); + + getWindow().addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN); + } + + // Listen for UI visibility events + getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(this); + + // Change volume button behavior + setVolumeControlStream(AudioManager.STREAM_MUSIC); + + // Inflate the content + setContentView(R.layout.activity_game); + + // Start the spinner + spinner = SpinnerDialog.displayDialog(this, getResources().getString(R.string.conn_establishing_title), + getResources().getString(R.string.conn_establishing_msg), true); + + // Read the stream preferences + prefConfig = PreferenceConfiguration.readPreferences(this); + switch (prefConfig.decoder) { + case PreferenceConfiguration.FORCE_SOFTWARE_DECODER: + drFlags |= VideoDecoderRenderer.FLAG_FORCE_SOFTWARE_DECODING; + break; + case PreferenceConfiguration.AUTOSELECT_DECODER: + break; + case PreferenceConfiguration.FORCE_HARDWARE_DECODER: + drFlags |= VideoDecoderRenderer.FLAG_FORCE_HARDWARE_DECODING; + break; + } + + if (prefConfig.stretchVideo) { + drFlags |= VideoDecoderRenderer.FLAG_FILL_SCREEN; + } + + Display display = getWindowManager().getDefaultDisplay(); + display.getSize(screenSize); + + // Listen for events on the game surface + SurfaceView sv = (SurfaceView) findViewById(R.id.surfaceView); + sv.setOnGenericMotionListener(this); + sv.setOnTouchListener(this); + + // Warn the user if they're on a metered connection + checkDataConnection(); + + // Make sure Wi-Fi is fully powered up + WifiManager wifiMgr = (WifiManager) getSystemService(Context.WIFI_SERVICE); + wifiLock = wifiMgr.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "Limelight"); + wifiLock.setReferenceCounted(false); + wifiLock.acquire(); + + String host = Game.this.getIntent().getStringExtra(EXTRA_HOST); + String app = Game.this.getIntent().getStringExtra(EXTRA_APP); + String uniqueId = Game.this.getIntent().getStringExtra(EXTRA_UNIQUEID); boolean remote = Game.this.getIntent().getBooleanExtra(EXTRA_STREAMING_REMOTE, false); - - decoderRenderer = new ConfigurableDecoderRenderer(); - decoderRenderer.initializeWithFlags(drFlags); + + decoderRenderer = new ConfigurableDecoderRenderer(); + decoderRenderer.initializeWithFlags(drFlags); - StreamConfiguration config = new StreamConfiguration.Builder() + StreamConfiguration config = new StreamConfiguration.Builder() .setResolution(prefConfig.width, prefConfig.height) .setRefreshRate(prefConfig.fps) .setApp(app) @@ -191,305 +191,305 @@ public class Game extends Activity implements SurfaceHolder.Callback, .setRemote(remote) .build(); - // Initialize the connection - conn = new NvConnection(host, uniqueId, Game.this, config, PlatformBinding.getCryptoProvider(this)); - keybTranslator = new KeyboardTranslator(conn); - controllerHandler = new ControllerHandler(conn, this, prefConfig.multiController, prefConfig.deadzonePercentage); + // Initialize the connection + conn = new NvConnection(host, uniqueId, Game.this, config, PlatformBinding.getCryptoProvider(this)); + keybTranslator = new KeyboardTranslator(conn); + controllerHandler = new ControllerHandler(conn, this, prefConfig.multiController, prefConfig.deadzonePercentage); InputManager inputManager = (InputManager) getSystemService(Context.INPUT_SERVICE); inputManager.registerInputDeviceListener(controllerHandler, null); - - SurfaceHolder sh = sv.getHolder(); - if (prefConfig.stretchVideo || !decoderRenderer.isHardwareAccelerated()) { - // Set the surface to the size of the video - sh.setFixedSize(prefConfig.width, prefConfig.height); - } - - // Initialize touch contexts - for (int i = 0; i < touchContextMap.length; i++) { - touchContextMap[i] = new TouchContext(conn, i, + + SurfaceHolder sh = sv.getHolder(); + if (prefConfig.stretchVideo || !decoderRenderer.isHardwareAccelerated()) { + // Set the surface to the size of the video + sh.setFixedSize(prefConfig.width, prefConfig.height); + } + + // Initialize touch contexts + for (int i = 0; i < touchContextMap.length; i++) { + touchContextMap[i] = new TouchContext(conn, i, ((double)prefConfig.width / (double)screenSize.x), ((double)prefConfig.height / (double)screenSize.y)); - } - - if (LimelightBuildProps.ROOT_BUILD) { - // Start watching for raw input - evdevWatcher = new EvdevWatcher(this); - evdevWatcher.start(); - } - - // The connection will be started when the surface gets created - sh.addCallback(this); - } - - private void resizeSurfaceWithAspectRatio(SurfaceView sv, double vidWidth, double vidHeight) - { - // Get the visible width of the activity - double visibleWidth = getWindow().getDecorView().getWidth(); - - ViewGroup.LayoutParams lp = sv.getLayoutParams(); - - // Calculate the new size of the SurfaceView - lp.width = (int) visibleWidth; - lp.height = (int) ((vidHeight / vidWidth) * visibleWidth); + } - // Apply the size change - sv.setLayoutParams(lp); - } - - private void checkDataConnection() - { - ConnectivityManager mgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); - if (mgr.isActiveNetworkMetered()) { - displayTransientMessage(getResources().getString(R.string.conn_metered)); - } - } + if (LimelightBuildProps.ROOT_BUILD) { + // Start watching for raw input + evdevWatcher = new EvdevWatcher(this); + evdevWatcher.start(); + } - @SuppressLint("InlinedApi") - private final Runnable hideSystemUi = new Runnable() { - @Override - public void run() { - // Use immersive mode on 4.4+ or standard low profile on previous builds - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { - Game.this.getWindow().getDecorView().setSystemUiVisibility( - View.SYSTEM_UI_FLAG_LAYOUT_STABLE | - View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | - View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | - View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | - View.SYSTEM_UI_FLAG_FULLSCREEN | - View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); - } - else { - Game.this.getWindow().getDecorView().setSystemUiVisibility( - View.SYSTEM_UI_FLAG_FULLSCREEN | - View.SYSTEM_UI_FLAG_LOW_PROFILE); - } - } - }; + // The connection will be started when the surface gets created + sh.addCallback(this); + } - private void hideSystemUi(int delay) { - Handler h = getWindow().getDecorView().getHandler(); - if (h != null) { - h.removeCallbacks(hideSystemUi); - h.postDelayed(hideSystemUi, delay); - } - } - - @Override - protected void onStop() { - super.onStop(); - - SpinnerDialog.closeDialogs(this); - Dialog.closeDialogs(); + private void resizeSurfaceWithAspectRatio(SurfaceView sv, double vidWidth, double vidHeight) + { + // Get the visible width of the activity + double visibleWidth = getWindow().getDecorView().getWidth(); + + ViewGroup.LayoutParams lp = sv.getLayoutParams(); + + // Calculate the new size of the SurfaceView + lp.width = (int) visibleWidth; + lp.height = (int) ((vidHeight / vidWidth) * visibleWidth); + + // Apply the size change + sv.setLayoutParams(lp); + } + + private void checkDataConnection() + { + ConnectivityManager mgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); + if (mgr.isActiveNetworkMetered()) { + displayTransientMessage(getResources().getString(R.string.conn_metered)); + } + } + + @SuppressLint("InlinedApi") + private final Runnable hideSystemUi = new Runnable() { + @Override + public void run() { + // Use immersive mode on 4.4+ or standard low profile on previous builds + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { + Game.this.getWindow().getDecorView().setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE | + View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | + View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | + View.SYSTEM_UI_FLAG_FULLSCREEN | + View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + } + else { + Game.this.getWindow().getDecorView().setSystemUiVisibility( + View.SYSTEM_UI_FLAG_FULLSCREEN | + View.SYSTEM_UI_FLAG_LOW_PROFILE); + } + } + }; + + private void hideSystemUi(int delay) { + Handler h = getWindow().getDecorView().getHandler(); + if (h != null) { + h.removeCallbacks(hideSystemUi); + h.postDelayed(hideSystemUi, delay); + } + } + + @Override + protected void onStop() { + super.onStop(); + + SpinnerDialog.closeDialogs(this); + Dialog.closeDialogs(); InputManager inputManager = (InputManager) getSystemService(Context.INPUT_SERVICE); inputManager.unregisterInputDeviceListener(controllerHandler); - - displayedFailureDialog = true; - stopConnection(); - - int averageEndToEndLat = decoderRenderer.getAverageEndToEndLatency(); - int averageDecoderLat = decoderRenderer.getAverageDecoderLatency(); - String message = null; - if (averageEndToEndLat > 0) { - message = getResources().getString(R.string.conn_client_latency)+" "+averageEndToEndLat+" ms"; - if (averageDecoderLat > 0) { - message += " ("+getResources().getString(R.string.conn_client_latency_hw)+" "+averageDecoderLat+" ms)"; - } - } - else if (averageDecoderLat > 0) { - message = getResources().getString(R.string.conn_hardware_latency)+" "+averageDecoderLat+" ms"; - } - - if (message != null) { - Toast.makeText(this, message, Toast.LENGTH_LONG).show(); - } - finish(); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - - wifiLock.release(); - } - - private final Runnable toggleGrab = new Runnable() { - @Override - public void run() { - - if (evdevWatcher != null) { - if (grabbedInput) { - evdevWatcher.ungrabAll(); - } - else { - evdevWatcher.regrabAll(); - } - } - - grabbedInput = !grabbedInput; - } - }; - - // Returns true if the key stroke was consumed - private boolean handleSpecialKeys(short translatedKey, boolean down) { - int modifierMask = 0; - - // Mask off the high byte - translatedKey &= 0xff; - - if (translatedKey == KeyboardTranslator.VK_CONTROL) { - modifierMask = KeyboardPacket.MODIFIER_CTRL; - } - else if (translatedKey == KeyboardTranslator.VK_SHIFT) { - modifierMask = KeyboardPacket.MODIFIER_SHIFT; - } - else if (translatedKey == KeyboardTranslator.VK_ALT) { - modifierMask = KeyboardPacket.MODIFIER_ALT; - } - - if (down) { - this.modifierFlags |= modifierMask; - } - else { - this.modifierFlags &= ~modifierMask; - } - - // Check if Ctrl+Shift+Z is pressed - if (translatedKey == KeyboardTranslator.VK_Z && - (modifierFlags & (KeyboardPacket.MODIFIER_CTRL | KeyboardPacket.MODIFIER_SHIFT)) == - (KeyboardPacket.MODIFIER_CTRL | KeyboardPacket.MODIFIER_SHIFT)) - { - if (down) { - // Now that we've pressed the magic combo - // we'll wait for one of the keys to come up - grabComboDown = true; - } - else { - // Toggle the grab if Z comes up - Handler h = getWindow().getDecorView().getHandler(); - if (h != null) { - h.postDelayed(toggleGrab, 250); - } - - grabComboDown = false; - } - - return true; - } - // Toggle the grab if control or shift comes up - else if (grabComboDown) { - Handler h = getWindow().getDecorView().getHandler(); - if (h != null) { - h.postDelayed(toggleGrab, 250); - } - - grabComboDown = false; - return true; - } - - // Not a special combo - return false; - } - - private static byte getModifierState(KeyEvent event) { - byte modifier = 0; - if (event.isShiftPressed()) { - modifier |= KeyboardPacket.MODIFIER_SHIFT; - } - if (event.isCtrlPressed()) { - modifier |= KeyboardPacket.MODIFIER_CTRL; - } - if (event.isAltPressed()) { - modifier |= KeyboardPacket.MODIFIER_ALT; - } - return modifier; - } - - private byte getModifierState() { - return (byte) modifierFlags; - } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - // Pass-through virtual navigation keys - if ((event.getFlags() & KeyEvent.FLAG_VIRTUAL_HARD_KEY) != 0) { - return super.onKeyDown(keyCode, event); - } - - // Try the controller handler first - boolean handled = controllerHandler.handleButtonDown(event); - if (!handled) { - // Try the keyboard handler - short translated = keybTranslator.translate(event.getKeyCode()); - if (translated == 0) { - return super.onKeyDown(keyCode, event); - } - - // Let this method take duplicate key down events - if (handleSpecialKeys(translated, true)) { - return true; - } - - // Eat repeat down events - if (event.getRepeatCount() > 0) { - return true; - } - - // Pass through keyboard input if we're not grabbing - if (!grabbedInput) { - return super.onKeyDown(keyCode, event); - } - - keybTranslator.sendKeyDown(translated, - getModifierState(event)); - } - - return true; - } - - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - // Pass-through virtual navigation keys - if ((event.getFlags() & KeyEvent.FLAG_VIRTUAL_HARD_KEY) != 0) { - return super.onKeyUp(keyCode, event); - } - - // Try the controller handler first - boolean handled = controllerHandler.handleButtonUp(event); - if (!handled) { - // Try the keyboard handler - short translated = keybTranslator.translate(event.getKeyCode()); - if (translated == 0) { - return super.onKeyUp(keyCode, event); - } - - if (handleSpecialKeys(translated, false)) { - return true; - } - - // Pass through keyboard input if we're not grabbing - if (!grabbedInput) { - return super.onKeyUp(keyCode, event); - } - - keybTranslator.sendKeyUp(translated, - getModifierState(event)); - } - - return true; - } - - private TouchContext getTouchContext(int actionIndex) - { - if (actionIndex < touchContextMap.length) { - return touchContextMap[actionIndex]; - } - else { - return null; - } - } + displayedFailureDialog = true; + stopConnection(); + + int averageEndToEndLat = decoderRenderer.getAverageEndToEndLatency(); + int averageDecoderLat = decoderRenderer.getAverageDecoderLatency(); + String message = null; + if (averageEndToEndLat > 0) { + message = getResources().getString(R.string.conn_client_latency)+" "+averageEndToEndLat+" ms"; + if (averageDecoderLat > 0) { + message += " ("+getResources().getString(R.string.conn_client_latency_hw)+" "+averageDecoderLat+" ms)"; + } + } + else if (averageDecoderLat > 0) { + message = getResources().getString(R.string.conn_hardware_latency)+" "+averageDecoderLat+" ms"; + } + + if (message != null) { + Toast.makeText(this, message, Toast.LENGTH_LONG).show(); + } + + finish(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + + wifiLock.release(); + } + + private final Runnable toggleGrab = new Runnable() { + @Override + public void run() { + + if (evdevWatcher != null) { + if (grabbedInput) { + evdevWatcher.ungrabAll(); + } + else { + evdevWatcher.regrabAll(); + } + } + + grabbedInput = !grabbedInput; + } + }; + + // Returns true if the key stroke was consumed + private boolean handleSpecialKeys(short translatedKey, boolean down) { + int modifierMask = 0; + + // Mask off the high byte + translatedKey &= 0xff; + + if (translatedKey == KeyboardTranslator.VK_CONTROL) { + modifierMask = KeyboardPacket.MODIFIER_CTRL; + } + else if (translatedKey == KeyboardTranslator.VK_SHIFT) { + modifierMask = KeyboardPacket.MODIFIER_SHIFT; + } + else if (translatedKey == KeyboardTranslator.VK_ALT) { + modifierMask = KeyboardPacket.MODIFIER_ALT; + } + + if (down) { + this.modifierFlags |= modifierMask; + } + else { + this.modifierFlags &= ~modifierMask; + } + + // Check if Ctrl+Shift+Z is pressed + if (translatedKey == KeyboardTranslator.VK_Z && + (modifierFlags & (KeyboardPacket.MODIFIER_CTRL | KeyboardPacket.MODIFIER_SHIFT)) == + (KeyboardPacket.MODIFIER_CTRL | KeyboardPacket.MODIFIER_SHIFT)) + { + if (down) { + // Now that we've pressed the magic combo + // we'll wait for one of the keys to come up + grabComboDown = true; + } + else { + // Toggle the grab if Z comes up + Handler h = getWindow().getDecorView().getHandler(); + if (h != null) { + h.postDelayed(toggleGrab, 250); + } + + grabComboDown = false; + } + + return true; + } + // Toggle the grab if control or shift comes up + else if (grabComboDown) { + Handler h = getWindow().getDecorView().getHandler(); + if (h != null) { + h.postDelayed(toggleGrab, 250); + } + + grabComboDown = false; + return true; + } + + // Not a special combo + return false; + } + + private static byte getModifierState(KeyEvent event) { + byte modifier = 0; + if (event.isShiftPressed()) { + modifier |= KeyboardPacket.MODIFIER_SHIFT; + } + if (event.isCtrlPressed()) { + modifier |= KeyboardPacket.MODIFIER_CTRL; + } + if (event.isAltPressed()) { + modifier |= KeyboardPacket.MODIFIER_ALT; + } + return modifier; + } + + private byte getModifierState() { + return (byte) modifierFlags; + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + // Pass-through virtual navigation keys + if ((event.getFlags() & KeyEvent.FLAG_VIRTUAL_HARD_KEY) != 0) { + return super.onKeyDown(keyCode, event); + } + + // Try the controller handler first + boolean handled = controllerHandler.handleButtonDown(event); + if (!handled) { + // Try the keyboard handler + short translated = keybTranslator.translate(event.getKeyCode()); + if (translated == 0) { + return super.onKeyDown(keyCode, event); + } + + // Let this method take duplicate key down events + if (handleSpecialKeys(translated, true)) { + return true; + } + + // Eat repeat down events + if (event.getRepeatCount() > 0) { + return true; + } + + // Pass through keyboard input if we're not grabbing + if (!grabbedInput) { + return super.onKeyDown(keyCode, event); + } + + keybTranslator.sendKeyDown(translated, + getModifierState(event)); + } + + return true; + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + // Pass-through virtual navigation keys + if ((event.getFlags() & KeyEvent.FLAG_VIRTUAL_HARD_KEY) != 0) { + return super.onKeyUp(keyCode, event); + } + + // Try the controller handler first + boolean handled = controllerHandler.handleButtonUp(event); + if (!handled) { + // Try the keyboard handler + short translated = keybTranslator.translate(event.getKeyCode()); + if (translated == 0) { + return super.onKeyUp(keyCode, event); + } + + if (handleSpecialKeys(translated, false)) { + return true; + } + + // Pass through keyboard input if we're not grabbing + if (!grabbedInput) { + return super.onKeyUp(keyCode, event); + } + + keybTranslator.sendKeyUp(translated, + getModifierState(event)); + } + + return true; + } + + private TouchContext getTouchContext(int actionIndex) + { + if (actionIndex < touchContextMap.length) { + return touchContextMap[actionIndex]; + } + else { + return null; + } + } @Override public void showKeyboard() { @@ -498,27 +498,27 @@ public class Game extends Activity implements SurfaceHolder.Callback, inputManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY); } - // Returns true if the event was consumed - private boolean handleMotionEvent(MotionEvent event) { - // Pass through keyboard input if we're not grabbing - if (!grabbedInput) { - return false; - } + // Returns true if the event was consumed + private boolean handleMotionEvent(MotionEvent event) { + // Pass through keyboard input if we're not grabbing + if (!grabbedInput) { + return false; + } - if ((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { - if (controllerHandler.handleMotionEvent(event)) { - return true; - } - } - else if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) - { - // This case is for touch-based input devices - if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN || - event.getSource() == InputDevice.SOURCE_STYLUS) - { - int actionIndex = event.getActionIndex(); + if ((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { + if (controllerHandler.handleMotionEvent(event)) { + return true; + } + } + else if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) + { + // This case is for touch-based input devices + if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN || + event.getSource() == InputDevice.SOURCE_STYLUS) + { + int actionIndex = event.getActionIndex(); - int eventX = (int)event.getX(actionIndex); + int eventX = (int)event.getX(actionIndex); int eventY = (int)event.getY(actionIndex); // Special handling for 3 finger gesture @@ -536,19 +536,19 @@ public class Game extends Activity implements SurfaceHolder.Callback, return true; } - TouchContext context = getTouchContext(actionIndex); - if (context == null) { - return false; - } + TouchContext context = getTouchContext(actionIndex); + if (context == null) { + return false; + } - switch (event.getActionMasked()) - { - case MotionEvent.ACTION_POINTER_DOWN: - case MotionEvent.ACTION_DOWN: - context.touchDownEvent(eventX, eventY); - break; - case MotionEvent.ACTION_POINTER_UP: - case MotionEvent.ACTION_UP: + switch (event.getActionMasked()) + { + case MotionEvent.ACTION_POINTER_DOWN: + case MotionEvent.ACTION_DOWN: + context.touchDownEvent(eventX, eventY); + break; + case MotionEvent.ACTION_POINTER_UP: + case MotionEvent.ACTION_UP: if (event.getPointerCount() == 1) { // All fingers up if (SystemClock.uptimeMillis() - threeFingerDownTime < THREE_FINGER_TAP_THRESHOLD) { @@ -557,15 +557,15 @@ public class Game extends Activity implements SurfaceHolder.Callback, return true; } } - context.touchUpEvent(eventX, eventY); - if (actionIndex == 0 && event.getPointerCount() > 1 && !context.isCancelled()) { - // The original secondary touch now becomes primary - context.touchDownEvent((int)event.getX(1), (int)event.getY(1)); - } - break; - case MotionEvent.ACTION_MOVE: - // ACTION_MOVE is special because it always has actionIndex == 0 - // We'll call the move handlers for all indexes manually + context.touchUpEvent(eventX, eventY); + if (actionIndex == 0 && event.getPointerCount() > 1 && !context.isCancelled()) { + // The original secondary touch now becomes primary + context.touchDownEvent((int)event.getX(1), (int)event.getY(1)); + } + break; + case MotionEvent.ACTION_MOVE: + // ACTION_MOVE is special because it always has actionIndex == 0 + // We'll call the move handlers for all indexes manually // First process the historical events for (int i = 0; i < event.getHistorySize(); i++) { @@ -588,48 +588,48 @@ public class Game extends Activity implements SurfaceHolder.Callback, (int)event.getY(aTouchContextMap.getActionIndex())); } } - break; - default: - return false; - } - } - // This case is for mice - else if (event.getSource() == InputDevice.SOURCE_MOUSE) - { - int changedButtons = event.getButtonState() ^ lastButtonState; - - if (event.getActionMasked() == MotionEvent.ACTION_SCROLL) { - // Send the vertical scroll packet - byte vScrollClicks = (byte) event.getAxisValue(MotionEvent.AXIS_VSCROLL); - conn.sendMouseScroll(vScrollClicks); - } + break; + default: + return false; + } + } + // This case is for mice + else if (event.getSource() == InputDevice.SOURCE_MOUSE) + { + int changedButtons = event.getButtonState() ^ lastButtonState; - if ((changedButtons & MotionEvent.BUTTON_PRIMARY) != 0) { - if ((event.getButtonState() & MotionEvent.BUTTON_PRIMARY) != 0) { - conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_LEFT); - } - else { - conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_LEFT); - } - } + if (event.getActionMasked() == MotionEvent.ACTION_SCROLL) { + // Send the vertical scroll packet + byte vScrollClicks = (byte) event.getAxisValue(MotionEvent.AXIS_VSCROLL); + conn.sendMouseScroll(vScrollClicks); + } - if ((changedButtons & MotionEvent.BUTTON_SECONDARY) != 0) { - if ((event.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0) { - conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_RIGHT); - } - else { - conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_RIGHT); - } - } + if ((changedButtons & MotionEvent.BUTTON_PRIMARY) != 0) { + if ((event.getButtonState() & MotionEvent.BUTTON_PRIMARY) != 0) { + conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_LEFT); + } + else { + conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_LEFT); + } + } - if ((changedButtons & MotionEvent.BUTTON_TERTIARY) != 0) { - if ((event.getButtonState() & MotionEvent.BUTTON_TERTIARY) != 0) { - conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_MIDDLE); - } - else { - conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_MIDDLE); - } - } + if ((changedButtons & MotionEvent.BUTTON_SECONDARY) != 0) { + if ((event.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0) { + conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_RIGHT); + } + else { + conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_RIGHT); + } + } + + if ((changedButtons & MotionEvent.BUTTON_TERTIARY) != 0) { + if ((event.getButtonState() & MotionEvent.BUTTON_TERTIARY) != 0) { + conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_MIDDLE); + } + else { + conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_MIDDLE); + } + } // First process the history for (int i = 0; i < event.getHistorySize(); i++) { @@ -640,256 +640,256 @@ public class Game extends Activity implements SurfaceHolder.Callback, updateMousePosition((int)event.getX(), (int)event.getY()); lastButtonState = event.getButtonState(); - } - else - { - // Unknown source - return false; - } + } + else + { + // Unknown source + return false; + } - // Handled a known source - return true; - } + // Handled a known source + return true; + } - // Unknown class - return false; - } - - @Override - public boolean onTouchEvent(MotionEvent event) { + // Unknown class + return false; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { return handleMotionEvent(event) || super.onTouchEvent(event); } - @Override - public boolean onGenericMotionEvent(MotionEvent event) { + @Override + public boolean onGenericMotionEvent(MotionEvent event) { return handleMotionEvent(event) || super.onGenericMotionEvent(event); } - - private void updateMousePosition(int eventX, int eventY) { - // Send a mouse move if we already have a mouse location - // and the mouse coordinates change - if (lastMouseX != Integer.MIN_VALUE && - lastMouseY != Integer.MIN_VALUE && - !(lastMouseX == eventX && lastMouseY == eventY)) - { - int deltaX = eventX - lastMouseX; - int deltaY = eventY - lastMouseY; - - // Scale the deltas if the device resolution is different - // than the stream resolution - deltaX = (int)Math.round((double)deltaX * ((double)prefConfig.width / (double)screenSize.x)); - deltaY = (int)Math.round((double)deltaY * ((double)prefConfig.height / (double)screenSize.y)); - - conn.sendMouseMove((short)deltaX, (short)deltaY); - } - - // Update pointer location for delta calculation next time - lastMouseX = eventX; - lastMouseY = eventY; - } - @Override - public boolean onGenericMotion(View v, MotionEvent event) { - return handleMotionEvent(event); - } + private void updateMousePosition(int eventX, int eventY) { + // Send a mouse move if we already have a mouse location + // and the mouse coordinates change + if (lastMouseX != Integer.MIN_VALUE && + lastMouseY != Integer.MIN_VALUE && + !(lastMouseX == eventX && lastMouseY == eventY)) + { + int deltaX = eventX - lastMouseX; + int deltaY = eventY - lastMouseY; - @SuppressLint("ClickableViewAccessibility") - @Override - public boolean onTouch(View v, MotionEvent event) { - return handleMotionEvent(event); - } + // Scale the deltas if the device resolution is different + // than the stream resolution + deltaX = (int)Math.round((double)deltaX * ((double)prefConfig.width / (double)screenSize.x)); + deltaY = (int)Math.round((double)deltaY * ((double)prefConfig.height / (double)screenSize.y)); - @Override - public void stageStarting(Stage stage) { - if (spinner != null) { - spinner.setMessage(getResources().getString(R.string.conn_starting)+" "+stage.getName()); - } - } + conn.sendMouseMove((short)deltaX, (short)deltaY); + } - @Override - public void stageComplete(Stage stage) { - } - - private void stopConnection() { - if (connecting || connected) { - connecting = connected = false; - conn.stop(); - } - - // Close the Evdev watcher to allow use of captured input devices - if (evdevWatcher != null) { - evdevWatcher.shutdown(); - evdevWatcher = null; - } - } + // Update pointer location for delta calculation next time + lastMouseX = eventX; + lastMouseY = eventY; + } - @Override - public void stageFailed(Stage stage) { - if (spinner != null) { - spinner.dismiss(); - spinner = null; - } + @Override + public boolean onGenericMotion(View v, MotionEvent event) { + return handleMotionEvent(event); + } - if (!displayedFailureDialog) { - displayedFailureDialog = true; - stopConnection(); - Dialog.displayDialog(this, getResources().getString(R.string.conn_error_title), - getResources().getString(R.string.conn_error_msg)+" "+stage.getName(), true); - } - } + @SuppressLint("ClickableViewAccessibility") + @Override + public boolean onTouch(View v, MotionEvent event) { + return handleMotionEvent(event); + } - @Override - public void connectionTerminated(Exception e) { - if (!displayedFailureDialog) { - displayedFailureDialog = true; - e.printStackTrace(); - - stopConnection(); - Dialog.displayDialog(this, getResources().getString(R.string.conn_terminated_title), - getResources().getString(R.string.conn_terminated_msg), true); - } - } + @Override + public void stageStarting(Stage stage) { + if (spinner != null) { + spinner.setMessage(getResources().getString(R.string.conn_starting)+" "+stage.getName()); + } + } - @Override - public void connectionStarted() { - if (spinner != null) { - spinner.dismiss(); - spinner = null; - } - - connecting = false; - connected = true; - - hideSystemUi(1000); - } + @Override + public void stageComplete(Stage stage) { + } - @Override - public void displayMessage(final String message) { - runOnUiThread(new Runnable() { - @Override - public void run() { - Toast.makeText(Game.this, message, Toast.LENGTH_LONG).show(); - } - }); - } + private void stopConnection() { + if (connecting || connected) { + connecting = connected = false; + conn.stop(); + } - @Override - public void displayTransientMessage(final String message) { - if (!prefConfig.disableWarnings) { - runOnUiThread(new Runnable() { - @Override - public void run() { - Toast.makeText(Game.this, message, Toast.LENGTH_LONG).show(); - } - }); - } - } + // Close the Evdev watcher to allow use of captured input devices + if (evdevWatcher != null) { + evdevWatcher.shutdown(); + evdevWatcher = null; + } + } - @Override - public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { - } + @Override + public void stageFailed(Stage stage) { + if (spinner != null) { + spinner.dismiss(); + spinner = null; + } - @Override - public void surfaceCreated(SurfaceHolder holder) { - if (!connected && !connecting) { - connecting = true; - - // Resize the surface to match the aspect ratio of the video - // This must be done after the surface is created. - if (!prefConfig.stretchVideo && decoderRenderer.isHardwareAccelerated()) { - resizeSurfaceWithAspectRatio((SurfaceView) findViewById(R.id.surfaceView), + if (!displayedFailureDialog) { + displayedFailureDialog = true; + stopConnection(); + Dialog.displayDialog(this, getResources().getString(R.string.conn_error_title), + getResources().getString(R.string.conn_error_msg)+" "+stage.getName(), true); + } + } + + @Override + public void connectionTerminated(Exception e) { + if (!displayedFailureDialog) { + displayedFailureDialog = true; + e.printStackTrace(); + + stopConnection(); + Dialog.displayDialog(this, getResources().getString(R.string.conn_terminated_title), + getResources().getString(R.string.conn_terminated_msg), true); + } + } + + @Override + public void connectionStarted() { + if (spinner != null) { + spinner.dismiss(); + spinner = null; + } + + connecting = false; + connected = true; + + hideSystemUi(1000); + } + + @Override + public void displayMessage(final String message) { + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(Game.this, message, Toast.LENGTH_LONG).show(); + } + }); + } + + @Override + public void displayTransientMessage(final String message) { + if (!prefConfig.disableWarnings) { + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(Game.this, message, Toast.LENGTH_LONG).show(); + } + }); + } + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + } + + @Override + public void surfaceCreated(SurfaceHolder holder) { + if (!connected && !connecting) { + connecting = true; + + // Resize the surface to match the aspect ratio of the video + // This must be done after the surface is created. + if (!prefConfig.stretchVideo && decoderRenderer.isHardwareAccelerated()) { + resizeSurfaceWithAspectRatio((SurfaceView) findViewById(R.id.surfaceView), prefConfig.width, prefConfig.height); - } - - conn.start(PlatformBinding.getDeviceName(), holder, drFlags, - PlatformBinding.getAudioRenderer(), decoderRenderer); - } - } + } - @Override - public void surfaceDestroyed(SurfaceHolder holder) { - if (connected) { - stopConnection(); - } - } + conn.start(PlatformBinding.getDeviceName(), holder, drFlags, + PlatformBinding.getAudioRenderer(), decoderRenderer); + } + } - @Override - public void mouseMove(int deltaX, int deltaY) { - conn.sendMouseMove((short) deltaX, (short) deltaY); - } + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + if (connected) { + stopConnection(); + } + } - @Override - public void mouseButtonEvent(int buttonId, boolean down) { - byte buttonIndex; - - switch (buttonId) - { - case EvdevListener.BUTTON_LEFT: - buttonIndex = MouseButtonPacket.BUTTON_LEFT; - break; - case EvdevListener.BUTTON_MIDDLE: - buttonIndex = MouseButtonPacket.BUTTON_MIDDLE; - break; - case EvdevListener.BUTTON_RIGHT: - buttonIndex = MouseButtonPacket.BUTTON_RIGHT; - break; - default: - LimeLog.warning("Unhandled button: "+buttonId); - return; - } - - if (down) { - conn.sendMouseButtonDown(buttonIndex); - } - else { - conn.sendMouseButtonUp(buttonIndex); - } - } + @Override + public void mouseMove(int deltaX, int deltaY) { + conn.sendMouseMove((short) deltaX, (short) deltaY); + } - @Override - public void mouseScroll(byte amount) { - conn.sendMouseScroll(amount); - } + @Override + public void mouseButtonEvent(int buttonId, boolean down) { + byte buttonIndex; - @Override - public void keyboardEvent(boolean buttonDown, short keyCode) { - short keyMap = keybTranslator.translate(keyCode); - if (keyMap != 0) { - if (handleSpecialKeys(keyMap, buttonDown)) { - return; - } - - if (buttonDown) { - keybTranslator.sendKeyDown(keyMap, getModifierState()); - } - else { - keybTranslator.sendKeyUp(keyMap, getModifierState()); - } - } - } + switch (buttonId) + { + case EvdevListener.BUTTON_LEFT: + buttonIndex = MouseButtonPacket.BUTTON_LEFT; + break; + case EvdevListener.BUTTON_MIDDLE: + buttonIndex = MouseButtonPacket.BUTTON_MIDDLE; + break; + case EvdevListener.BUTTON_RIGHT: + buttonIndex = MouseButtonPacket.BUTTON_RIGHT; + break; + default: + LimeLog.warning("Unhandled button: "+buttonId); + return; + } - @Override - public void onSystemUiVisibilityChange(int visibility) { - // Don't do anything if we're not connected - if (!connected) { - return; - } - - // This flag is set for all devices - if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) { - hideSystemUi(2000); - } - // This flag is only set on 4.4+ - else if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT && - (visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) { - hideSystemUi(2000); - } - // This flag is only set before 4.4+ - else if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.KITKAT && - (visibility & View.SYSTEM_UI_FLAG_LOW_PROFILE) == 0) { - hideSystemUi(2000); - } - } + if (down) { + conn.sendMouseButtonDown(buttonIndex); + } + else { + conn.sendMouseButtonUp(buttonIndex); + } + } + + @Override + public void mouseScroll(byte amount) { + conn.sendMouseScroll(amount); + } + + @Override + public void keyboardEvent(boolean buttonDown, short keyCode) { + short keyMap = keybTranslator.translate(keyCode); + if (keyMap != 0) { + if (handleSpecialKeys(keyMap, buttonDown)) { + return; + } + + if (buttonDown) { + keybTranslator.sendKeyDown(keyMap, getModifierState()); + } + else { + keybTranslator.sendKeyUp(keyMap, getModifierState()); + } + } + } + + @Override + public void onSystemUiVisibilityChange(int visibility) { + // Don't do anything if we're not connected + if (!connected) { + return; + } + + // This flag is set for all devices + if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) { + hideSystemUi(2000); + } + // This flag is only set on 4.4+ + else if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT && + (visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) { + hideSystemUi(2000); + } + // This flag is only set before 4.4+ + else if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.KITKAT && + (visibility & View.SYSTEM_UI_FLAG_LOW_PROFILE) == 0) { + hideSystemUi(2000); + } + } } diff --git a/app/src/main/java/com/limelight/PcView.java b/app/src/main/java/com/limelight/PcView.java index 74c74474..c6a44a9e 100644 --- a/app/src/main/java/com/limelight/PcView.java +++ b/app/src/main/java/com/limelight/PcView.java @@ -50,69 +50,69 @@ import android.widget.AdapterView.AdapterContextMenuInfo; public class PcView extends Activity implements AdapterFragmentCallbacks { private RelativeLayout noPcFoundLayout; private PcGridAdapter pcGridAdapter; - private ComputerManagerService.ComputerManagerBinder managerBinder; - private boolean freezeUpdates, runningPolling; - private final ServiceConnection serviceConnection = new ServiceConnection() { - public void onServiceConnected(ComponentName className, IBinder binder) { - final ComputerManagerService.ComputerManagerBinder localBinder = - ((ComputerManagerService.ComputerManagerBinder)binder); - - // Wait in a separate thread to avoid stalling the UI - new Thread() { - @Override - public void run() { - // Wait for the binder to be ready - localBinder.waitForReady(); - - // Now make the binder visible - managerBinder = localBinder; - - // Start updates - startComputerUpdates(); - - // Force a keypair to be generated early to avoid discovery delays - new AndroidCryptoProvider(PcView.this).getClientCertificate(); - } - }.start(); - } + private ComputerManagerService.ComputerManagerBinder managerBinder; + private boolean freezeUpdates, runningPolling; + private final ServiceConnection serviceConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder binder) { + final ComputerManagerService.ComputerManagerBinder localBinder = + ((ComputerManagerService.ComputerManagerBinder)binder); - public void onServiceDisconnected(ComponentName className) { - managerBinder = null; - } - }; - - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - - // Reinitialize views just in case orientation changed - initializeViews(); - } - - private final static int APP_LIST_ID = 1; - private final static int PAIR_ID = 2; - private final static int UNPAIR_ID = 3; - private final static int WOL_ID = 4; - private final static int DELETE_ID = 5; - - private void initializeViews() { - setContentView(R.layout.activity_pc_view); + // Wait in a separate thread to avoid stalling the UI + new Thread() { + @Override + public void run() { + // Wait for the binder to be ready + localBinder.waitForReady(); + + // Now make the binder visible + managerBinder = localBinder; + + // Start updates + startComputerUpdates(); + + // Force a keypair to be generated early to avoid discovery delays + new AndroidCryptoProvider(PcView.this).getClientCertificate(); + } + }.start(); + } + + public void onServiceDisconnected(ComponentName className) { + managerBinder = null; + } + }; + + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + // Reinitialize views just in case orientation changed + initializeViews(); + } + + private final static int APP_LIST_ID = 1; + private final static int PAIR_ID = 2; + private final static int UNPAIR_ID = 3; + private final static int WOL_ID = 4; + private final static int DELETE_ID = 5; + + private void initializeViews() { + setContentView(R.layout.activity_pc_view); UiHelper.notifyNewRootView(this); // Set default preferences if we've never been run PreferenceManager.setDefaultValues(this, R.xml.preferences, false); - // Setup the list view + // Setup the list view ImageButton settingsButton = (ImageButton) findViewById(R.id.settingsButton); ImageButton addComputerButton = (ImageButton) findViewById(R.id.manuallyAddPc); - settingsButton.setOnClickListener(new OnClickListener() { + settingsButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { startActivity(new Intent(PcView.this, StreamSettings.class)); } }); - addComputerButton.setOnClickListener(new OnClickListener() { + addComputerButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Intent i = new Intent(PcView.this, AddComputerManually.class); @@ -133,114 +133,114 @@ public class PcView extends Activity implements AdapterFragmentCallbacks { } pcGridAdapter.notifyDataSetChanged(); } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + + @Override + protected void onCreate(Bundle 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 - bindService(new Intent(PcView.this, ComputerManagerService.class), serviceConnection, - Service.BIND_AUTO_CREATE); + 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 + bindService(new Intent(PcView.this, ComputerManagerService.class), serviceConnection, + Service.BIND_AUTO_CREATE); pcGridAdapter = new PcGridAdapter(this, PreferenceConfiguration.readPreferences(this).listMode, PreferenceConfiguration.readPreferences(this).smallIconMode); - - initializeViews(); - } - - private void startComputerUpdates() { - if (managerBinder != null) { - if (runningPolling) { - return; - } - - freezeUpdates = false; - managerBinder.startPolling(new ComputerManagerListener() { - @Override - public void notifyComputerUpdated(final ComputerDetails details) { - if (!freezeUpdates) { - PcView.this.runOnUiThread(new Runnable() { - @Override - public void run() { - updateComputer(details); - } - }); - } - } - }); - runningPolling = true; - } - } - - private void stopComputerUpdates(boolean wait) { - if (managerBinder != null) { - if (!runningPolling) { - return; - } - - freezeUpdates = true; - - managerBinder.stopPolling(); - - if (wait) { - managerBinder.waitForPollingStopped(); - } - - runningPolling = false; - } - } - - @Override - public void onDestroy() { - super.onDestroy(); - - if (managerBinder != null) { - unbindService(serviceConnection); - } - } - - @Override - protected void onResume() { - super.onResume(); - - startComputerUpdates(); - } - - @Override - protected void onPause() { - super.onPause(); - - stopComputerUpdates(false); - } - - @Override - protected void onStop() { - super.onStop(); - - Dialog.closeDialogs(); - } - - @Override - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { - stopComputerUpdates(false); - - // Call superclass - super.onCreateContextMenu(menu, v, menuInfo); + + initializeViews(); + } + + private void startComputerUpdates() { + if (managerBinder != null) { + if (runningPolling) { + return; + } + + freezeUpdates = false; + managerBinder.startPolling(new ComputerManagerListener() { + @Override + public void notifyComputerUpdated(final ComputerDetails details) { + if (!freezeUpdates) { + PcView.this.runOnUiThread(new Runnable() { + @Override + public void run() { + updateComputer(details); + } + }); + } + } + }); + runningPolling = true; + } + } + + private void stopComputerUpdates(boolean wait) { + if (managerBinder != null) { + if (!runningPolling) { + return; + } + + freezeUpdates = true; + + managerBinder.stopPolling(); + + if (wait) { + managerBinder.waitForPollingStopped(); + } + + runningPolling = false; + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + + if (managerBinder != null) { + unbindService(serviceConnection); + } + } + + @Override + protected void onResume() { + super.onResume(); + + startComputerUpdates(); + } + + @Override + protected void onPause() { + super.onPause(); + + stopComputerUpdates(false); + } + + @Override + protected void onStop() { + super.onStop(); + + Dialog.closeDialogs(); + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { + stopComputerUpdates(false); + + // Call superclass + super.onCreateContextMenu(menu, v, menuInfo); AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo; ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(info.position); if (computer == null || computer.details == null || computer.details.reachability == ComputerDetails.Reachability.UNKNOWN) { - startComputerUpdates(); - return; + startComputerUpdates(); + return; } // Inflate the context menu @@ -260,94 +260,94 @@ public class PcView extends Activity implements AdapterFragmentCallbacks { menu.add(Menu.NONE, DELETE_ID, 2, getResources().getString(R.string.pcview_menu_delete_pc)); } } - - @Override - public void onContextMenuClosed(Menu menu) { - startComputerUpdates(); - } - - private void doPair(final ComputerDetails computer) { - if (computer.reachability == ComputerDetails.Reachability.OFFLINE) { - Toast.makeText(PcView.this, getResources().getString(R.string.pair_pc_offline), Toast.LENGTH_SHORT).show(); - return; - } - if (computer.runningGameId != 0) { - Toast.makeText(PcView.this, getResources().getString(R.string.pair_pc_ingame), Toast.LENGTH_LONG).show(); - return; - } - if (managerBinder == null) { - Toast.makeText(PcView.this, getResources().getString(R.string.error_manager_not_running), Toast.LENGTH_LONG).show(); - return; - } - - Toast.makeText(PcView.this, getResources().getString(R.string.pairing), Toast.LENGTH_SHORT).show(); - new Thread(new Runnable() { - @Override - public void run() { - NvHTTP httpConn; - String message; + + @Override + public void onContextMenuClosed(Menu menu) { + startComputerUpdates(); + } + + private void doPair(final ComputerDetails computer) { + if (computer.reachability == ComputerDetails.Reachability.OFFLINE) { + Toast.makeText(PcView.this, getResources().getString(R.string.pair_pc_offline), Toast.LENGTH_SHORT).show(); + return; + } + if (computer.runningGameId != 0) { + Toast.makeText(PcView.this, getResources().getString(R.string.pair_pc_ingame), Toast.LENGTH_LONG).show(); + return; + } + if (managerBinder == null) { + Toast.makeText(PcView.this, getResources().getString(R.string.error_manager_not_running), Toast.LENGTH_LONG).show(); + return; + } + + Toast.makeText(PcView.this, getResources().getString(R.string.pairing), Toast.LENGTH_SHORT).show(); + new Thread(new Runnable() { + @Override + public void run() { + NvHTTP httpConn; + String message; boolean success = false; - try { - // Stop updates and wait while pairing - stopComputerUpdates(true); - - InetAddress addr = null; - if (computer.reachability == ComputerDetails.Reachability.LOCAL) { - addr = computer.localIp; - } - else if (computer.reachability == ComputerDetails.Reachability.REMOTE) { - addr = computer.remoteIp; - } - - httpConn = new NvHTTP(addr, - managerBinder.getUniqueId(), - PlatformBinding.getDeviceName(), - PlatformBinding.getCryptoProvider(PcView.this)); - if (httpConn.getPairState() == PairingManager.PairState.PAIRED) { + try { + // Stop updates and wait while pairing + stopComputerUpdates(true); + + InetAddress addr = null; + if (computer.reachability == ComputerDetails.Reachability.LOCAL) { + addr = computer.localIp; + } + else if (computer.reachability == ComputerDetails.Reachability.REMOTE) { + addr = computer.remoteIp; + } + + httpConn = new NvHTTP(addr, + managerBinder.getUniqueId(), + PlatformBinding.getDeviceName(), + PlatformBinding.getCryptoProvider(PcView.this)); + if (httpConn.getPairState() == PairingManager.PairState.PAIRED) { // Don't display any toast, but open the app list - message = null; + message = null; success = true; - } - else { - final String pinStr = PairingManager.generatePinString(); - - // Spin the dialog off in a thread because it blocks - Dialog.displayDialog(PcView.this, getResources().getString(R.string.pair_pairing_title), - getResources().getString(R.string.pair_pairing_msg)+" "+pinStr, false); - - PairingManager.PairState pairState = httpConn.pair(pinStr); - if (pairState == PairingManager.PairState.PIN_WRONG) { - message = getResources().getString(R.string.pair_incorrect_pin); - } - else if (pairState == PairingManager.PairState.FAILED) { - message = getResources().getString(R.string.pair_fail); - } - else if (pairState == PairingManager.PairState.PAIRED) { + } + else { + final String pinStr = PairingManager.generatePinString(); + + // Spin the dialog off in a thread because it blocks + Dialog.displayDialog(PcView.this, getResources().getString(R.string.pair_pairing_title), + getResources().getString(R.string.pair_pairing_msg)+" "+pinStr, false); + + PairingManager.PairState pairState = httpConn.pair(pinStr); + if (pairState == PairingManager.PairState.PIN_WRONG) { + message = getResources().getString(R.string.pair_incorrect_pin); + } + else if (pairState == PairingManager.PairState.FAILED) { + message = getResources().getString(R.string.pair_fail); + } + else if (pairState == PairingManager.PairState.PAIRED) { // Just navigate to the app view without displaying a toast message = null; success = true; - } - else { - // Should be no other values - message = null; - } - } - } catch (UnknownHostException e) { - message = getResources().getString(R.string.error_unknown_host); - } catch (FileNotFoundException e) { - message = getResources().getString(R.string.error_404); - } catch (Exception e) { + } + else { + // Should be no other values + message = null; + } + } + } catch (UnknownHostException e) { + message = getResources().getString(R.string.error_unknown_host); + } catch (FileNotFoundException e) { + message = getResources().getString(R.string.error_404); + } catch (Exception e) { e.printStackTrace(); - message = e.getMessage(); - } - - Dialog.closeDialogs(); - - final String toastMessage = message; + message = e.getMessage(); + } + + Dialog.closeDialogs(); + + final String toastMessage = message; final boolean toastSuccess = success; - runOnUiThread(new Runnable() { - @Override - public void run() { + runOnUiThread(new Runnable() { + @Override + public void run() { if (toastMessage != null) { Toast.makeText(PcView.this, toastMessage, Toast.LENGTH_LONG).show(); } @@ -356,124 +356,124 @@ public class PcView extends Activity implements AdapterFragmentCallbacks { // Open the app list after a successful pairing attemp doAppList(computer); } - } - }); - - // Start polling again - startComputerUpdates(); - } - }).start(); - } - - private void doWakeOnLan(final ComputerDetails computer) { - if (computer.reachability != ComputerDetails.Reachability.OFFLINE) { - Toast.makeText(PcView.this, getResources().getString(R.string.wol_pc_online), Toast.LENGTH_SHORT).show(); - return; - } - - if (computer.macAddress == null) { - Toast.makeText(PcView.this, getResources().getString(R.string.wol_no_mac), Toast.LENGTH_SHORT).show(); - return; - } - - Toast.makeText(PcView.this, getResources().getString(R.string.wol_waking_pc), Toast.LENGTH_SHORT).show(); - new Thread(new Runnable() { - @Override - public void run() { - String message; - try { - WakeOnLanSender.sendWolPacket(computer); - message = getResources().getString(R.string.wol_waking_msg); - } catch (IOException e) { - message = getResources().getString(R.string.wol_fail); - } - - final String toastMessage = message; - runOnUiThread(new Runnable() { - @Override - public void run() { - Toast.makeText(PcView.this, toastMessage, Toast.LENGTH_LONG).show(); - } - }); - } - }).start(); - } - - private void doUnpair(final ComputerDetails computer) { - if (computer.reachability == ComputerDetails.Reachability.OFFLINE) { - Toast.makeText(PcView.this, getResources().getString(R.string.error_pc_offline), Toast.LENGTH_SHORT).show(); - return; - } - if (managerBinder == null) { - Toast.makeText(PcView.this, getResources().getString(R.string.error_manager_not_running), Toast.LENGTH_LONG).show(); - return; - } - - Toast.makeText(PcView.this, getResources().getString(R.string.unpairing), Toast.LENGTH_SHORT).show(); - new Thread(new Runnable() { - @Override - public void run() { - NvHTTP httpConn; - String message; - try { - InetAddress addr = null; - if (computer.reachability == ComputerDetails.Reachability.LOCAL) { - addr = computer.localIp; - } - else if (computer.reachability == ComputerDetails.Reachability.REMOTE) { - addr = computer.remoteIp; - } - - httpConn = new NvHTTP(addr, - managerBinder.getUniqueId(), - PlatformBinding.getDeviceName(), - PlatformBinding.getCryptoProvider(PcView.this)); - if (httpConn.getPairState() == PairingManager.PairState.PAIRED) { - httpConn.unpair(); - if (httpConn.getPairState() == PairingManager.PairState.NOT_PAIRED) { - message = getResources().getString(R.string.unpair_success); - } - else { - message = getResources().getString(R.string.unpair_fail); - } - } - else { - message = getResources().getString(R.string.unpair_error); - } - } catch (UnknownHostException e) { - message = getResources().getString(R.string.error_unknown_host); - } catch (FileNotFoundException e) { - message = getResources().getString(R.string.error_404); - } catch (Exception e) { - message = e.getMessage(); - } - - final String toastMessage = message; - runOnUiThread(new Runnable() { - @Override - public void run() { - Toast.makeText(PcView.this, toastMessage, Toast.LENGTH_LONG).show(); - } - }); - } - }).start(); - } - - private void doAppList(ComputerDetails computer) { - if (computer.reachability == ComputerDetails.Reachability.OFFLINE) { - Toast.makeText(PcView.this, getResources().getString(R.string.error_pc_offline), Toast.LENGTH_SHORT).show(); - return; - } - if (managerBinder == null) { - Toast.makeText(PcView.this, getResources().getString(R.string.error_manager_not_running), Toast.LENGTH_LONG).show(); - return; - } - - Intent i = new Intent(this, AppView.class); - i.putExtra(AppView.NAME_EXTRA, computer.name); - i.putExtra(AppView.UUID_EXTRA, computer.uuid.toString()); - startActivity(i); - } + } + }); + + // Start polling again + startComputerUpdates(); + } + }).start(); + } + + private void doWakeOnLan(final ComputerDetails computer) { + if (computer.reachability != ComputerDetails.Reachability.OFFLINE) { + Toast.makeText(PcView.this, getResources().getString(R.string.wol_pc_online), Toast.LENGTH_SHORT).show(); + return; + } + + if (computer.macAddress == null) { + Toast.makeText(PcView.this, getResources().getString(R.string.wol_no_mac), Toast.LENGTH_SHORT).show(); + return; + } + + Toast.makeText(PcView.this, getResources().getString(R.string.wol_waking_pc), Toast.LENGTH_SHORT).show(); + new Thread(new Runnable() { + @Override + public void run() { + String message; + try { + WakeOnLanSender.sendWolPacket(computer); + message = getResources().getString(R.string.wol_waking_msg); + } catch (IOException e) { + message = getResources().getString(R.string.wol_fail); + } + + final String toastMessage = message; + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(PcView.this, toastMessage, Toast.LENGTH_LONG).show(); + } + }); + } + }).start(); + } + + private void doUnpair(final ComputerDetails computer) { + if (computer.reachability == ComputerDetails.Reachability.OFFLINE) { + Toast.makeText(PcView.this, getResources().getString(R.string.error_pc_offline), Toast.LENGTH_SHORT).show(); + return; + } + if (managerBinder == null) { + Toast.makeText(PcView.this, getResources().getString(R.string.error_manager_not_running), Toast.LENGTH_LONG).show(); + return; + } + + Toast.makeText(PcView.this, getResources().getString(R.string.unpairing), Toast.LENGTH_SHORT).show(); + new Thread(new Runnable() { + @Override + public void run() { + NvHTTP httpConn; + String message; + try { + InetAddress addr = null; + if (computer.reachability == ComputerDetails.Reachability.LOCAL) { + addr = computer.localIp; + } + else if (computer.reachability == ComputerDetails.Reachability.REMOTE) { + addr = computer.remoteIp; + } + + httpConn = new NvHTTP(addr, + managerBinder.getUniqueId(), + PlatformBinding.getDeviceName(), + PlatformBinding.getCryptoProvider(PcView.this)); + if (httpConn.getPairState() == PairingManager.PairState.PAIRED) { + httpConn.unpair(); + if (httpConn.getPairState() == PairingManager.PairState.NOT_PAIRED) { + message = getResources().getString(R.string.unpair_success); + } + else { + message = getResources().getString(R.string.unpair_fail); + } + } + else { + message = getResources().getString(R.string.unpair_error); + } + } catch (UnknownHostException e) { + message = getResources().getString(R.string.error_unknown_host); + } catch (FileNotFoundException e) { + message = getResources().getString(R.string.error_404); + } catch (Exception e) { + message = e.getMessage(); + } + + final String toastMessage = message; + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(PcView.this, toastMessage, Toast.LENGTH_LONG).show(); + } + }); + } + }).start(); + } + + private void doAppList(ComputerDetails computer) { + if (computer.reachability == ComputerDetails.Reachability.OFFLINE) { + Toast.makeText(PcView.this, getResources().getString(R.string.error_pc_offline), Toast.LENGTH_SHORT).show(); + return; + } + if (managerBinder == null) { + Toast.makeText(PcView.this, getResources().getString(R.string.error_manager_not_running), Toast.LENGTH_LONG).show(); + return; + } + + Intent i = new Intent(this, AppView.class); + i.putExtra(AppView.NAME_EXTRA, computer.name); + i.putExtra(AppView.UUID_EXTRA, computer.uuid.toString()); + startActivity(i); + } @Override public boolean onContextItemSelected(MenuItem item) { @@ -482,75 +482,75 @@ public class PcView extends Activity implements AdapterFragmentCallbacks { switch (item.getItemId()) { case PAIR_ID: - doPair(computer.details); - return true; - + doPair(computer.details); + return true; + case UNPAIR_ID: - doUnpair(computer.details); - return true; - + doUnpair(computer.details); + return true; + case WOL_ID: - doWakeOnLan(computer.details); - return true; - + doWakeOnLan(computer.details); + return true; + case DELETE_ID: - if (managerBinder == null) { - Toast.makeText(PcView.this, getResources().getString(R.string.error_manager_not_running), Toast.LENGTH_LONG).show(); - return true; - } - managerBinder.removeComputer(computer.details.name); - removeComputer(computer.details); - return true; - + if (managerBinder == null) { + Toast.makeText(PcView.this, getResources().getString(R.string.error_manager_not_running), Toast.LENGTH_LONG).show(); + return true; + } + managerBinder.removeComputer(computer.details.name); + removeComputer(computer.details); + return true; + case APP_LIST_ID: - doAppList(computer.details); - return true; - + doAppList(computer.details); + return true; + default: return super.onContextItemSelected(item); } } private void removeComputer(ComputerDetails details) { - for (int i = 0; i < pcGridAdapter.getCount(); i++) { - ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(i); - - if (details.equals(computer.details)) { + for (int i = 0; i < pcGridAdapter.getCount(); i++) { + ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(i); + + if (details.equals(computer.details)) { pcGridAdapter.removeComputer(computer); pcGridAdapter.notifyDataSetChanged(); - break; - } - } + break; + } + } } - private void updateComputer(ComputerDetails details) { - ComputerObject existingEntry = null; - - for (int i = 0; i < pcGridAdapter.getCount(); i++) { - ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(i); - - // Check if this is the same computer - if (details.uuid.equals(computer.details.uuid)) { - existingEntry = computer; - break; - } - } - - if (existingEntry != null) { - // Replace the information in the existing entry - existingEntry.details = details; - } - else { - // Add a new entry + private void updateComputer(ComputerDetails details) { + ComputerObject existingEntry = null; + + for (int i = 0; i < pcGridAdapter.getCount(); i++) { + ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(i); + + // Check if this is the same computer + if (details.uuid.equals(computer.details.uuid)) { + existingEntry = computer; + break; + } + } + + if (existingEntry != null) { + // Replace the information in the existing entry + existingEntry.details = details; + } + else { + // Add a new entry pcGridAdapter.addComputer(new ComputerObject(details)); // Remove the "Discovery in progress" view noPcFoundLayout.setVisibility(View.INVISIBLE); - } + } // Notify the view that the data has changed pcGridAdapter.notifyDataSetChanged(); - } + } @Override public int getAdapterFragmentLayoutId() { @@ -584,15 +584,15 @@ public class PcView extends Activity implements AdapterFragmentCallbacks { } public class ComputerObject { - public ComputerDetails details; - - public ComputerObject(ComputerDetails details) { - this.details = details; - } - - @Override - public String toString() { - return details.name; - } - } + public ComputerDetails details; + + public ComputerObject(ComputerDetails details) { + this.details = details; + } + + @Override + public String toString() { + return details.name; + } + } } diff --git a/app/src/main/java/com/limelight/binding/PlatformBinding.java b/app/src/main/java/com/limelight/binding/PlatformBinding.java index e159dd4e..b37c7645 100644 --- a/app/src/main/java/com/limelight/binding/PlatformBinding.java +++ b/app/src/main/java/com/limelight/binding/PlatformBinding.java @@ -8,17 +8,17 @@ import com.limelight.nvstream.av.audio.AudioRenderer; import com.limelight.nvstream.http.LimelightCryptoProvider; public class PlatformBinding { - public static String getDeviceName() { - String deviceName = android.os.Build.MODEL; + public static String getDeviceName() { + String deviceName = android.os.Build.MODEL; deviceName = deviceName.replace(" ", ""); return deviceName; - } - - public static AudioRenderer getAudioRenderer() { - return new AndroidAudioRenderer(); - } - - public static LimelightCryptoProvider getCryptoProvider(Context c) { - return new AndroidCryptoProvider(c); - } + } + + public static AudioRenderer getAudioRenderer() { + return new AndroidAudioRenderer(); + } + + public static LimelightCryptoProvider getCryptoProvider(Context c) { + return new AndroidCryptoProvider(c); + } } diff --git a/app/src/main/java/com/limelight/binding/audio/AndroidAudioRenderer.java b/app/src/main/java/com/limelight/binding/audio/AndroidAudioRenderer.java index 83c5fbb7..9f849a9e 100644 --- a/app/src/main/java/com/limelight/binding/audio/AndroidAudioRenderer.java +++ b/app/src/main/java/com/limelight/binding/audio/AndroidAudioRenderer.java @@ -9,27 +9,27 @@ import com.limelight.nvstream.av.audio.AudioRenderer; public class AndroidAudioRenderer implements AudioRenderer { - private static final int FRAME_SIZE = 960; - - private AudioTrack track; + private static final int FRAME_SIZE = 960; - @Override - public boolean streamInitialized(int channelCount, int sampleRate) { - int channelConfig; - int bufferSize; + private AudioTrack track; - switch (channelCount) - { - case 1: - channelConfig = AudioFormat.CHANNEL_OUT_MONO; - break; - case 2: - channelConfig = AudioFormat.CHANNEL_OUT_STEREO; - break; - default: - LimeLog.severe("Decoder returned unhandled channel count"); - return false; - } + @Override + public boolean streamInitialized(int channelCount, int sampleRate) { + int channelConfig; + int bufferSize; + + switch (channelCount) + { + case 1: + channelConfig = AudioFormat.CHANNEL_OUT_MONO; + break; + case 2: + channelConfig = AudioFormat.CHANNEL_OUT_STEREO; + break; + default: + LimeLog.severe("Decoder returned unhandled channel count"); + return false; + } // We're not supposed to request less than the minimum // buffer size for our buffer, but it appears that we can @@ -72,26 +72,26 @@ public class AndroidAudioRenderer implements AudioRenderer { AudioTrack.MODE_STREAM); track.play(); } - - LimeLog.info("Audio track buffer size: "+bufferSize); - return true; - } + LimeLog.info("Audio track buffer size: "+bufferSize); - @Override - public void playDecodedAudio(byte[] audioData, int offset, int length) { - track.write(audioData, offset, length); - } + return true; + } - @Override - public void streamClosing() { - if (track != null) { - track.release(); - } - } + @Override + public void playDecodedAudio(byte[] audioData, int offset, int length) { + track.write(audioData, offset, length); + } - @Override - public int getCapabilities() { - return 0; - } + @Override + public void streamClosing() { + if (track != null) { + track.release(); + } + } + + @Override + public int getCapabilities() { + return 0; + } } diff --git a/app/src/main/java/com/limelight/binding/crypto/AndroidCryptoProvider.java b/app/src/main/java/com/limelight/binding/crypto/AndroidCryptoProvider.java index 879482fa..a210ba61 100644 --- a/app/src/main/java/com/limelight/binding/crypto/AndroidCryptoProvider.java +++ b/app/src/main/java/com/limelight/binding/crypto/AndroidCryptoProvider.java @@ -45,239 +45,239 @@ import com.limelight.nvstream.http.LimelightCryptoProvider; public class AndroidCryptoProvider implements LimelightCryptoProvider { - private final File certFile; - private final File keyFile; - - private X509Certificate cert; - private RSAPrivateKey key; - private byte[] pemCertBytes; - - private static final Object globalCryptoLock = new Object(); - - static { - // Install the Bouncy Castle provider - Security.addProvider(new BouncyCastleProvider()); - } - - public AndroidCryptoProvider(Context c) { - String dataPath = c.getFilesDir().getAbsolutePath(); - - certFile = new File(dataPath + File.separator + "client.crt"); - keyFile = new File(dataPath + File.separator + "client.key"); - } - - private byte[] loadFileToBytes(File f) { - if (!f.exists()) { - return null; - } - - try { - FileInputStream fin = new FileInputStream(f); - byte[] fileData = new byte[(int) f.length()]; - if (fin.read(fileData) != f.length()) { + private final File certFile; + private final File keyFile; + + private X509Certificate cert; + private RSAPrivateKey key; + private byte[] pemCertBytes; + + private static final Object globalCryptoLock = new Object(); + + static { + // Install the Bouncy Castle provider + Security.addProvider(new BouncyCastleProvider()); + } + + public AndroidCryptoProvider(Context c) { + String dataPath = c.getFilesDir().getAbsolutePath(); + + certFile = new File(dataPath + File.separator + "client.crt"); + keyFile = new File(dataPath + File.separator + "client.key"); + } + + private byte[] loadFileToBytes(File f) { + if (!f.exists()) { + return null; + } + + try { + FileInputStream fin = new FileInputStream(f); + byte[] fileData = new byte[(int) f.length()]; + if (fin.read(fileData) != f.length()) { // Failed to read fileData = null; } - fin.close(); - return fileData; - } catch (IOException e) { - return null; - } - } - - private boolean loadCertKeyPair() { - byte[] certBytes = loadFileToBytes(certFile); - byte[] keyBytes = loadFileToBytes(keyFile); - - // If either file was missing, we definitely can't succeed - if (certBytes == null || keyBytes == null) { - LimeLog.info("Missing cert or key; need to generate a new one"); - return false; - } - - try { - CertificateFactory certFactory = CertificateFactory.getInstance("X.509", "BC"); - cert = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(certBytes)); - pemCertBytes = certBytes; - KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC"); - key = (RSAPrivateKey) keyFactory.generatePrivate(new PKCS8EncodedKeySpec(keyBytes)); - } catch (CertificateException e) { - // May happen if the cert is corrupt - LimeLog.warning("Corrupted certificate"); - return false; - } catch (NoSuchAlgorithmException e) { - // Should never happen - e.printStackTrace(); - return false; - } catch (InvalidKeySpecException e) { - // May happen if the key is corrupt - LimeLog.warning("Corrupted key"); - return false; - } catch (NoSuchProviderException e) { - // Should never happen - e.printStackTrace(); - return false; - } - - return true; - } - - @SuppressLint("TrulyRandom") - private boolean generateCertKeyPair() { - byte[] snBytes = new byte[8]; - new SecureRandom().nextBytes(snBytes); - - KeyPair keyPair; - try { - KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC"); - keyPairGenerator.initialize(2048); - keyPair = keyPairGenerator.generateKeyPair(); - } catch (NoSuchAlgorithmException e1) { - // Should never happen - e1.printStackTrace(); - return false; - } catch (NoSuchProviderException e) { - // Should never happen - e.printStackTrace(); - return false; - } - - Date now = new Date(); - - // Expires in 20 years - Calendar calendar = Calendar.getInstance(); - calendar.setTime(now); - calendar.add(Calendar.YEAR, 20); - Date expirationDate = calendar.getTime(); - - BigInteger serial = new BigInteger(snBytes).abs(); - - X500NameBuilder nameBuilder = new X500NameBuilder(BCStyle.INSTANCE); - nameBuilder.addRDN(BCStyle.CN, "NVIDIA GameStream Client"); - X500Name name = nameBuilder.build(); - - X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder(name, serial, now, expirationDate, Locale.ENGLISH, name, - SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded())); - - try { - ContentSigner sigGen = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BouncyCastleProvider.PROVIDER_NAME).build(keyPair.getPrivate()); - cert = new JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME).getCertificate(certBuilder.build(sigGen)); - key = (RSAPrivateKey) keyPair.getPrivate(); - } catch (Exception e) { - // Nothing should go wrong here - e.printStackTrace(); - return false; - } - - LimeLog.info("Generated a new key pair"); - - // Save the resulting pair - saveCertKeyPair(); - - return true; - } - - private void saveCertKeyPair() { - try { - FileOutputStream certOut = new FileOutputStream(certFile); - FileOutputStream keyOut = new FileOutputStream(keyFile); - - // Write the certificate in OpenSSL PEM format (important for the server) - StringWriter strWriter = new StringWriter(); - JcaPEMWriter pemWriter = new JcaPEMWriter(strWriter); - pemWriter.writeObject(cert); - pemWriter.close(); - - // Line endings MUST be UNIX for the PC to accept the cert properly - OutputStreamWriter certWriter = new OutputStreamWriter(certOut); - String pemStr = strWriter.getBuffer().toString(); - for (int i = 0; i < pemStr.length(); i++) { - char c = pemStr.charAt(i); - if (c != '\r') - certWriter.append(c); - } - certWriter.close(); - - // Write the private out in PKCS8 format - keyOut.write(key.getEncoded()); - - certOut.close(); - keyOut.close(); - - LimeLog.info("Saved generated key pair to disk"); - } catch (IOException e) { - // This isn't good because it means we'll have - // to re-pair next time - e.printStackTrace(); - } - } - - public X509Certificate getClientCertificate() { - // Use a lock here to ensure only one guy will be generating or loading - // the certificate and key at a time - synchronized (globalCryptoLock) { - // Return a loaded cert if we have one - if (cert != null) { - return cert; - } - - // No loaded cert yet, let's see if we have one on disk - if (loadCertKeyPair()) { - // Got one - return cert; - } - - // Try to generate a new key pair - if (!generateCertKeyPair()) { - // Failed - return null; - } - - // Load the generated pair - loadCertKeyPair(); - return cert; - } - } + fin.close(); + return fileData; + } catch (IOException e) { + return null; + } + } - public RSAPrivateKey getClientPrivateKey() { - // Use a lock here to ensure only one guy will be generating or loading - // the certificate and key at a time - synchronized (globalCryptoLock) { - // Return a loaded key if we have one - if (key != null) { - return key; - } - - // No loaded key yet, let's see if we have one on disk - if (loadCertKeyPair()) { - // Got one - return key; - } - - // Try to generate a new key pair - if (!generateCertKeyPair()) { - // Failed - return null; - } - - // Load the generated pair - loadCertKeyPair(); - return key; - } - } - - public byte[] getPemEncodedClientCertificate() { - synchronized (globalCryptoLock) { - // Call our helper function to do the cert loading/generation for us - getClientCertificate(); - - // Return a cached value if we have it - return pemCertBytes; - } - } + private boolean loadCertKeyPair() { + byte[] certBytes = loadFileToBytes(certFile); + byte[] keyBytes = loadFileToBytes(keyFile); - @Override - public String encodeBase64String(byte[] data) { - return Base64.encodeToString(data, Base64.NO_WRAP); - } + // If either file was missing, we definitely can't succeed + if (certBytes == null || keyBytes == null) { + LimeLog.info("Missing cert or key; need to generate a new one"); + return false; + } + + try { + CertificateFactory certFactory = CertificateFactory.getInstance("X.509", "BC"); + cert = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(certBytes)); + pemCertBytes = certBytes; + KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC"); + key = (RSAPrivateKey) keyFactory.generatePrivate(new PKCS8EncodedKeySpec(keyBytes)); + } catch (CertificateException e) { + // May happen if the cert is corrupt + LimeLog.warning("Corrupted certificate"); + return false; + } catch (NoSuchAlgorithmException e) { + // Should never happen + e.printStackTrace(); + return false; + } catch (InvalidKeySpecException e) { + // May happen if the key is corrupt + LimeLog.warning("Corrupted key"); + return false; + } catch (NoSuchProviderException e) { + // Should never happen + e.printStackTrace(); + return false; + } + + return true; + } + + @SuppressLint("TrulyRandom") + private boolean generateCertKeyPair() { + byte[] snBytes = new byte[8]; + new SecureRandom().nextBytes(snBytes); + + KeyPair keyPair; + try { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC"); + keyPairGenerator.initialize(2048); + keyPair = keyPairGenerator.generateKeyPair(); + } catch (NoSuchAlgorithmException e1) { + // Should never happen + e1.printStackTrace(); + return false; + } catch (NoSuchProviderException e) { + // Should never happen + e.printStackTrace(); + return false; + } + + Date now = new Date(); + + // Expires in 20 years + Calendar calendar = Calendar.getInstance(); + calendar.setTime(now); + calendar.add(Calendar.YEAR, 20); + Date expirationDate = calendar.getTime(); + + BigInteger serial = new BigInteger(snBytes).abs(); + + X500NameBuilder nameBuilder = new X500NameBuilder(BCStyle.INSTANCE); + nameBuilder.addRDN(BCStyle.CN, "NVIDIA GameStream Client"); + X500Name name = nameBuilder.build(); + + X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder(name, serial, now, expirationDate, Locale.ENGLISH, name, + SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded())); + + try { + ContentSigner sigGen = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BouncyCastleProvider.PROVIDER_NAME).build(keyPair.getPrivate()); + cert = new JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME).getCertificate(certBuilder.build(sigGen)); + key = (RSAPrivateKey) keyPair.getPrivate(); + } catch (Exception e) { + // Nothing should go wrong here + e.printStackTrace(); + return false; + } + + LimeLog.info("Generated a new key pair"); + + // Save the resulting pair + saveCertKeyPair(); + + return true; + } + + private void saveCertKeyPair() { + try { + FileOutputStream certOut = new FileOutputStream(certFile); + FileOutputStream keyOut = new FileOutputStream(keyFile); + + // Write the certificate in OpenSSL PEM format (important for the server) + StringWriter strWriter = new StringWriter(); + JcaPEMWriter pemWriter = new JcaPEMWriter(strWriter); + pemWriter.writeObject(cert); + pemWriter.close(); + + // Line endings MUST be UNIX for the PC to accept the cert properly + OutputStreamWriter certWriter = new OutputStreamWriter(certOut); + String pemStr = strWriter.getBuffer().toString(); + for (int i = 0; i < pemStr.length(); i++) { + char c = pemStr.charAt(i); + if (c != '\r') + certWriter.append(c); + } + certWriter.close(); + + // Write the private out in PKCS8 format + keyOut.write(key.getEncoded()); + + certOut.close(); + keyOut.close(); + + LimeLog.info("Saved generated key pair to disk"); + } catch (IOException e) { + // This isn't good because it means we'll have + // to re-pair next time + e.printStackTrace(); + } + } + + public X509Certificate getClientCertificate() { + // Use a lock here to ensure only one guy will be generating or loading + // the certificate and key at a time + synchronized (globalCryptoLock) { + // Return a loaded cert if we have one + if (cert != null) { + return cert; + } + + // No loaded cert yet, let's see if we have one on disk + if (loadCertKeyPair()) { + // Got one + return cert; + } + + // Try to generate a new key pair + if (!generateCertKeyPair()) { + // Failed + return null; + } + + // Load the generated pair + loadCertKeyPair(); + return cert; + } + } + + public RSAPrivateKey getClientPrivateKey() { + // Use a lock here to ensure only one guy will be generating or loading + // the certificate and key at a time + synchronized (globalCryptoLock) { + // Return a loaded key if we have one + if (key != null) { + return key; + } + + // No loaded key yet, let's see if we have one on disk + if (loadCertKeyPair()) { + // Got one + return key; + } + + // Try to generate a new key pair + if (!generateCertKeyPair()) { + // Failed + return null; + } + + // Load the generated pair + loadCertKeyPair(); + return key; + } + } + + public byte[] getPemEncodedClientCertificate() { + synchronized (globalCryptoLock) { + // Call our helper function to do the cert loading/generation for us + getClientCertificate(); + + // Return a cached value if we have it + return pemCertBytes; + } + } + + @Override + public String encodeBase64String(byte[] data) { + return Base64.encodeToString(data, Base64.NO_WRAP); + } } diff --git a/app/src/main/java/com/limelight/binding/input/ControllerHandler.java b/app/src/main/java/com/limelight/binding/input/ControllerHandler.java index aa84d530..24551bad 100644 --- a/app/src/main/java/com/limelight/binding/input/ControllerHandler.java +++ b/app/src/main/java/com/limelight/binding/input/ControllerHandler.java @@ -17,23 +17,23 @@ import com.limelight.utils.Vector2d; public class ControllerHandler implements InputManager.InputDeviceListener { - private static final int MAXIMUM_BUMPER_UP_DELAY_MS = 100; + private static final int MAXIMUM_BUMPER_UP_DELAY_MS = 100; private static final int START_DOWN_TIME_KEYB_MS = 750; - - private static final int MINIMUM_BUTTON_DOWN_TIME_MS = 25; - - private static final int EMULATING_SPECIAL = 0x1; - private static final int EMULATING_SELECT = 0x2; - - private static final int EMULATED_SPECIAL_UP_DELAY_MS = 100; - private static final int EMULATED_SELECT_UP_DELAY_MS = 30; - - private final Vector2d inputVector = new Vector2d(); - - private final HashMap contexts = new HashMap(); - - private final NvConnection conn; + + private static final int MINIMUM_BUTTON_DOWN_TIME_MS = 25; + + private static final int EMULATING_SPECIAL = 0x1; + private static final int EMULATING_SELECT = 0x2; + + private static final int EMULATED_SPECIAL_UP_DELAY_MS = 100; + private static final int EMULATED_SELECT_UP_DELAY_MS = 30; + + private final Vector2d inputVector = new Vector2d(); + + private final HashMap contexts = new HashMap(); + + private final NvConnection conn; private final double stickDeadzone; private final ControllerContext defaultContext = new ControllerContext(); private final GameGestures gestures; @@ -41,9 +41,9 @@ public class ControllerHandler implements InputManager.InputDeviceListener { private final boolean multiControllerEnabled; private short currentControllers; - - public ControllerHandler(NvConnection conn, GameGestures gestures, boolean multiControllerEnabled, int deadzonePercentage) { - this.conn = conn; + + public ControllerHandler(NvConnection conn, GameGestures gestures, boolean multiControllerEnabled, int deadzonePercentage) { + this.conn = conn; this.gestures = gestures; this.multiControllerEnabled = multiControllerEnabled; @@ -82,20 +82,20 @@ public class ControllerHandler implements InputManager.InputDeviceListener { defaultContext.leftTriggerAxis = MotionEvent.AXIS_BRAKE; defaultContext.rightTriggerAxis = MotionEvent.AXIS_GAS; defaultContext.controllerNumber = (short) 0; - } - - private static InputDevice.MotionRange getMotionRangeForJoystickAxis(InputDevice dev, int axis) { - InputDevice.MotionRange range; - - // First get the axis for SOURCE_JOYSTICK - range = dev.getMotionRange(axis, InputDevice.SOURCE_JOYSTICK); - if (range == null) { - // Now try the axis for SOURCE_GAMEPAD - range = dev.getMotionRange(axis, InputDevice.SOURCE_GAMEPAD); - } - - return range; - } + } + + private static InputDevice.MotionRange getMotionRangeForJoystickAxis(InputDevice dev, int axis) { + InputDevice.MotionRange range; + + // First get the axis for SOURCE_JOYSTICK + range = dev.getMotionRange(axis, InputDevice.SOURCE_JOYSTICK); + if (range == null) { + // Now try the axis for SOURCE_GAMEPAD + range = dev.getMotionRange(axis, InputDevice.SOURCE_GAMEPAD); + } + + return range; + } private short assignNewControllerNumber() { for (short i = 0; i < 4; i++) { @@ -137,9 +137,9 @@ public class ControllerHandler implements InputManager.InputDeviceListener { LimeLog.info("Controller number "+controllerNumber+" is now available"); currentControllers &= ~(1 << controllerNumber); } - - private ControllerContext createContextForDevice(InputDevice dev) { - ControllerContext context = new ControllerContext(); + + private ControllerContext createContextForDevice(InputDevice dev) { + ControllerContext context = new ControllerContext(); String devName = dev.getName(); LimeLog.info("Creating controller context for device: "+devName); @@ -148,91 +148,91 @@ public class ControllerHandler implements InputManager.InputDeviceListener { context.id = dev.getId(); context.leftStickXAxis = MotionEvent.AXIS_X; - context.leftStickYAxis = MotionEvent.AXIS_Y; + context.leftStickYAxis = MotionEvent.AXIS_Y; if (getMotionRangeForJoystickAxis(dev, context.leftStickXAxis) != null && getMotionRangeForJoystickAxis(dev, context.leftStickYAxis) != null) { // This is a gamepad hasGameController = true; context.hasJoystickAxes = true; } - - InputDevice.MotionRange leftTriggerRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_LTRIGGER); - InputDevice.MotionRange rightTriggerRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RTRIGGER); - InputDevice.MotionRange brakeRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_BRAKE); - InputDevice.MotionRange gasRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_GAS); - if (leftTriggerRange != null && rightTriggerRange != null) - { - // Some controllers use LTRIGGER and RTRIGGER (like Ouya) - context.leftTriggerAxis = MotionEvent.AXIS_LTRIGGER; - context.rightTriggerAxis = MotionEvent.AXIS_RTRIGGER; - } - else if (brakeRange != null && gasRange != null) - { - // Others use GAS and BRAKE (like Moga) - context.leftTriggerAxis = MotionEvent.AXIS_BRAKE; - context.rightTriggerAxis = MotionEvent.AXIS_GAS; - } - else - { - InputDevice.MotionRange rxRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RX); - InputDevice.MotionRange ryRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RY); - if (rxRange != null && ryRange != null && devName != null) { - if (devName.contains("Xbox") || devName.contains("XBox") || devName.contains("X-Box")) { - // Xbox controllers use RX and RY for right stick - context.rightStickXAxis = MotionEvent.AXIS_RX; - context.rightStickYAxis = MotionEvent.AXIS_RY; - - // Xbox controllers use Z and RZ for triggers - context.leftTriggerAxis = MotionEvent.AXIS_Z; - context.rightTriggerAxis = MotionEvent.AXIS_RZ; - context.triggersIdleNegative = true; - context.isXboxController = true; - } - else { - // DS4 controller uses RX and RY for triggers - context.leftTriggerAxis = MotionEvent.AXIS_RX; - context.rightTriggerAxis = MotionEvent.AXIS_RY; - context.triggersIdleNegative = true; - - context.isDualShock4 = true; - } - } - } - - if (context.rightStickXAxis == -1 && context.rightStickYAxis == -1) { - InputDevice.MotionRange zRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_Z); - InputDevice.MotionRange rzRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RZ); - - // Most other controllers use Z and RZ for the right stick - if (zRange != null && rzRange != null) { - context.rightStickXAxis = MotionEvent.AXIS_Z; - context.rightStickYAxis = MotionEvent.AXIS_RZ; - } - else { - InputDevice.MotionRange rxRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RX); - InputDevice.MotionRange ryRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RY); - - // Try RX and RY now - if (rxRange != null && ryRange != null) { - context.rightStickXAxis = MotionEvent.AXIS_RX; - context.rightStickYAxis = MotionEvent.AXIS_RY; - } - } - } - - // Some devices have "hats" for d-pads - InputDevice.MotionRange hatXRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_HAT_X); - InputDevice.MotionRange hatYRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_HAT_Y); - if (hatXRange != null && hatYRange != null) { - context.hatXAxis = MotionEvent.AXIS_HAT_X; - context.hatYAxis = MotionEvent.AXIS_HAT_Y; - } - - if (context.leftStickXAxis != -1 && context.leftStickYAxis != -1) { + + InputDevice.MotionRange leftTriggerRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_LTRIGGER); + InputDevice.MotionRange rightTriggerRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RTRIGGER); + InputDevice.MotionRange brakeRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_BRAKE); + InputDevice.MotionRange gasRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_GAS); + if (leftTriggerRange != null && rightTriggerRange != null) + { + // Some controllers use LTRIGGER and RTRIGGER (like Ouya) + context.leftTriggerAxis = MotionEvent.AXIS_LTRIGGER; + context.rightTriggerAxis = MotionEvent.AXIS_RTRIGGER; + } + else if (brakeRange != null && gasRange != null) + { + // Others use GAS and BRAKE (like Moga) + context.leftTriggerAxis = MotionEvent.AXIS_BRAKE; + context.rightTriggerAxis = MotionEvent.AXIS_GAS; + } + else + { + InputDevice.MotionRange rxRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RX); + InputDevice.MotionRange ryRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RY); + if (rxRange != null && ryRange != null && devName != null) { + if (devName.contains("Xbox") || devName.contains("XBox") || devName.contains("X-Box")) { + // Xbox controllers use RX and RY for right stick + context.rightStickXAxis = MotionEvent.AXIS_RX; + context.rightStickYAxis = MotionEvent.AXIS_RY; + + // Xbox controllers use Z and RZ for triggers + context.leftTriggerAxis = MotionEvent.AXIS_Z; + context.rightTriggerAxis = MotionEvent.AXIS_RZ; + context.triggersIdleNegative = true; + context.isXboxController = true; + } + else { + // DS4 controller uses RX and RY for triggers + context.leftTriggerAxis = MotionEvent.AXIS_RX; + context.rightTriggerAxis = MotionEvent.AXIS_RY; + context.triggersIdleNegative = true; + + context.isDualShock4 = true; + } + } + } + + if (context.rightStickXAxis == -1 && context.rightStickYAxis == -1) { + InputDevice.MotionRange zRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_Z); + InputDevice.MotionRange rzRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RZ); + + // Most other controllers use Z and RZ for the right stick + if (zRange != null && rzRange != null) { + context.rightStickXAxis = MotionEvent.AXIS_Z; + context.rightStickYAxis = MotionEvent.AXIS_RZ; + } + else { + InputDevice.MotionRange rxRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RX); + InputDevice.MotionRange ryRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RY); + + // Try RX and RY now + if (rxRange != null && ryRange != null) { + context.rightStickXAxis = MotionEvent.AXIS_RX; + context.rightStickYAxis = MotionEvent.AXIS_RY; + } + } + } + + // Some devices have "hats" for d-pads + InputDevice.MotionRange hatXRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_HAT_X); + InputDevice.MotionRange hatYRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_HAT_Y); + if (hatXRange != null && hatYRange != null) { + context.hatXAxis = MotionEvent.AXIS_HAT_X; + context.hatYAxis = MotionEvent.AXIS_HAT_Y; + } + + if (context.leftStickXAxis != -1 && context.leftStickYAxis != -1) { context.leftStickDeadzoneRadius = (float) stickDeadzone; - } - - if (context.rightStickXAxis != -1 && context.rightStickYAxis != -1) { + } + + if (context.rightStickXAxis != -1 && context.rightStickYAxis != -1) { context.rightStickDeadzoneRadius = (float) stickDeadzone; } @@ -300,114 +300,114 @@ public class ControllerHandler implements InputManager.InputDeviceListener { LimeLog.info("Assigned as controller "+context.controllerNumber); return context; - } - - private ControllerContext getContextForDevice(InputDevice dev) { - // Unknown devices use the default context - if (dev == null) { - return defaultContext; - } - - String descriptor = dev.getDescriptor(); - - // Return the existing context if it exists - ControllerContext context = contexts.get(descriptor); - if (context != null) { - return context; - } - - // Otherwise create a new context + } + + private ControllerContext getContextForDevice(InputDevice dev) { + // Unknown devices use the default context + if (dev == null) { + return defaultContext; + } + + String descriptor = dev.getDescriptor(); + + // Return the existing context if it exists + ControllerContext context = contexts.get(descriptor); + if (context != null) { + return context; + } + + // Otherwise create a new context context = createContextForDevice(dev); - contexts.put(descriptor, context); - - return context; - } - - private void sendControllerInputPacket(ControllerContext context) { - conn.sendControllerInput(context.controllerNumber, context.inputMap, + contexts.put(descriptor, context); + + return context; + } + + private void sendControllerInputPacket(ControllerContext context) { + conn.sendControllerInput(context.controllerNumber, context.inputMap, context.leftTrigger, context.rightTrigger, context.leftStickX, context.leftStickY, context.rightStickX, context.rightStickY); - } + } // Return a valid keycode, 0 to consume, or -1 to not consume the event // Device MAY BE NULL - private int handleRemapping(ControllerContext context, KeyEvent event) { + private int handleRemapping(ControllerContext context, KeyEvent event) { // For remotes, don't capture the back button - if (context.isRemote) { + if (context.isRemote) { if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { return -1; } } if (context.isDualShock4) { - switch (event.getKeyCode()) { - case KeyEvent.KEYCODE_BUTTON_Y: - return KeyEvent.KEYCODE_BUTTON_L1; - - case KeyEvent.KEYCODE_BUTTON_Z: - return KeyEvent.KEYCODE_BUTTON_R1; - - case KeyEvent.KEYCODE_BUTTON_C: - return KeyEvent.KEYCODE_BUTTON_B; - - case KeyEvent.KEYCODE_BUTTON_X: - return KeyEvent.KEYCODE_BUTTON_Y; - - case KeyEvent.KEYCODE_BUTTON_B: - return KeyEvent.KEYCODE_BUTTON_A; - - case KeyEvent.KEYCODE_BUTTON_A: - return KeyEvent.KEYCODE_BUTTON_X; - - case KeyEvent.KEYCODE_BUTTON_SELECT: - return KeyEvent.KEYCODE_BUTTON_THUMBL; - - case KeyEvent.KEYCODE_BUTTON_START: - return KeyEvent.KEYCODE_BUTTON_THUMBR; - - case KeyEvent.KEYCODE_BUTTON_L2: - return KeyEvent.KEYCODE_BUTTON_SELECT; - - case KeyEvent.KEYCODE_BUTTON_R2: - return KeyEvent.KEYCODE_BUTTON_START; - - // These are duplicate trigger events - case KeyEvent.KEYCODE_BUTTON_R1: - case KeyEvent.KEYCODE_BUTTON_L1: - return 0; - } - } - - if (context.hatXAxis != -1 && context.hatYAxis != -1) { - switch (event.getKeyCode()) { - // These are duplicate dpad events for hat input - case KeyEvent.KEYCODE_DPAD_LEFT: - case KeyEvent.KEYCODE_DPAD_RIGHT: - case KeyEvent.KEYCODE_DPAD_CENTER: - case KeyEvent.KEYCODE_DPAD_UP: - case KeyEvent.KEYCODE_DPAD_DOWN: - return 0; - } - } - else if (context.hatXAxis == -1 && - context.hatYAxis == -1 && - context.isXboxController && - event.getKeyCode() == KeyEvent.KEYCODE_UNKNOWN) { - // If there's not a proper Xbox controller mapping, we'll translate the raw d-pad - // scan codes into proper key codes - switch (event.getScanCode()) - { - case 704: - return KeyEvent.KEYCODE_DPAD_LEFT; - case 705: - return KeyEvent.KEYCODE_DPAD_RIGHT; - case 706: - return KeyEvent.KEYCODE_DPAD_UP; - case 707: - return KeyEvent.KEYCODE_DPAD_DOWN; - } - } + switch (event.getKeyCode()) { + case KeyEvent.KEYCODE_BUTTON_Y: + return KeyEvent.KEYCODE_BUTTON_L1; + + case KeyEvent.KEYCODE_BUTTON_Z: + return KeyEvent.KEYCODE_BUTTON_R1; + + case KeyEvent.KEYCODE_BUTTON_C: + return KeyEvent.KEYCODE_BUTTON_B; + + case KeyEvent.KEYCODE_BUTTON_X: + return KeyEvent.KEYCODE_BUTTON_Y; + + case KeyEvent.KEYCODE_BUTTON_B: + return KeyEvent.KEYCODE_BUTTON_A; + + case KeyEvent.KEYCODE_BUTTON_A: + return KeyEvent.KEYCODE_BUTTON_X; + + case KeyEvent.KEYCODE_BUTTON_SELECT: + return KeyEvent.KEYCODE_BUTTON_THUMBL; + + case KeyEvent.KEYCODE_BUTTON_START: + return KeyEvent.KEYCODE_BUTTON_THUMBR; + + case KeyEvent.KEYCODE_BUTTON_L2: + return KeyEvent.KEYCODE_BUTTON_SELECT; + + case KeyEvent.KEYCODE_BUTTON_R2: + return KeyEvent.KEYCODE_BUTTON_START; + + // These are duplicate trigger events + case KeyEvent.KEYCODE_BUTTON_R1: + case KeyEvent.KEYCODE_BUTTON_L1: + return 0; + } + } + + if (context.hatXAxis != -1 && context.hatYAxis != -1) { + switch (event.getKeyCode()) { + // These are duplicate dpad events for hat input + case KeyEvent.KEYCODE_DPAD_LEFT: + case KeyEvent.KEYCODE_DPAD_RIGHT: + case KeyEvent.KEYCODE_DPAD_CENTER: + case KeyEvent.KEYCODE_DPAD_UP: + case KeyEvent.KEYCODE_DPAD_DOWN: + return 0; + } + } + else if (context.hatXAxis == -1 && + context.hatYAxis == -1 && + context.isXboxController && + event.getKeyCode() == KeyEvent.KEYCODE_UNKNOWN) { + // If there's not a proper Xbox controller mapping, we'll translate the raw d-pad + // scan codes into proper key codes + switch (event.getScanCode()) + { + case 704: + return KeyEvent.KEYCODE_DPAD_LEFT; + case 705: + return KeyEvent.KEYCODE_DPAD_RIGHT; + case 706: + return KeyEvent.KEYCODE_DPAD_UP; + case 707: + return KeyEvent.KEYCODE_DPAD_DOWN; + } + } // Past here we can fixup the keycode and potentially trigger // another special case so we need to remember what keycode we're using @@ -439,21 +439,21 @@ public class ControllerHandler implements InputManager.InputDeviceListener { // Emulate the select button with mode return KeyEvent.KEYCODE_BUTTON_SELECT; } - - return keyCode; - } + + return keyCode; + } private Vector2d populateCachedVector(float x, float y) { // Reinitialize our cached Vector2d object inputVector.initialize(x, y); return inputVector; } - - private void handleDeadZone(Vector2d stickVector, float deadzoneRadius) { - if (stickVector.getMagnitude() <= deadzoneRadius) { - // Deadzone + + private void handleDeadZone(Vector2d stickVector, float deadzoneRadius) { + if (stickVector.getMagnitude() <= deadzoneRadius) { + // Deadzone stickVector.initialize(0, 0); - } + } // We're not normalizing here because we let the computer handle the deadzones. // Normalizing can make the deadzones larger than they should be after the computer also @@ -518,9 +518,9 @@ public class ControllerHandler implements InputManager.InputDeviceListener { sendControllerInputPacket(context); } - - public boolean handleMotionEvent(MotionEvent event) { - ControllerContext context = getContextForDevice(event.getDevice()); + + public boolean handleMotionEvent(MotionEvent event) { + ControllerContext context = getContextForDevice(event.getDevice()); float lsX = 0, lsY = 0, rsX = 0, rsY = 0, rt = 0, lt = 0, hatX = 0, hatY = 0; // We purposefully ignore the historical values in the motion event as it makes @@ -548,255 +548,255 @@ public class ControllerHandler implements InputManager.InputDeviceListener { handleAxisSet(context, lsX, lsY, rsX, rsY, lt, rt, hatX, hatY); - return true; - } - - public boolean handleButtonUp(KeyEvent event) { - ControllerContext context = getContextForDevice(event.getDevice()); + return true; + } + + public boolean handleButtonUp(KeyEvent event) { + ControllerContext context = getContextForDevice(event.getDevice()); int keyCode = handleRemapping(context, event); - if (keyCode == 0) { - return true; - } - - // If the button hasn't been down long enough, sleep for a bit before sending the up event - // This allows "instant" button presses (like OUYA's virtual menu button) to work. This - // path should not be triggered during normal usage. - if (SystemClock.uptimeMillis() - event.getDownTime() < ControllerHandler.MINIMUM_BUTTON_DOWN_TIME_MS) - { - // Since our sleep time is so short (10 ms), it shouldn't cause a problem doing this in the - // UI thread. - try { - Thread.sleep(ControllerHandler.MINIMUM_BUTTON_DOWN_TIME_MS); - } catch (InterruptedException ignored) {} - } - - switch (keyCode) { - case KeyEvent.KEYCODE_BUTTON_MODE: + if (keyCode == 0) { + return true; + } + + // If the button hasn't been down long enough, sleep for a bit before sending the up event + // This allows "instant" button presses (like OUYA's virtual menu button) to work. This + // path should not be triggered during normal usage. + if (SystemClock.uptimeMillis() - event.getDownTime() < ControllerHandler.MINIMUM_BUTTON_DOWN_TIME_MS) + { + // Since our sleep time is so short (10 ms), it shouldn't cause a problem doing this in the + // UI thread. + try { + Thread.sleep(ControllerHandler.MINIMUM_BUTTON_DOWN_TIME_MS); + } catch (InterruptedException ignored) {} + } + + switch (keyCode) { + case KeyEvent.KEYCODE_BUTTON_MODE: context.inputMap &= ~ControllerPacket.SPECIAL_BUTTON_FLAG; - break; - case KeyEvent.KEYCODE_BUTTON_START: - case KeyEvent.KEYCODE_MENU: + break; + case KeyEvent.KEYCODE_BUTTON_START: + case KeyEvent.KEYCODE_MENU: if (SystemClock.uptimeMillis() - context.startDownTime > ControllerHandler.START_DOWN_TIME_KEYB_MS) { gestures.showKeyboard(); } context.inputMap &= ~ControllerPacket.PLAY_FLAG; - break; - case KeyEvent.KEYCODE_BACK: - case KeyEvent.KEYCODE_BUTTON_SELECT: + break; + case KeyEvent.KEYCODE_BACK: + case KeyEvent.KEYCODE_BUTTON_SELECT: context.inputMap &= ~ControllerPacket.BACK_FLAG; - break; - case KeyEvent.KEYCODE_DPAD_LEFT: + break; + case KeyEvent.KEYCODE_DPAD_LEFT: context.inputMap &= ~ControllerPacket.LEFT_FLAG; - break; - case KeyEvent.KEYCODE_DPAD_RIGHT: + break; + case KeyEvent.KEYCODE_DPAD_RIGHT: context.inputMap &= ~ControllerPacket.RIGHT_FLAG; - break; - case KeyEvent.KEYCODE_DPAD_UP: + break; + case KeyEvent.KEYCODE_DPAD_UP: context.inputMap &= ~ControllerPacket.UP_FLAG; - break; - case KeyEvent.KEYCODE_DPAD_DOWN: + break; + case KeyEvent.KEYCODE_DPAD_DOWN: context.inputMap &= ~ControllerPacket.DOWN_FLAG; - break; - case KeyEvent.KEYCODE_BUTTON_B: + break; + case KeyEvent.KEYCODE_BUTTON_B: context.inputMap &= ~ControllerPacket.B_FLAG; - break; - case KeyEvent.KEYCODE_DPAD_CENTER: - case KeyEvent.KEYCODE_BUTTON_A: + break; + case KeyEvent.KEYCODE_DPAD_CENTER: + case KeyEvent.KEYCODE_BUTTON_A: context.inputMap &= ~ControllerPacket.A_FLAG; - break; - case KeyEvent.KEYCODE_BUTTON_X: + break; + case KeyEvent.KEYCODE_BUTTON_X: context.inputMap &= ~ControllerPacket.X_FLAG; - break; - case KeyEvent.KEYCODE_BUTTON_Y: + break; + case KeyEvent.KEYCODE_BUTTON_Y: context.inputMap &= ~ControllerPacket.Y_FLAG; - break; - case KeyEvent.KEYCODE_BUTTON_L1: + break; + case KeyEvent.KEYCODE_BUTTON_L1: context.inputMap &= ~ControllerPacket.LB_FLAG; context.lastLbUpTime = SystemClock.uptimeMillis(); - break; - case KeyEvent.KEYCODE_BUTTON_R1: + break; + case KeyEvent.KEYCODE_BUTTON_R1: context.inputMap &= ~ControllerPacket.RB_FLAG; context.lastRbUpTime = SystemClock.uptimeMillis(); - break; - case KeyEvent.KEYCODE_BUTTON_THUMBL: + break; + case KeyEvent.KEYCODE_BUTTON_THUMBL: context.inputMap &= ~ControllerPacket.LS_CLK_FLAG; - break; - case KeyEvent.KEYCODE_BUTTON_THUMBR: + break; + case KeyEvent.KEYCODE_BUTTON_THUMBR: context.inputMap &= ~ControllerPacket.RS_CLK_FLAG; - break; - case KeyEvent.KEYCODE_BUTTON_L2: + break; + case KeyEvent.KEYCODE_BUTTON_L2: context.leftTrigger = 0; - break; - case KeyEvent.KEYCODE_BUTTON_R2: + break; + case KeyEvent.KEYCODE_BUTTON_R2: context.rightTrigger = 0; - break; - default: - return false; - } - - // Check if we're emulating the select button - if ((context.emulatingButtonFlags & ControllerHandler.EMULATING_SELECT) != 0) - { - // If either start or LB is up, select comes up too - if ((context.inputMap & ControllerPacket.PLAY_FLAG) == 0 || - (context.inputMap & ControllerPacket.LB_FLAG) == 0) - { + break; + default: + return false; + } + + // Check if we're emulating the select button + if ((context.emulatingButtonFlags & ControllerHandler.EMULATING_SELECT) != 0) + { + // If either start or LB is up, select comes up too + if ((context.inputMap & ControllerPacket.PLAY_FLAG) == 0 || + (context.inputMap & ControllerPacket.LB_FLAG) == 0) + { context.inputMap &= ~ControllerPacket.BACK_FLAG; context.emulatingButtonFlags &= ~ControllerHandler.EMULATING_SELECT; - - try { - Thread.sleep(EMULATED_SELECT_UP_DELAY_MS); - } catch (InterruptedException ignored) {} - } - } - - // Check if we're emulating the special button - if ((context.emulatingButtonFlags & ControllerHandler.EMULATING_SPECIAL) != 0) - { - // If either start or select and RB is up, the special button comes up too - if ((context.inputMap & ControllerPacket.PLAY_FLAG) == 0 || - ((context.inputMap & ControllerPacket.BACK_FLAG) == 0 && - (context.inputMap & ControllerPacket.RB_FLAG) == 0)) - { + + try { + Thread.sleep(EMULATED_SELECT_UP_DELAY_MS); + } catch (InterruptedException ignored) {} + } + } + + // Check if we're emulating the special button + if ((context.emulatingButtonFlags & ControllerHandler.EMULATING_SPECIAL) != 0) + { + // If either start or select and RB is up, the special button comes up too + if ((context.inputMap & ControllerPacket.PLAY_FLAG) == 0 || + ((context.inputMap & ControllerPacket.BACK_FLAG) == 0 && + (context.inputMap & ControllerPacket.RB_FLAG) == 0)) + { context.inputMap &= ~ControllerPacket.SPECIAL_BUTTON_FLAG; context.emulatingButtonFlags &= ~ControllerHandler.EMULATING_SPECIAL; - - try { - Thread.sleep(EMULATED_SPECIAL_UP_DELAY_MS); - } catch (InterruptedException ignored) {} - } - } - - sendControllerInputPacket(context); - return true; - } - - public boolean handleButtonDown(KeyEvent event) { - ControllerContext context = getContextForDevice(event.getDevice()); + + try { + Thread.sleep(EMULATED_SPECIAL_UP_DELAY_MS); + } catch (InterruptedException ignored) {} + } + } + + sendControllerInputPacket(context); + return true; + } + + public boolean handleButtonDown(KeyEvent event) { + ControllerContext context = getContextForDevice(event.getDevice()); int keyCode = handleRemapping(context, event); - if (keyCode == 0) { - return true; - } - - switch (keyCode) { - case KeyEvent.KEYCODE_BUTTON_MODE: + if (keyCode == 0) { + return true; + } + + switch (keyCode) { + case KeyEvent.KEYCODE_BUTTON_MODE: context.inputMap |= ControllerPacket.SPECIAL_BUTTON_FLAG; - break; - case KeyEvent.KEYCODE_BUTTON_START: - case KeyEvent.KEYCODE_MENU: + break; + case KeyEvent.KEYCODE_BUTTON_START: + case KeyEvent.KEYCODE_MENU: if (event.getRepeatCount() == 0) { context.startDownTime = SystemClock.uptimeMillis(); } context.inputMap |= ControllerPacket.PLAY_FLAG; - break; - case KeyEvent.KEYCODE_BACK: - case KeyEvent.KEYCODE_BUTTON_SELECT: + break; + case KeyEvent.KEYCODE_BACK: + case KeyEvent.KEYCODE_BUTTON_SELECT: context.inputMap |= ControllerPacket.BACK_FLAG; - break; - case KeyEvent.KEYCODE_DPAD_LEFT: + break; + case KeyEvent.KEYCODE_DPAD_LEFT: context.inputMap |= ControllerPacket.LEFT_FLAG; - break; - case KeyEvent.KEYCODE_DPAD_RIGHT: + break; + case KeyEvent.KEYCODE_DPAD_RIGHT: context.inputMap |= ControllerPacket.RIGHT_FLAG; - break; - case KeyEvent.KEYCODE_DPAD_UP: + break; + case KeyEvent.KEYCODE_DPAD_UP: context.inputMap |= ControllerPacket.UP_FLAG; - break; - case KeyEvent.KEYCODE_DPAD_DOWN: + break; + case KeyEvent.KEYCODE_DPAD_DOWN: context.inputMap |= ControllerPacket.DOWN_FLAG; - break; - case KeyEvent.KEYCODE_BUTTON_B: + break; + case KeyEvent.KEYCODE_BUTTON_B: context.inputMap |= ControllerPacket.B_FLAG; - break; - case KeyEvent.KEYCODE_DPAD_CENTER: - case KeyEvent.KEYCODE_BUTTON_A: + break; + case KeyEvent.KEYCODE_DPAD_CENTER: + case KeyEvent.KEYCODE_BUTTON_A: context.inputMap |= ControllerPacket.A_FLAG; - break; - case KeyEvent.KEYCODE_BUTTON_X: + break; + case KeyEvent.KEYCODE_BUTTON_X: context.inputMap |= ControllerPacket.X_FLAG; - break; - case KeyEvent.KEYCODE_BUTTON_Y: + break; + case KeyEvent.KEYCODE_BUTTON_Y: context.inputMap |= ControllerPacket.Y_FLAG; - break; - case KeyEvent.KEYCODE_BUTTON_L1: + break; + case KeyEvent.KEYCODE_BUTTON_L1: context.inputMap |= ControllerPacket.LB_FLAG; - break; - case KeyEvent.KEYCODE_BUTTON_R1: + break; + case KeyEvent.KEYCODE_BUTTON_R1: context.inputMap |= ControllerPacket.RB_FLAG; - break; - case KeyEvent.KEYCODE_BUTTON_THUMBL: + break; + case KeyEvent.KEYCODE_BUTTON_THUMBL: context.inputMap |= ControllerPacket.LS_CLK_FLAG; - break; - case KeyEvent.KEYCODE_BUTTON_THUMBR: + break; + case KeyEvent.KEYCODE_BUTTON_THUMBR: context.inputMap |= ControllerPacket.RS_CLK_FLAG; - break; - case KeyEvent.KEYCODE_BUTTON_L2: + break; + case KeyEvent.KEYCODE_BUTTON_L2: context.leftTrigger = (byte)0xFF; - break; - case KeyEvent.KEYCODE_BUTTON_R2: + break; + case KeyEvent.KEYCODE_BUTTON_R2: context.rightTrigger = (byte)0xFF; - break; - default: - return false; - } - - // Start+LB acts like select for controllers with one button - if ((context.inputMap & ControllerPacket.PLAY_FLAG) != 0 && - ((context.inputMap & ControllerPacket.LB_FLAG) != 0 || - SystemClock.uptimeMillis() - context.lastLbUpTime <= MAXIMUM_BUMPER_UP_DELAY_MS)) - { + break; + default: + return false; + } + + // Start+LB acts like select for controllers with one button + if ((context.inputMap & ControllerPacket.PLAY_FLAG) != 0 && + ((context.inputMap & ControllerPacket.LB_FLAG) != 0 || + SystemClock.uptimeMillis() - context.lastLbUpTime <= MAXIMUM_BUMPER_UP_DELAY_MS)) + { context.inputMap &= ~(ControllerPacket.PLAY_FLAG | ControllerPacket.LB_FLAG); context.inputMap |= ControllerPacket.BACK_FLAG; context.emulatingButtonFlags |= ControllerHandler.EMULATING_SELECT; - } - - // We detect select+start or start+RB as the special button combo - if (((context.inputMap & ControllerPacket.RB_FLAG) != 0 || - (SystemClock.uptimeMillis() - context.lastRbUpTime <= MAXIMUM_BUMPER_UP_DELAY_MS) || - (context.inputMap & ControllerPacket.BACK_FLAG) != 0) && - (context.inputMap & ControllerPacket.PLAY_FLAG) != 0) - { + } + + // We detect select+start or start+RB as the special button combo + if (((context.inputMap & ControllerPacket.RB_FLAG) != 0 || + (SystemClock.uptimeMillis() - context.lastRbUpTime <= MAXIMUM_BUMPER_UP_DELAY_MS) || + (context.inputMap & ControllerPacket.BACK_FLAG) != 0) && + (context.inputMap & ControllerPacket.PLAY_FLAG) != 0) + { context.inputMap &= ~(ControllerPacket.BACK_FLAG | ControllerPacket.PLAY_FLAG | ControllerPacket.RB_FLAG); context.inputMap |= ControllerPacket.SPECIAL_BUTTON_FLAG; context.emulatingButtonFlags |= ControllerHandler.EMULATING_SPECIAL; - } + } // Send a new input packet if this is the first instance of a button down event // or anytime if we're emulating a button if (event.getRepeatCount() == 0 || context.emulatingButtonFlags != 0) { sendControllerInputPacket(context); } - return true; - } + return true; + } class ControllerContext { public String name; public int id; - public int leftStickXAxis = -1; - public int leftStickYAxis = -1; - public float leftStickDeadzoneRadius; + public int leftStickXAxis = -1; + public int leftStickYAxis = -1; + public float leftStickDeadzoneRadius; - public int rightStickXAxis = -1; - public int rightStickYAxis = -1; - public float rightStickDeadzoneRadius; - - public int leftTriggerAxis = -1; - public int rightTriggerAxis = -1; - public boolean triggersIdleNegative; + public int rightStickXAxis = -1; + public int rightStickYAxis = -1; + public float rightStickDeadzoneRadius; + + public int leftTriggerAxis = -1; + public int rightTriggerAxis = -1; + public boolean triggersIdleNegative; public float triggerDeadzone; - - public int hatXAxis = -1; - public int hatYAxis = -1; - - public boolean isDualShock4; - public boolean isXboxController; + + public int hatXAxis = -1; + public int hatYAxis = -1; + + public boolean isDualShock4; + public boolean isXboxController; public boolean backIsStart; public boolean modeIsSelect; public boolean isRemote; @@ -822,5 +822,5 @@ public class ControllerHandler implements InputManager.InputDeviceListener { public long lastRbUpTime = 0; public long startDownTime = 0; - } + } } diff --git a/app/src/main/java/com/limelight/binding/input/KeyboardTranslator.java b/app/src/main/java/com/limelight/binding/input/KeyboardTranslator.java index bfaf2dbc..f087215f 100644 --- a/app/src/main/java/com/limelight/binding/input/KeyboardTranslator.java +++ b/app/src/main/java/com/limelight/binding/input/KeyboardTranslator.java @@ -23,8 +23,8 @@ public class KeyboardTranslator extends KeycodeTranslator { public static final int VK_Z = 90; public static final int VK_ALT = 18; public static final int VK_NUMPAD0 = 96; - public static final int VK_BACK_SLASH = 92; - public static final int VK_CAPS_LOCK = 20; + public static final int VK_BACK_SLASH = 92; + public static final int VK_CAPS_LOCK = 20; public static final int VK_CLEAR = 12; public static final int VK_COMMA = 44; public static final int VK_CONTROL = 17; diff --git a/app/src/main/java/com/limelight/binding/input/TouchContext.java b/app/src/main/java/com/limelight/binding/input/TouchContext.java index 179e5034..113771c6 100644 --- a/app/src/main/java/com/limelight/binding/input/TouchContext.java +++ b/app/src/main/java/com/limelight/binding/input/TouchContext.java @@ -4,95 +4,95 @@ import com.limelight.nvstream.NvConnection; import com.limelight.nvstream.input.MouseButtonPacket; public class TouchContext { - private int lastTouchX = 0; - private int lastTouchY = 0; - private int originalTouchX = 0; - private int originalTouchY = 0; - private long originalTouchTime = 0; + private int lastTouchX = 0; + private int lastTouchY = 0; + private int originalTouchX = 0; + private int originalTouchY = 0; + private long originalTouchTime = 0; private boolean cancelled; - - private final NvConnection conn; - private final int actionIndex; + + private final NvConnection conn; + private final int actionIndex; private final double xFactor; private final double yFactor; - - private static final int TAP_MOVEMENT_THRESHOLD = 10; - private static final int TAP_TIME_THRESHOLD = 250; - - public TouchContext(NvConnection conn, int actionIndex, double xFactor, double yFactor) - { - this.conn = conn; - this.actionIndex = actionIndex; + + private static final int TAP_MOVEMENT_THRESHOLD = 10; + private static final int TAP_TIME_THRESHOLD = 250; + + public TouchContext(NvConnection conn, int actionIndex, double xFactor, double yFactor) + { + this.conn = conn; + this.actionIndex = actionIndex; this.xFactor = xFactor; this.yFactor = yFactor; - } + } public int getActionIndex() { return actionIndex; } - - private boolean isTap() - { - int xDelta = Math.abs(lastTouchX - originalTouchX); - int yDelta = Math.abs(lastTouchY - originalTouchY); - long timeDelta = System.currentTimeMillis() - originalTouchTime; - - return xDelta <= TAP_MOVEMENT_THRESHOLD && - yDelta <= TAP_MOVEMENT_THRESHOLD && - timeDelta <= TAP_TIME_THRESHOLD; - } - - private byte getMouseButtonIndex() - { - if (actionIndex == 1) { - return MouseButtonPacket.BUTTON_RIGHT; - } - else { - return MouseButtonPacket.BUTTON_LEFT; - } - } - - public boolean touchDownEvent(int eventX, int eventY) - { - originalTouchX = lastTouchX = eventX; - originalTouchY = lastTouchY = eventY; - originalTouchTime = System.currentTimeMillis(); + + private boolean isTap() + { + int xDelta = Math.abs(lastTouchX - originalTouchX); + int yDelta = Math.abs(lastTouchY - originalTouchY); + long timeDelta = System.currentTimeMillis() - originalTouchTime; + + return xDelta <= TAP_MOVEMENT_THRESHOLD && + yDelta <= TAP_MOVEMENT_THRESHOLD && + timeDelta <= TAP_TIME_THRESHOLD; + } + + private byte getMouseButtonIndex() + { + if (actionIndex == 1) { + return MouseButtonPacket.BUTTON_RIGHT; + } + else { + return MouseButtonPacket.BUTTON_LEFT; + } + } + + public boolean touchDownEvent(int eventX, int eventY) + { + originalTouchX = lastTouchX = eventX; + originalTouchY = lastTouchY = eventY; + originalTouchTime = System.currentTimeMillis(); cancelled = false; return true; - } - - public void touchUpEvent(int eventX, int eventY) - { + } + + public void touchUpEvent(int eventX, int eventY) + { if (cancelled) { return; } - if (isTap()) - { - byte buttonIndex = getMouseButtonIndex(); - - // Lower the mouse button - conn.sendMouseButtonDown(buttonIndex); - - // We need to sleep a bit here because some games - // do input detection by polling - try { - Thread.sleep(100); - } catch (InterruptedException ignored) {} - - // Raise the mouse button - conn.sendMouseButtonUp(buttonIndex); - } - } - - public boolean touchMoveEvent(int eventX, int eventY) + if (isTap()) + { + byte buttonIndex = getMouseButtonIndex(); + + // Lower the mouse button + conn.sendMouseButtonDown(buttonIndex); + + // We need to sleep a bit here because some games + // do input detection by polling + try { + Thread.sleep(100); + } catch (InterruptedException ignored) {} + + // Raise the mouse button + conn.sendMouseButtonUp(buttonIndex); + } + } + + public boolean touchMoveEvent(int eventX, int eventY) { if (eventX != lastTouchX || eventY != lastTouchY) - { - // We only send moves for the primary touch point - if (actionIndex == 0) { + { + // We only send moves for the primary touch point + if (actionIndex == 0) { int deltaX = eventX - lastTouchX; int deltaY = eventY - lastTouchY; @@ -100,15 +100,15 @@ public class TouchContext { deltaX = (int)Math.round((double)deltaX * xFactor); deltaY = (int)Math.round((double)deltaY * yFactor); - conn.sendMouseMove((short)deltaX, (short)deltaY); - } - - lastTouchX = eventX; - lastTouchY = eventY; - } - - return true; - } + conn.sendMouseMove((short)deltaX, (short)deltaY); + } + + lastTouchX = eventX; + lastTouchY = eventY; + } + + return true; + } public void cancelTouch() { cancelled = true; diff --git a/app/src/main/java/com/limelight/binding/input/evdev/EvdevEvent.java b/app/src/main/java/com/limelight/binding/input/evdev/EvdevEvent.java index 0addf697..5e04f168 100644 --- a/app/src/main/java/com/limelight/binding/input/evdev/EvdevEvent.java +++ b/app/src/main/java/com/limelight/binding/input/evdev/EvdevEvent.java @@ -1,41 +1,41 @@ package com.limelight.binding.input.evdev; public class EvdevEvent { - public static final int EVDEV_MIN_EVENT_SIZE = 16; - public static final int EVDEV_MAX_EVENT_SIZE = 24; - - /* Event types */ - public static final short EV_SYN = 0x00; - public static final short EV_KEY = 0x01; - public static final short EV_REL = 0x02; - public static final short EV_MSC = 0x04; - - /* Relative axes */ - public static final short REL_X = 0x00; - public static final short REL_Y = 0x01; - public static final short REL_WHEEL = 0x08; - - /* Buttons */ - public static final short BTN_LEFT = 0x110; - public static final short BTN_RIGHT = 0x111; - public static final short BTN_MIDDLE = 0x112; - public static final short BTN_SIDE = 0x113; - public static final short BTN_EXTRA = 0x114; - public static final short BTN_FORWARD = 0x115; - public static final short BTN_BACK = 0x116; - public static final short BTN_TASK = 0x117; - public static final short BTN_GAMEPAD = 0x130; - - /* Keys */ - public static final short KEY_Q = 16; - - public final short type; - public final short code; - public final int value; - - public EvdevEvent(short type, short code, int value) { - this.type = type; - this.code = code; - this.value = value; - } + public static final int EVDEV_MIN_EVENT_SIZE = 16; + public static final int EVDEV_MAX_EVENT_SIZE = 24; + + /* Event types */ + public static final short EV_SYN = 0x00; + public static final short EV_KEY = 0x01; + public static final short EV_REL = 0x02; + public static final short EV_MSC = 0x04; + + /* Relative axes */ + public static final short REL_X = 0x00; + public static final short REL_Y = 0x01; + public static final short REL_WHEEL = 0x08; + + /* Buttons */ + public static final short BTN_LEFT = 0x110; + public static final short BTN_RIGHT = 0x111; + public static final short BTN_MIDDLE = 0x112; + public static final short BTN_SIDE = 0x113; + public static final short BTN_EXTRA = 0x114; + public static final short BTN_FORWARD = 0x115; + public static final short BTN_BACK = 0x116; + public static final short BTN_TASK = 0x117; + public static final short BTN_GAMEPAD = 0x130; + + /* Keys */ + public static final short KEY_Q = 16; + + public final short type; + public final short code; + public final int value; + + public EvdevEvent(short type, short code, int value) { + this.type = type; + this.code = code; + this.value = value; + } } diff --git a/app/src/main/java/com/limelight/binding/input/evdev/EvdevHandler.java b/app/src/main/java/com/limelight/binding/input/evdev/EvdevHandler.java index f60d751e..0a164ff9 100644 --- a/app/src/main/java/com/limelight/binding/input/evdev/EvdevHandler.java +++ b/app/src/main/java/com/limelight/binding/input/evdev/EvdevHandler.java @@ -7,161 +7,161 @@ import com.limelight.LimeLog; public class EvdevHandler { - private final String absolutePath; - private final EvdevListener listener; - private boolean shutdown = false; - private int fd = -1; - - private final Thread handlerThread = new Thread() { - @Override - public void run() { - // All the finally blocks here make this code look like a mess - // but it's important that we get this right to avoid causing - // system-wide input problems. - - // Open the /dev/input/eventX file - fd = EvdevReader.open(absolutePath); - if (fd == -1) { - LimeLog.warning("Unable to open "+absolutePath); - return; - } + private final String absolutePath; + private final EvdevListener listener; + private boolean shutdown = false; + private int fd = -1; - try { - // Check if it's a mouse or keyboard, but not a gamepad - if ((!EvdevReader.isMouse(fd) && !EvdevReader.isAlphaKeyboard(fd)) || - EvdevReader.isGamepad(fd)) { - // We only handle keyboards and mice - return; - } + private final Thread handlerThread = new Thread() { + @Override + public void run() { + // All the finally blocks here make this code look like a mess + // but it's important that we get this right to avoid causing + // system-wide input problems. - // Grab it for ourselves - if (!EvdevReader.grab(fd)) { - LimeLog.warning("Unable to grab "+absolutePath); - return; - } + // Open the /dev/input/eventX file + fd = EvdevReader.open(absolutePath); + if (fd == -1) { + LimeLog.warning("Unable to open "+absolutePath); + return; + } - LimeLog.info("Grabbed device for raw keyboard/mouse input: "+absolutePath); + try { + // Check if it's a mouse or keyboard, but not a gamepad + if ((!EvdevReader.isMouse(fd) && !EvdevReader.isAlphaKeyboard(fd)) || + EvdevReader.isGamepad(fd)) { + // We only handle keyboards and mice + return; + } - ByteBuffer buffer = ByteBuffer.allocate(EvdevEvent.EVDEV_MAX_EVENT_SIZE).order(ByteOrder.nativeOrder()); + // Grab it for ourselves + if (!EvdevReader.grab(fd)) { + LimeLog.warning("Unable to grab "+absolutePath); + return; + } - try { - int deltaX = 0; - int deltaY = 0; - byte deltaScroll = 0; + LimeLog.info("Grabbed device for raw keyboard/mouse input: "+absolutePath); - while (!isInterrupted() && !shutdown) { - EvdevEvent event = EvdevReader.read(fd, buffer); - if (event == null) { - return; - } + ByteBuffer buffer = ByteBuffer.allocate(EvdevEvent.EVDEV_MAX_EVENT_SIZE).order(ByteOrder.nativeOrder()); - switch (event.type) - { - case EvdevEvent.EV_SYN: - if (deltaX != 0 || deltaY != 0) { - listener.mouseMove(deltaX, deltaY); - deltaX = deltaY = 0; - } - if (deltaScroll != 0) { - listener.mouseScroll(deltaScroll); - deltaScroll = 0; - } - break; + try { + int deltaX = 0; + int deltaY = 0; + byte deltaScroll = 0; - case EvdevEvent.EV_REL: - switch (event.code) - { - case EvdevEvent.REL_X: - deltaX = event.value; - break; - case EvdevEvent.REL_Y: - deltaY = event.value; - break; - case EvdevEvent.REL_WHEEL: - deltaScroll = (byte) event.value; - break; - } - break; + while (!isInterrupted() && !shutdown) { + EvdevEvent event = EvdevReader.read(fd, buffer); + if (event == null) { + return; + } - case EvdevEvent.EV_KEY: - switch (event.code) - { - case EvdevEvent.BTN_LEFT: - listener.mouseButtonEvent(EvdevListener.BUTTON_LEFT, - event.value != 0); - break; - case EvdevEvent.BTN_MIDDLE: - listener.mouseButtonEvent(EvdevListener.BUTTON_MIDDLE, - event.value != 0); - break; - case EvdevEvent.BTN_RIGHT: - listener.mouseButtonEvent(EvdevListener.BUTTON_RIGHT, - event.value != 0); - break; - - case EvdevEvent.BTN_SIDE: - case EvdevEvent.BTN_EXTRA: - case EvdevEvent.BTN_FORWARD: - case EvdevEvent.BTN_BACK: - case EvdevEvent.BTN_TASK: - // Other unhandled mouse buttons - break; - - default: - // We got some unrecognized button. This means - // someone is trying to use the other device in this - // "combination" input device. We'll try to handle - // it via keyboard, but we're not going to disconnect - // if we can't - short keyCode = EvdevTranslator.translateEvdevKeyCode(event.code); - if (keyCode != 0) { - listener.keyboardEvent(event.value != 0, keyCode); - } - break; - } - break; - - case EvdevEvent.EV_MSC: - break; - } - } - } finally { - // Release our grab - EvdevReader.ungrab(fd); - } - } finally { - // Close the file - EvdevReader.close(fd); - } - } - }; - - public EvdevHandler(String absolutePath, EvdevListener listener) { - this.absolutePath = absolutePath; - this.listener = listener; - } - - public void start() { - handlerThread.start(); - } - - public void stop() { - // Close the fd. It doesn't matter if this races - // with the handler thread. We'll close this out from - // under the thread to wake it up - if (fd != -1) { - EvdevReader.close(fd); - } - - shutdown = true; - handlerThread.interrupt(); - - try { - handlerThread.join(); - } catch (InterruptedException ignored) {} - } - - public void notifyDeleted() { - stop(); - } + switch (event.type) + { + case EvdevEvent.EV_SYN: + if (deltaX != 0 || deltaY != 0) { + listener.mouseMove(deltaX, deltaY); + deltaX = deltaY = 0; + } + if (deltaScroll != 0) { + listener.mouseScroll(deltaScroll); + deltaScroll = 0; + } + break; + + case EvdevEvent.EV_REL: + switch (event.code) + { + case EvdevEvent.REL_X: + deltaX = event.value; + break; + case EvdevEvent.REL_Y: + deltaY = event.value; + break; + case EvdevEvent.REL_WHEEL: + deltaScroll = (byte) event.value; + break; + } + break; + + case EvdevEvent.EV_KEY: + switch (event.code) + { + case EvdevEvent.BTN_LEFT: + listener.mouseButtonEvent(EvdevListener.BUTTON_LEFT, + event.value != 0); + break; + case EvdevEvent.BTN_MIDDLE: + listener.mouseButtonEvent(EvdevListener.BUTTON_MIDDLE, + event.value != 0); + break; + case EvdevEvent.BTN_RIGHT: + listener.mouseButtonEvent(EvdevListener.BUTTON_RIGHT, + event.value != 0); + break; + + case EvdevEvent.BTN_SIDE: + case EvdevEvent.BTN_EXTRA: + case EvdevEvent.BTN_FORWARD: + case EvdevEvent.BTN_BACK: + case EvdevEvent.BTN_TASK: + // Other unhandled mouse buttons + break; + + default: + // We got some unrecognized button. This means + // someone is trying to use the other device in this + // "combination" input device. We'll try to handle + // it via keyboard, but we're not going to disconnect + // if we can't + short keyCode = EvdevTranslator.translateEvdevKeyCode(event.code); + if (keyCode != 0) { + listener.keyboardEvent(event.value != 0, keyCode); + } + break; + } + break; + + case EvdevEvent.EV_MSC: + break; + } + } + } finally { + // Release our grab + EvdevReader.ungrab(fd); + } + } finally { + // Close the file + EvdevReader.close(fd); + } + } + }; + + public EvdevHandler(String absolutePath, EvdevListener listener) { + this.absolutePath = absolutePath; + this.listener = listener; + } + + public void start() { + handlerThread.start(); + } + + public void stop() { + // Close the fd. It doesn't matter if this races + // with the handler thread. We'll close this out from + // under the thread to wake it up + if (fd != -1) { + EvdevReader.close(fd); + } + + shutdown = true; + handlerThread.interrupt(); + + try { + handlerThread.join(); + } catch (InterruptedException ignored) {} + } + + public void notifyDeleted() { + stop(); + } } diff --git a/app/src/main/java/com/limelight/binding/input/evdev/EvdevListener.java b/app/src/main/java/com/limelight/binding/input/evdev/EvdevListener.java index 909627a5..926a637d 100644 --- a/app/src/main/java/com/limelight/binding/input/evdev/EvdevListener.java +++ b/app/src/main/java/com/limelight/binding/input/evdev/EvdevListener.java @@ -1,12 +1,12 @@ package com.limelight.binding.input.evdev; public interface EvdevListener { - public static final int BUTTON_LEFT = 1; - public static final int BUTTON_MIDDLE = 2; - public static final int BUTTON_RIGHT = 3; - - public void mouseMove(int deltaX, int deltaY); - public void mouseButtonEvent(int buttonId, boolean down); - public void mouseScroll(byte amount); - public void keyboardEvent(boolean buttonDown, short keyCode); + public static final int BUTTON_LEFT = 1; + public static final int BUTTON_MIDDLE = 2; + public static final int BUTTON_RIGHT = 3; + + public void mouseMove(int deltaX, int deltaY); + public void mouseButtonEvent(int buttonId, boolean down); + public void mouseScroll(byte amount); + public void keyboardEvent(boolean buttonDown, short keyCode); } diff --git a/app/src/main/java/com/limelight/binding/input/evdev/EvdevReader.java b/app/src/main/java/com/limelight/binding/input/evdev/EvdevReader.java index 3ee2ddcd..f6c4b0e5 100644 --- a/app/src/main/java/com/limelight/binding/input/evdev/EvdevReader.java +++ b/app/src/main/java/com/limelight/binding/input/evdev/EvdevReader.java @@ -8,9 +8,9 @@ import java.util.Locale; import com.limelight.LimeLog; public class EvdevReader { - static { - System.loadLibrary("evdev_reader"); - } + static { + System.loadLibrary("evdev_reader"); + } public static void patchSeLinuxPolicies() { // @@ -30,76 +30,76 @@ public class EvdevReader { "\"allow untrusted_app input_device chr_file { open read write ioctl }\""); } } - - // Requires root to chmod /dev/input/eventX - public static void setPermissions(String[] files, int octalPermissions) { + + // Requires root to chmod /dev/input/eventX + public static void setPermissions(String[] files, int octalPermissions) { EvdevShell shell = EvdevShell.getInstance(); for (String file : files) { shell.runCommand(String.format((Locale)null, "chmod %o %s", octalPermissions, file)); } - } - - // Returns the fd to be passed to other function or -1 on error - public static native int open(String fileName); - - // Prevent other apps (including Android itself) from using the device while "grabbed" - public static native boolean grab(int fd); - public static native boolean ungrab(int fd); - - // Used for checking device capabilities - public static native boolean hasRelAxis(int fd, short axis); - public static native boolean hasAbsAxis(int fd, short axis); - public static native boolean hasKey(int fd, short key); - - public static boolean isMouse(int fd) { - // This is the same check that Android does in EventHub.cpp - return hasRelAxis(fd, EvdevEvent.REL_X) && - hasRelAxis(fd, EvdevEvent.REL_Y) && - hasKey(fd, EvdevEvent.BTN_LEFT); - } - - public static boolean isAlphaKeyboard(int fd) { - // This is the same check that Android does in EventHub.cpp - return hasKey(fd, EvdevEvent.KEY_Q); - } - - public static boolean isGamepad(int fd) { - return hasKey(fd, EvdevEvent.BTN_GAMEPAD); - } - - // Returns the bytes read or -1 on error - private static native int read(int fd, byte[] buffer); - - // Takes a byte buffer to use to read the output into. - // This buffer MUST be in native byte order and at least - // EVDEV_MAX_EVENT_SIZE bytes long. - public static EvdevEvent read(int fd, ByteBuffer buffer) { - int bytesRead = read(fd, buffer.array()); - if (bytesRead < 0) { - LimeLog.warning("Failed to read: "+bytesRead); - return null; - } - else if (bytesRead < EvdevEvent.EVDEV_MIN_EVENT_SIZE) { - LimeLog.warning("Short read: "+bytesRead); - return null; - } - - buffer.limit(bytesRead); - buffer.rewind(); - - // Throw away the time stamp - if (bytesRead == EvdevEvent.EVDEV_MAX_EVENT_SIZE) { - buffer.getLong(); - buffer.getLong(); - } else { - buffer.getInt(); - buffer.getInt(); - } - - return new EvdevEvent(buffer.getShort(), buffer.getShort(), buffer.getInt()); - } - - // Closes the fd from open() - public static native int close(int fd); + } + + // Returns the fd to be passed to other function or -1 on error + public static native int open(String fileName); + + // Prevent other apps (including Android itself) from using the device while "grabbed" + public static native boolean grab(int fd); + public static native boolean ungrab(int fd); + + // Used for checking device capabilities + public static native boolean hasRelAxis(int fd, short axis); + public static native boolean hasAbsAxis(int fd, short axis); + public static native boolean hasKey(int fd, short key); + + public static boolean isMouse(int fd) { + // This is the same check that Android does in EventHub.cpp + return hasRelAxis(fd, EvdevEvent.REL_X) && + hasRelAxis(fd, EvdevEvent.REL_Y) && + hasKey(fd, EvdevEvent.BTN_LEFT); + } + + public static boolean isAlphaKeyboard(int fd) { + // This is the same check that Android does in EventHub.cpp + return hasKey(fd, EvdevEvent.KEY_Q); + } + + public static boolean isGamepad(int fd) { + return hasKey(fd, EvdevEvent.BTN_GAMEPAD); + } + + // Returns the bytes read or -1 on error + private static native int read(int fd, byte[] buffer); + + // Takes a byte buffer to use to read the output into. + // This buffer MUST be in native byte order and at least + // EVDEV_MAX_EVENT_SIZE bytes long. + public static EvdevEvent read(int fd, ByteBuffer buffer) { + int bytesRead = read(fd, buffer.array()); + if (bytesRead < 0) { + LimeLog.warning("Failed to read: "+bytesRead); + return null; + } + else if (bytesRead < EvdevEvent.EVDEV_MIN_EVENT_SIZE) { + LimeLog.warning("Short read: "+bytesRead); + return null; + } + + buffer.limit(bytesRead); + buffer.rewind(); + + // Throw away the time stamp + if (bytesRead == EvdevEvent.EVDEV_MAX_EVENT_SIZE) { + buffer.getLong(); + buffer.getLong(); + } else { + buffer.getInt(); + buffer.getInt(); + } + + return new EvdevEvent(buffer.getShort(), buffer.getShort(), buffer.getInt()); + } + + // Closes the fd from open() + public static native int close(int fd); } diff --git a/app/src/main/java/com/limelight/binding/input/evdev/EvdevTranslator.java b/app/src/main/java/com/limelight/binding/input/evdev/EvdevTranslator.java index e7e376c2..303799dd 100644 --- a/app/src/main/java/com/limelight/binding/input/evdev/EvdevTranslator.java +++ b/app/src/main/java/com/limelight/binding/input/evdev/EvdevTranslator.java @@ -4,136 +4,136 @@ import android.view.KeyEvent; public class EvdevTranslator { - private static final short[] EVDEV_KEY_CODES = { - 0, //KeyEvent.VK_RESERVED - KeyEvent.KEYCODE_ESCAPE, - KeyEvent.KEYCODE_1, - KeyEvent.KEYCODE_2, - KeyEvent.KEYCODE_3, - KeyEvent.KEYCODE_4, - KeyEvent.KEYCODE_5, - KeyEvent.KEYCODE_6, - KeyEvent.KEYCODE_7, - KeyEvent.KEYCODE_8, - KeyEvent.KEYCODE_9, - KeyEvent.KEYCODE_0, - KeyEvent.KEYCODE_MINUS, - KeyEvent.KEYCODE_EQUALS, - KeyEvent.KEYCODE_DEL, - KeyEvent.KEYCODE_TAB, - KeyEvent.KEYCODE_Q, - KeyEvent.KEYCODE_W, - KeyEvent.KEYCODE_E, - KeyEvent.KEYCODE_R, - KeyEvent.KEYCODE_T, - KeyEvent.KEYCODE_Y, - KeyEvent.KEYCODE_U, - KeyEvent.KEYCODE_I, - KeyEvent.KEYCODE_O, - KeyEvent.KEYCODE_P, - KeyEvent.KEYCODE_LEFT_BRACKET, - KeyEvent.KEYCODE_RIGHT_BRACKET, - KeyEvent.KEYCODE_ENTER, - KeyEvent.KEYCODE_CTRL_LEFT, - KeyEvent.KEYCODE_A, - KeyEvent.KEYCODE_S, - KeyEvent.KEYCODE_D, - KeyEvent.KEYCODE_F, - KeyEvent.KEYCODE_G, - KeyEvent.KEYCODE_H, - KeyEvent.KEYCODE_J, - KeyEvent.KEYCODE_K, - KeyEvent.KEYCODE_L, - KeyEvent.KEYCODE_SEMICOLON, - KeyEvent.KEYCODE_APOSTROPHE, - KeyEvent.KEYCODE_GRAVE, - KeyEvent.KEYCODE_SHIFT_LEFT, - KeyEvent.KEYCODE_BACKSLASH, - KeyEvent.KEYCODE_Z, - KeyEvent.KEYCODE_X, - KeyEvent.KEYCODE_C, - KeyEvent.KEYCODE_V, - KeyEvent.KEYCODE_B, - KeyEvent.KEYCODE_N, - KeyEvent.KEYCODE_M, - KeyEvent.KEYCODE_COMMA, - KeyEvent.KEYCODE_PERIOD, - KeyEvent.KEYCODE_SLASH, - KeyEvent.KEYCODE_SHIFT_RIGHT, - KeyEvent.KEYCODE_NUMPAD_MULTIPLY, - KeyEvent.KEYCODE_ALT_LEFT, - KeyEvent.KEYCODE_SPACE, - KeyEvent.KEYCODE_CAPS_LOCK, - KeyEvent.KEYCODE_F1, - KeyEvent.KEYCODE_F2, - KeyEvent.KEYCODE_F3, - KeyEvent.KEYCODE_F4, - KeyEvent.KEYCODE_F5, - KeyEvent.KEYCODE_F6, - KeyEvent.KEYCODE_F7, - KeyEvent.KEYCODE_F8, - KeyEvent.KEYCODE_F9, - KeyEvent.KEYCODE_F10, - KeyEvent.KEYCODE_NUM_LOCK, - KeyEvent.KEYCODE_SCROLL_LOCK, - KeyEvent.KEYCODE_NUMPAD_7, - KeyEvent.KEYCODE_NUMPAD_8, - KeyEvent.KEYCODE_NUMPAD_9, - KeyEvent.KEYCODE_NUMPAD_SUBTRACT, - KeyEvent.KEYCODE_NUMPAD_4, - KeyEvent.KEYCODE_NUMPAD_5, - KeyEvent.KEYCODE_NUMPAD_6, - KeyEvent.KEYCODE_NUMPAD_ADD, - KeyEvent.KEYCODE_NUMPAD_1, - KeyEvent.KEYCODE_NUMPAD_2, - KeyEvent.KEYCODE_NUMPAD_3, - KeyEvent.KEYCODE_NUMPAD_0, - KeyEvent.KEYCODE_NUMPAD_DOT, - 0, - 0, //KeyEvent.VK_ZENKAKUHANKAKU, - 0, //KeyEvent.VK_102ND, - KeyEvent.KEYCODE_F11, - KeyEvent.KEYCODE_F12, - 0, //KeyEvent.VK_RO, - 0, //KeyEvent.VK_KATAKANA, - 0, //KeyEvent.VK_HIRAGANA, - 0, //KeyEvent.VK_HENKAN, - 0, //KeyEvent.VK_KATAKANAHIRAGANA, - 0, //KeyEvent.VK_MUHENKAN, - 0, //KeyEvent.VK_KPJPCOMMA, - KeyEvent.KEYCODE_NUMPAD_ENTER, - KeyEvent.KEYCODE_CTRL_RIGHT, - KeyEvent.KEYCODE_NUMPAD_DIVIDE, - KeyEvent.KEYCODE_SYSRQ, - KeyEvent.KEYCODE_ALT_RIGHT, - 0, //KeyEvent.VK_LINEFEED, - KeyEvent.KEYCODE_HOME, - KeyEvent.KEYCODE_DPAD_UP, - KeyEvent.KEYCODE_PAGE_UP, - KeyEvent.KEYCODE_DPAD_LEFT, - KeyEvent.KEYCODE_DPAD_RIGHT, - KeyEvent.KEYCODE_MOVE_END, - KeyEvent.KEYCODE_DPAD_DOWN, - KeyEvent.KEYCODE_PAGE_DOWN, - KeyEvent.KEYCODE_INSERT, - KeyEvent.KEYCODE_FORWARD_DEL, - 0, //KeyEvent.VK_MACRO, - 0, //KeyEvent.VK_MUTE, - 0, //KeyEvent.VK_VOLUMEDOWN, - 0, //KeyEvent.VK_VOLUMEUP, - 0, //KeyEvent.VK_POWER, /* SC System Power Down */ - KeyEvent.KEYCODE_NUMPAD_EQUALS, - 0, //KeyEvent.VK_KPPLUSMINUS, - KeyEvent.KEYCODE_BREAK, - 0, //KeyEvent.VK_SCALE, /* AL Compiz Scale (Expose) */ - }; - - public static short translateEvdevKeyCode(short evdevKeyCode) { - if (evdevKeyCode < EVDEV_KEY_CODES.length) { - return EVDEV_KEY_CODES[evdevKeyCode]; - } - - return 0; - } + private static final short[] EVDEV_KEY_CODES = { + 0, //KeyEvent.VK_RESERVED + KeyEvent.KEYCODE_ESCAPE, + KeyEvent.KEYCODE_1, + KeyEvent.KEYCODE_2, + KeyEvent.KEYCODE_3, + KeyEvent.KEYCODE_4, + KeyEvent.KEYCODE_5, + KeyEvent.KEYCODE_6, + KeyEvent.KEYCODE_7, + KeyEvent.KEYCODE_8, + KeyEvent.KEYCODE_9, + KeyEvent.KEYCODE_0, + KeyEvent.KEYCODE_MINUS, + KeyEvent.KEYCODE_EQUALS, + KeyEvent.KEYCODE_DEL, + KeyEvent.KEYCODE_TAB, + KeyEvent.KEYCODE_Q, + KeyEvent.KEYCODE_W, + KeyEvent.KEYCODE_E, + KeyEvent.KEYCODE_R, + KeyEvent.KEYCODE_T, + KeyEvent.KEYCODE_Y, + KeyEvent.KEYCODE_U, + KeyEvent.KEYCODE_I, + KeyEvent.KEYCODE_O, + KeyEvent.KEYCODE_P, + KeyEvent.KEYCODE_LEFT_BRACKET, + KeyEvent.KEYCODE_RIGHT_BRACKET, + KeyEvent.KEYCODE_ENTER, + KeyEvent.KEYCODE_CTRL_LEFT, + KeyEvent.KEYCODE_A, + KeyEvent.KEYCODE_S, + KeyEvent.KEYCODE_D, + KeyEvent.KEYCODE_F, + KeyEvent.KEYCODE_G, + KeyEvent.KEYCODE_H, + KeyEvent.KEYCODE_J, + KeyEvent.KEYCODE_K, + KeyEvent.KEYCODE_L, + KeyEvent.KEYCODE_SEMICOLON, + KeyEvent.KEYCODE_APOSTROPHE, + KeyEvent.KEYCODE_GRAVE, + KeyEvent.KEYCODE_SHIFT_LEFT, + KeyEvent.KEYCODE_BACKSLASH, + KeyEvent.KEYCODE_Z, + KeyEvent.KEYCODE_X, + KeyEvent.KEYCODE_C, + KeyEvent.KEYCODE_V, + KeyEvent.KEYCODE_B, + KeyEvent.KEYCODE_N, + KeyEvent.KEYCODE_M, + KeyEvent.KEYCODE_COMMA, + KeyEvent.KEYCODE_PERIOD, + KeyEvent.KEYCODE_SLASH, + KeyEvent.KEYCODE_SHIFT_RIGHT, + KeyEvent.KEYCODE_NUMPAD_MULTIPLY, + KeyEvent.KEYCODE_ALT_LEFT, + KeyEvent.KEYCODE_SPACE, + KeyEvent.KEYCODE_CAPS_LOCK, + KeyEvent.KEYCODE_F1, + KeyEvent.KEYCODE_F2, + KeyEvent.KEYCODE_F3, + KeyEvent.KEYCODE_F4, + KeyEvent.KEYCODE_F5, + KeyEvent.KEYCODE_F6, + KeyEvent.KEYCODE_F7, + KeyEvent.KEYCODE_F8, + KeyEvent.KEYCODE_F9, + KeyEvent.KEYCODE_F10, + KeyEvent.KEYCODE_NUM_LOCK, + KeyEvent.KEYCODE_SCROLL_LOCK, + KeyEvent.KEYCODE_NUMPAD_7, + KeyEvent.KEYCODE_NUMPAD_8, + KeyEvent.KEYCODE_NUMPAD_9, + KeyEvent.KEYCODE_NUMPAD_SUBTRACT, + KeyEvent.KEYCODE_NUMPAD_4, + KeyEvent.KEYCODE_NUMPAD_5, + KeyEvent.KEYCODE_NUMPAD_6, + KeyEvent.KEYCODE_NUMPAD_ADD, + KeyEvent.KEYCODE_NUMPAD_1, + KeyEvent.KEYCODE_NUMPAD_2, + KeyEvent.KEYCODE_NUMPAD_3, + KeyEvent.KEYCODE_NUMPAD_0, + KeyEvent.KEYCODE_NUMPAD_DOT, + 0, + 0, //KeyEvent.VK_ZENKAKUHANKAKU, + 0, //KeyEvent.VK_102ND, + KeyEvent.KEYCODE_F11, + KeyEvent.KEYCODE_F12, + 0, //KeyEvent.VK_RO, + 0, //KeyEvent.VK_KATAKANA, + 0, //KeyEvent.VK_HIRAGANA, + 0, //KeyEvent.VK_HENKAN, + 0, //KeyEvent.VK_KATAKANAHIRAGANA, + 0, //KeyEvent.VK_MUHENKAN, + 0, //KeyEvent.VK_KPJPCOMMA, + KeyEvent.KEYCODE_NUMPAD_ENTER, + KeyEvent.KEYCODE_CTRL_RIGHT, + KeyEvent.KEYCODE_NUMPAD_DIVIDE, + KeyEvent.KEYCODE_SYSRQ, + KeyEvent.KEYCODE_ALT_RIGHT, + 0, //KeyEvent.VK_LINEFEED, + KeyEvent.KEYCODE_HOME, + KeyEvent.KEYCODE_DPAD_UP, + KeyEvent.KEYCODE_PAGE_UP, + KeyEvent.KEYCODE_DPAD_LEFT, + KeyEvent.KEYCODE_DPAD_RIGHT, + KeyEvent.KEYCODE_MOVE_END, + KeyEvent.KEYCODE_DPAD_DOWN, + KeyEvent.KEYCODE_PAGE_DOWN, + KeyEvent.KEYCODE_INSERT, + KeyEvent.KEYCODE_FORWARD_DEL, + 0, //KeyEvent.VK_MACRO, + 0, //KeyEvent.VK_MUTE, + 0, //KeyEvent.VK_VOLUMEDOWN, + 0, //KeyEvent.VK_VOLUMEUP, + 0, //KeyEvent.VK_POWER, /* SC System Power Down */ + KeyEvent.KEYCODE_NUMPAD_EQUALS, + 0, //KeyEvent.VK_KPPLUSMINUS, + KeyEvent.KEYCODE_BREAK, + 0, //KeyEvent.VK_SCALE, /* AL Compiz Scale (Expose) */ + }; + + public static short translateEvdevKeyCode(short evdevKeyCode) { + if (evdevKeyCode < EVDEV_KEY_CODES.length) { + return EVDEV_KEY_CODES[evdevKeyCode]; + } + + return 0; + } } diff --git a/app/src/main/java/com/limelight/binding/input/evdev/EvdevWatcher.java b/app/src/main/java/com/limelight/binding/input/evdev/EvdevWatcher.java index e7665826..731168e0 100644 --- a/app/src/main/java/com/limelight/binding/input/evdev/EvdevWatcher.java +++ b/app/src/main/java/com/limelight/binding/input/evdev/EvdevWatcher.java @@ -10,115 +10,115 @@ import android.os.FileObserver; @SuppressWarnings("ALL") public class EvdevWatcher { - private static final String PATH = "/dev/input"; - private static final String REQUIRED_FILE_PREFIX = "event"; - - private final HashMap handlers = new HashMap(); - private boolean shutdown = false; - private boolean init = false; - private boolean ungrabbed = false; - private EvdevListener listener; - private Thread startThread; + private static final String PATH = "/dev/input"; + private static final String REQUIRED_FILE_PREFIX = "event"; + + private final HashMap handlers = new HashMap(); + private boolean shutdown = false; + private boolean init = false; + private boolean ungrabbed = false; + private EvdevListener listener; + private Thread startThread; private static boolean patchedSeLinuxPolicies = false; - - private FileObserver observer = new FileObserver(PATH, FileObserver.CREATE | FileObserver.DELETE) { - @Override - public void onEvent(int event, String fileName) { - if (fileName == null) { - return; - } - - if (!fileName.startsWith(REQUIRED_FILE_PREFIX)) { - return; - } - - synchronized (handlers) { - if (shutdown) { - return; - } - - if ((event & FileObserver.CREATE) != 0) { - LimeLog.info("Starting evdev handler for "+fileName); - - if (!init) { - // If this a real new device, update permissions again so we can read it - EvdevReader.setPermissions(new String[]{PATH + "/" + fileName}, 0666); - } - - EvdevHandler handler = new EvdevHandler(PATH + "/" + fileName, listener); - - // If we're ungrabbed now, don't start the handler - if (!ungrabbed) { - handler.start(); - } - - handlers.put(fileName, handler); - } - - if ((event & FileObserver.DELETE) != 0) { - LimeLog.info("Halting evdev handler for "+fileName); - - EvdevHandler handler = handlers.remove(fileName); - if (handler != null) { - handler.notifyDeleted(); - } - } - } - } - }; - - public EvdevWatcher(EvdevListener listener) { - this.listener = listener; - } - - private File[] rundownWithPermissionsChange(int newPermissions) { - // Rundown existing files - File devInputDir = new File(PATH); - File[] files = devInputDir.listFiles(); - if (files == null) { - return new File[0]; - } - - // Set desired permissions - String[] filePaths = new String[files.length]; - for (int i = 0; i < files.length; i++) { - filePaths[i] = files[i].getAbsolutePath(); - } - EvdevReader.setPermissions(filePaths, newPermissions); - - return files; - } - - public void ungrabAll() { - synchronized (handlers) { - // Note that we're ungrabbed for now - ungrabbed = true; - - // Stop all handlers - for (EvdevHandler handler : handlers.values()) { - handler.stop(); - } - } - } - - public void regrabAll() { - synchronized (handlers) { - // We're regrabbing everything now - ungrabbed = false; - - for (Map.Entry entry : handlers.entrySet()) { - // We need to recreate each entry since we can't reuse a stopped one - entry.setValue(new EvdevHandler(PATH + "/" + entry.getKey(), listener)); - entry.getValue().start(); - } - } - } - - public void start() { - startThread = new Thread() { - @Override - public void run() { + + private FileObserver observer = new FileObserver(PATH, FileObserver.CREATE | FileObserver.DELETE) { + @Override + public void onEvent(int event, String fileName) { + if (fileName == null) { + return; + } + + if (!fileName.startsWith(REQUIRED_FILE_PREFIX)) { + return; + } + + synchronized (handlers) { + if (shutdown) { + return; + } + + if ((event & FileObserver.CREATE) != 0) { + LimeLog.info("Starting evdev handler for "+fileName); + + if (!init) { + // If this a real new device, update permissions again so we can read it + EvdevReader.setPermissions(new String[]{PATH + "/" + fileName}, 0666); + } + + EvdevHandler handler = new EvdevHandler(PATH + "/" + fileName, listener); + + // If we're ungrabbed now, don't start the handler + if (!ungrabbed) { + handler.start(); + } + + handlers.put(fileName, handler); + } + + if ((event & FileObserver.DELETE) != 0) { + LimeLog.info("Halting evdev handler for "+fileName); + + EvdevHandler handler = handlers.remove(fileName); + if (handler != null) { + handler.notifyDeleted(); + } + } + } + } + }; + + public EvdevWatcher(EvdevListener listener) { + this.listener = listener; + } + + private File[] rundownWithPermissionsChange(int newPermissions) { + // Rundown existing files + File devInputDir = new File(PATH); + File[] files = devInputDir.listFiles(); + if (files == null) { + return new File[0]; + } + + // Set desired permissions + String[] filePaths = new String[files.length]; + for (int i = 0; i < files.length; i++) { + filePaths[i] = files[i].getAbsolutePath(); + } + EvdevReader.setPermissions(filePaths, newPermissions); + + return files; + } + + public void ungrabAll() { + synchronized (handlers) { + // Note that we're ungrabbed for now + ungrabbed = true; + + // Stop all handlers + for (EvdevHandler handler : handlers.values()) { + handler.stop(); + } + } + } + + public void regrabAll() { + synchronized (handlers) { + // We're regrabbing everything now + ungrabbed = false; + + for (Map.Entry entry : handlers.entrySet()) { + // We need to recreate each entry since we can't reuse a stopped one + entry.setValue(new EvdevHandler(PATH + "/" + entry.getKey(), listener)); + entry.getValue().start(); + } + } + } + + public void start() { + startThread = new Thread() { + @Override + public void run() { // Initialize the root shell EvdevShell.getInstance().startShell(); @@ -128,61 +128,61 @@ public class EvdevWatcher { patchedSeLinuxPolicies = true; } - // List all files and allow us access - File[] files = rundownWithPermissionsChange(0666); - - init = true; - for (File f : files) { - observer.onEvent(FileObserver.CREATE, f.getName()); - } - - // Done with initial onEvent calls - init = false; - - // Start watching for new files - observer.startWatching(); - - synchronized (startThread) { - // Wait to be awoken again by shutdown() - try { - startThread.wait(); - } catch (InterruptedException e) {} - } - - // Giveup eventX permissions - rundownWithPermissionsChange(0660); + // List all files and allow us access + File[] files = rundownWithPermissionsChange(0666); + + init = true; + for (File f : files) { + observer.onEvent(FileObserver.CREATE, f.getName()); + } + + // Done with initial onEvent calls + init = false; + + // Start watching for new files + observer.startWatching(); + + synchronized (startThread) { + // Wait to be awoken again by shutdown() + try { + startThread.wait(); + } catch (InterruptedException e) {} + } + + // Giveup eventX permissions + rundownWithPermissionsChange(0660); // Kill the root shell try { EvdevShell.getInstance().stopShell(); } catch (InterruptedException e) {} - } - }; - startThread.start(); - } - - public void shutdown() { - // Let start thread cleanup on it's own sweet time - synchronized (startThread) { - startThread.notify(); - } - - // Stop the observer - observer.stopWatching(); - - synchronized (handlers) { - // Stop creating new handlers - shutdown = true; - - // If we've already ungrabbed, there's nothing else to do - if (ungrabbed) { - return; - } - - // Stop all handlers - for (EvdevHandler handler : handlers.values()) { - handler.stop(); - } - } - } + } + }; + startThread.start(); + } + + public void shutdown() { + // Let start thread cleanup on it's own sweet time + synchronized (startThread) { + startThread.notify(); + } + + // Stop the observer + observer.stopWatching(); + + synchronized (handlers) { + // Stop creating new handlers + shutdown = true; + + // If we've already ungrabbed, there's nothing else to do + if (ungrabbed) { + return; + } + + // Stop all handlers + for (EvdevHandler handler : handlers.values()) { + handler.stop(); + } + } + } } diff --git a/app/src/main/java/com/limelight/binding/video/AndroidCpuDecoderRenderer.java b/app/src/main/java/com/limelight/binding/video/AndroidCpuDecoderRenderer.java index 60ed4297..7d93ebf5 100644 --- a/app/src/main/java/com/limelight/binding/video/AndroidCpuDecoderRenderer.java +++ b/app/src/main/java/com/limelight/binding/video/AndroidCpuDecoderRenderer.java @@ -20,140 +20,140 @@ import com.limelight.nvstream.av.video.cpu.AvcDecoder; @SuppressWarnings("EmptyCatchBlock") public class AndroidCpuDecoderRenderer extends EnhancedDecoderRenderer { - private Thread rendererThread, decoderThread; - private int targetFps; - - private static final int DECODER_BUFFER_SIZE = 92*1024; - private ByteBuffer decoderBuffer; - - // Only sleep if the difference is above this value - private static final int WAIT_CEILING_MS = 5; - - private static final int LOW_PERF = 1; - private static final int MED_PERF = 2; - private static final int HIGH_PERF = 3; - - private int totalFrames; - private long totalTimeMs; - - private final int cpuCount = Runtime.getRuntime().availableProcessors(); - - @SuppressWarnings("unused") - private int findOptimalPerformanceLevel() { - StringBuilder cpuInfo = new StringBuilder(); - BufferedReader br = null; - try { - br = new BufferedReader(new FileReader(new File("/proc/cpuinfo"))); - for (;;) { - int ch = br.read(); - if (ch == -1) - break; - cpuInfo.append((char)ch); - } - - // Here we're doing very simple heuristics based on CPU model - String cpuInfoStr = cpuInfo.toString(); + private Thread rendererThread, decoderThread; + private int targetFps; - // We order them from greatest to least for proper detection - // of devices with multiple sets of cores (like Exynos 5 Octa) - // TODO Make this better (only even kind of works on ARM) - if (Build.FINGERPRINT.contains("generic")) { - // Emulator - return LOW_PERF; - } - else if (cpuInfoStr.contains("0xc0f")) { - // Cortex-A15 - return MED_PERF; - } - else if (cpuInfoStr.contains("0xc09")) { - // Cortex-A9 - return LOW_PERF; - } - else if (cpuInfoStr.contains("0xc07")) { - // Cortex-A7 - return LOW_PERF; - } - else { - // Didn't have anything we're looking for - return MED_PERF; - } - } catch (IOException e) { - } finally { - if (br != null) { - try { - br.close(); - } catch (IOException e) {} - } - } - - // Couldn't read cpuinfo, so assume medium - return MED_PERF; - } - - @Override - public boolean setup(int width, int height, int redrawRate, Object renderTarget, int drFlags) { - this.targetFps = redrawRate; - - int perfLevel = LOW_PERF; //findOptimalPerformanceLevel(); - int threadCount; - - int avcFlags = 0; - switch (perfLevel) { - case HIGH_PERF: - // Single threaded low latency decode is ideal but hard to acheive - avcFlags = AvcDecoder.LOW_LATENCY_DECODE; - threadCount = 1; - break; + private static final int DECODER_BUFFER_SIZE = 92*1024; + private ByteBuffer decoderBuffer; - case LOW_PERF: - // Disable the loop filter for performance reasons - avcFlags = AvcDecoder.FAST_BILINEAR_FILTERING; - - // Use plenty of threads to try to utilize the CPU as best we can - threadCount = cpuCount - 1; - break; + // Only sleep if the difference is above this value + private static final int WAIT_CEILING_MS = 5; - default: - case MED_PERF: - avcFlags = AvcDecoder.BILINEAR_FILTERING; - - // Only use 2 threads to minimize frame processing latency - threadCount = 2; - break; - } - - // If the user wants quality, we'll remove the low IQ flags - if ((drFlags & VideoDecoderRenderer.FLAG_PREFER_QUALITY) != 0) { - // Make sure the loop filter is enabled - avcFlags &= ~AvcDecoder.DISABLE_LOOP_FILTER; - - // Disable the non-compliant speed optimizations - avcFlags &= ~AvcDecoder.FAST_DECODE; - - LimeLog.info("Using high quality decoding"); - } - - SurfaceHolder sh = (SurfaceHolder)renderTarget; - sh.setFormat(PixelFormat.RGBX_8888); - - int err = AvcDecoder.init(width, height, avcFlags, threadCount); - if (err != 0) { - throw new IllegalStateException("AVC decoder initialization failure: "+err); - } - - if (!AvcDecoder.setRenderTarget(sh.getSurface())) { + private static final int LOW_PERF = 1; + private static final int MED_PERF = 2; + private static final int HIGH_PERF = 3; + + private int totalFrames; + private long totalTimeMs; + + private final int cpuCount = Runtime.getRuntime().availableProcessors(); + + @SuppressWarnings("unused") + private int findOptimalPerformanceLevel() { + StringBuilder cpuInfo = new StringBuilder(); + BufferedReader br = null; + try { + br = new BufferedReader(new FileReader(new File("/proc/cpuinfo"))); + for (;;) { + int ch = br.read(); + if (ch == -1) + break; + cpuInfo.append((char)ch); + } + + // Here we're doing very simple heuristics based on CPU model + String cpuInfoStr = cpuInfo.toString(); + + // We order them from greatest to least for proper detection + // of devices with multiple sets of cores (like Exynos 5 Octa) + // TODO Make this better (only even kind of works on ARM) + if (Build.FINGERPRINT.contains("generic")) { + // Emulator + return LOW_PERF; + } + else if (cpuInfoStr.contains("0xc0f")) { + // Cortex-A15 + return MED_PERF; + } + else if (cpuInfoStr.contains("0xc09")) { + // Cortex-A9 + return LOW_PERF; + } + else if (cpuInfoStr.contains("0xc07")) { + // Cortex-A7 + return LOW_PERF; + } + else { + // Didn't have anything we're looking for + return MED_PERF; + } + } catch (IOException e) { + } finally { + if (br != null) { + try { + br.close(); + } catch (IOException e) {} + } + } + + // Couldn't read cpuinfo, so assume medium + return MED_PERF; + } + + @Override + public boolean setup(int width, int height, int redrawRate, Object renderTarget, int drFlags) { + this.targetFps = redrawRate; + + int perfLevel = LOW_PERF; //findOptimalPerformanceLevel(); + int threadCount; + + int avcFlags = 0; + switch (perfLevel) { + case HIGH_PERF: + // Single threaded low latency decode is ideal but hard to acheive + avcFlags = AvcDecoder.LOW_LATENCY_DECODE; + threadCount = 1; + break; + + case LOW_PERF: + // Disable the loop filter for performance reasons + avcFlags = AvcDecoder.FAST_BILINEAR_FILTERING; + + // Use plenty of threads to try to utilize the CPU as best we can + threadCount = cpuCount - 1; + break; + + default: + case MED_PERF: + avcFlags = AvcDecoder.BILINEAR_FILTERING; + + // Only use 2 threads to minimize frame processing latency + threadCount = 2; + break; + } + + // If the user wants quality, we'll remove the low IQ flags + if ((drFlags & VideoDecoderRenderer.FLAG_PREFER_QUALITY) != 0) { + // Make sure the loop filter is enabled + avcFlags &= ~AvcDecoder.DISABLE_LOOP_FILTER; + + // Disable the non-compliant speed optimizations + avcFlags &= ~AvcDecoder.FAST_DECODE; + + LimeLog.info("Using high quality decoding"); + } + + SurfaceHolder sh = (SurfaceHolder)renderTarget; + sh.setFormat(PixelFormat.RGBX_8888); + + int err = AvcDecoder.init(width, height, avcFlags, threadCount); + if (err != 0) { + throw new IllegalStateException("AVC decoder initialization failure: "+err); + } + + if (!AvcDecoder.setRenderTarget(sh.getSurface())) { return false; } - - decoderBuffer = ByteBuffer.allocate(DECODER_BUFFER_SIZE + AvcDecoder.getInputPaddingSize()); - - LimeLog.info("Using software decoding (performance level: "+perfLevel+")"); - - return true; - } - @Override - public boolean start(final VideoDepacketizer depacketizer) { + decoderBuffer = ByteBuffer.allocate(DECODER_BUFFER_SIZE + AvcDecoder.getInputPaddingSize()); + + LimeLog.info("Using software decoding (performance level: "+perfLevel+")"); + + return true; + } + + @Override + public boolean start(final VideoDepacketizer depacketizer) { decoderThread = new Thread() { @Override public void run() { @@ -174,112 +174,112 @@ public class AndroidCpuDecoderRenderer extends EnhancedDecoderRenderer { decoderThread.setPriority(Thread.MAX_PRIORITY - 1); decoderThread.start(); - rendererThread = new Thread() { - @Override - public void run() { - long nextFrameTime = System.currentTimeMillis(); - DecodeUnit du; - while (!isInterrupted()) - { - long diff = nextFrameTime - System.currentTimeMillis(); + rendererThread = new Thread() { + @Override + public void run() { + long nextFrameTime = System.currentTimeMillis(); + DecodeUnit du; + while (!isInterrupted()) + { + long diff = nextFrameTime - System.currentTimeMillis(); - if (diff > WAIT_CEILING_MS) { + if (diff > WAIT_CEILING_MS) { try { Thread.sleep(diff - WAIT_CEILING_MS); } catch (InterruptedException e) { return; } continue; - } + } - nextFrameTime = computePresentationTimeMs(targetFps); - AvcDecoder.redraw(); - } - } - }; - rendererThread.setName("Video - Renderer (CPU)"); - rendererThread.setPriority(Thread.MAX_PRIORITY); - rendererThread.start(); - return true; - } - - private long computePresentationTimeMs(int frameRate) { - return System.currentTimeMillis() + (1000 / frameRate); - } + nextFrameTime = computePresentationTimeMs(targetFps); + AvcDecoder.redraw(); + } + } + }; + rendererThread.setName("Video - Renderer (CPU)"); + rendererThread.setPriority(Thread.MAX_PRIORITY); + rendererThread.start(); + return true; + } - @Override - public void stop() { - rendererThread.interrupt(); + private long computePresentationTimeMs(int frameRate) { + return System.currentTimeMillis() + (1000 / frameRate); + } + + @Override + public void stop() { + rendererThread.interrupt(); decoderThread.interrupt(); - - try { + + try { rendererThread.join(); } catch (InterruptedException e) { } try { decoderThread.join(); } catch (InterruptedException e) { } - } + } - @Override - public void release() { - AvcDecoder.destroy(); - } + @Override + public void release() { + AvcDecoder.destroy(); + } - private boolean submitDecodeUnit(DecodeUnit decodeUnit) { - byte[] data; - - // Use the reserved decoder buffer if this decode unit will fit - if (decodeUnit.getDataLength() <= DECODER_BUFFER_SIZE) { - decoderBuffer.clear(); - - for (ByteBufferDescriptor bbd : decodeUnit.getBufferList()) { - decoderBuffer.put(bbd.data, bbd.offset, bbd.length); - } - - data = decoderBuffer.array(); - } - else { - data = new byte[decodeUnit.getDataLength()+AvcDecoder.getInputPaddingSize()]; - - int offset = 0; - for (ByteBufferDescriptor bbd : decodeUnit.getBufferList()) { - System.arraycopy(bbd.data, bbd.offset, data, offset, bbd.length); - offset += bbd.length; - } - } - - boolean success = (AvcDecoder.decode(data, 0, decodeUnit.getDataLength()) == 0); - if (success) { - long timeAfterDecode = System.currentTimeMillis(); - - // Add delta time to the totals (excluding probable outliers) - long delta = timeAfterDecode - decodeUnit.getReceiveTimestamp(); - if (delta >= 0 && delta < 1000) { - totalTimeMs += delta; - totalFrames++; - } - } - - return success; - } + private boolean submitDecodeUnit(DecodeUnit decodeUnit) { + byte[] data; - @Override - public int getCapabilities() { - return 0; - } + // Use the reserved decoder buffer if this decode unit will fit + if (decodeUnit.getDataLength() <= DECODER_BUFFER_SIZE) { + decoderBuffer.clear(); - @Override - public int getAverageDecoderLatency() { - return 0; - } + for (ByteBufferDescriptor bbd : decodeUnit.getBufferList()) { + decoderBuffer.put(bbd.data, bbd.offset, bbd.length); + } - @Override - public int getAverageEndToEndLatency() { - if (totalFrames == 0) { - return 0; - } - return (int)(totalTimeMs / totalFrames); - } + data = decoderBuffer.array(); + } + else { + data = new byte[decodeUnit.getDataLength()+AvcDecoder.getInputPaddingSize()]; + + int offset = 0; + for (ByteBufferDescriptor bbd : decodeUnit.getBufferList()) { + System.arraycopy(bbd.data, bbd.offset, data, offset, bbd.length); + offset += bbd.length; + } + } + + boolean success = (AvcDecoder.decode(data, 0, decodeUnit.getDataLength()) == 0); + if (success) { + long timeAfterDecode = System.currentTimeMillis(); + + // Add delta time to the totals (excluding probable outliers) + long delta = timeAfterDecode - decodeUnit.getReceiveTimestamp(); + if (delta >= 0 && delta < 1000) { + totalTimeMs += delta; + totalFrames++; + } + } + + return success; + } + + @Override + public int getCapabilities() { + return 0; + } + + @Override + public int getAverageDecoderLatency() { + return 0; + } + + @Override + public int getAverageEndToEndLatency() { + if (totalFrames == 0) { + return 0; + } + return (int)(totalTimeMs / totalFrames); + } @Override public String getDecoderName() { diff --git a/app/src/main/java/com/limelight/binding/video/ConfigurableDecoderRenderer.java b/app/src/main/java/com/limelight/binding/video/ConfigurableDecoderRenderer.java index e0528416..071b2294 100644 --- a/app/src/main/java/com/limelight/binding/video/ConfigurableDecoderRenderer.java +++ b/app/src/main/java/com/limelight/binding/video/ConfigurableDecoderRenderer.java @@ -5,75 +5,75 @@ import com.limelight.nvstream.av.video.VideoDepacketizer; public class ConfigurableDecoderRenderer extends EnhancedDecoderRenderer { - private EnhancedDecoderRenderer decoderRenderer; - - @Override - public void release() { - if (decoderRenderer != null) { - decoderRenderer.release(); - } - } + private EnhancedDecoderRenderer decoderRenderer; - @Override - public boolean setup(int width, int height, int redrawRate, Object renderTarget, int drFlags) { - if (decoderRenderer == null) { - throw new IllegalStateException("ConfigurableDecoderRenderer not initialized"); - } - return decoderRenderer.setup(width, height, redrawRate, renderTarget, drFlags); - } - - public void initializeWithFlags(int drFlags) { - if ((drFlags & VideoDecoderRenderer.FLAG_FORCE_HARDWARE_DECODING) != 0 || - ((drFlags & VideoDecoderRenderer.FLAG_FORCE_SOFTWARE_DECODING) == 0 && - MediaCodecHelper.findProbableSafeDecoder() != null)) { - decoderRenderer = new MediaCodecDecoderRenderer(); - } - else { - decoderRenderer = new AndroidCpuDecoderRenderer(); - } - } - - public boolean isHardwareAccelerated() { - if (decoderRenderer == null) { - throw new IllegalStateException("ConfigurableDecoderRenderer not initialized"); - } - return (decoderRenderer instanceof MediaCodecDecoderRenderer); - } + @Override + public void release() { + if (decoderRenderer != null) { + decoderRenderer.release(); + } + } - @Override - public boolean start(VideoDepacketizer depacketizer) { - return decoderRenderer.start(depacketizer); - } + @Override + public boolean setup(int width, int height, int redrawRate, Object renderTarget, int drFlags) { + if (decoderRenderer == null) { + throw new IllegalStateException("ConfigurableDecoderRenderer not initialized"); + } + return decoderRenderer.setup(width, height, redrawRate, renderTarget, drFlags); + } - @Override - public void stop() { - decoderRenderer.stop(); - } + public void initializeWithFlags(int drFlags) { + if ((drFlags & VideoDecoderRenderer.FLAG_FORCE_HARDWARE_DECODING) != 0 || + ((drFlags & VideoDecoderRenderer.FLAG_FORCE_SOFTWARE_DECODING) == 0 && + MediaCodecHelper.findProbableSafeDecoder() != null)) { + decoderRenderer = new MediaCodecDecoderRenderer(); + } + else { + decoderRenderer = new AndroidCpuDecoderRenderer(); + } + } - @Override - public int getCapabilities() { - return decoderRenderer.getCapabilities(); - } + public boolean isHardwareAccelerated() { + if (decoderRenderer == null) { + throw new IllegalStateException("ConfigurableDecoderRenderer not initialized"); + } + return (decoderRenderer instanceof MediaCodecDecoderRenderer); + } - @Override - public int getAverageDecoderLatency() { - if (decoderRenderer != null) { - return decoderRenderer.getAverageDecoderLatency(); - } - else { - return 0; - } - } + @Override + public boolean start(VideoDepacketizer depacketizer) { + return decoderRenderer.start(depacketizer); + } - @Override - public int getAverageEndToEndLatency() { - if (decoderRenderer != null) { - return decoderRenderer.getAverageEndToEndLatency(); - } - else { - return 0; - } - } + @Override + public void stop() { + decoderRenderer.stop(); + } + + @Override + public int getCapabilities() { + return decoderRenderer.getCapabilities(); + } + + @Override + public int getAverageDecoderLatency() { + if (decoderRenderer != null) { + return decoderRenderer.getAverageDecoderLatency(); + } + else { + return 0; + } + } + + @Override + public int getAverageEndToEndLatency() { + if (decoderRenderer != null) { + return decoderRenderer.getAverageEndToEndLatency(); + } + else { + return 0; + } + } @Override public String getDecoderName() { diff --git a/app/src/main/java/com/limelight/binding/video/MediaCodecDecoderRenderer.java b/app/src/main/java/com/limelight/binding/video/MediaCodecDecoderRenderer.java index 5b73ed8a..5a24de9b 100644 --- a/app/src/main/java/com/limelight/binding/video/MediaCodecDecoderRenderer.java +++ b/app/src/main/java/com/limelight/binding/video/MediaCodecDecoderRenderer.java @@ -25,391 +25,391 @@ import android.view.SurfaceHolder; @SuppressWarnings("unused") public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer { - private ByteBuffer[] videoDecoderInputBuffers; - private MediaCodec videoDecoder; - private Thread rendererThread; - private boolean needsSpsBitstreamFixup, isExynos4; - private VideoDepacketizer depacketizer; - private boolean adaptivePlayback; - private int initialWidth, initialHeight; + private ByteBuffer[] videoDecoderInputBuffers; + private MediaCodec videoDecoder; + private Thread rendererThread; + private boolean needsSpsBitstreamFixup, isExynos4; + private VideoDepacketizer depacketizer; + private boolean adaptivePlayback; + private int initialWidth, initialHeight; private boolean needsBaselineSpsHack; private SeqParameterSet savedSps; - - private long lastTimestampUs; - private long totalTimeMs; - private long decoderTimeMs; - private int totalFrames; - - private String decoderName; - private int numSpsIn; - private int numPpsIn; - private int numIframeIn; - - private static final boolean ENABLE_ASYNC_RENDERER = false; - - @TargetApi(Build.VERSION_CODES.KITKAT) - public MediaCodecDecoderRenderer() { - //dumpDecoders(); - - MediaCodecInfo decoder = MediaCodecHelper.findProbableSafeDecoder(); - if (decoder == null) { - decoder = MediaCodecHelper.findFirstDecoder(); - } - if (decoder == null) { - // This case is handled later in setup() - return; - } - - decoderName = decoder.getName(); - - // Set decoder-specific attributes - adaptivePlayback = MediaCodecHelper.decoderSupportsAdaptivePlayback(decoderName, decoder); - needsSpsBitstreamFixup = MediaCodecHelper.decoderNeedsSpsBitstreamRestrictions(decoderName, decoder); + + private long lastTimestampUs; + private long totalTimeMs; + private long decoderTimeMs; + private int totalFrames; + + private String decoderName; + private int numSpsIn; + private int numPpsIn; + private int numIframeIn; + + private static final boolean ENABLE_ASYNC_RENDERER = false; + + @TargetApi(Build.VERSION_CODES.KITKAT) + public MediaCodecDecoderRenderer() { + //dumpDecoders(); + + MediaCodecInfo decoder = MediaCodecHelper.findProbableSafeDecoder(); + if (decoder == null) { + decoder = MediaCodecHelper.findFirstDecoder(); + } + if (decoder == null) { + // This case is handled later in setup() + return; + } + + decoderName = decoder.getName(); + + // Set decoder-specific attributes + adaptivePlayback = MediaCodecHelper.decoderSupportsAdaptivePlayback(decoderName, decoder); + needsSpsBitstreamFixup = MediaCodecHelper.decoderNeedsSpsBitstreamRestrictions(decoderName, decoder); needsBaselineSpsHack = MediaCodecHelper.decoderNeedsBaselineSpsHack(decoderName, decoder); isExynos4 = MediaCodecHelper.isExynos4Device(); if (needsSpsBitstreamFixup) { - LimeLog.info("Decoder "+decoderName+" needs SPS bitstream restrictions fixup"); - } + LimeLog.info("Decoder "+decoderName+" needs SPS bitstream restrictions fixup"); + } if (needsBaselineSpsHack) { LimeLog.info("Decoder "+decoderName+" needs baseline SPS hack"); } - if (isExynos4) { - LimeLog.info("Decoder "+decoderName+" is on Exynos 4"); - } - } - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - @Override - public boolean setup(int width, int height, int redrawRate, Object renderTarget, int drFlags) { - this.initialWidth = width; - this.initialHeight = height; - - if (decoderName == null) { - LimeLog.severe("No available hardware decoder!"); - return false; - } - - // Codecs have been known to throw all sorts of crazy runtime exceptions - // due to implementation problems - try { - videoDecoder = MediaCodec.createByCodecName(decoderName); - } catch (Exception e) { - return false; - } - - MediaFormat videoFormat = MediaFormat.createVideoFormat("video/avc", width, height); - - // Adaptive playback can also be enabled by the whitelist on pre-KitKat devices - // so we don't fill these pre-KitKat - if (adaptivePlayback && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - videoFormat.setInteger(MediaFormat.KEY_MAX_WIDTH, width); - videoFormat.setInteger(MediaFormat.KEY_MAX_HEIGHT, height); - } - - // On Lollipop, we use asynchronous mode to avoid having a busy looping renderer thread - if (ENABLE_ASYNC_RENDERER && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - videoDecoder.setCallback(new MediaCodec.Callback() { - @Override - public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) { - LimeLog.info("Output format changed"); - LimeLog.info("New output Format: " + format); - } - - @Override - public void onOutputBufferAvailable(MediaCodec codec, int index, - BufferInfo info) { - try { - // FIXME: It looks like we can't frameskip here - codec.releaseOutputBuffer(index, true); - } catch (Exception e) { - handleDecoderException(MediaCodecDecoderRenderer.this, e, null, 0); - } - } - - @Override - public void onInputBufferAvailable(MediaCodec codec, int index) { - try { - submitDecodeUnit(depacketizer.takeNextDecodeUnit(), codec.getInputBuffer(index), index); - } catch (InterruptedException e) { - // What do we do here? - e.printStackTrace(); - } catch (Exception e) { - handleDecoderException(MediaCodecDecoderRenderer.this, e, null, 0); - } - } - - @Override - public void onError(MediaCodec codec, CodecException e) { - if (e.isTransient()) { - LimeLog.warning(e.getDiagnosticInfo()); - e.printStackTrace(); - } - else { - LimeLog.severe(e.getDiagnosticInfo()); - e.printStackTrace(); - } - } - }); - } - - videoDecoder.configure(videoFormat, ((SurfaceHolder)renderTarget).getSurface(), null, 0); - videoDecoder.setVideoScalingMode(MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT); - - LimeLog.info("Using hardware decoding"); - - return true; - } - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - private void handleDecoderException(MediaCodecDecoderRenderer dr, Exception e, ByteBuffer buf, int codecFlags) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - if (e instanceof CodecException) { - CodecException codecExc = (CodecException) e; - - if (codecExc.isTransient()) { - // We'll let transient exceptions go - LimeLog.warning(codecExc.getDiagnosticInfo()); - return; - } - - LimeLog.severe(codecExc.getDiagnosticInfo()); - } - } - - if (buf != null || codecFlags != 0) { - throw new RendererException(dr, e, buf, codecFlags); - } - else { - throw new RendererException(dr, e); - } - } - - private void startRendererThread() - { - rendererThread = new Thread() { - @SuppressWarnings("deprecation") - @Override - public void run() { - BufferInfo info = new BufferInfo(); - DecodeUnit du = null; - int inputIndex = -1; - while (!isInterrupted()) - { - // In order to get as much data to the decoder as early as possible, - // try to submit up to 5 decode units at once without blocking. - if (inputIndex == -1 && du == null) { - try { - for (int i = 0; i < 5; i++) { - inputIndex = videoDecoder.dequeueInputBuffer(0); - du = depacketizer.pollNextDecodeUnit(); + if (isExynos4) { + LimeLog.info("Decoder "+decoderName+" is on Exynos 4"); + } + } - // Stop if we can't get a DU or input buffer - if (du == null || inputIndex == -1) { - break; - } - - submitDecodeUnit(du, videoDecoderInputBuffers[inputIndex], inputIndex); - - du = null; - inputIndex = -1; - } - } catch (Exception e) { - inputIndex = -1; - handleDecoderException(MediaCodecDecoderRenderer.this, e, null, 0); - } - } - - // Grab an input buffer if we don't have one already. - // This way we can have one ready hopefully by the time - // the depacketizer is done with this frame. It's important - // that this can timeout because it's possible that we could exhaust - // the decoder's input buffers and deadlocks because aren't pulling - // frames out of the other end. - if (inputIndex == -1) { - try { - // If we've got a DU waiting to be given to the decoder, - // wait a full 3 ms for an input buffer. Otherwise - // just see if we can get one immediately. - inputIndex = videoDecoder.dequeueInputBuffer(du != null ? 3000 : 0); - } catch (Exception e) { - inputIndex = -1; - handleDecoderException(MediaCodecDecoderRenderer.this, e, null, 0); - } - } - - // Grab a decode unit if we don't have one already - if (du == null) { - du = depacketizer.pollNextDecodeUnit(); - } - - // If we've got both a decode unit and an input buffer, we'll - // submit now. Otherwise, we wait until we have one. - if (du != null && inputIndex >= 0) { - submitDecodeUnit(du, videoDecoderInputBuffers[inputIndex], inputIndex); - - // DU and input buffer have both been consumed - du = null; - inputIndex = -1; - } - - // Try to output a frame - try { - int outIndex = videoDecoder.dequeueOutputBuffer(info, 0); + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + @Override + public boolean setup(int width, int height, int redrawRate, Object renderTarget, int drFlags) { + this.initialWidth = width; + this.initialHeight = height; - if (outIndex >= 0) { - long presentationTimeUs = info.presentationTimeUs; - int lastIndex = outIndex; - - // Get the last output buffer in the queue - while ((outIndex = videoDecoder.dequeueOutputBuffer(info, 0)) >= 0) { - videoDecoder.releaseOutputBuffer(lastIndex, false); - lastIndex = outIndex; - presentationTimeUs = info.presentationTimeUs; - } - - // Render the last buffer - videoDecoder.releaseOutputBuffer(lastIndex, true); - - // Add delta time to the totals (excluding probable outliers) - long delta = System.currentTimeMillis()-(presentationTimeUs/1000); - if (delta >= 0 && delta < 1000) { - decoderTimeMs += delta; - totalTimeMs += delta; - } - } else { - switch (outIndex) { - case MediaCodec.INFO_TRY_AGAIN_LATER: - // Getting an input buffer may already block - // so don't park if we still need to do that - if (inputIndex >= 0) { - LockSupport.parkNanos(1); - } - break; - case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED: - LimeLog.info("Output buffers changed"); - break; - case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED: - LimeLog.info("Output format changed"); - LimeLog.info("New output Format: " + videoDecoder.getOutputFormat()); - break; - default: - break; - } - } - } catch (Exception e) { - handleDecoderException(MediaCodecDecoderRenderer.this, e, null, 0); - } - } - } - }; - rendererThread.setName("Video - Renderer (MediaCodec)"); - rendererThread.setPriority(Thread.MAX_PRIORITY); - rendererThread.start(); - } + if (decoderName == null) { + LimeLog.severe("No available hardware decoder!"); + return false; + } - @SuppressWarnings("deprecation") - @Override - public boolean start(VideoDepacketizer depacketizer) { - this.depacketizer = depacketizer; - - // Start the decoder - videoDecoder.start(); - - // On devices pre-Lollipop, we'll use a rendering thread - if (!ENABLE_ASYNC_RENDERER || Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - videoDecoderInputBuffers = videoDecoder.getInputBuffers(); - startRendererThread(); - } - return true; - } + // Codecs have been known to throw all sorts of crazy runtime exceptions + // due to implementation problems + try { + videoDecoder = MediaCodec.createByCodecName(decoderName); + } catch (Exception e) { + return false; + } - @Override - public void stop() { - if (rendererThread != null) { - // Halt the rendering thread - rendererThread.interrupt(); - try { - rendererThread.join(); - } catch (InterruptedException ignored) { } - } - - // Stop the decoder - videoDecoder.stop(); - } + MediaFormat videoFormat = MediaFormat.createVideoFormat("video/avc", width, height); - @Override - public void release() { - if (videoDecoder != null) { - videoDecoder.release(); - } - } - - private void queueInputBuffer(int inputBufferIndex, int offset, int length, long timestampUs, int codecFlags) { - // Try 25 times to submit the input buffer before throwing a real exception - int i; - Exception lastException = null; - - for (i = 0; i < 25; i++) { - try { - videoDecoder.queueInputBuffer(inputBufferIndex, - 0, length, - timestampUs, codecFlags); - break; - } catch (Exception e) { - handleDecoderException(this, e, null, codecFlags); - lastException = e; - } - } - - if (i == 25) { - throw new RendererException(this, lastException, null, codecFlags); - } - } + // Adaptive playback can also be enabled by the whitelist on pre-KitKat devices + // so we don't fill these pre-KitKat + if (adaptivePlayback && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + videoFormat.setInteger(MediaFormat.KEY_MAX_WIDTH, width); + videoFormat.setInteger(MediaFormat.KEY_MAX_HEIGHT, height); + } - @SuppressWarnings("deprecation") - private void submitDecodeUnit(DecodeUnit decodeUnit, ByteBuffer buf, int inputBufferIndex) { - long currentTime = System.currentTimeMillis(); - long delta = currentTime-decodeUnit.getReceiveTimestamp(); - if (delta >= 0 && delta < 1000) { - totalTimeMs += currentTime-decodeUnit.getReceiveTimestamp(); - totalFrames++; - } - - long timestampUs = currentTime * 1000; - if (timestampUs <= lastTimestampUs) { - // We can't submit multiple buffers with the same timestamp - // so bump it up by one before queuing - timestampUs = lastTimestampUs + 1; - } - lastTimestampUs = timestampUs; - - // Clear old input data - buf.clear(); - - int codecFlags = 0; - int decodeUnitFlags = decodeUnit.getFlags(); - if ((decodeUnitFlags & DecodeUnit.DU_FLAG_CODEC_CONFIG) != 0) { - codecFlags |= MediaCodec.BUFFER_FLAG_CODEC_CONFIG; - } - if ((decodeUnitFlags & DecodeUnit.DU_FLAG_SYNC_FRAME) != 0) { - codecFlags |= MediaCodec.BUFFER_FLAG_SYNC_FRAME; - numIframeIn++; - } + // On Lollipop, we use asynchronous mode to avoid having a busy looping renderer thread + if (ENABLE_ASYNC_RENDERER && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + videoDecoder.setCallback(new MediaCodec.Callback() { + @Override + public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) { + LimeLog.info("Output format changed"); + LimeLog.info("New output Format: " + format); + } + + @Override + public void onOutputBufferAvailable(MediaCodec codec, int index, + BufferInfo info) { + try { + // FIXME: It looks like we can't frameskip here + codec.releaseOutputBuffer(index, true); + } catch (Exception e) { + handleDecoderException(MediaCodecDecoderRenderer.this, e, null, 0); + } + } + + @Override + public void onInputBufferAvailable(MediaCodec codec, int index) { + try { + submitDecodeUnit(depacketizer.takeNextDecodeUnit(), codec.getInputBuffer(index), index); + } catch (InterruptedException e) { + // What do we do here? + e.printStackTrace(); + } catch (Exception e) { + handleDecoderException(MediaCodecDecoderRenderer.this, e, null, 0); + } + } + + @Override + public void onError(MediaCodec codec, CodecException e) { + if (e.isTransient()) { + LimeLog.warning(e.getDiagnosticInfo()); + e.printStackTrace(); + } + else { + LimeLog.severe(e.getDiagnosticInfo()); + e.printStackTrace(); + } + } + }); + } + + videoDecoder.configure(videoFormat, ((SurfaceHolder)renderTarget).getSurface(), null, 0); + videoDecoder.setVideoScalingMode(MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT); + + LimeLog.info("Using hardware decoding"); + + return true; + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + private void handleDecoderException(MediaCodecDecoderRenderer dr, Exception e, ByteBuffer buf, int codecFlags) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + if (e instanceof CodecException) { + CodecException codecExc = (CodecException) e; + + if (codecExc.isTransient()) { + // We'll let transient exceptions go + LimeLog.warning(codecExc.getDiagnosticInfo()); + return; + } + + LimeLog.severe(codecExc.getDiagnosticInfo()); + } + } + + if (buf != null || codecFlags != 0) { + throw new RendererException(dr, e, buf, codecFlags); + } + else { + throw new RendererException(dr, e); + } + } + + private void startRendererThread() + { + rendererThread = new Thread() { + @SuppressWarnings("deprecation") + @Override + public void run() { + BufferInfo info = new BufferInfo(); + DecodeUnit du = null; + int inputIndex = -1; + while (!isInterrupted()) + { + // In order to get as much data to the decoder as early as possible, + // try to submit up to 5 decode units at once without blocking. + if (inputIndex == -1 && du == null) { + try { + for (int i = 0; i < 5; i++) { + inputIndex = videoDecoder.dequeueInputBuffer(0); + du = depacketizer.pollNextDecodeUnit(); + + // Stop if we can't get a DU or input buffer + if (du == null || inputIndex == -1) { + break; + } + + submitDecodeUnit(du, videoDecoderInputBuffers[inputIndex], inputIndex); + + du = null; + inputIndex = -1; + } + } catch (Exception e) { + inputIndex = -1; + handleDecoderException(MediaCodecDecoderRenderer.this, e, null, 0); + } + } + + // Grab an input buffer if we don't have one already. + // This way we can have one ready hopefully by the time + // the depacketizer is done with this frame. It's important + // that this can timeout because it's possible that we could exhaust + // the decoder's input buffers and deadlocks because aren't pulling + // frames out of the other end. + if (inputIndex == -1) { + try { + // If we've got a DU waiting to be given to the decoder, + // wait a full 3 ms for an input buffer. Otherwise + // just see if we can get one immediately. + inputIndex = videoDecoder.dequeueInputBuffer(du != null ? 3000 : 0); + } catch (Exception e) { + inputIndex = -1; + handleDecoderException(MediaCodecDecoderRenderer.this, e, null, 0); + } + } + + // Grab a decode unit if we don't have one already + if (du == null) { + du = depacketizer.pollNextDecodeUnit(); + } + + // If we've got both a decode unit and an input buffer, we'll + // submit now. Otherwise, we wait until we have one. + if (du != null && inputIndex >= 0) { + submitDecodeUnit(du, videoDecoderInputBuffers[inputIndex], inputIndex); + + // DU and input buffer have both been consumed + du = null; + inputIndex = -1; + } + + // Try to output a frame + try { + int outIndex = videoDecoder.dequeueOutputBuffer(info, 0); + + if (outIndex >= 0) { + long presentationTimeUs = info.presentationTimeUs; + int lastIndex = outIndex; + + // Get the last output buffer in the queue + while ((outIndex = videoDecoder.dequeueOutputBuffer(info, 0)) >= 0) { + videoDecoder.releaseOutputBuffer(lastIndex, false); + lastIndex = outIndex; + presentationTimeUs = info.presentationTimeUs; + } + + // Render the last buffer + videoDecoder.releaseOutputBuffer(lastIndex, true); + + // Add delta time to the totals (excluding probable outliers) + long delta = System.currentTimeMillis()-(presentationTimeUs/1000); + if (delta >= 0 && delta < 1000) { + decoderTimeMs += delta; + totalTimeMs += delta; + } + } else { + switch (outIndex) { + case MediaCodec.INFO_TRY_AGAIN_LATER: + // Getting an input buffer may already block + // so don't park if we still need to do that + if (inputIndex >= 0) { + LockSupport.parkNanos(1); + } + break; + case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED: + LimeLog.info("Output buffers changed"); + break; + case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED: + LimeLog.info("Output format changed"); + LimeLog.info("New output Format: " + videoDecoder.getOutputFormat()); + break; + default: + break; + } + } + } catch (Exception e) { + handleDecoderException(MediaCodecDecoderRenderer.this, e, null, 0); + } + } + } + }; + rendererThread.setName("Video - Renderer (MediaCodec)"); + rendererThread.setPriority(Thread.MAX_PRIORITY); + rendererThread.start(); + } + + @SuppressWarnings("deprecation") + @Override + public boolean start(VideoDepacketizer depacketizer) { + this.depacketizer = depacketizer; + + // Start the decoder + videoDecoder.start(); + + // On devices pre-Lollipop, we'll use a rendering thread + if (!ENABLE_ASYNC_RENDERER || Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + videoDecoderInputBuffers = videoDecoder.getInputBuffers(); + startRendererThread(); + } + return true; + } + + @Override + public void stop() { + if (rendererThread != null) { + // Halt the rendering thread + rendererThread.interrupt(); + try { + rendererThread.join(); + } catch (InterruptedException ignored) { } + } + + // Stop the decoder + videoDecoder.stop(); + } + + @Override + public void release() { + if (videoDecoder != null) { + videoDecoder.release(); + } + } + + private void queueInputBuffer(int inputBufferIndex, int offset, int length, long timestampUs, int codecFlags) { + // Try 25 times to submit the input buffer before throwing a real exception + int i; + Exception lastException = null; + + for (i = 0; i < 25; i++) { + try { + videoDecoder.queueInputBuffer(inputBufferIndex, + 0, length, + timestampUs, codecFlags); + break; + } catch (Exception e) { + handleDecoderException(this, e, null, codecFlags); + lastException = e; + } + } + + if (i == 25) { + throw new RendererException(this, lastException, null, codecFlags); + } + } + + @SuppressWarnings("deprecation") + private void submitDecodeUnit(DecodeUnit decodeUnit, ByteBuffer buf, int inputBufferIndex) { + long currentTime = System.currentTimeMillis(); + long delta = currentTime-decodeUnit.getReceiveTimestamp(); + if (delta >= 0 && delta < 1000) { + totalTimeMs += currentTime-decodeUnit.getReceiveTimestamp(); + totalFrames++; + } + + long timestampUs = currentTime * 1000; + if (timestampUs <= lastTimestampUs) { + // We can't submit multiple buffers with the same timestamp + // so bump it up by one before queuing + timestampUs = lastTimestampUs + 1; + } + lastTimestampUs = timestampUs; + + // Clear old input data + buf.clear(); + + int codecFlags = 0; + int decodeUnitFlags = decodeUnit.getFlags(); + if ((decodeUnitFlags & DecodeUnit.DU_FLAG_CODEC_CONFIG) != 0) { + codecFlags |= MediaCodec.BUFFER_FLAG_CODEC_CONFIG; + } + if ((decodeUnitFlags & DecodeUnit.DU_FLAG_SYNC_FRAME) != 0) { + codecFlags |= MediaCodec.BUFFER_FLAG_SYNC_FRAME; + numIframeIn++; + } boolean needsSpsReplay = false; - - if ((decodeUnitFlags & DecodeUnit.DU_FLAG_CODEC_CONFIG) != 0) { - ByteBufferDescriptor header = decodeUnit.getBufferList().get(0); - if (header.data[header.offset+4] == 0x67) { - numSpsIn++; - - ByteBuffer spsBuf = ByteBuffer.wrap(header.data); - - // Skip to the start of the NALU data - spsBuf.position(header.offset+5); - - SeqParameterSet sps = SeqParameterSet.read(spsBuf); + + if ((decodeUnitFlags & DecodeUnit.DU_FLAG_CODEC_CONFIG) != 0) { + ByteBufferDescriptor header = decodeUnit.getBufferList().get(0); + if (header.data[header.offset+4] == 0x67) { + numSpsIn++; + + ByteBuffer spsBuf = ByteBuffer.wrap(header.data); + + // Skip to the start of the NALU data + spsBuf.position(header.offset+5); + + SeqParameterSet sps = SeqParameterSet.read(spsBuf); // Some decoders rely on H264 level to decide how many buffers are needed // Since we only need one frame buffered, we'll set the level as low as we can @@ -427,30 +427,30 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer { else { // Leave the profile alone (currently 5.0) } - - // TI OMAP4 requires a reference frame count of 1 to decode successfully. Exynos 4 - // also requires this fixup. - // - // I'm doing this fixup for all devices because I haven't seen any devices that - // this causes issues for. At worst, it seems to do nothing and at best it fixes - // issues with video lag, hangs, and crashes. - LimeLog.info("Patching num_ref_frames in SPS"); - sps.num_ref_frames = 1; - - if (needsSpsBitstreamFixup || isExynos4) { - // The SPS that comes in the current H264 bytestream doesn't set bitstream_restriction_flag - // or max_dec_frame_buffering which increases decoding latency on Tegra. - LimeLog.info("Adding bitstream restrictions"); - sps.vuiParams.bitstreamRestriction = new VUIParameters.BitstreamRestriction(); - sps.vuiParams.bitstreamRestriction.motion_vectors_over_pic_boundaries_flag = true; - sps.vuiParams.bitstreamRestriction.max_bytes_per_pic_denom = 2; - sps.vuiParams.bitstreamRestriction.max_bits_per_mb_denom = 1; - sps.vuiParams.bitstreamRestriction.log2_max_mv_length_horizontal = 16; - sps.vuiParams.bitstreamRestriction.log2_max_mv_length_vertical = 16; - sps.vuiParams.bitstreamRestriction.num_reorder_frames = 0; - sps.vuiParams.bitstreamRestriction.max_dec_frame_buffering = 1; - } + // TI OMAP4 requires a reference frame count of 1 to decode successfully. Exynos 4 + // also requires this fixup. + // + // I'm doing this fixup for all devices because I haven't seen any devices that + // this causes issues for. At worst, it seems to do nothing and at best it fixes + // issues with video lag, hangs, and crashes. + LimeLog.info("Patching num_ref_frames in SPS"); + sps.num_ref_frames = 1; + + if (needsSpsBitstreamFixup || isExynos4) { + // The SPS that comes in the current H264 bytestream doesn't set bitstream_restriction_flag + // or max_dec_frame_buffering which increases decoding latency on Tegra. + LimeLog.info("Adding bitstream restrictions"); + + sps.vuiParams.bitstreamRestriction = new VUIParameters.BitstreamRestriction(); + sps.vuiParams.bitstreamRestriction.motion_vectors_over_pic_boundaries_flag = true; + sps.vuiParams.bitstreamRestriction.max_bytes_per_pic_denom = 2; + sps.vuiParams.bitstreamRestriction.max_bits_per_mb_denom = 1; + sps.vuiParams.bitstreamRestriction.log2_max_mv_length_horizontal = 16; + sps.vuiParams.bitstreamRestriction.log2_max_mv_length_vertical = 16; + sps.vuiParams.bitstreamRestriction.num_reorder_frames = 0; + sps.vuiParams.bitstreamRestriction.max_dec_frame_buffering = 1; + } // If we need to hack this SPS to say we're baseline, do so now if (needsBaselineSpsHack) { @@ -458,21 +458,21 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer { sps.profile_idc = 66; savedSps = sps; } - - // Write the annex B header - buf.put(header.data, header.offset, 5); - - // Write the modified SPS to the input buffer - sps.write(buf); - - queueInputBuffer(inputBufferIndex, - 0, buf.position(), - timestampUs, codecFlags); - - depacketizer.freeDecodeUnit(decodeUnit); - return; - } else if (header.data[header.offset+4] == 0x68) { - numPpsIn++; + + // Write the annex B header + buf.put(header.data, header.offset, 5); + + // Write the modified SPS to the input buffer + sps.write(buf); + + queueInputBuffer(inputBufferIndex, + 0, buf.position(), + timestampUs, codecFlags); + + depacketizer.freeDecodeUnit(decodeUnit); + return; + } else if (header.data[header.offset+4] == 0x68) { + numPpsIn++; if (needsBaselineSpsHack) { LimeLog.info("Saw PPS; disabling SPS hack"); @@ -481,25 +481,25 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer { // Give the decoder the SPS again with the proper profile now needsSpsReplay = true; } - } - } + } + } - // Copy data from our buffer list into the input buffer - for (ByteBufferDescriptor desc : decodeUnit.getBufferList()) - { - buf.put(desc.data, desc.offset, desc.length); - } + // Copy data from our buffer list into the input buffer + for (ByteBufferDescriptor desc : decodeUnit.getBufferList()) + { + buf.put(desc.data, desc.offset, desc.length); + } - queueInputBuffer(inputBufferIndex, - 0, decodeUnit.getDataLength(), - timestampUs, codecFlags); - - depacketizer.freeDecodeUnit(decodeUnit); + queueInputBuffer(inputBufferIndex, + 0, decodeUnit.getDataLength(), + timestampUs, codecFlags); + + depacketizer.freeDecodeUnit(decodeUnit); if (needsSpsReplay) { replaySps(); } - } + } private void replaySps() { int inputIndex = videoDecoder.dequeueInputBuffer(-1); @@ -528,27 +528,27 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer { LimeLog.info("SPS replay complete"); } - @Override - public int getCapabilities() { - return adaptivePlayback ? - VideoDecoderRenderer.CAPABILITY_ADAPTIVE_RESOLUTION : 0; - } + @Override + public int getCapabilities() { + return adaptivePlayback ? + VideoDecoderRenderer.CAPABILITY_ADAPTIVE_RESOLUTION : 0; + } - @Override - public int getAverageDecoderLatency() { - if (totalFrames == 0) { - return 0; - } - return (int)(decoderTimeMs / totalFrames); - } + @Override + public int getAverageDecoderLatency() { + if (totalFrames == 0) { + return 0; + } + return (int)(decoderTimeMs / totalFrames); + } - @Override - public int getAverageEndToEndLatency() { - if (totalFrames == 0) { - return 0; - } - return (int)(totalTimeMs / totalFrames); - } + @Override + public int getAverageEndToEndLatency() { + if (totalFrames == 0) { + return 0; + } + return (int)(totalTimeMs / totalFrames); + } @Override public String getDecoderName() { @@ -556,62 +556,62 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer { } public class RendererException extends RuntimeException { - private static final long serialVersionUID = 8985937536997012406L; - - private final Exception originalException; - private final MediaCodecDecoderRenderer renderer; - private ByteBuffer currentBuffer; - private int currentCodecFlags; - - public RendererException(MediaCodecDecoderRenderer renderer, Exception e) { - this.renderer = renderer; - this.originalException = e; - } - - public RendererException(MediaCodecDecoderRenderer renderer, Exception e, ByteBuffer currentBuffer, int currentCodecFlags) { - this.renderer = renderer; - this.originalException = e; - this.currentBuffer = currentBuffer; - this.currentCodecFlags = currentCodecFlags; - } - - public String toString() { - String str = ""; - - str += "Decoder: "+renderer.decoderName+"\n"; - str += "Initial video dimensions: "+renderer.initialWidth+"x"+renderer.initialHeight+"\n"; - str += "In stats: "+renderer.numSpsIn+", "+renderer.numPpsIn+", "+renderer.numIframeIn+"\n"; - str += "Total frames: "+renderer.totalFrames+"\n"; - - if (currentBuffer != null) { - str += "Current buffer: "; - currentBuffer.flip(); - while (currentBuffer.hasRemaining() && currentBuffer.position() < 10) { - str += String.format((Locale)null, "%02x ", currentBuffer.get()); - } - str += "\n"; - str += "Buffer codec flags: "+currentCodecFlags+"\n"; - } - - str += "Is Exynos 4: "+renderer.isExynos4+"\n"; - - str += "/proc/cpuinfo:\n"; - try { - str += MediaCodecHelper.readCpuinfo(); - } catch (Exception e) { - str += e.getMessage(); - } - - str += "Full decoder dump:\n"; - try { - str += MediaCodecHelper.dumpDecoders(); - } catch (Exception e) { - str += e.getMessage(); - } - - str += originalException.toString(); - - return str; - } - } + private static final long serialVersionUID = 8985937536997012406L; + + private final Exception originalException; + private final MediaCodecDecoderRenderer renderer; + private ByteBuffer currentBuffer; + private int currentCodecFlags; + + public RendererException(MediaCodecDecoderRenderer renderer, Exception e) { + this.renderer = renderer; + this.originalException = e; + } + + public RendererException(MediaCodecDecoderRenderer renderer, Exception e, ByteBuffer currentBuffer, int currentCodecFlags) { + this.renderer = renderer; + this.originalException = e; + this.currentBuffer = currentBuffer; + this.currentCodecFlags = currentCodecFlags; + } + + public String toString() { + String str = ""; + + str += "Decoder: "+renderer.decoderName+"\n"; + str += "Initial video dimensions: "+renderer.initialWidth+"x"+renderer.initialHeight+"\n"; + str += "In stats: "+renderer.numSpsIn+", "+renderer.numPpsIn+", "+renderer.numIframeIn+"\n"; + str += "Total frames: "+renderer.totalFrames+"\n"; + + if (currentBuffer != null) { + str += "Current buffer: "; + currentBuffer.flip(); + while (currentBuffer.hasRemaining() && currentBuffer.position() < 10) { + str += String.format((Locale)null, "%02x ", currentBuffer.get()); + } + str += "\n"; + str += "Buffer codec flags: "+currentCodecFlags+"\n"; + } + + str += "Is Exynos 4: "+renderer.isExynos4+"\n"; + + str += "/proc/cpuinfo:\n"; + try { + str += MediaCodecHelper.readCpuinfo(); + } catch (Exception e) { + str += e.getMessage(); + } + + str += "Full decoder dump:\n"; + try { + str += MediaCodecHelper.dumpDecoders(); + } catch (Exception e) { + str += e.getMessage(); + } + + str += originalException.toString(); + + return str; + } + } } diff --git a/app/src/main/java/com/limelight/binding/video/MediaCodecHelper.java b/app/src/main/java/com/limelight/binding/video/MediaCodecHelper.java index 76f96104..d3befe7d 100644 --- a/app/src/main/java/com/limelight/binding/video/MediaCodecHelper.java +++ b/app/src/main/java/com/limelight/binding/video/MediaCodecHelper.java @@ -72,8 +72,8 @@ public class MediaCodecHelper { @TargetApi(Build.VERSION_CODES.KITKAT) public static boolean decoderSupportsAdaptivePlayback(String decoderName, MediaCodecInfo decoderInfo) { /* - FIXME: Intel's decoder on Nexus Player forces the high latency path if adaptive playback is enabled - so we'll keep it off for now, since we don't know whether other devices also do the same + FIXME: Intel's decoder on Nexus Player forces the high latency path if adaptive playback is enabled + so we'll keep it off for now, since we don't know whether other devices also do the same if (isDecoderInList(whitelistedAdaptiveResolutionPrefixes, decoderName)) { LimeLog.info("Adaptive playback supported (whitelist)"); diff --git a/app/src/main/java/com/limelight/computers/ComputerDatabaseManager.java b/app/src/main/java/com/limelight/computers/ComputerDatabaseManager.java index 701b1222..c5c5585d 100644 --- a/app/src/main/java/com/limelight/computers/ComputerDatabaseManager.java +++ b/app/src/main/java/com/limelight/computers/ComputerDatabaseManager.java @@ -17,153 +17,153 @@ import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; public class ComputerDatabaseManager { - private static final String COMPUTER_DB_NAME = "computers.db"; - private static final String COMPUTER_TABLE_NAME = "Computers"; - private static final String COMPUTER_NAME_COLUMN_NAME = "ComputerName"; - private static final String COMPUTER_UUID_COLUMN_NAME = "UUID"; - private static final String LOCAL_IP_COLUMN_NAME = "LocalIp"; - private static final String REMOTE_IP_COLUMN_NAME = "RemoteIp"; - private static final String MAC_COLUMN_NAME = "Mac"; - - private SQLiteDatabase computerDb; - - public ComputerDatabaseManager(Context c) { - try { - // Create or open an existing DB - computerDb = c.openOrCreateDatabase(COMPUTER_DB_NAME, 0, null); - } catch (SQLiteException e) { - // Delete the DB and try again - c.deleteDatabase(COMPUTER_DB_NAME); - computerDb = c.openOrCreateDatabase(COMPUTER_DB_NAME, 0, null); - } - initializeDb(); - } - - public void close() { - computerDb.close(); - } - - private void initializeDb() { - // Create tables if they aren't already there - computerDb.execSQL(String.format((Locale)null, "CREATE TABLE IF NOT EXISTS %s(%s TEXT PRIMARY KEY," + - " %s TEXT NOT NULL, %s TEXT NOT NULL, %s TEXT NOT NULL, %s TEXT NOT NULL)", - COMPUTER_TABLE_NAME, - COMPUTER_NAME_COLUMN_NAME, COMPUTER_UUID_COLUMN_NAME, LOCAL_IP_COLUMN_NAME, - REMOTE_IP_COLUMN_NAME, MAC_COLUMN_NAME)); - } - - public void deleteComputer(String name) { - computerDb.delete(COMPUTER_TABLE_NAME, COMPUTER_NAME_COLUMN_NAME+"='"+name+"'", null); - } - - public boolean updateComputer(ComputerDetails details) { - ContentValues values = new ContentValues(); - values.put(COMPUTER_NAME_COLUMN_NAME, details.name); - values.put(COMPUTER_UUID_COLUMN_NAME, details.uuid.toString()); - values.put(LOCAL_IP_COLUMN_NAME, details.localIp.getAddress()); - values.put(REMOTE_IP_COLUMN_NAME, details.remoteIp.getAddress()); - values.put(MAC_COLUMN_NAME, details.macAddress); - return -1 != computerDb.insertWithOnConflict(COMPUTER_TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_REPLACE); - } - - public List getAllComputers() { - Cursor c = computerDb.rawQuery("SELECT * FROM "+COMPUTER_TABLE_NAME, null); - LinkedList computerList = new LinkedList(); - while (c.moveToNext()) { - ComputerDetails details = new ComputerDetails(); - - details.name = c.getString(0); - - String uuidStr = c.getString(1); - try { - details.uuid = UUID.fromString(uuidStr); - } catch (IllegalArgumentException e) { - // We'll delete this entry - LimeLog.severe("DB: Corrupted UUID for "+details.name); - } - - try { - details.localIp = InetAddress.getByAddress(c.getBlob(2)); - } catch (UnknownHostException e) { - // We'll delete this entry - LimeLog.severe("DB: Corrupted local IP for "+details.name); - } - - try { - details.remoteIp = InetAddress.getByAddress(c.getBlob(3)); - } catch (UnknownHostException e) { - // We'll delete this entry - LimeLog.severe("DB: Corrupted remote IP for "+details.name); - } - - details.macAddress = c.getString(4); - - // This signifies we don't have dynamic state (like pair state) + private static final String COMPUTER_DB_NAME = "computers.db"; + private static final String COMPUTER_TABLE_NAME = "Computers"; + private static final String COMPUTER_NAME_COLUMN_NAME = "ComputerName"; + private static final String COMPUTER_UUID_COLUMN_NAME = "UUID"; + private static final String LOCAL_IP_COLUMN_NAME = "LocalIp"; + private static final String REMOTE_IP_COLUMN_NAME = "RemoteIp"; + private static final String MAC_COLUMN_NAME = "Mac"; + + private SQLiteDatabase computerDb; + + public ComputerDatabaseManager(Context c) { + try { + // Create or open an existing DB + computerDb = c.openOrCreateDatabase(COMPUTER_DB_NAME, 0, null); + } catch (SQLiteException e) { + // Delete the DB and try again + c.deleteDatabase(COMPUTER_DB_NAME); + computerDb = c.openOrCreateDatabase(COMPUTER_DB_NAME, 0, null); + } + initializeDb(); + } + + public void close() { + computerDb.close(); + } + + private void initializeDb() { + // Create tables if they aren't already there + computerDb.execSQL(String.format((Locale)null, "CREATE TABLE IF NOT EXISTS %s(%s TEXT PRIMARY KEY," + + " %s TEXT NOT NULL, %s TEXT NOT NULL, %s TEXT NOT NULL, %s TEXT NOT NULL)", + COMPUTER_TABLE_NAME, + COMPUTER_NAME_COLUMN_NAME, COMPUTER_UUID_COLUMN_NAME, LOCAL_IP_COLUMN_NAME, + REMOTE_IP_COLUMN_NAME, MAC_COLUMN_NAME)); + } + + public void deleteComputer(String name) { + computerDb.delete(COMPUTER_TABLE_NAME, COMPUTER_NAME_COLUMN_NAME+"='"+name+"'", null); + } + + public boolean updateComputer(ComputerDetails details) { + ContentValues values = new ContentValues(); + values.put(COMPUTER_NAME_COLUMN_NAME, details.name); + values.put(COMPUTER_UUID_COLUMN_NAME, details.uuid.toString()); + values.put(LOCAL_IP_COLUMN_NAME, details.localIp.getAddress()); + values.put(REMOTE_IP_COLUMN_NAME, details.remoteIp.getAddress()); + values.put(MAC_COLUMN_NAME, details.macAddress); + return -1 != computerDb.insertWithOnConflict(COMPUTER_TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_REPLACE); + } + + public List getAllComputers() { + Cursor c = computerDb.rawQuery("SELECT * FROM "+COMPUTER_TABLE_NAME, null); + LinkedList computerList = new LinkedList(); + while (c.moveToNext()) { + ComputerDetails details = new ComputerDetails(); + + details.name = c.getString(0); + + String uuidStr = c.getString(1); + try { + details.uuid = UUID.fromString(uuidStr); + } catch (IllegalArgumentException e) { + // We'll delete this entry + LimeLog.severe("DB: Corrupted UUID for "+details.name); + } + + try { + details.localIp = InetAddress.getByAddress(c.getBlob(2)); + } catch (UnknownHostException e) { + // We'll delete this entry + LimeLog.severe("DB: Corrupted local IP for "+details.name); + } + + try { + details.remoteIp = InetAddress.getByAddress(c.getBlob(3)); + } catch (UnknownHostException e) { + // We'll delete this entry + LimeLog.severe("DB: Corrupted remote IP for "+details.name); + } + + details.macAddress = c.getString(4); + + // This signifies we don't have dynamic state (like pair state) details.state = ComputerDetails.State.UNKNOWN; details.reachability = ComputerDetails.Reachability.UNKNOWN; - - // If a field is corrupt or missing, skip the database entry - if (details.uuid == null || details.localIp == null || details.remoteIp == null || - details.macAddress == null) { - continue; - } + + // If a field is corrupt or missing, skip the database entry + if (details.uuid == null || details.localIp == null || details.remoteIp == null || + details.macAddress == null) { + continue; + } computerList.add(details); - } - - c.close(); - - return computerList; - } - - public ComputerDetails getComputerByName(String name) { - Cursor c = computerDb.rawQuery("SELECT * FROM "+COMPUTER_TABLE_NAME+" WHERE "+COMPUTER_NAME_COLUMN_NAME+"='"+name+"'", null); - ComputerDetails details = new ComputerDetails(); - if (!c.moveToFirst()) { - // No matching computer - c.close(); - return null; - } + } - details.name = c.getString(0); - - String uuidStr = c.getString(1); - try { - details.uuid = UUID.fromString(uuidStr); - } catch (IllegalArgumentException e) { - // We'll delete this entry - LimeLog.severe("DB: Corrupted UUID for "+details.name); - } - - try { - details.localIp = InetAddress.getByAddress(c.getBlob(2)); - } catch (UnknownHostException e) { - // We'll delete this entry - LimeLog.severe("DB: Corrupted local IP for "+details.name); - } - - try { - details.remoteIp = InetAddress.getByAddress(c.getBlob(3)); - } catch (UnknownHostException e) { - // We'll delete this entry - LimeLog.severe("DB: Corrupted remote IP for "+details.name); - } - - details.macAddress = c.getString(4); - - c.close(); + c.close(); + + return computerList; + } + + public ComputerDetails getComputerByName(String name) { + Cursor c = computerDb.rawQuery("SELECT * FROM "+COMPUTER_TABLE_NAME+" WHERE "+COMPUTER_NAME_COLUMN_NAME+"='"+name+"'", null); + ComputerDetails details = new ComputerDetails(); + if (!c.moveToFirst()) { + // No matching computer + c.close(); + return null; + } + + details.name = c.getString(0); + + String uuidStr = c.getString(1); + try { + details.uuid = UUID.fromString(uuidStr); + } catch (IllegalArgumentException e) { + // We'll delete this entry + LimeLog.severe("DB: Corrupted UUID for "+details.name); + } + + try { + details.localIp = InetAddress.getByAddress(c.getBlob(2)); + } catch (UnknownHostException e) { + // We'll delete this entry + LimeLog.severe("DB: Corrupted local IP for "+details.name); + } + + try { + details.remoteIp = InetAddress.getByAddress(c.getBlob(3)); + } catch (UnknownHostException e) { + // We'll delete this entry + LimeLog.severe("DB: Corrupted remote IP for "+details.name); + } + + details.macAddress = c.getString(4); + + c.close(); details.state = ComputerDetails.State.UNKNOWN; details.reachability = ComputerDetails.Reachability.UNKNOWN; - - // If a field is corrupt or missing, delete the database entry - if (details.uuid == null || details.localIp == null || details.remoteIp == null || - details.macAddress == null) { - deleteComputer(details.name); - return null; - } - - return details; - } + + // If a field is corrupt or missing, delete the database entry + if (details.uuid == null || details.localIp == null || details.remoteIp == null || + details.macAddress == null) { + deleteComputer(details.name); + return null; + } + + return details; + } } diff --git a/app/src/main/java/com/limelight/computers/ComputerManagerListener.java b/app/src/main/java/com/limelight/computers/ComputerManagerListener.java index 51e21a0a..43083602 100644 --- a/app/src/main/java/com/limelight/computers/ComputerManagerListener.java +++ b/app/src/main/java/com/limelight/computers/ComputerManagerListener.java @@ -3,5 +3,5 @@ package com.limelight.computers; import com.limelight.nvstream.http.ComputerDetails; public interface ComputerManagerListener { - public void notifyComputerUpdated(ComputerDetails details); + public void notifyComputerUpdated(ComputerDetails details); } diff --git a/app/src/main/java/com/limelight/computers/ComputerManagerService.java b/app/src/main/java/com/limelight/computers/ComputerManagerService.java index b85b0bf6..3d331f6d 100644 --- a/app/src/main/java/com/limelight/computers/ComputerManagerService.java +++ b/app/src/main/java/com/limelight/computers/ComputerManagerService.java @@ -29,39 +29,39 @@ import android.os.IBinder; import org.xmlpull.v1.XmlPullParserException; public class ComputerManagerService extends Service { - private static final int POLLING_PERIOD_MS = 3000; - private static final int MDNS_QUERY_PERIOD_MS = 1000; - - private final ComputerManagerBinder binder = new ComputerManagerBinder(); - - private ComputerDatabaseManager dbManager; - private final AtomicInteger dbRefCount = new AtomicInteger(0); - - private IdentityManager idManager; - private final LinkedList pollingTuples = new LinkedList(); - private ComputerManagerListener listener = null; - private final AtomicInteger activePolls = new AtomicInteger(0); + private static final int POLLING_PERIOD_MS = 3000; + private static final int MDNS_QUERY_PERIOD_MS = 1000; + + private final ComputerManagerBinder binder = new ComputerManagerBinder(); + + private ComputerDatabaseManager dbManager; + private final AtomicInteger dbRefCount = new AtomicInteger(0); + + private IdentityManager idManager; + private final LinkedList pollingTuples = new LinkedList(); + private ComputerManagerListener listener = null; + private final AtomicInteger activePolls = new AtomicInteger(0); private boolean pollingActive = false; - private DiscoveryService.DiscoveryBinder discoveryBinder; - private final ServiceConnection discoveryServiceConnection = new ServiceConnection() { - public void onServiceConnected(ComponentName className, IBinder binder) { - synchronized (discoveryServiceConnection) { - DiscoveryService.DiscoveryBinder privateBinder = ((DiscoveryService.DiscoveryBinder)binder); - - // Set us as the event listener - privateBinder.setListener(createDiscoveryListener()); - - // Signal a possible waiter that we're all setup - discoveryBinder = privateBinder; - discoveryServiceConnection.notifyAll(); - } - } + private DiscoveryService.DiscoveryBinder discoveryBinder; + private final ServiceConnection discoveryServiceConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder binder) { + synchronized (discoveryServiceConnection) { + DiscoveryService.DiscoveryBinder privateBinder = ((DiscoveryService.DiscoveryBinder)binder); - public void onServiceDisconnected(ComponentName className) { - discoveryBinder = null; - } - }; + // Set us as the event listener + privateBinder.setListener(createDiscoveryListener()); + + // Signal a possible waiter that we're all setup + discoveryBinder = privateBinder; + discoveryServiceConnection.notifyAll(); + } + } + + public void onServiceDisconnected(ComponentName className) { + discoveryBinder = null; + } + }; // Returns true if the details object was modified private boolean runPoll(ComputerDetails details, boolean newPc) @@ -124,17 +124,17 @@ public class ComputerManagerService extends Service { t.setName("Polling thread for "+details.localIp.getHostAddress()); return t; } - - public class ComputerManagerBinder extends Binder { - public void startPolling(ComputerManagerListener listener) { + + public class ComputerManagerBinder extends Binder { + public void startPolling(ComputerManagerListener listener) { // Polling is active pollingActive = true; - // Set the listener - ComputerManagerService.this.listener = listener; - - // Start mDNS autodiscovery too - discoveryBinder.startDiscovery(MDNS_QUERY_PERIOD_MS); + // Set the listener + ComputerManagerService.this.listener = listener; + + // Start mDNS autodiscovery too + discoveryBinder.startDiscovery(MDNS_QUERY_PERIOD_MS); synchronized (pollingTuples) { for (PollingTuple tuple : pollingTuples) { @@ -148,48 +148,48 @@ public class ComputerManagerService extends Service { } } } - } - - public void waitForReady() { - synchronized (discoveryServiceConnection) { - try { - while (discoveryBinder == null) { - // Wait for the bind notification - discoveryServiceConnection.wait(1000); - } - } catch (InterruptedException ignored) { - } - } - } - - public void waitForPollingStopped() { - while (activePolls.get() != 0) { - try { - Thread.sleep(250); - } catch (InterruptedException ignored) {} - } - } - - public boolean addComputerBlocking(InetAddress addr) { - return ComputerManagerService.this.addComputerBlocking(addr); - } - - public void removeComputer(String name) { - ComputerManagerService.this.removeComputer(name); - } - - public void stopPolling() { - // Just call the unbind handler to cleanup - ComputerManagerService.this.onUnbind(null); - } + } + + public void waitForReady() { + synchronized (discoveryServiceConnection) { + try { + while (discoveryBinder == null) { + // Wait for the bind notification + discoveryServiceConnection.wait(1000); + } + } catch (InterruptedException ignored) { + } + } + } + + public void waitForPollingStopped() { + while (activePolls.get() != 0) { + try { + Thread.sleep(250); + } catch (InterruptedException ignored) {} + } + } + + public boolean addComputerBlocking(InetAddress addr) { + return ComputerManagerService.this.addComputerBlocking(addr); + } + + public void removeComputer(String name) { + ComputerManagerService.this.removeComputer(name); + } + + public void stopPolling() { + // Just call the unbind handler to cleanup + ComputerManagerService.this.onUnbind(null); + } public ApplistPoller createAppListPoller(ComputerDetails computer) { return new ApplistPoller(computer); } - - public String getUniqueId() { - return idManager.getUniqueId(); - } + + public String getUniqueId() { + return idManager.getUniqueId(); + } public ComputerDetails getComputer(UUID uuid) { synchronized (pollingTuples) { @@ -202,14 +202,14 @@ public class ComputerManagerService extends Service { return null; } - } - - @Override - public boolean onUnbind(Intent intent) { - // Stop mDNS autodiscovery - discoveryBinder.stopDiscovery(); - - // Stop polling + } + + @Override + public boolean onUnbind(Intent intent) { + // Stop mDNS autodiscovery + discoveryBinder.stopDiscovery(); + + // Stop polling pollingActive = false; synchronized (pollingTuples) { for (PollingTuple tuple : pollingTuples) { @@ -220,33 +220,33 @@ public class ComputerManagerService extends Service { } } } - - // Remove the listener - listener = null; - - return false; - } - - private MdnsDiscoveryListener createDiscoveryListener() { - return new MdnsDiscoveryListener() { - @Override - public void notifyComputerAdded(MdnsComputer computer) { - // Kick off a serverinfo poll on this machine - addComputerBlocking(computer.getAddress()); - } - @Override - public void notifyComputerRemoved(MdnsComputer computer) { - // Nothing to do here - } + // Remove the listener + listener = null; - @Override - public void notifyDiscoveryFailure(Exception e) { - LimeLog.severe("mDNS discovery failed"); - e.printStackTrace(); - } - }; - } + return false; + } + + private MdnsDiscoveryListener createDiscoveryListener() { + return new MdnsDiscoveryListener() { + @Override + public void notifyComputerAdded(MdnsComputer computer) { + // Kick off a serverinfo poll on this machine + addComputerBlocking(computer.getAddress()); + } + + @Override + public void notifyComputerRemoved(MdnsComputer computer) { + // Nothing to do here + } + + @Override + public void notifyDiscoveryFailure(Exception e) { + LimeLog.severe("mDNS discovery failed"); + e.printStackTrace(); + } + }; + } private void addTuple(ComputerDetails details) { synchronized (pollingTuples) { @@ -278,17 +278,17 @@ public class ComputerManagerService extends Service { } } - public boolean addComputerBlocking(InetAddress addr) { - // Setup a placeholder - ComputerDetails fakeDetails = new ComputerDetails(); - fakeDetails.localIp = addr; - fakeDetails.remoteIp = addr; + public boolean addComputerBlocking(InetAddress addr) { + // Setup a placeholder + ComputerDetails fakeDetails = new ComputerDetails(); + fakeDetails.localIp = addr; + fakeDetails.remoteIp = addr; - // Block while we try to fill the details + // Block while we try to fill the details runPoll(fakeDetails, true); - - // If the machine is reachable, it was successful - if (fakeDetails.state == ComputerDetails.State.ONLINE) { + + // If the machine is reachable, it was successful + if (fakeDetails.state == ComputerDetails.State.ONLINE) { LimeLog.info("New PC ("+fakeDetails.name+") is UUID "+fakeDetails.uuid); // Start a polling thread for this machine @@ -298,15 +298,15 @@ public class ComputerManagerService extends Service { else { return false; } - } - - public void removeComputer(String name) { - if (!getLocalDatabaseReference()) { - return; - } - - // Remove it from the database - dbManager.deleteComputer(name); + } + + public void removeComputer(String name) { + if (!getLocalDatabaseReference()) { + return; + } + + // Remove it from the database + dbManager.deleteComputer(name); synchronized (pollingTuples) { // Remove the computer from the computer list @@ -321,31 +321,31 @@ public class ComputerManagerService extends Service { } } } - - releaseLocalDatabaseReference(); - } - - private boolean getLocalDatabaseReference() { - if (dbRefCount.get() == 0) { - return false; - } - - dbRefCount.incrementAndGet(); - return true; - } - - private void releaseLocalDatabaseReference() { - if (dbRefCount.decrementAndGet() == 0) { - dbManager.close(); - } - } - - private ComputerDetails tryPollIp(ComputerDetails details, InetAddress ipAddr) { - try { - NvHTTP http = new NvHTTP(ipAddr, idManager.getUniqueId(), - null, PlatformBinding.getCryptoProvider(ComputerManagerService.this)); - ComputerDetails newDetails = http.getComputerDetails(); + releaseLocalDatabaseReference(); + } + + private boolean getLocalDatabaseReference() { + if (dbRefCount.get() == 0) { + return false; + } + + dbRefCount.incrementAndGet(); + return true; + } + + private void releaseLocalDatabaseReference() { + if (dbRefCount.decrementAndGet() == 0) { + dbManager.close(); + } + } + + private ComputerDetails tryPollIp(ComputerDetails details, InetAddress ipAddr) { + try { + NvHTTP http = new NvHTTP(ipAddr, idManager.getUniqueId(), + null, PlatformBinding.getCryptoProvider(ComputerManagerService.this)); + + ComputerDetails newDetails = http.getComputerDetails(); // Check if this is the PC we expected if (details.uuid != null && newDetails.uuid != null && @@ -356,58 +356,58 @@ public class ComputerManagerService extends Service { } return newDetails; - } catch (Exception e) { - return null; - } - } - - private boolean pollComputer(ComputerDetails details, boolean localFirst) { - ComputerDetails polledDetails; + } catch (Exception e) { + return null; + } + } + + private boolean pollComputer(ComputerDetails details, boolean localFirst) { + ComputerDetails polledDetails; // If the local address is routable across the Internet, // always consider this PC remote to be conservative if (details.localIp.equals(details.remoteIp)) { localFirst = false; } - - if (localFirst) { - polledDetails = tryPollIp(details, details.localIp); - } - else { - polledDetails = tryPollIp(details, details.remoteIp); - } - - if (polledDetails == null && !details.localIp.equals(details.remoteIp)) { - // Failed, so let's try the fallback - if (!localFirst) { - polledDetails = tryPollIp(details, details.localIp); - } - else { - polledDetails = tryPollIp(details, details.remoteIp); - } - - // The fallback poll worked - if (polledDetails != null) { - polledDetails.reachability = !localFirst ? ComputerDetails.Reachability.LOCAL : - ComputerDetails.Reachability.REMOTE; - } - } - else if (polledDetails != null) { - polledDetails.reachability = localFirst ? ComputerDetails.Reachability.LOCAL : - ComputerDetails.Reachability.REMOTE; - } - - // Machine was unreachable both tries - if (polledDetails == null) { - return false; - } - - // If we got here, it's reachable - details.update(polledDetails); - return true; - } - - private boolean doPollMachine(ComputerDetails details) { + + if (localFirst) { + polledDetails = tryPollIp(details, details.localIp); + } + else { + polledDetails = tryPollIp(details, details.remoteIp); + } + + if (polledDetails == null && !details.localIp.equals(details.remoteIp)) { + // Failed, so let's try the fallback + if (!localFirst) { + polledDetails = tryPollIp(details, details.localIp); + } + else { + polledDetails = tryPollIp(details, details.remoteIp); + } + + // The fallback poll worked + if (polledDetails != null) { + polledDetails.reachability = !localFirst ? ComputerDetails.Reachability.LOCAL : + ComputerDetails.Reachability.REMOTE; + } + } + else if (polledDetails != null) { + polledDetails.reachability = localFirst ? ComputerDetails.Reachability.LOCAL : + ComputerDetails.Reachability.REMOTE; + } + + // Machine was unreachable both tries + if (polledDetails == null) { + return false; + } + + // If we got here, it's reachable + details.update(polledDetails); + return true; + } + + private boolean doPollMachine(ComputerDetails details) { if (details.reachability == ComputerDetails.Reachability.UNKNOWN || details.reachability == ComputerDetails.Reachability.OFFLINE) { // Always try local first to avoid potential UDP issues when @@ -420,20 +420,20 @@ public class ComputerManagerService extends Service { // always try that one first return pollComputer(details, details.reachability == ComputerDetails.Reachability.LOCAL); } - } - - @Override - public void onCreate() { - // Bind to the discovery service - bindService(new Intent(this, DiscoveryService.class), - discoveryServiceConnection, Service.BIND_AUTO_CREATE); - - // Lookup or generate this device's UID - idManager = new IdentityManager(this); - - // Initialize the DB - dbManager = new ComputerDatabaseManager(this); - dbRefCount.set(1); + } + + @Override + public void onCreate() { + // Bind to the discovery service + bindService(new Intent(this, DiscoveryService.class), + discoveryServiceConnection, Service.BIND_AUTO_CREATE); + + // Lookup or generate this device's UID + idManager = new IdentityManager(this); + + // Initialize the DB + dbManager = new ComputerDatabaseManager(this); + dbRefCount.set(1); // Grab known machines into our computer list if (!getLocalDatabaseReference()) { @@ -446,25 +446,25 @@ public class ComputerManagerService extends Service { } releaseLocalDatabaseReference(); - } - - @Override - public void onDestroy() { - if (discoveryBinder != null) { - // Unbind from the discovery service - unbindService(discoveryServiceConnection); - } - - // FIXME: Should await termination here but we have timeout issues in HttpURLConnection - - // Remove the initial DB reference - releaseLocalDatabaseReference(); - } - - @Override - public IBinder onBind(Intent intent) { - return binder; - } + } + + @Override + public void onDestroy() { + if (discoveryBinder != null) { + // Unbind from the discovery service + unbindService(discoveryServiceConnection); + } + + // FIXME: Should await termination here but we have timeout issues in HttpURLConnection + + // Remove the initial DB reference + releaseLocalDatabaseReference(); + } + + @Override + public IBinder onBind(Intent intent) { + return binder; + } public class ApplistPoller { private Thread thread; diff --git a/app/src/main/java/com/limelight/computers/IdentityManager.java b/app/src/main/java/com/limelight/computers/IdentityManager.java index 8734d124..e6251d4f 100644 --- a/app/src/main/java/com/limelight/computers/IdentityManager.java +++ b/app/src/main/java/com/limelight/computers/IdentityManager.java @@ -12,75 +12,75 @@ import com.limelight.LimeLog; import android.content.Context; public class IdentityManager { - private static final String UNIQUE_ID_FILE_NAME = "uniqueid"; - private static final int UID_SIZE_IN_BYTES = 8; - - private String uniqueId; - - public IdentityManager(Context c) { - uniqueId = loadUniqueId(c); - if (uniqueId == null) { - uniqueId = generateNewUniqueId(c); - } - - LimeLog.info("UID is now: "+uniqueId); - } - - public String getUniqueId() { - return uniqueId; - } - - private static String loadUniqueId(Context c) { - // 2 Hex digits per byte - char[] uid = new char[UID_SIZE_IN_BYTES * 2]; - InputStreamReader reader = null; - LimeLog.info("Reading UID from disk"); - try { - reader = new InputStreamReader(c.openFileInput(UNIQUE_ID_FILE_NAME)); - if (reader.read(uid) != UID_SIZE_IN_BYTES * 2) - { - LimeLog.severe("UID file data is truncated"); - return null; - } - return new String(uid); - } catch (FileNotFoundException e) { - LimeLog.info("No UID file found"); - return null; - } catch (IOException e) { - LimeLog.severe("Error while reading UID file"); - e.printStackTrace(); - return null; - } finally { - if (reader != null) { - try { - reader.close(); - } catch (IOException ignored) {} - } - } - } - - private static String generateNewUniqueId(Context c) { - // Generate a new UID hex string - LimeLog.info("Generating new UID"); - String uidStr = String.format((Locale)null, "%016x", new Random().nextLong()); - - OutputStreamWriter writer = null; - try { - writer = new OutputStreamWriter(c.openFileOutput(UNIQUE_ID_FILE_NAME, 0)); - writer.write(uidStr); - LimeLog.info("UID written to disk"); - } catch (IOException e) { - LimeLog.severe("Error while writing UID file"); - e.printStackTrace(); - } finally { - if (writer != null) { - try { - writer.close(); - } catch (IOException ignored) {} - } - } - - // We can return a UID even if I/O fails - return uidStr; - } + private static final String UNIQUE_ID_FILE_NAME = "uniqueid"; + private static final int UID_SIZE_IN_BYTES = 8; + + private String uniqueId; + + public IdentityManager(Context c) { + uniqueId = loadUniqueId(c); + if (uniqueId == null) { + uniqueId = generateNewUniqueId(c); + } + + LimeLog.info("UID is now: "+uniqueId); + } + + public String getUniqueId() { + return uniqueId; + } + + private static String loadUniqueId(Context c) { + // 2 Hex digits per byte + char[] uid = new char[UID_SIZE_IN_BYTES * 2]; + InputStreamReader reader = null; + LimeLog.info("Reading UID from disk"); + try { + reader = new InputStreamReader(c.openFileInput(UNIQUE_ID_FILE_NAME)); + if (reader.read(uid) != UID_SIZE_IN_BYTES * 2) + { + LimeLog.severe("UID file data is truncated"); + return null; + } + return new String(uid); + } catch (FileNotFoundException e) { + LimeLog.info("No UID file found"); + return null; + } catch (IOException e) { + LimeLog.severe("Error while reading UID file"); + e.printStackTrace(); + return null; + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException ignored) {} + } + } + } + + private static String generateNewUniqueId(Context c) { + // Generate a new UID hex string + LimeLog.info("Generating new UID"); + String uidStr = String.format((Locale)null, "%016x", new Random().nextLong()); + + OutputStreamWriter writer = null; + try { + writer = new OutputStreamWriter(c.openFileOutput(UNIQUE_ID_FILE_NAME, 0)); + writer.write(uidStr); + LimeLog.info("UID written to disk"); + } catch (IOException e) { + LimeLog.severe("Error while writing UID file"); + e.printStackTrace(); + } finally { + if (writer != null) { + try { + writer.close(); + } catch (IOException ignored) {} + } + } + + // We can return a UID even if I/O fails + return uidStr; + } } diff --git a/app/src/main/java/com/limelight/discovery/DiscoveryService.java b/app/src/main/java/com/limelight/discovery/DiscoveryService.java index 7e6497fa..5f68fb02 100644 --- a/app/src/main/java/com/limelight/discovery/DiscoveryService.java +++ b/app/src/main/java/com/limelight/discovery/DiscoveryService.java @@ -15,76 +15,76 @@ import android.os.Binder; import android.os.IBinder; public class DiscoveryService extends Service { - - private MdnsDiscoveryAgent discoveryAgent; - private MdnsDiscoveryListener boundListener; - private MulticastLock multicastLock; - - public class DiscoveryBinder extends Binder { - public void setListener(MdnsDiscoveryListener listener) { - boundListener = listener; - } - - public void startDiscovery(int queryIntervalMs) { - multicastLock.acquire(); - discoveryAgent.startDiscovery(queryIntervalMs); - } - - public void stopDiscovery() { - discoveryAgent.stopDiscovery(); - multicastLock.release(); - } - - public List getComputerSet() { - return discoveryAgent.getComputerSet(); - } - } - - @Override - public void onCreate() { - WifiManager wifiMgr = (WifiManager) getSystemService(Context.WIFI_SERVICE); - multicastLock = wifiMgr.createMulticastLock("Limelight mDNS"); - multicastLock.setReferenceCounted(false); - - discoveryAgent = new MdnsDiscoveryAgent(new MdnsDiscoveryListener() { - @Override - public void notifyComputerAdded(MdnsComputer computer) { - if (boundListener != null) { - boundListener.notifyComputerAdded(computer); - } - } - @Override - public void notifyComputerRemoved(MdnsComputer computer) { - if (boundListener != null) { - boundListener.notifyComputerRemoved(computer); - } - } + private MdnsDiscoveryAgent discoveryAgent; + private MdnsDiscoveryListener boundListener; + private MulticastLock multicastLock; - @Override - public void notifyDiscoveryFailure(Exception e) { - if (boundListener != null) { - boundListener.notifyDiscoveryFailure(e); - } - } - }); - } - - private final DiscoveryBinder binder = new DiscoveryBinder(); - - @Override - public IBinder onBind(Intent intent) { - return binder; - } - - @Override - public boolean onUnbind(Intent intent) { - // Stop any discovery session - discoveryAgent.stopDiscovery(); - multicastLock.release(); - - // Unbind the listener - boundListener = null; - return false; - } + public class DiscoveryBinder extends Binder { + public void setListener(MdnsDiscoveryListener listener) { + boundListener = listener; + } + + public void startDiscovery(int queryIntervalMs) { + multicastLock.acquire(); + discoveryAgent.startDiscovery(queryIntervalMs); + } + + public void stopDiscovery() { + discoveryAgent.stopDiscovery(); + multicastLock.release(); + } + + public List getComputerSet() { + return discoveryAgent.getComputerSet(); + } + } + + @Override + public void onCreate() { + WifiManager wifiMgr = (WifiManager) getSystemService(Context.WIFI_SERVICE); + multicastLock = wifiMgr.createMulticastLock("Limelight mDNS"); + multicastLock.setReferenceCounted(false); + + discoveryAgent = new MdnsDiscoveryAgent(new MdnsDiscoveryListener() { + @Override + public void notifyComputerAdded(MdnsComputer computer) { + if (boundListener != null) { + boundListener.notifyComputerAdded(computer); + } + } + + @Override + public void notifyComputerRemoved(MdnsComputer computer) { + if (boundListener != null) { + boundListener.notifyComputerRemoved(computer); + } + } + + @Override + public void notifyDiscoveryFailure(Exception e) { + if (boundListener != null) { + boundListener.notifyDiscoveryFailure(e); + } + } + }); + } + + private final DiscoveryBinder binder = new DiscoveryBinder(); + + @Override + public IBinder onBind(Intent intent) { + return binder; + } + + @Override + public boolean onUnbind(Intent intent) { + // Stop any discovery session + discoveryAgent.stopDiscovery(); + multicastLock.release(); + + // Unbind the listener + boundListener = null; + return false; + } } diff --git a/app/src/main/java/com/limelight/nvstream/av/video/cpu/AvcDecoder.java b/app/src/main/java/com/limelight/nvstream/av/video/cpu/AvcDecoder.java index d3e504f2..326fc18d 100644 --- a/app/src/main/java/com/limelight/nvstream/av/video/cpu/AvcDecoder.java +++ b/app/src/main/java/com/limelight/nvstream/av/video/cpu/AvcDecoder.java @@ -1,44 +1,44 @@ package com.limelight.nvstream.av.video.cpu; public class AvcDecoder { - static { - // FFMPEG dependencies - System.loadLibrary("avutil-52"); - System.loadLibrary("swresample-0"); - System.loadLibrary("swscale-2"); - System.loadLibrary("avcodec-55"); - System.loadLibrary("avformat-55"); - - System.loadLibrary("nv_avc_dec"); - } - - /** Disables the deblocking filter at the cost of image quality */ - public static final int DISABLE_LOOP_FILTER = 0x1; - /** Uses the low latency decode flag (disables multithreading) */ - public static final int LOW_LATENCY_DECODE = 0x2; - /** Threads process each slice, rather than each frame */ - public static final int SLICE_THREADING = 0x4; - /** Uses nonstandard speedup tricks */ - public static final int FAST_DECODE = 0x8; - /** Uses bilinear filtering instead of bicubic */ - public static final int BILINEAR_FILTERING = 0x10; - /** Uses a faster bilinear filtering with lower image quality */ - public static final int FAST_BILINEAR_FILTERING = 0x20; - /** Disables color conversion (output is NV21) */ - public static final int NO_COLOR_CONVERSION = 0x40; - - public static native int init(int width, int height, int perflvl, int threadcount); - public static native void destroy(); - - // Rendering API when NO_COLOR_CONVERSION == 0 - public static native boolean setRenderTarget(Object androidSurface); - public static native boolean getRgbFrameInt(int[] rgbFrame, int bufferSize); - public static native boolean getRgbFrame(byte[] rgbFrame, int bufferSize); - public static native boolean redraw(); + static { + // FFMPEG dependencies + System.loadLibrary("avutil-52"); + System.loadLibrary("swresample-0"); + System.loadLibrary("swscale-2"); + System.loadLibrary("avcodec-55"); + System.loadLibrary("avformat-55"); - // Rendering API when NO_COLOR_CONVERSION == 1 - public static native boolean getRawFrame(byte[] yuvFrame, int bufferSize); - - public static native int getInputPaddingSize(); - public static native int decode(byte[] indata, int inoff, int inlen); + System.loadLibrary("nv_avc_dec"); + } + + /** Disables the deblocking filter at the cost of image quality */ + public static final int DISABLE_LOOP_FILTER = 0x1; + /** Uses the low latency decode flag (disables multithreading) */ + public static final int LOW_LATENCY_DECODE = 0x2; + /** Threads process each slice, rather than each frame */ + public static final int SLICE_THREADING = 0x4; + /** Uses nonstandard speedup tricks */ + public static final int FAST_DECODE = 0x8; + /** Uses bilinear filtering instead of bicubic */ + public static final int BILINEAR_FILTERING = 0x10; + /** Uses a faster bilinear filtering with lower image quality */ + public static final int FAST_BILINEAR_FILTERING = 0x20; + /** Disables color conversion (output is NV21) */ + public static final int NO_COLOR_CONVERSION = 0x40; + + public static native int init(int width, int height, int perflvl, int threadcount); + public static native void destroy(); + + // Rendering API when NO_COLOR_CONVERSION == 0 + public static native boolean setRenderTarget(Object androidSurface); + public static native boolean getRgbFrameInt(int[] rgbFrame, int bufferSize); + public static native boolean getRgbFrame(byte[] rgbFrame, int bufferSize); + public static native boolean redraw(); + + // Rendering API when NO_COLOR_CONVERSION == 1 + public static native boolean getRawFrame(byte[] yuvFrame, int bufferSize); + + public static native int getInputPaddingSize(); + public static native int decode(byte[] indata, int inoff, int inlen); } diff --git a/app/src/main/java/com/limelight/preferences/AddComputerManually.java b/app/src/main/java/com/limelight/preferences/AddComputerManually.java index 71f84746..c484df75 100644 --- a/app/src/main/java/com/limelight/preferences/AddComputerManually.java +++ b/app/src/main/java/com/limelight/preferences/AddComputerManually.java @@ -25,127 +25,127 @@ import android.widget.TextView; import android.widget.Toast; public class AddComputerManually extends Activity { - private TextView hostText; - private ComputerManagerService.ComputerManagerBinder managerBinder; - private final LinkedBlockingQueue computersToAdd = new LinkedBlockingQueue(); - private Thread addThread; - private final ServiceConnection serviceConnection = new ServiceConnection() { - public void onServiceConnected(ComponentName className, final IBinder binder) { - managerBinder = ((ComputerManagerService.ComputerManagerBinder)binder); - startAddThread(); - } + private TextView hostText; + private ComputerManagerService.ComputerManagerBinder managerBinder; + private final LinkedBlockingQueue computersToAdd = new LinkedBlockingQueue(); + private Thread addThread; + private final ServiceConnection serviceConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, final IBinder binder) { + managerBinder = ((ComputerManagerService.ComputerManagerBinder)binder); + startAddThread(); + } - public void onServiceDisconnected(ComponentName className) { - joinAddThread(); - managerBinder = null; - } - }; - - private void doAddPc(String host) { - String msg; - boolean finish = false; + public void onServiceDisconnected(ComponentName className) { + joinAddThread(); + managerBinder = null; + } + }; + + private void doAddPc(String host) { + String msg; + boolean finish = false; SpinnerDialog dialog = SpinnerDialog.displayDialog(this, getResources().getString(R.string.title_add_pc), - getResources().getString(R.string.msg_add_pc), false); + getResources().getString(R.string.msg_add_pc), false); - try { - InetAddress addr = InetAddress.getByName(host); - - if (!managerBinder.addComputerBlocking(addr)){ - msg = getResources().getString(R.string.addpc_fail); - } - else { - msg = getResources().getString(R.string.addpc_success); - finish = true; - } - } catch (UnknownHostException e) { - msg = getResources().getString(R.string.addpc_unknown_host); - } + try { + InetAddress addr = InetAddress.getByName(host); + + if (!managerBinder.addComputerBlocking(addr)){ + msg = getResources().getString(R.string.addpc_fail); + } + else { + msg = getResources().getString(R.string.addpc_success); + finish = true; + } + } catch (UnknownHostException e) { + msg = getResources().getString(R.string.addpc_unknown_host); + } dialog.dismiss(); final boolean toastFinish = finish; - final String toastMsg = msg; - AddComputerManually.this.runOnUiThread(new Runnable() { - @Override - public void run() { - Toast.makeText(AddComputerManually.this, toastMsg, Toast.LENGTH_LONG).show(); - - if (toastFinish && !isFinishing()) { - // Close the activity - AddComputerManually.this.finish(); - } - } - }); - } - - private void startAddThread() { - addThread = new Thread() { - @Override - public void run() { - while (!isInterrupted()) { - String computer; - - try { - computer = computersToAdd.take(); - } catch (InterruptedException e) { - return; - } - - doAddPc(computer); - } - } - }; - addThread.setName("UI - AddComputerManually"); - addThread.start(); - } - - private void joinAddThread() { - if (addThread != null) { - addThread.interrupt(); - - try { - addThread.join(); - } catch (InterruptedException ignored) {} - - addThread = null; - } - } + final String toastMsg = msg; + AddComputerManually.this.runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(AddComputerManually.this, toastMsg, Toast.LENGTH_LONG).show(); - @Override - protected void onStop() { - super.onStop(); - - Dialog.closeDialogs(); + if (toastFinish && !isFinishing()) { + // Close the activity + AddComputerManually.this.finish(); + } + } + }); + } + + private void startAddThread() { + addThread = new Thread() { + @Override + public void run() { + while (!isInterrupted()) { + String computer; + + try { + computer = computersToAdd.take(); + } catch (InterruptedException e) { + return; + } + + doAddPc(computer); + } + } + }; + addThread.setName("UI - AddComputerManually"); + addThread.start(); + } + + private void joinAddThread() { + if (addThread != null) { + addThread.interrupt(); + + try { + addThread.join(); + } catch (InterruptedException ignored) {} + + addThread = null; + } + } + + @Override + protected void onStop() { + super.onStop(); + + Dialog.closeDialogs(); SpinnerDialog.closeDialogs(this); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - - if (managerBinder != null) { - joinAddThread(); - unbindService(serviceConnection); - } - } - - @Override - protected void onCreate(Bundle 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); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + + if (managerBinder != null) { + joinAddThread(); + unbindService(serviceConnection); + } + } + + @Override + protected void onCreate(Bundle 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); UiHelper.notifyNewRootView(this); - this.hostText = (TextView) findViewById(R.id.hostTextView); + this.hostText = (TextView) findViewById(R.id.hostTextView); hostText.setImeOptions(EditorInfo.IME_ACTION_DONE); hostText.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override @@ -165,9 +165,9 @@ public class AddComputerManually extends Activity { return false; } }); - - // Bind to the ComputerManager service - bindService(new Intent(AddComputerManually.this, - ComputerManagerService.class), serviceConnection, Service.BIND_AUTO_CREATE); - } + + // Bind to the ComputerManager service + bindService(new Intent(AddComputerManually.this, + ComputerManagerService.class), serviceConnection, Service.BIND_AUTO_CREATE); + } } diff --git a/app/src/main/java/com/limelight/utils/Dialog.java b/app/src/main/java/com/limelight/utils/Dialog.java index 720f687a..0621ba7f 100644 --- a/app/src/main/java/com/limelight/utils/Dialog.java +++ b/app/src/main/java/com/limelight/utils/Dialog.java @@ -7,71 +7,71 @@ import android.app.AlertDialog; import android.content.DialogInterface; public class Dialog implements Runnable { - private final String title; + private final String title; private final String message; - private final Activity activity; - private final boolean endAfterDismiss; - - private AlertDialog alert; - - private static final ArrayList rundownDialogs = new ArrayList(); - - private Dialog(Activity activity, String title, String message, boolean endAfterDismiss) - { - this.activity = activity; - this.title = title; - this.message = message; - this.endAfterDismiss = endAfterDismiss; - } - - public static void closeDialogs() - { - synchronized (rundownDialogs) { - for (Dialog d : rundownDialogs) { - if (d.alert.isShowing()) { - d.alert.dismiss(); - } - } - - rundownDialogs.clear(); - } - } - - public static void displayDialog(Activity activity, String title, String message, boolean endAfterDismiss) - { - activity.runOnUiThread(new Dialog(activity, title, message, endAfterDismiss)); - } - - @Override - public void run() { - // If we're dying, don't bother creating a dialog - if (activity.isFinishing()) - return; - - alert = new AlertDialog.Builder(activity).create(); + private final Activity activity; + private final boolean endAfterDismiss; - alert.setTitle(title); - alert.setMessage(message); - alert.setCancelable(false); - alert.setCanceledOnTouchOutside(false); + private AlertDialog alert; + + private static final ArrayList rundownDialogs = new ArrayList(); + + private Dialog(Activity activity, String title, String message, boolean endAfterDismiss) + { + this.activity = activity; + this.title = title; + this.message = message; + this.endAfterDismiss = endAfterDismiss; + } + + public static void closeDialogs() + { + synchronized (rundownDialogs) { + for (Dialog d : rundownDialogs) { + if (d.alert.isShowing()) { + d.alert.dismiss(); + } + } + + rundownDialogs.clear(); + } + } + + public static void displayDialog(Activity activity, String title, String message, boolean endAfterDismiss) + { + activity.runOnUiThread(new Dialog(activity, title, message, endAfterDismiss)); + } + + @Override + public void run() { + // If we're dying, don't bother creating a dialog + if (activity.isFinishing()) + return; + + alert = new AlertDialog.Builder(activity).create(); + + alert.setTitle(title); + alert.setMessage(message); + alert.setCancelable(false); + alert.setCanceledOnTouchOutside(false); - alert.setButton(AlertDialog.BUTTON_NEUTRAL, "OK", new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - synchronized (rundownDialogs) { - rundownDialogs.remove(Dialog.this); - alert.dismiss(); - } - - if (endAfterDismiss) { - activity.finish(); - } - } - }); - - synchronized (rundownDialogs) { - rundownDialogs.add(this); - alert.show(); - } - } + alert.setButton(AlertDialog.BUTTON_NEUTRAL, "OK", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + synchronized (rundownDialogs) { + rundownDialogs.remove(Dialog.this); + alert.dismiss(); + } + + if (endAfterDismiss) { + activity.finish(); + } + } + }); + + synchronized (rundownDialogs) { + rundownDialogs.add(this); + alert.show(); + } + } } diff --git a/app/src/main/java/com/limelight/utils/SpinnerDialog.java b/app/src/main/java/com/limelight/utils/SpinnerDialog.java index df97b2c3..7b65435b 100644 --- a/app/src/main/java/com/limelight/utils/SpinnerDialog.java +++ b/app/src/main/java/com/limelight/utils/SpinnerDialog.java @@ -9,112 +9,112 @@ import android.content.DialogInterface; import android.content.DialogInterface.OnCancelListener; public class SpinnerDialog implements Runnable,OnCancelListener { - private final String title; + private final String title; private final String message; - private final Activity activity; - private ProgressDialog progress; - private final boolean finish; - - private static final ArrayList rundownDialogs = new ArrayList(); - - private SpinnerDialog(Activity activity, String title, String message, boolean finish) - { - this.activity = activity; - this.title = title; - this.message = message; - this.progress = null; - this.finish = finish; - } - - public static SpinnerDialog displayDialog(Activity activity, String title, String message, boolean finish) - { - SpinnerDialog spinner = new SpinnerDialog(activity, title, message, finish); - activity.runOnUiThread(spinner); - return spinner; - } - - public static void closeDialogs(Activity activity) - { - synchronized (rundownDialogs) { - Iterator i = rundownDialogs.iterator(); - while (i.hasNext()) { - SpinnerDialog dialog = i.next(); - if (dialog.activity == activity) { - i.remove(); - if (dialog.progress.isShowing()) { - dialog.progress.dismiss(); - } - } - } - } - } - - public void dismiss() - { - // Running again with progress != null will destroy it - activity.runOnUiThread(this); - } - - public void setMessage(final String message) - { - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - progress.setMessage(message); - } - }); - } - - @Override - public void run() { + private final Activity activity; + private ProgressDialog progress; + private final boolean finish; - // If we're dying, don't bother doing anything - if (activity.isFinishing()) { - return; - } - - if (progress == null) - { - progress = new ProgressDialog(activity); - - progress.setTitle(title); - progress.setMessage(message); - progress.setProgressStyle(ProgressDialog.STYLE_SPINNER); - progress.setOnCancelListener(this); - - // If we want to finish the activity when this is killed, make it cancellable - if (finish) - { - progress.setCancelable(true); - progress.setCanceledOnTouchOutside(false); - } - else - { - progress.setCancelable(false); - } - - synchronized (rundownDialogs) { - rundownDialogs.add(this); - progress.show(); - } - } - else - { - synchronized (rundownDialogs) { - if (rundownDialogs.remove(this) && progress.isShowing()) { - progress.dismiss(); - } - } - } - } + private static final ArrayList rundownDialogs = new ArrayList(); - @Override - public void onCancel(DialogInterface dialog) { - synchronized (rundownDialogs) { - rundownDialogs.remove(this); - } - - // This will only be called if finish was true, so we don't need to check again - activity.finish(); - } + private SpinnerDialog(Activity activity, String title, String message, boolean finish) + { + this.activity = activity; + this.title = title; + this.message = message; + this.progress = null; + this.finish = finish; + } + + public static SpinnerDialog displayDialog(Activity activity, String title, String message, boolean finish) + { + SpinnerDialog spinner = new SpinnerDialog(activity, title, message, finish); + activity.runOnUiThread(spinner); + return spinner; + } + + public static void closeDialogs(Activity activity) + { + synchronized (rundownDialogs) { + Iterator i = rundownDialogs.iterator(); + while (i.hasNext()) { + SpinnerDialog dialog = i.next(); + if (dialog.activity == activity) { + i.remove(); + if (dialog.progress.isShowing()) { + dialog.progress.dismiss(); + } + } + } + } + } + + public void dismiss() + { + // Running again with progress != null will destroy it + activity.runOnUiThread(this); + } + + public void setMessage(final String message) + { + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + progress.setMessage(message); + } + }); + } + + @Override + public void run() { + + // If we're dying, don't bother doing anything + if (activity.isFinishing()) { + return; + } + + if (progress == null) + { + progress = new ProgressDialog(activity); + + progress.setTitle(title); + progress.setMessage(message); + progress.setProgressStyle(ProgressDialog.STYLE_SPINNER); + progress.setOnCancelListener(this); + + // If we want to finish the activity when this is killed, make it cancellable + if (finish) + { + progress.setCancelable(true); + progress.setCanceledOnTouchOutside(false); + } + else + { + progress.setCancelable(false); + } + + synchronized (rundownDialogs) { + rundownDialogs.add(this); + progress.show(); + } + } + else + { + synchronized (rundownDialogs) { + if (rundownDialogs.remove(this) && progress.isShowing()) { + progress.dismiss(); + } + } + } + } + + @Override + public void onCancel(DialogInterface dialog) { + synchronized (rundownDialogs) { + rundownDialogs.remove(this); + } + + // This will only be called if finish was true, so we don't need to check again + activity.finish(); + } }