Tabs -> Spaces

This commit is contained in:
Cameron Gutman 2015-02-07 11:54:46 -05:00
parent 10204afdb4
commit 2fdecc551a
28 changed files with 4135 additions and 4135 deletions

View File

@ -57,13 +57,13 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
private int consecutiveAppListFailures = 0; private int consecutiveAppListFailures = 0;
private final static int CONSECUTIVE_FAILURE_LIMIT = 3; private final static int CONSECUTIVE_FAILURE_LIMIT = 3;
private final static int START_OR_RESUME_ID = 1; private final static int START_OR_RESUME_ID = 1;
private final static int QUIT_ID = 2; private final static int QUIT_ID = 2;
private final static int CANCEL_ID = 3; private final static int CANCEL_ID = 3;
private final static int START_WTIH_QUIT = 4; private final static int START_WTIH_QUIT = 4;
public final static String NAME_EXTRA = "Name"; 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 ComputerManagerService.ComputerManagerBinder managerBinder;
private final ServiceConnection serviceConnection = new ServiceConnection() { private final ServiceConnection serviceConnection = new ServiceConnection() {
@ -184,32 +184,32 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
} }
} }
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
String locale = PreferenceConfiguration.readPreferences(this).language; String locale = PreferenceConfiguration.readPreferences(this).language;
if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) { if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
Configuration config = new Configuration(getResources().getConfiguration()); Configuration config = new Configuration(getResources().getConfiguration());
config.locale = new Locale(locale); config.locale = new Locale(locale);
getResources().updateConfiguration(config, getResources().getDisplayMetrics()); getResources().updateConfiguration(config, getResources().getDisplayMetrics());
} }
setContentView(R.layout.activity_app_view); setContentView(R.layout.activity_app_view);
UiHelper.notifyNewRootView(this); UiHelper.notifyNewRootView(this);
uuidString = getIntent().getStringExtra(UUID_EXTRA); uuidString = getIntent().getStringExtra(UUID_EXTRA);
String labelText = getResources().getString(R.string.title_applist)+" "+getIntent().getStringExtra(NAME_EXTRA); String labelText = getResources().getString(R.string.title_applist)+" "+getIntent().getStringExtra(NAME_EXTRA);
TextView label = (TextView) findViewById(R.id.appListText); TextView label = (TextView) findViewById(R.id.appListText);
setTitle(labelText); setTitle(labelText);
label.setText(labelText); label.setText(labelText);
// Bind to the computer manager service // Bind to the computer manager service
bindService(new Intent(this, ComputerManagerService.class), serviceConnection, bindService(new Intent(this, ComputerManagerService.class), serviceConnection,
Service.BIND_AUTO_CREATE); Service.BIND_AUTO_CREATE);
} }
private void populateAppGridWithCache() { private void populateAppGridWithCache() {
try { try {
@ -234,24 +234,24 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
getResources().getString(R.string.applist_refresh_msg), true); getResources().getString(R.string.applist_refresh_msg), true);
} }
@Override @Override
protected void onDestroy() { protected void onDestroy() {
super.onDestroy(); super.onDestroy();
SpinnerDialog.closeDialogs(this); SpinnerDialog.closeDialogs(this);
Dialog.closeDialogs(); Dialog.closeDialogs();
if (managerBinder != null) { if (managerBinder != null) {
unbindService(serviceConnection); unbindService(serviceConnection);
} }
} }
@Override @Override
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
startComputerUpdates(); startComputerUpdates();
} }
@Override @Override
protected void onPause() { protected void onPause() {
@ -260,48 +260,48 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
stopComputerUpdates(); stopComputerUpdates();
} }
private int getRunningAppId() { private int getRunningAppId() {
int runningAppId = -1; int runningAppId = -1;
for (int i = 0; i < appGridAdapter.getCount(); i++) { for (int i = 0; i < appGridAdapter.getCount(); i++) {
AppObject app = (AppObject) appGridAdapter.getItem(i); AppObject app = (AppObject) appGridAdapter.getItem(i);
if (app.app == null) { if (app.app == null) {
continue; continue;
} }
if (app.app.getIsRunning()) { if (app.app.getIsRunning()) {
runningAppId = app.app.getAppId(); runningAppId = app.app.getAppId();
break; break;
} }
} }
return runningAppId; return runningAppId;
} }
@Override @Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo); super.onCreateContextMenu(menu, v, menuInfo);
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo; AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
AppObject selectedApp = (AppObject) appGridAdapter.getItem(info.position); AppObject selectedApp = (AppObject) appGridAdapter.getItem(info.position);
if (selectedApp == null || selectedApp.app == null) { if (selectedApp == null || selectedApp.app == null) {
return; return;
} }
int runningAppId = getRunningAppId(); int runningAppId = getRunningAppId();
if (runningAppId != -1) { 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, 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)); 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, 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)); menu.add(Menu.NONE, CANCEL_ID, 2, getResources().getString(R.string.applist_menu_cancel));
} }
} }
} }
@Override @Override
public void onContextMenuClosed(Menu menu) { public void onContextMenuClosed(Menu menu) {
} }
private void displayQuitConfirmationDialog(final Runnable onYes, final Runnable onNo) { private void displayQuitConfirmationDialog(final Runnable onYes, final Runnable onNo) {
DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() { DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
@ -414,57 +414,57 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
}); });
} }
private void doStart(NvApp app) { private void doStart(NvApp app) {
Intent intent = new Intent(this, Game.class); Intent intent = new Intent(this, Game.class);
intent.putExtra(Game.EXTRA_HOST, intent.putExtra(Game.EXTRA_HOST,
computer.reachability == ComputerDetails.Reachability.LOCAL ? computer.reachability == ComputerDetails.Reachability.LOCAL ?
computer.localIp.getHostAddress() : computer.remoteIp.getHostAddress()); computer.localIp.getHostAddress() : computer.remoteIp.getHostAddress());
intent.putExtra(Game.EXTRA_APP, app.getAppName()); intent.putExtra(Game.EXTRA_APP, app.getAppName());
intent.putExtra(Game.EXTRA_UNIQUEID, managerBinder.getUniqueId()); intent.putExtra(Game.EXTRA_UNIQUEID, managerBinder.getUniqueId());
intent.putExtra(Game.EXTRA_STREAMING_REMOTE, intent.putExtra(Game.EXTRA_STREAMING_REMOTE,
computer.reachability != ComputerDetails.Reachability.LOCAL); computer.reachability != ComputerDetails.Reachability.LOCAL);
startActivity(intent); startActivity(intent);
} }
private void doQuit(final NvApp app) { private void doQuit(final NvApp app) {
Toast.makeText(AppView.this, getResources().getString(R.string.applist_quit_app)+" "+app.getAppName()+"...", Toast.LENGTH_SHORT).show(); Toast.makeText(AppView.this, getResources().getString(R.string.applist_quit_app)+" "+app.getAppName()+"...", Toast.LENGTH_SHORT).show();
new Thread(new Runnable() { new Thread(new Runnable() {
@Override @Override
public void run() { public void run() {
NvHTTP httpConn; NvHTTP httpConn;
String message; String message;
try { try {
httpConn = new NvHTTP(getAddress(), httpConn = new NvHTTP(getAddress(),
managerBinder.getUniqueId(), null, PlatformBinding.getCryptoProvider(AppView.this)); managerBinder.getUniqueId(), null, PlatformBinding.getCryptoProvider(AppView.this));
if (httpConn.quitApp()) { if (httpConn.quitApp()) {
message = getResources().getString(R.string.applist_quit_success)+" "+app.getAppName(); message = getResources().getString(R.string.applist_quit_success)+" "+app.getAppName();
} }
else { else {
message = getResources().getString(R.string.applist_quit_fail)+" "+app.getAppName(); message = getResources().getString(R.string.applist_quit_fail)+" "+app.getAppName();
} }
} catch (UnknownHostException e) { } catch (UnknownHostException e) {
message = getResources().getString(R.string.error_unknown_host); message = getResources().getString(R.string.error_unknown_host);
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
message = getResources().getString(R.string.error_404); message = getResources().getString(R.string.error_404);
} catch (Exception e) { } catch (Exception e) {
message = e.getMessage(); message = e.getMessage();
} finally { } finally {
// Trigger a poll immediately // Trigger a poll immediately
if (poller != null) { if (poller != null) {
poller.pollNow(); poller.pollNow();
} }
} }
final String toastMessage = message; final String toastMessage = message;
runOnUiThread(new Runnable() { runOnUiThread(new Runnable() {
@Override @Override
public void run() { public void run() {
Toast.makeText(AppView.this, toastMessage, Toast.LENGTH_LONG).show(); Toast.makeText(AppView.this, toastMessage, Toast.LENGTH_LONG).show();
} }
}); });
} }
}).start(); }).start();
} }
@Override @Override
public int getAdapterFragmentLayoutId() { public int getAdapterFragmentLayoutId() {
@ -497,15 +497,15 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
} }
public class AppObject { public class AppObject {
public final NvApp app; public final NvApp app;
public AppObject(NvApp app) { public AppObject(NvApp app) {
this.app = app; this.app = app;
} }
@Override @Override
public String toString() { public String toString() {
return app.getAppName(); return app.getAppName();
} }
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -50,69 +50,69 @@ import android.widget.AdapterView.AdapterContextMenuInfo;
public class PcView extends Activity implements AdapterFragmentCallbacks { public class PcView extends Activity implements AdapterFragmentCallbacks {
private RelativeLayout noPcFoundLayout; private RelativeLayout noPcFoundLayout;
private PcGridAdapter pcGridAdapter; private PcGridAdapter pcGridAdapter;
private ComputerManagerService.ComputerManagerBinder managerBinder; private ComputerManagerService.ComputerManagerBinder managerBinder;
private boolean freezeUpdates, runningPolling; private boolean freezeUpdates, runningPolling;
private final ServiceConnection serviceConnection = new ServiceConnection() { private final ServiceConnection serviceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder binder) { public void onServiceConnected(ComponentName className, IBinder binder) {
final ComputerManagerService.ComputerManagerBinder localBinder = final ComputerManagerService.ComputerManagerBinder localBinder =
((ComputerManagerService.ComputerManagerBinder)binder); ((ComputerManagerService.ComputerManagerBinder)binder);
// Wait in a separate thread to avoid stalling the UI // Wait in a separate thread to avoid stalling the UI
new Thread() { new Thread() {
@Override @Override
public void run() { public void run() {
// Wait for the binder to be ready // Wait for the binder to be ready
localBinder.waitForReady(); localBinder.waitForReady();
// Now make the binder visible // Now make the binder visible
managerBinder = localBinder; managerBinder = localBinder;
// Start updates // Start updates
startComputerUpdates(); startComputerUpdates();
// Force a keypair to be generated early to avoid discovery delays // Force a keypair to be generated early to avoid discovery delays
new AndroidCryptoProvider(PcView.this).getClientCertificate(); new AndroidCryptoProvider(PcView.this).getClientCertificate();
} }
}.start(); }.start();
} }
public void onServiceDisconnected(ComponentName className) { public void onServiceDisconnected(ComponentName className) {
managerBinder = null; managerBinder = null;
} }
}; };
public void onConfigurationChanged(Configuration newConfig) { public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig); super.onConfigurationChanged(newConfig);
// Reinitialize views just in case orientation changed // Reinitialize views just in case orientation changed
initializeViews(); initializeViews();
} }
private final static int APP_LIST_ID = 1; private final static int APP_LIST_ID = 1;
private final static int PAIR_ID = 2; private final static int PAIR_ID = 2;
private final static int UNPAIR_ID = 3; private final static int UNPAIR_ID = 3;
private final static int WOL_ID = 4; private final static int WOL_ID = 4;
private final static int DELETE_ID = 5; private final static int DELETE_ID = 5;
private void initializeViews() { private void initializeViews() {
setContentView(R.layout.activity_pc_view); setContentView(R.layout.activity_pc_view);
UiHelper.notifyNewRootView(this); UiHelper.notifyNewRootView(this);
// Set default preferences if we've never been run // Set default preferences if we've never been run
PreferenceManager.setDefaultValues(this, R.xml.preferences, false); PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
// Setup the list view // Setup the list view
ImageButton settingsButton = (ImageButton) findViewById(R.id.settingsButton); ImageButton settingsButton = (ImageButton) findViewById(R.id.settingsButton);
ImageButton addComputerButton = (ImageButton) findViewById(R.id.manuallyAddPc); ImageButton addComputerButton = (ImageButton) findViewById(R.id.manuallyAddPc);
settingsButton.setOnClickListener(new OnClickListener() { settingsButton.setOnClickListener(new OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
startActivity(new Intent(PcView.this, StreamSettings.class)); startActivity(new Intent(PcView.this, StreamSettings.class));
} }
}); });
addComputerButton.setOnClickListener(new OnClickListener() { addComputerButton.setOnClickListener(new OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
Intent i = new Intent(PcView.this, AddComputerManually.class); Intent i = new Intent(PcView.this, AddComputerManually.class);
@ -134,113 +134,113 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
pcGridAdapter.notifyDataSetChanged(); pcGridAdapter.notifyDataSetChanged();
} }
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
String locale = PreferenceConfiguration.readPreferences(this).language; String locale = PreferenceConfiguration.readPreferences(this).language;
if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) { if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
Configuration config = new Configuration(getResources().getConfiguration()); Configuration config = new Configuration(getResources().getConfiguration());
config.locale = new Locale(locale); config.locale = new Locale(locale);
getResources().updateConfiguration(config, getResources().getDisplayMetrics()); getResources().updateConfiguration(config, getResources().getDisplayMetrics());
} }
// Bind to the computer manager service // Bind to the computer manager service
bindService(new Intent(PcView.this, ComputerManagerService.class), serviceConnection, bindService(new Intent(PcView.this, ComputerManagerService.class), serviceConnection,
Service.BIND_AUTO_CREATE); Service.BIND_AUTO_CREATE);
pcGridAdapter = new PcGridAdapter(this, pcGridAdapter = new PcGridAdapter(this,
PreferenceConfiguration.readPreferences(this).listMode, PreferenceConfiguration.readPreferences(this).listMode,
PreferenceConfiguration.readPreferences(this).smallIconMode); PreferenceConfiguration.readPreferences(this).smallIconMode);
initializeViews(); initializeViews();
} }
private void startComputerUpdates() { private void startComputerUpdates() {
if (managerBinder != null) { if (managerBinder != null) {
if (runningPolling) { if (runningPolling) {
return; return;
} }
freezeUpdates = false; freezeUpdates = false;
managerBinder.startPolling(new ComputerManagerListener() { managerBinder.startPolling(new ComputerManagerListener() {
@Override @Override
public void notifyComputerUpdated(final ComputerDetails details) { public void notifyComputerUpdated(final ComputerDetails details) {
if (!freezeUpdates) { if (!freezeUpdates) {
PcView.this.runOnUiThread(new Runnable() { PcView.this.runOnUiThread(new Runnable() {
@Override @Override
public void run() { public void run() {
updateComputer(details); updateComputer(details);
} }
}); });
} }
} }
}); });
runningPolling = true; runningPolling = true;
} }
} }
private void stopComputerUpdates(boolean wait) { private void stopComputerUpdates(boolean wait) {
if (managerBinder != null) { if (managerBinder != null) {
if (!runningPolling) { if (!runningPolling) {
return; return;
} }
freezeUpdates = true; freezeUpdates = true;
managerBinder.stopPolling(); managerBinder.stopPolling();
if (wait) { if (wait) {
managerBinder.waitForPollingStopped(); managerBinder.waitForPollingStopped();
} }
runningPolling = false; runningPolling = false;
} }
} }
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
if (managerBinder != null) { if (managerBinder != null) {
unbindService(serviceConnection); unbindService(serviceConnection);
} }
} }
@Override @Override
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
startComputerUpdates(); startComputerUpdates();
} }
@Override @Override
protected void onPause() { protected void onPause() {
super.onPause(); super.onPause();
stopComputerUpdates(false); stopComputerUpdates(false);
} }
@Override @Override
protected void onStop() { protected void onStop() {
super.onStop(); super.onStop();
Dialog.closeDialogs(); Dialog.closeDialogs();
} }
@Override @Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
stopComputerUpdates(false); stopComputerUpdates(false);
// Call superclass // Call superclass
super.onCreateContextMenu(menu, v, menuInfo); super.onCreateContextMenu(menu, v, menuInfo);
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo; AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(info.position); ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(info.position);
if (computer == null || computer.details == null || if (computer == null || computer.details == null ||
computer.details.reachability == ComputerDetails.Reachability.UNKNOWN) { computer.details.reachability == ComputerDetails.Reachability.UNKNOWN) {
startComputerUpdates(); startComputerUpdates();
return; return;
} }
// Inflate the context menu // Inflate the context menu
@ -261,93 +261,93 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
} }
} }
@Override @Override
public void onContextMenuClosed(Menu menu) { public void onContextMenuClosed(Menu menu) {
startComputerUpdates(); startComputerUpdates();
} }
private void doPair(final ComputerDetails computer) { private void doPair(final ComputerDetails computer) {
if (computer.reachability == ComputerDetails.Reachability.OFFLINE) { if (computer.reachability == ComputerDetails.Reachability.OFFLINE) {
Toast.makeText(PcView.this, getResources().getString(R.string.pair_pc_offline), Toast.LENGTH_SHORT).show(); Toast.makeText(PcView.this, getResources().getString(R.string.pair_pc_offline), Toast.LENGTH_SHORT).show();
return; return;
} }
if (computer.runningGameId != 0) { if (computer.runningGameId != 0) {
Toast.makeText(PcView.this, getResources().getString(R.string.pair_pc_ingame), Toast.LENGTH_LONG).show(); Toast.makeText(PcView.this, getResources().getString(R.string.pair_pc_ingame), Toast.LENGTH_LONG).show();
return; return;
} }
if (managerBinder == null) { if (managerBinder == null) {
Toast.makeText(PcView.this, getResources().getString(R.string.error_manager_not_running), Toast.LENGTH_LONG).show(); Toast.makeText(PcView.this, getResources().getString(R.string.error_manager_not_running), Toast.LENGTH_LONG).show();
return; return;
} }
Toast.makeText(PcView.this, getResources().getString(R.string.pairing), Toast.LENGTH_SHORT).show(); Toast.makeText(PcView.this, getResources().getString(R.string.pairing), Toast.LENGTH_SHORT).show();
new Thread(new Runnable() { new Thread(new Runnable() {
@Override @Override
public void run() { public void run() {
NvHTTP httpConn; NvHTTP httpConn;
String message; String message;
boolean success = false; boolean success = false;
try { try {
// Stop updates and wait while pairing // Stop updates and wait while pairing
stopComputerUpdates(true); stopComputerUpdates(true);
InetAddress addr = null; InetAddress addr = null;
if (computer.reachability == ComputerDetails.Reachability.LOCAL) { if (computer.reachability == ComputerDetails.Reachability.LOCAL) {
addr = computer.localIp; addr = computer.localIp;
} }
else if (computer.reachability == ComputerDetails.Reachability.REMOTE) { else if (computer.reachability == ComputerDetails.Reachability.REMOTE) {
addr = computer.remoteIp; addr = computer.remoteIp;
} }
httpConn = new NvHTTP(addr, httpConn = new NvHTTP(addr,
managerBinder.getUniqueId(), managerBinder.getUniqueId(),
PlatformBinding.getDeviceName(), PlatformBinding.getDeviceName(),
PlatformBinding.getCryptoProvider(PcView.this)); PlatformBinding.getCryptoProvider(PcView.this));
if (httpConn.getPairState() == PairingManager.PairState.PAIRED) { if (httpConn.getPairState() == PairingManager.PairState.PAIRED) {
// Don't display any toast, but open the app list // Don't display any toast, but open the app list
message = null; message = null;
success = true; success = true;
} }
else { else {
final String pinStr = PairingManager.generatePinString(); final String pinStr = PairingManager.generatePinString();
// Spin the dialog off in a thread because it blocks // Spin the dialog off in a thread because it blocks
Dialog.displayDialog(PcView.this, getResources().getString(R.string.pair_pairing_title), Dialog.displayDialog(PcView.this, getResources().getString(R.string.pair_pairing_title),
getResources().getString(R.string.pair_pairing_msg)+" "+pinStr, false); getResources().getString(R.string.pair_pairing_msg)+" "+pinStr, false);
PairingManager.PairState pairState = httpConn.pair(pinStr); PairingManager.PairState pairState = httpConn.pair(pinStr);
if (pairState == PairingManager.PairState.PIN_WRONG) { if (pairState == PairingManager.PairState.PIN_WRONG) {
message = getResources().getString(R.string.pair_incorrect_pin); message = getResources().getString(R.string.pair_incorrect_pin);
} }
else if (pairState == PairingManager.PairState.FAILED) { else if (pairState == PairingManager.PairState.FAILED) {
message = getResources().getString(R.string.pair_fail); message = getResources().getString(R.string.pair_fail);
} }
else if (pairState == PairingManager.PairState.PAIRED) { else if (pairState == PairingManager.PairState.PAIRED) {
// Just navigate to the app view without displaying a toast // Just navigate to the app view without displaying a toast
message = null; message = null;
success = true; success = true;
} }
else { else {
// Should be no other values // Should be no other values
message = null; message = null;
} }
} }
} catch (UnknownHostException e) { } catch (UnknownHostException e) {
message = getResources().getString(R.string.error_unknown_host); message = getResources().getString(R.string.error_unknown_host);
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
message = getResources().getString(R.string.error_404); message = getResources().getString(R.string.error_404);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
message = e.getMessage(); message = e.getMessage();
} }
Dialog.closeDialogs(); Dialog.closeDialogs();
final String toastMessage = message; final String toastMessage = message;
final boolean toastSuccess = success; final boolean toastSuccess = success;
runOnUiThread(new Runnable() { runOnUiThread(new Runnable() {
@Override @Override
public void run() { public void run() {
if (toastMessage != null) { if (toastMessage != null) {
Toast.makeText(PcView.this, toastMessage, Toast.LENGTH_LONG).show(); 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 // Open the app list after a successful pairing attemp
doAppList(computer); doAppList(computer);
} }
} }
}); });
// Start polling again // Start polling again
startComputerUpdates(); startComputerUpdates();
} }
}).start(); }).start();
} }
private void doWakeOnLan(final ComputerDetails computer) { private void doWakeOnLan(final ComputerDetails computer) {
if (computer.reachability != ComputerDetails.Reachability.OFFLINE) { if (computer.reachability != ComputerDetails.Reachability.OFFLINE) {
Toast.makeText(PcView.this, getResources().getString(R.string.wol_pc_online), Toast.LENGTH_SHORT).show(); Toast.makeText(PcView.this, getResources().getString(R.string.wol_pc_online), Toast.LENGTH_SHORT).show();
return; return;
} }
if (computer.macAddress == null) { if (computer.macAddress == null) {
Toast.makeText(PcView.this, getResources().getString(R.string.wol_no_mac), Toast.LENGTH_SHORT).show(); Toast.makeText(PcView.this, getResources().getString(R.string.wol_no_mac), Toast.LENGTH_SHORT).show();
return; return;
} }
Toast.makeText(PcView.this, getResources().getString(R.string.wol_waking_pc), Toast.LENGTH_SHORT).show(); Toast.makeText(PcView.this, getResources().getString(R.string.wol_waking_pc), Toast.LENGTH_SHORT).show();
new Thread(new Runnable() { new Thread(new Runnable() {
@Override @Override
public void run() { public void run() {
String message; String message;
try { try {
WakeOnLanSender.sendWolPacket(computer); WakeOnLanSender.sendWolPacket(computer);
message = getResources().getString(R.string.wol_waking_msg); message = getResources().getString(R.string.wol_waking_msg);
} catch (IOException e) { } catch (IOException e) {
message = getResources().getString(R.string.wol_fail); message = getResources().getString(R.string.wol_fail);
} }
final String toastMessage = message; final String toastMessage = message;
runOnUiThread(new Runnable() { runOnUiThread(new Runnable() {
@Override @Override
public void run() { public void run() {
Toast.makeText(PcView.this, toastMessage, Toast.LENGTH_LONG).show(); Toast.makeText(PcView.this, toastMessage, Toast.LENGTH_LONG).show();
} }
}); });
} }
}).start(); }).start();
} }
private void doUnpair(final ComputerDetails computer) { private void doUnpair(final ComputerDetails computer) {
if (computer.reachability == ComputerDetails.Reachability.OFFLINE) { if (computer.reachability == ComputerDetails.Reachability.OFFLINE) {
Toast.makeText(PcView.this, getResources().getString(R.string.error_pc_offline), Toast.LENGTH_SHORT).show(); Toast.makeText(PcView.this, getResources().getString(R.string.error_pc_offline), Toast.LENGTH_SHORT).show();
return; return;
} }
if (managerBinder == null) { if (managerBinder == null) {
Toast.makeText(PcView.this, getResources().getString(R.string.error_manager_not_running), Toast.LENGTH_LONG).show(); Toast.makeText(PcView.this, getResources().getString(R.string.error_manager_not_running), Toast.LENGTH_LONG).show();
return; return;
} }
Toast.makeText(PcView.this, getResources().getString(R.string.unpairing), Toast.LENGTH_SHORT).show(); Toast.makeText(PcView.this, getResources().getString(R.string.unpairing), Toast.LENGTH_SHORT).show();
new Thread(new Runnable() { new Thread(new Runnable() {
@Override @Override
public void run() { public void run() {
NvHTTP httpConn; NvHTTP httpConn;
String message; String message;
try { try {
InetAddress addr = null; InetAddress addr = null;
if (computer.reachability == ComputerDetails.Reachability.LOCAL) { if (computer.reachability == ComputerDetails.Reachability.LOCAL) {
addr = computer.localIp; addr = computer.localIp;
} }
else if (computer.reachability == ComputerDetails.Reachability.REMOTE) { else if (computer.reachability == ComputerDetails.Reachability.REMOTE) {
addr = computer.remoteIp; addr = computer.remoteIp;
} }
httpConn = new NvHTTP(addr, httpConn = new NvHTTP(addr,
managerBinder.getUniqueId(), managerBinder.getUniqueId(),
PlatformBinding.getDeviceName(), PlatformBinding.getDeviceName(),
PlatformBinding.getCryptoProvider(PcView.this)); PlatformBinding.getCryptoProvider(PcView.this));
if (httpConn.getPairState() == PairingManager.PairState.PAIRED) { if (httpConn.getPairState() == PairingManager.PairState.PAIRED) {
httpConn.unpair(); httpConn.unpair();
if (httpConn.getPairState() == PairingManager.PairState.NOT_PAIRED) { if (httpConn.getPairState() == PairingManager.PairState.NOT_PAIRED) {
message = getResources().getString(R.string.unpair_success); message = getResources().getString(R.string.unpair_success);
} }
else { else {
message = getResources().getString(R.string.unpair_fail); message = getResources().getString(R.string.unpair_fail);
} }
} }
else { else {
message = getResources().getString(R.string.unpair_error); message = getResources().getString(R.string.unpair_error);
} }
} catch (UnknownHostException e) { } catch (UnknownHostException e) {
message = getResources().getString(R.string.error_unknown_host); message = getResources().getString(R.string.error_unknown_host);
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
message = getResources().getString(R.string.error_404); message = getResources().getString(R.string.error_404);
} catch (Exception e) { } catch (Exception e) {
message = e.getMessage(); message = e.getMessage();
} }
final String toastMessage = message; final String toastMessage = message;
runOnUiThread(new Runnable() { runOnUiThread(new Runnable() {
@Override @Override
public void run() { public void run() {
Toast.makeText(PcView.this, toastMessage, Toast.LENGTH_LONG).show(); Toast.makeText(PcView.this, toastMessage, Toast.LENGTH_LONG).show();
} }
}); });
} }
}).start(); }).start();
} }
private void doAppList(ComputerDetails computer) { private void doAppList(ComputerDetails computer) {
if (computer.reachability == ComputerDetails.Reachability.OFFLINE) { if (computer.reachability == ComputerDetails.Reachability.OFFLINE) {
Toast.makeText(PcView.this, getResources().getString(R.string.error_pc_offline), Toast.LENGTH_SHORT).show(); Toast.makeText(PcView.this, getResources().getString(R.string.error_pc_offline), Toast.LENGTH_SHORT).show();
return; return;
} }
if (managerBinder == null) { if (managerBinder == null) {
Toast.makeText(PcView.this, getResources().getString(R.string.error_manager_not_running), Toast.LENGTH_LONG).show(); Toast.makeText(PcView.this, getResources().getString(R.string.error_manager_not_running), Toast.LENGTH_LONG).show();
return; return;
} }
Intent i = new Intent(this, AppView.class); Intent i = new Intent(this, AppView.class);
i.putExtra(AppView.NAME_EXTRA, computer.name); i.putExtra(AppView.NAME_EXTRA, computer.name);
i.putExtra(AppView.UUID_EXTRA, computer.uuid.toString()); i.putExtra(AppView.UUID_EXTRA, computer.uuid.toString());
startActivity(i); startActivity(i);
} }
@Override @Override
public boolean onContextItemSelected(MenuItem item) { public boolean onContextItemSelected(MenuItem item) {
@ -482,29 +482,29 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
switch (item.getItemId()) switch (item.getItemId())
{ {
case PAIR_ID: case PAIR_ID:
doPair(computer.details); doPair(computer.details);
return true; return true;
case UNPAIR_ID: case UNPAIR_ID:
doUnpair(computer.details); doUnpair(computer.details);
return true; return true;
case WOL_ID: case WOL_ID:
doWakeOnLan(computer.details); doWakeOnLan(computer.details);
return true; return true;
case DELETE_ID: case DELETE_ID:
if (managerBinder == null) { if (managerBinder == null) {
Toast.makeText(PcView.this, getResources().getString(R.string.error_manager_not_running), Toast.LENGTH_LONG).show(); Toast.makeText(PcView.this, getResources().getString(R.string.error_manager_not_running), Toast.LENGTH_LONG).show();
return true; return true;
} }
managerBinder.removeComputer(computer.details.name); managerBinder.removeComputer(computer.details.name);
removeComputer(computer.details); removeComputer(computer.details);
return true; return true;
case APP_LIST_ID: case APP_LIST_ID:
doAppList(computer.details); doAppList(computer.details);
return true; return true;
default: default:
return super.onContextItemSelected(item); return super.onContextItemSelected(item);
@ -512,45 +512,45 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
} }
private void removeComputer(ComputerDetails details) { private void removeComputer(ComputerDetails details) {
for (int i = 0; i < pcGridAdapter.getCount(); i++) { for (int i = 0; i < pcGridAdapter.getCount(); i++) {
ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(i); ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(i);
if (details.equals(computer.details)) { if (details.equals(computer.details)) {
pcGridAdapter.removeComputer(computer); pcGridAdapter.removeComputer(computer);
pcGridAdapter.notifyDataSetChanged(); pcGridAdapter.notifyDataSetChanged();
break; break;
} }
} }
} }
private void updateComputer(ComputerDetails details) { private void updateComputer(ComputerDetails details) {
ComputerObject existingEntry = null; ComputerObject existingEntry = null;
for (int i = 0; i < pcGridAdapter.getCount(); i++) { for (int i = 0; i < pcGridAdapter.getCount(); i++) {
ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(i); ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(i);
// Check if this is the same computer // Check if this is the same computer
if (details.uuid.equals(computer.details.uuid)) { if (details.uuid.equals(computer.details.uuid)) {
existingEntry = computer; existingEntry = computer;
break; break;
} }
} }
if (existingEntry != null) { if (existingEntry != null) {
// Replace the information in the existing entry // Replace the information in the existing entry
existingEntry.details = details; existingEntry.details = details;
} }
else { else {
// Add a new entry // Add a new entry
pcGridAdapter.addComputer(new ComputerObject(details)); pcGridAdapter.addComputer(new ComputerObject(details));
// Remove the "Discovery in progress" view // Remove the "Discovery in progress" view
noPcFoundLayout.setVisibility(View.INVISIBLE); noPcFoundLayout.setVisibility(View.INVISIBLE);
} }
// Notify the view that the data has changed // Notify the view that the data has changed
pcGridAdapter.notifyDataSetChanged(); pcGridAdapter.notifyDataSetChanged();
} }
@Override @Override
public int getAdapterFragmentLayoutId() { public int getAdapterFragmentLayoutId() {
@ -584,15 +584,15 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
} }
public class ComputerObject { public class ComputerObject {
public ComputerDetails details; public ComputerDetails details;
public ComputerObject(ComputerDetails details) { public ComputerObject(ComputerDetails details) {
this.details = details; this.details = details;
} }
@Override @Override
public String toString() { public String toString() {
return details.name; return details.name;
} }
} }
} }

View File

@ -8,17 +8,17 @@ import com.limelight.nvstream.av.audio.AudioRenderer;
import com.limelight.nvstream.http.LimelightCryptoProvider; import com.limelight.nvstream.http.LimelightCryptoProvider;
public class PlatformBinding { public class PlatformBinding {
public static String getDeviceName() { public static String getDeviceName() {
String deviceName = android.os.Build.MODEL; String deviceName = android.os.Build.MODEL;
deviceName = deviceName.replace(" ", ""); deviceName = deviceName.replace(" ", "");
return deviceName; return deviceName;
} }
public static AudioRenderer getAudioRenderer() { public static AudioRenderer getAudioRenderer() {
return new AndroidAudioRenderer(); return new AndroidAudioRenderer();
} }
public static LimelightCryptoProvider getCryptoProvider(Context c) { public static LimelightCryptoProvider getCryptoProvider(Context c) {
return new AndroidCryptoProvider(c); return new AndroidCryptoProvider(c);
} }
} }

View File

@ -9,27 +9,27 @@ import com.limelight.nvstream.av.audio.AudioRenderer;
public class AndroidAudioRenderer implements AudioRenderer { public class AndroidAudioRenderer implements AudioRenderer {
private static final int FRAME_SIZE = 960; private static final int FRAME_SIZE = 960;
private AudioTrack track; private AudioTrack track;
@Override @Override
public boolean streamInitialized(int channelCount, int sampleRate) { public boolean streamInitialized(int channelCount, int sampleRate) {
int channelConfig; int channelConfig;
int bufferSize; int bufferSize;
switch (channelCount) switch (channelCount)
{ {
case 1: case 1:
channelConfig = AudioFormat.CHANNEL_OUT_MONO; channelConfig = AudioFormat.CHANNEL_OUT_MONO;
break; break;
case 2: case 2:
channelConfig = AudioFormat.CHANNEL_OUT_STEREO; channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
break; break;
default: default:
LimeLog.severe("Decoder returned unhandled channel count"); LimeLog.severe("Decoder returned unhandled channel count");
return false; return false;
} }
// We're not supposed to request less than the minimum // We're not supposed to request less than the minimum
// buffer size for our buffer, but it appears that we can // buffer size for our buffer, but it appears that we can
@ -73,25 +73,25 @@ public class AndroidAudioRenderer implements AudioRenderer {
track.play(); track.play();
} }
LimeLog.info("Audio track buffer size: "+bufferSize); LimeLog.info("Audio track buffer size: "+bufferSize);
return true; return true;
} }
@Override @Override
public void playDecodedAudio(byte[] audioData, int offset, int length) { public void playDecodedAudio(byte[] audioData, int offset, int length) {
track.write(audioData, offset, length); track.write(audioData, offset, length);
} }
@Override @Override
public void streamClosing() { public void streamClosing() {
if (track != null) { if (track != null) {
track.release(); track.release();
} }
} }
@Override @Override
public int getCapabilities() { public int getCapabilities() {
return 0; return 0;
} }
} }

View File

@ -45,239 +45,239 @@ import com.limelight.nvstream.http.LimelightCryptoProvider;
public class AndroidCryptoProvider implements LimelightCryptoProvider { public class AndroidCryptoProvider implements LimelightCryptoProvider {
private final File certFile; private final File certFile;
private final File keyFile; private final File keyFile;
private X509Certificate cert; private X509Certificate cert;
private RSAPrivateKey key; private RSAPrivateKey key;
private byte[] pemCertBytes; private byte[] pemCertBytes;
private static final Object globalCryptoLock = new Object(); private static final Object globalCryptoLock = new Object();
static { static {
// Install the Bouncy Castle provider // Install the Bouncy Castle provider
Security.addProvider(new BouncyCastleProvider()); Security.addProvider(new BouncyCastleProvider());
} }
public AndroidCryptoProvider(Context c) { public AndroidCryptoProvider(Context c) {
String dataPath = c.getFilesDir().getAbsolutePath(); String dataPath = c.getFilesDir().getAbsolutePath();
certFile = new File(dataPath + File.separator + "client.crt"); certFile = new File(dataPath + File.separator + "client.crt");
keyFile = new File(dataPath + File.separator + "client.key"); keyFile = new File(dataPath + File.separator + "client.key");
} }
private byte[] loadFileToBytes(File f) { private byte[] loadFileToBytes(File f) {
if (!f.exists()) { if (!f.exists()) {
return null; return null;
} }
try { try {
FileInputStream fin = new FileInputStream(f); FileInputStream fin = new FileInputStream(f);
byte[] fileData = new byte[(int) f.length()]; byte[] fileData = new byte[(int) f.length()];
if (fin.read(fileData) != f.length()) { if (fin.read(fileData) != f.length()) {
// Failed to read // Failed to read
fileData = null; fileData = null;
} }
fin.close(); fin.close();
return fileData; return fileData;
} catch (IOException e) { } catch (IOException e) {
return null; return null;
} }
} }
private boolean loadCertKeyPair() { private boolean loadCertKeyPair() {
byte[] certBytes = loadFileToBytes(certFile); byte[] certBytes = loadFileToBytes(certFile);
byte[] keyBytes = loadFileToBytes(keyFile); byte[] keyBytes = loadFileToBytes(keyFile);
// If either file was missing, we definitely can't succeed // If either file was missing, we definitely can't succeed
if (certBytes == null || keyBytes == null) { if (certBytes == null || keyBytes == null) {
LimeLog.info("Missing cert or key; need to generate a new one"); LimeLog.info("Missing cert or key; need to generate a new one");
return false; return false;
} }
try { try {
CertificateFactory certFactory = CertificateFactory.getInstance("X.509", "BC"); CertificateFactory certFactory = CertificateFactory.getInstance("X.509", "BC");
cert = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(certBytes)); cert = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(certBytes));
pemCertBytes = certBytes; pemCertBytes = certBytes;
KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC"); KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC");
key = (RSAPrivateKey) keyFactory.generatePrivate(new PKCS8EncodedKeySpec(keyBytes)); key = (RSAPrivateKey) keyFactory.generatePrivate(new PKCS8EncodedKeySpec(keyBytes));
} catch (CertificateException e) { } catch (CertificateException e) {
// May happen if the cert is corrupt // May happen if the cert is corrupt
LimeLog.warning("Corrupted certificate"); LimeLog.warning("Corrupted certificate");
return false; return false;
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException e) {
// Should never happen // Should never happen
e.printStackTrace(); e.printStackTrace();
return false; return false;
} catch (InvalidKeySpecException e) { } catch (InvalidKeySpecException e) {
// May happen if the key is corrupt // May happen if the key is corrupt
LimeLog.warning("Corrupted key"); LimeLog.warning("Corrupted key");
return false; return false;
} catch (NoSuchProviderException e) { } catch (NoSuchProviderException e) {
// Should never happen // Should never happen
e.printStackTrace(); e.printStackTrace();
return false; return false;
} }
return true; return true;
} }
@SuppressLint("TrulyRandom") @SuppressLint("TrulyRandom")
private boolean generateCertKeyPair() { private boolean generateCertKeyPair() {
byte[] snBytes = new byte[8]; byte[] snBytes = new byte[8];
new SecureRandom().nextBytes(snBytes); new SecureRandom().nextBytes(snBytes);
KeyPair keyPair; KeyPair keyPair;
try { try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC"); KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC");
keyPairGenerator.initialize(2048); keyPairGenerator.initialize(2048);
keyPair = keyPairGenerator.generateKeyPair(); keyPair = keyPairGenerator.generateKeyPair();
} catch (NoSuchAlgorithmException e1) { } catch (NoSuchAlgorithmException e1) {
// Should never happen // Should never happen
e1.printStackTrace(); e1.printStackTrace();
return false; return false;
} catch (NoSuchProviderException e) { } catch (NoSuchProviderException e) {
// Should never happen // Should never happen
e.printStackTrace(); e.printStackTrace();
return false; return false;
} }
Date now = new Date(); Date now = new Date();
// Expires in 20 years // Expires in 20 years
Calendar calendar = Calendar.getInstance(); Calendar calendar = Calendar.getInstance();
calendar.setTime(now); calendar.setTime(now);
calendar.add(Calendar.YEAR, 20); calendar.add(Calendar.YEAR, 20);
Date expirationDate = calendar.getTime(); Date expirationDate = calendar.getTime();
BigInteger serial = new BigInteger(snBytes).abs(); BigInteger serial = new BigInteger(snBytes).abs();
X500NameBuilder nameBuilder = new X500NameBuilder(BCStyle.INSTANCE); X500NameBuilder nameBuilder = new X500NameBuilder(BCStyle.INSTANCE);
nameBuilder.addRDN(BCStyle.CN, "NVIDIA GameStream Client"); nameBuilder.addRDN(BCStyle.CN, "NVIDIA GameStream Client");
X500Name name = nameBuilder.build(); X500Name name = nameBuilder.build();
X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder(name, serial, now, expirationDate, Locale.ENGLISH, name, X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder(name, serial, now, expirationDate, Locale.ENGLISH, name,
SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded())); SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded()));
try { try {
ContentSigner sigGen = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BouncyCastleProvider.PROVIDER_NAME).build(keyPair.getPrivate()); ContentSigner sigGen = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BouncyCastleProvider.PROVIDER_NAME).build(keyPair.getPrivate());
cert = new JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME).getCertificate(certBuilder.build(sigGen)); cert = new JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME).getCertificate(certBuilder.build(sigGen));
key = (RSAPrivateKey) keyPair.getPrivate(); key = (RSAPrivateKey) keyPair.getPrivate();
} catch (Exception e) { } catch (Exception e) {
// Nothing should go wrong here // Nothing should go wrong here
e.printStackTrace(); e.printStackTrace();
return false; return false;
} }
LimeLog.info("Generated a new key pair"); LimeLog.info("Generated a new key pair");
// Save the resulting pair // Save the resulting pair
saveCertKeyPair(); saveCertKeyPair();
return true; return true;
} }
private void saveCertKeyPair() { private void saveCertKeyPair() {
try { try {
FileOutputStream certOut = new FileOutputStream(certFile); FileOutputStream certOut = new FileOutputStream(certFile);
FileOutputStream keyOut = new FileOutputStream(keyFile); FileOutputStream keyOut = new FileOutputStream(keyFile);
// Write the certificate in OpenSSL PEM format (important for the server) // Write the certificate in OpenSSL PEM format (important for the server)
StringWriter strWriter = new StringWriter(); StringWriter strWriter = new StringWriter();
JcaPEMWriter pemWriter = new JcaPEMWriter(strWriter); JcaPEMWriter pemWriter = new JcaPEMWriter(strWriter);
pemWriter.writeObject(cert); pemWriter.writeObject(cert);
pemWriter.close(); pemWriter.close();
// Line endings MUST be UNIX for the PC to accept the cert properly // Line endings MUST be UNIX for the PC to accept the cert properly
OutputStreamWriter certWriter = new OutputStreamWriter(certOut); OutputStreamWriter certWriter = new OutputStreamWriter(certOut);
String pemStr = strWriter.getBuffer().toString(); String pemStr = strWriter.getBuffer().toString();
for (int i = 0; i < pemStr.length(); i++) { for (int i = 0; i < pemStr.length(); i++) {
char c = pemStr.charAt(i); char c = pemStr.charAt(i);
if (c != '\r') if (c != '\r')
certWriter.append(c); certWriter.append(c);
} }
certWriter.close(); certWriter.close();
// Write the private out in PKCS8 format // Write the private out in PKCS8 format
keyOut.write(key.getEncoded()); keyOut.write(key.getEncoded());
certOut.close(); certOut.close();
keyOut.close(); keyOut.close();
LimeLog.info("Saved generated key pair to disk"); LimeLog.info("Saved generated key pair to disk");
} catch (IOException e) { } catch (IOException e) {
// This isn't good because it means we'll have // This isn't good because it means we'll have
// to re-pair next time // to re-pair next time
e.printStackTrace(); e.printStackTrace();
} }
} }
public X509Certificate getClientCertificate() { public X509Certificate getClientCertificate() {
// Use a lock here to ensure only one guy will be generating or loading // Use a lock here to ensure only one guy will be generating or loading
// the certificate and key at a time // the certificate and key at a time
synchronized (globalCryptoLock) { synchronized (globalCryptoLock) {
// Return a loaded cert if we have one // Return a loaded cert if we have one
if (cert != null) { if (cert != null) {
return cert; return cert;
} }
// No loaded cert yet, let's see if we have one on disk // No loaded cert yet, let's see if we have one on disk
if (loadCertKeyPair()) { if (loadCertKeyPair()) {
// Got one // Got one
return cert; return cert;
} }
// Try to generate a new key pair // Try to generate a new key pair
if (!generateCertKeyPair()) { if (!generateCertKeyPair()) {
// Failed // Failed
return null; return null;
} }
// Load the generated pair // Load the generated pair
loadCertKeyPair(); loadCertKeyPair();
return cert; return cert;
} }
} }
public RSAPrivateKey getClientPrivateKey() { public RSAPrivateKey getClientPrivateKey() {
// Use a lock here to ensure only one guy will be generating or loading // Use a lock here to ensure only one guy will be generating or loading
// the certificate and key at a time // the certificate and key at a time
synchronized (globalCryptoLock) { synchronized (globalCryptoLock) {
// Return a loaded key if we have one // Return a loaded key if we have one
if (key != null) { if (key != null) {
return key; return key;
} }
// No loaded key yet, let's see if we have one on disk // No loaded key yet, let's see if we have one on disk
if (loadCertKeyPair()) { if (loadCertKeyPair()) {
// Got one // Got one
return key; return key;
} }
// Try to generate a new key pair // Try to generate a new key pair
if (!generateCertKeyPair()) { if (!generateCertKeyPair()) {
// Failed // Failed
return null; return null;
} }
// Load the generated pair // Load the generated pair
loadCertKeyPair(); loadCertKeyPair();
return key; return key;
} }
} }
public byte[] getPemEncodedClientCertificate() { public byte[] getPemEncodedClientCertificate() {
synchronized (globalCryptoLock) { synchronized (globalCryptoLock) {
// Call our helper function to do the cert loading/generation for us // Call our helper function to do the cert loading/generation for us
getClientCertificate(); getClientCertificate();
// Return a cached value if we have it // Return a cached value if we have it
return pemCertBytes; return pemCertBytes;
} }
} }
@Override @Override
public String encodeBase64String(byte[] data) { public String encodeBase64String(byte[] data) {
return Base64.encodeToString(data, Base64.NO_WRAP); return Base64.encodeToString(data, Base64.NO_WRAP);
} }
} }

View File

@ -17,23 +17,23 @@ import com.limelight.utils.Vector2d;
public class ControllerHandler implements InputManager.InputDeviceListener { 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 START_DOWN_TIME_KEYB_MS = 750;
private static final int MINIMUM_BUTTON_DOWN_TIME_MS = 25; private static final int MINIMUM_BUTTON_DOWN_TIME_MS = 25;
private static final int EMULATING_SPECIAL = 0x1; private static final int EMULATING_SPECIAL = 0x1;
private static final int EMULATING_SELECT = 0x2; private static final int EMULATING_SELECT = 0x2;
private static final int EMULATED_SPECIAL_UP_DELAY_MS = 100; private static final int EMULATED_SPECIAL_UP_DELAY_MS = 100;
private static final int EMULATED_SELECT_UP_DELAY_MS = 30; private static final int EMULATED_SELECT_UP_DELAY_MS = 30;
private final Vector2d inputVector = new Vector2d(); private final Vector2d inputVector = new Vector2d();
private final HashMap<String, ControllerContext> contexts = new HashMap<String, ControllerContext>(); private final HashMap<String, ControllerContext> contexts = new HashMap<String, ControllerContext>();
private final NvConnection conn; private final NvConnection conn;
private final double stickDeadzone; private final double stickDeadzone;
private final ControllerContext defaultContext = new ControllerContext(); private final ControllerContext defaultContext = new ControllerContext();
private final GameGestures gestures; private final GameGestures gestures;
@ -42,8 +42,8 @@ public class ControllerHandler implements InputManager.InputDeviceListener {
private final boolean multiControllerEnabled; private final boolean multiControllerEnabled;
private short currentControllers; private short currentControllers;
public ControllerHandler(NvConnection conn, GameGestures gestures, boolean multiControllerEnabled, int deadzonePercentage) { public ControllerHandler(NvConnection conn, GameGestures gestures, boolean multiControllerEnabled, int deadzonePercentage) {
this.conn = conn; this.conn = conn;
this.gestures = gestures; this.gestures = gestures;
this.multiControllerEnabled = multiControllerEnabled; this.multiControllerEnabled = multiControllerEnabled;
@ -82,20 +82,20 @@ public class ControllerHandler implements InputManager.InputDeviceListener {
defaultContext.leftTriggerAxis = MotionEvent.AXIS_BRAKE; defaultContext.leftTriggerAxis = MotionEvent.AXIS_BRAKE;
defaultContext.rightTriggerAxis = MotionEvent.AXIS_GAS; defaultContext.rightTriggerAxis = MotionEvent.AXIS_GAS;
defaultContext.controllerNumber = (short) 0; defaultContext.controllerNumber = (short) 0;
} }
private static InputDevice.MotionRange getMotionRangeForJoystickAxis(InputDevice dev, int axis) { private static InputDevice.MotionRange getMotionRangeForJoystickAxis(InputDevice dev, int axis) {
InputDevice.MotionRange range; InputDevice.MotionRange range;
// First get the axis for SOURCE_JOYSTICK // First get the axis for SOURCE_JOYSTICK
range = dev.getMotionRange(axis, InputDevice.SOURCE_JOYSTICK); range = dev.getMotionRange(axis, InputDevice.SOURCE_JOYSTICK);
if (range == null) { if (range == null) {
// Now try the axis for SOURCE_GAMEPAD // Now try the axis for SOURCE_GAMEPAD
range = dev.getMotionRange(axis, InputDevice.SOURCE_GAMEPAD); range = dev.getMotionRange(axis, InputDevice.SOURCE_GAMEPAD);
} }
return range; return range;
} }
private short assignNewControllerNumber() { private short assignNewControllerNumber() {
for (short i = 0; i < 4; i++) { for (short i = 0; i < 4; i++) {
@ -138,8 +138,8 @@ public class ControllerHandler implements InputManager.InputDeviceListener {
currentControllers &= ~(1 << controllerNumber); currentControllers &= ~(1 << controllerNumber);
} }
private ControllerContext createContextForDevice(InputDevice dev) { private ControllerContext createContextForDevice(InputDevice dev) {
ControllerContext context = new ControllerContext(); ControllerContext context = new ControllerContext();
String devName = dev.getName(); String devName = dev.getName();
LimeLog.info("Creating controller context for device: "+devName); LimeLog.info("Creating controller context for device: "+devName);
@ -148,7 +148,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener {
context.id = dev.getId(); context.id = dev.getId();
context.leftStickXAxis = MotionEvent.AXIS_X; context.leftStickXAxis = MotionEvent.AXIS_X;
context.leftStickYAxis = MotionEvent.AXIS_Y; context.leftStickYAxis = MotionEvent.AXIS_Y;
if (getMotionRangeForJoystickAxis(dev, context.leftStickXAxis) != null && if (getMotionRangeForJoystickAxis(dev, context.leftStickXAxis) != null &&
getMotionRangeForJoystickAxis(dev, context.leftStickYAxis) != null) { getMotionRangeForJoystickAxis(dev, context.leftStickYAxis) != null) {
// This is a gamepad // This is a gamepad
@ -156,83 +156,83 @@ public class ControllerHandler implements InputManager.InputDeviceListener {
context.hasJoystickAxes = true; context.hasJoystickAxes = true;
} }
InputDevice.MotionRange leftTriggerRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_LTRIGGER); InputDevice.MotionRange leftTriggerRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_LTRIGGER);
InputDevice.MotionRange rightTriggerRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RTRIGGER); InputDevice.MotionRange rightTriggerRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RTRIGGER);
InputDevice.MotionRange brakeRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_BRAKE); InputDevice.MotionRange brakeRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_BRAKE);
InputDevice.MotionRange gasRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_GAS); InputDevice.MotionRange gasRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_GAS);
if (leftTriggerRange != null && rightTriggerRange != null) if (leftTriggerRange != null && rightTriggerRange != null)
{ {
// Some controllers use LTRIGGER and RTRIGGER (like Ouya) // Some controllers use LTRIGGER and RTRIGGER (like Ouya)
context.leftTriggerAxis = MotionEvent.AXIS_LTRIGGER; context.leftTriggerAxis = MotionEvent.AXIS_LTRIGGER;
context.rightTriggerAxis = MotionEvent.AXIS_RTRIGGER; context.rightTriggerAxis = MotionEvent.AXIS_RTRIGGER;
} }
else if (brakeRange != null && gasRange != null) else if (brakeRange != null && gasRange != null)
{ {
// Others use GAS and BRAKE (like Moga) // Others use GAS and BRAKE (like Moga)
context.leftTriggerAxis = MotionEvent.AXIS_BRAKE; context.leftTriggerAxis = MotionEvent.AXIS_BRAKE;
context.rightTriggerAxis = MotionEvent.AXIS_GAS; context.rightTriggerAxis = MotionEvent.AXIS_GAS;
} }
else else
{ {
InputDevice.MotionRange rxRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RX); InputDevice.MotionRange rxRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RX);
InputDevice.MotionRange ryRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RY); InputDevice.MotionRange ryRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RY);
if (rxRange != null && ryRange != null && devName != null) { if (rxRange != null && ryRange != null && devName != null) {
if (devName.contains("Xbox") || devName.contains("XBox") || devName.contains("X-Box")) { if (devName.contains("Xbox") || devName.contains("XBox") || devName.contains("X-Box")) {
// Xbox controllers use RX and RY for right stick // Xbox controllers use RX and RY for right stick
context.rightStickXAxis = MotionEvent.AXIS_RX; context.rightStickXAxis = MotionEvent.AXIS_RX;
context.rightStickYAxis = MotionEvent.AXIS_RY; context.rightStickYAxis = MotionEvent.AXIS_RY;
// Xbox controllers use Z and RZ for triggers // Xbox controllers use Z and RZ for triggers
context.leftTriggerAxis = MotionEvent.AXIS_Z; context.leftTriggerAxis = MotionEvent.AXIS_Z;
context.rightTriggerAxis = MotionEvent.AXIS_RZ; context.rightTriggerAxis = MotionEvent.AXIS_RZ;
context.triggersIdleNegative = true; context.triggersIdleNegative = true;
context.isXboxController = true; context.isXboxController = true;
} }
else { else {
// DS4 controller uses RX and RY for triggers // DS4 controller uses RX and RY for triggers
context.leftTriggerAxis = MotionEvent.AXIS_RX; context.leftTriggerAxis = MotionEvent.AXIS_RX;
context.rightTriggerAxis = MotionEvent.AXIS_RY; context.rightTriggerAxis = MotionEvent.AXIS_RY;
context.triggersIdleNegative = true; context.triggersIdleNegative = true;
context.isDualShock4 = true; context.isDualShock4 = true;
} }
} }
} }
if (context.rightStickXAxis == -1 && context.rightStickYAxis == -1) { if (context.rightStickXAxis == -1 && context.rightStickYAxis == -1) {
InputDevice.MotionRange zRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_Z); InputDevice.MotionRange zRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_Z);
InputDevice.MotionRange rzRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RZ); InputDevice.MotionRange rzRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RZ);
// Most other controllers use Z and RZ for the right stick // Most other controllers use Z and RZ for the right stick
if (zRange != null && rzRange != null) { if (zRange != null && rzRange != null) {
context.rightStickXAxis = MotionEvent.AXIS_Z; context.rightStickXAxis = MotionEvent.AXIS_Z;
context.rightStickYAxis = MotionEvent.AXIS_RZ; context.rightStickYAxis = MotionEvent.AXIS_RZ;
} }
else { else {
InputDevice.MotionRange rxRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RX); InputDevice.MotionRange rxRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RX);
InputDevice.MotionRange ryRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RY); InputDevice.MotionRange ryRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RY);
// Try RX and RY now // Try RX and RY now
if (rxRange != null && ryRange != null) { if (rxRange != null && ryRange != null) {
context.rightStickXAxis = MotionEvent.AXIS_RX; context.rightStickXAxis = MotionEvent.AXIS_RX;
context.rightStickYAxis = MotionEvent.AXIS_RY; context.rightStickYAxis = MotionEvent.AXIS_RY;
} }
} }
} }
// Some devices have "hats" for d-pads // Some devices have "hats" for d-pads
InputDevice.MotionRange hatXRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_HAT_X); InputDevice.MotionRange hatXRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_HAT_X);
InputDevice.MotionRange hatYRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_HAT_Y); InputDevice.MotionRange hatYRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_HAT_Y);
if (hatXRange != null && hatYRange != null) { if (hatXRange != null && hatYRange != null) {
context.hatXAxis = MotionEvent.AXIS_HAT_X; context.hatXAxis = MotionEvent.AXIS_HAT_X;
context.hatYAxis = MotionEvent.AXIS_HAT_Y; context.hatYAxis = MotionEvent.AXIS_HAT_Y;
} }
if (context.leftStickXAxis != -1 && context.leftStickYAxis != -1) { if (context.leftStickXAxis != -1 && context.leftStickYAxis != -1) {
context.leftStickDeadzoneRadius = (float) stickDeadzone; context.leftStickDeadzoneRadius = (float) stickDeadzone;
} }
if (context.rightStickXAxis != -1 && context.rightStickYAxis != -1) { if (context.rightStickXAxis != -1 && context.rightStickYAxis != -1) {
context.rightStickDeadzoneRadius = (float) stickDeadzone; context.rightStickDeadzoneRadius = (float) stickDeadzone;
} }
@ -300,114 +300,114 @@ public class ControllerHandler implements InputManager.InputDeviceListener {
LimeLog.info("Assigned as controller "+context.controllerNumber); LimeLog.info("Assigned as controller "+context.controllerNumber);
return context; return context;
} }
private ControllerContext getContextForDevice(InputDevice dev) { private ControllerContext getContextForDevice(InputDevice dev) {
// Unknown devices use the default context // Unknown devices use the default context
if (dev == null) { if (dev == null) {
return defaultContext; return defaultContext;
} }
String descriptor = dev.getDescriptor(); String descriptor = dev.getDescriptor();
// Return the existing context if it exists // Return the existing context if it exists
ControllerContext context = contexts.get(descriptor); ControllerContext context = contexts.get(descriptor);
if (context != null) { if (context != null) {
return context; return context;
} }
// Otherwise create a new context // Otherwise create a new context
context = createContextForDevice(dev); context = createContextForDevice(dev);
contexts.put(descriptor, context); contexts.put(descriptor, context);
return context; return context;
} }
private void sendControllerInputPacket(ControllerContext context) { private void sendControllerInputPacket(ControllerContext context) {
conn.sendControllerInput(context.controllerNumber, context.inputMap, conn.sendControllerInput(context.controllerNumber, context.inputMap,
context.leftTrigger, context.rightTrigger, context.leftTrigger, context.rightTrigger,
context.leftStickX, context.leftStickY, context.leftStickX, context.leftStickY,
context.rightStickX, context.rightStickY); context.rightStickX, context.rightStickY);
} }
// Return a valid keycode, 0 to consume, or -1 to not consume the event // Return a valid keycode, 0 to consume, or -1 to not consume the event
// Device MAY BE NULL // 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 // For remotes, don't capture the back button
if (context.isRemote) { if (context.isRemote) {
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
return -1; return -1;
} }
} }
if (context.isDualShock4) { if (context.isDualShock4) {
switch (event.getKeyCode()) { switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_BUTTON_Y: case KeyEvent.KEYCODE_BUTTON_Y:
return KeyEvent.KEYCODE_BUTTON_L1; return KeyEvent.KEYCODE_BUTTON_L1;
case KeyEvent.KEYCODE_BUTTON_Z: case KeyEvent.KEYCODE_BUTTON_Z:
return KeyEvent.KEYCODE_BUTTON_R1; return KeyEvent.KEYCODE_BUTTON_R1;
case KeyEvent.KEYCODE_BUTTON_C: case KeyEvent.KEYCODE_BUTTON_C:
return KeyEvent.KEYCODE_BUTTON_B; return KeyEvent.KEYCODE_BUTTON_B;
case KeyEvent.KEYCODE_BUTTON_X: case KeyEvent.KEYCODE_BUTTON_X:
return KeyEvent.KEYCODE_BUTTON_Y; return KeyEvent.KEYCODE_BUTTON_Y;
case KeyEvent.KEYCODE_BUTTON_B: case KeyEvent.KEYCODE_BUTTON_B:
return KeyEvent.KEYCODE_BUTTON_A; return KeyEvent.KEYCODE_BUTTON_A;
case KeyEvent.KEYCODE_BUTTON_A: case KeyEvent.KEYCODE_BUTTON_A:
return KeyEvent.KEYCODE_BUTTON_X; return KeyEvent.KEYCODE_BUTTON_X;
case KeyEvent.KEYCODE_BUTTON_SELECT: case KeyEvent.KEYCODE_BUTTON_SELECT:
return KeyEvent.KEYCODE_BUTTON_THUMBL; return KeyEvent.KEYCODE_BUTTON_THUMBL;
case KeyEvent.KEYCODE_BUTTON_START: case KeyEvent.KEYCODE_BUTTON_START:
return KeyEvent.KEYCODE_BUTTON_THUMBR; return KeyEvent.KEYCODE_BUTTON_THUMBR;
case KeyEvent.KEYCODE_BUTTON_L2: case KeyEvent.KEYCODE_BUTTON_L2:
return KeyEvent.KEYCODE_BUTTON_SELECT; return KeyEvent.KEYCODE_BUTTON_SELECT;
case KeyEvent.KEYCODE_BUTTON_R2: case KeyEvent.KEYCODE_BUTTON_R2:
return KeyEvent.KEYCODE_BUTTON_START; return KeyEvent.KEYCODE_BUTTON_START;
// These are duplicate trigger events // These are duplicate trigger events
case KeyEvent.KEYCODE_BUTTON_R1: case KeyEvent.KEYCODE_BUTTON_R1:
case KeyEvent.KEYCODE_BUTTON_L1: case KeyEvent.KEYCODE_BUTTON_L1:
return 0; return 0;
} }
} }
if (context.hatXAxis != -1 && context.hatYAxis != -1) { if (context.hatXAxis != -1 && context.hatYAxis != -1) {
switch (event.getKeyCode()) { switch (event.getKeyCode()) {
// These are duplicate dpad events for hat input // These are duplicate dpad events for hat input
case KeyEvent.KEYCODE_DPAD_LEFT: case KeyEvent.KEYCODE_DPAD_LEFT:
case KeyEvent.KEYCODE_DPAD_RIGHT: case KeyEvent.KEYCODE_DPAD_RIGHT:
case KeyEvent.KEYCODE_DPAD_CENTER: case KeyEvent.KEYCODE_DPAD_CENTER:
case KeyEvent.KEYCODE_DPAD_UP: case KeyEvent.KEYCODE_DPAD_UP:
case KeyEvent.KEYCODE_DPAD_DOWN: case KeyEvent.KEYCODE_DPAD_DOWN:
return 0; return 0;
} }
} }
else if (context.hatXAxis == -1 && else if (context.hatXAxis == -1 &&
context.hatYAxis == -1 && context.hatYAxis == -1 &&
context.isXboxController && context.isXboxController &&
event.getKeyCode() == KeyEvent.KEYCODE_UNKNOWN) { event.getKeyCode() == KeyEvent.KEYCODE_UNKNOWN) {
// If there's not a proper Xbox controller mapping, we'll translate the raw d-pad // If there's not a proper Xbox controller mapping, we'll translate the raw d-pad
// scan codes into proper key codes // scan codes into proper key codes
switch (event.getScanCode()) switch (event.getScanCode())
{ {
case 704: case 704:
return KeyEvent.KEYCODE_DPAD_LEFT; return KeyEvent.KEYCODE_DPAD_LEFT;
case 705: case 705:
return KeyEvent.KEYCODE_DPAD_RIGHT; return KeyEvent.KEYCODE_DPAD_RIGHT;
case 706: case 706:
return KeyEvent.KEYCODE_DPAD_UP; return KeyEvent.KEYCODE_DPAD_UP;
case 707: case 707:
return KeyEvent.KEYCODE_DPAD_DOWN; return KeyEvent.KEYCODE_DPAD_DOWN;
} }
} }
// Past here we can fixup the keycode and potentially trigger // Past here we can fixup the keycode and potentially trigger
// another special case so we need to remember what keycode we're using // another special case so we need to remember what keycode we're using
@ -440,8 +440,8 @@ public class ControllerHandler implements InputManager.InputDeviceListener {
return KeyEvent.KEYCODE_BUTTON_SELECT; return KeyEvent.KEYCODE_BUTTON_SELECT;
} }
return keyCode; return keyCode;
} }
private Vector2d populateCachedVector(float x, float y) { private Vector2d populateCachedVector(float x, float y) {
// Reinitialize our cached Vector2d object // Reinitialize our cached Vector2d object
@ -449,11 +449,11 @@ public class ControllerHandler implements InputManager.InputDeviceListener {
return inputVector; return inputVector;
} }
private void handleDeadZone(Vector2d stickVector, float deadzoneRadius) { private void handleDeadZone(Vector2d stickVector, float deadzoneRadius) {
if (stickVector.getMagnitude() <= deadzoneRadius) { if (stickVector.getMagnitude() <= deadzoneRadius) {
// Deadzone // Deadzone
stickVector.initialize(0, 0); stickVector.initialize(0, 0);
} }
// We're not normalizing here because we let the computer handle the deadzones. // 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 // Normalizing can make the deadzones larger than they should be after the computer also
@ -519,8 +519,8 @@ public class ControllerHandler implements InputManager.InputDeviceListener {
sendControllerInputPacket(context); sendControllerInputPacket(context);
} }
public boolean handleMotionEvent(MotionEvent event) { public boolean handleMotionEvent(MotionEvent event) {
ControllerContext context = getContextForDevice(event.getDevice()); ControllerContext context = getContextForDevice(event.getDevice());
float lsX = 0, lsY = 0, rsX = 0, rsY = 0, rt = 0, lt = 0, hatX = 0, hatY = 0; 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 // 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); handleAxisSet(context, lsX, lsY, rsX, rsY, lt, rt, hatX, hatY);
return true; return true;
} }
public boolean handleButtonUp(KeyEvent event) { public boolean handleButtonUp(KeyEvent event) {
ControllerContext context = getContextForDevice(event.getDevice()); ControllerContext context = getContextForDevice(event.getDevice());
int keyCode = handleRemapping(context, event); int keyCode = handleRemapping(context, event);
if (keyCode == 0) { if (keyCode == 0) {
return true; return true;
} }
// If the button hasn't been down long enough, sleep for a bit before sending the up event // 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 // This allows "instant" button presses (like OUYA's virtual menu button) to work. This
// path should not be triggered during normal usage. // path should not be triggered during normal usage.
if (SystemClock.uptimeMillis() - event.getDownTime() < ControllerHandler.MINIMUM_BUTTON_DOWN_TIME_MS) 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 // Since our sleep time is so short (10 ms), it shouldn't cause a problem doing this in the
// UI thread. // UI thread.
try { try {
Thread.sleep(ControllerHandler.MINIMUM_BUTTON_DOWN_TIME_MS); Thread.sleep(ControllerHandler.MINIMUM_BUTTON_DOWN_TIME_MS);
} catch (InterruptedException ignored) {} } catch (InterruptedException ignored) {}
} }
switch (keyCode) { switch (keyCode) {
case KeyEvent.KEYCODE_BUTTON_MODE: case KeyEvent.KEYCODE_BUTTON_MODE:
context.inputMap &= ~ControllerPacket.SPECIAL_BUTTON_FLAG; context.inputMap &= ~ControllerPacket.SPECIAL_BUTTON_FLAG;
break; break;
case KeyEvent.KEYCODE_BUTTON_START: case KeyEvent.KEYCODE_BUTTON_START:
case KeyEvent.KEYCODE_MENU: case KeyEvent.KEYCODE_MENU:
if (SystemClock.uptimeMillis() - context.startDownTime > ControllerHandler.START_DOWN_TIME_KEYB_MS) { if (SystemClock.uptimeMillis() - context.startDownTime > ControllerHandler.START_DOWN_TIME_KEYB_MS) {
gestures.showKeyboard(); gestures.showKeyboard();
} }
context.inputMap &= ~ControllerPacket.PLAY_FLAG; context.inputMap &= ~ControllerPacket.PLAY_FLAG;
break; break;
case KeyEvent.KEYCODE_BACK: case KeyEvent.KEYCODE_BACK:
case KeyEvent.KEYCODE_BUTTON_SELECT: case KeyEvent.KEYCODE_BUTTON_SELECT:
context.inputMap &= ~ControllerPacket.BACK_FLAG; context.inputMap &= ~ControllerPacket.BACK_FLAG;
break; break;
case KeyEvent.KEYCODE_DPAD_LEFT: case KeyEvent.KEYCODE_DPAD_LEFT:
context.inputMap &= ~ControllerPacket.LEFT_FLAG; context.inputMap &= ~ControllerPacket.LEFT_FLAG;
break; break;
case KeyEvent.KEYCODE_DPAD_RIGHT: case KeyEvent.KEYCODE_DPAD_RIGHT:
context.inputMap &= ~ControllerPacket.RIGHT_FLAG; context.inputMap &= ~ControllerPacket.RIGHT_FLAG;
break; break;
case KeyEvent.KEYCODE_DPAD_UP: case KeyEvent.KEYCODE_DPAD_UP:
context.inputMap &= ~ControllerPacket.UP_FLAG; context.inputMap &= ~ControllerPacket.UP_FLAG;
break; break;
case KeyEvent.KEYCODE_DPAD_DOWN: case KeyEvent.KEYCODE_DPAD_DOWN:
context.inputMap &= ~ControllerPacket.DOWN_FLAG; context.inputMap &= ~ControllerPacket.DOWN_FLAG;
break; break;
case KeyEvent.KEYCODE_BUTTON_B: case KeyEvent.KEYCODE_BUTTON_B:
context.inputMap &= ~ControllerPacket.B_FLAG; context.inputMap &= ~ControllerPacket.B_FLAG;
break; break;
case KeyEvent.KEYCODE_DPAD_CENTER: case KeyEvent.KEYCODE_DPAD_CENTER:
case KeyEvent.KEYCODE_BUTTON_A: case KeyEvent.KEYCODE_BUTTON_A:
context.inputMap &= ~ControllerPacket.A_FLAG; context.inputMap &= ~ControllerPacket.A_FLAG;
break; break;
case KeyEvent.KEYCODE_BUTTON_X: case KeyEvent.KEYCODE_BUTTON_X:
context.inputMap &= ~ControllerPacket.X_FLAG; context.inputMap &= ~ControllerPacket.X_FLAG;
break; break;
case KeyEvent.KEYCODE_BUTTON_Y: case KeyEvent.KEYCODE_BUTTON_Y:
context.inputMap &= ~ControllerPacket.Y_FLAG; context.inputMap &= ~ControllerPacket.Y_FLAG;
break; break;
case KeyEvent.KEYCODE_BUTTON_L1: case KeyEvent.KEYCODE_BUTTON_L1:
context.inputMap &= ~ControllerPacket.LB_FLAG; context.inputMap &= ~ControllerPacket.LB_FLAG;
context.lastLbUpTime = SystemClock.uptimeMillis(); context.lastLbUpTime = SystemClock.uptimeMillis();
break; break;
case KeyEvent.KEYCODE_BUTTON_R1: case KeyEvent.KEYCODE_BUTTON_R1:
context.inputMap &= ~ControllerPacket.RB_FLAG; context.inputMap &= ~ControllerPacket.RB_FLAG;
context.lastRbUpTime = SystemClock.uptimeMillis(); context.lastRbUpTime = SystemClock.uptimeMillis();
break; break;
case KeyEvent.KEYCODE_BUTTON_THUMBL: case KeyEvent.KEYCODE_BUTTON_THUMBL:
context.inputMap &= ~ControllerPacket.LS_CLK_FLAG; context.inputMap &= ~ControllerPacket.LS_CLK_FLAG;
break; break;
case KeyEvent.KEYCODE_BUTTON_THUMBR: case KeyEvent.KEYCODE_BUTTON_THUMBR:
context.inputMap &= ~ControllerPacket.RS_CLK_FLAG; context.inputMap &= ~ControllerPacket.RS_CLK_FLAG;
break; break;
case KeyEvent.KEYCODE_BUTTON_L2: case KeyEvent.KEYCODE_BUTTON_L2:
context.leftTrigger = 0; context.leftTrigger = 0;
break; break;
case KeyEvent.KEYCODE_BUTTON_R2: case KeyEvent.KEYCODE_BUTTON_R2:
context.rightTrigger = 0; context.rightTrigger = 0;
break; break;
default: default:
return false; return false;
} }
// Check if we're emulating the select button // Check if we're emulating the select button
if ((context.emulatingButtonFlags & ControllerHandler.EMULATING_SELECT) != 0) if ((context.emulatingButtonFlags & ControllerHandler.EMULATING_SELECT) != 0)
{ {
// If either start or LB is up, select comes up too // If either start or LB is up, select comes up too
if ((context.inputMap & ControllerPacket.PLAY_FLAG) == 0 || if ((context.inputMap & ControllerPacket.PLAY_FLAG) == 0 ||
(context.inputMap & ControllerPacket.LB_FLAG) == 0) (context.inputMap & ControllerPacket.LB_FLAG) == 0)
{ {
context.inputMap &= ~ControllerPacket.BACK_FLAG; context.inputMap &= ~ControllerPacket.BACK_FLAG;
context.emulatingButtonFlags &= ~ControllerHandler.EMULATING_SELECT; context.emulatingButtonFlags &= ~ControllerHandler.EMULATING_SELECT;
try { try {
Thread.sleep(EMULATED_SELECT_UP_DELAY_MS); Thread.sleep(EMULATED_SELECT_UP_DELAY_MS);
} catch (InterruptedException ignored) {} } catch (InterruptedException ignored) {}
} }
} }
// Check if we're emulating the special button // Check if we're emulating the special button
if ((context.emulatingButtonFlags & ControllerHandler.EMULATING_SPECIAL) != 0) if ((context.emulatingButtonFlags & ControllerHandler.EMULATING_SPECIAL) != 0)
{ {
// If either start or select and RB is up, the special button comes up too // If either start or select and RB is up, the special button comes up too
if ((context.inputMap & ControllerPacket.PLAY_FLAG) == 0 || if ((context.inputMap & ControllerPacket.PLAY_FLAG) == 0 ||
((context.inputMap & ControllerPacket.BACK_FLAG) == 0 && ((context.inputMap & ControllerPacket.BACK_FLAG) == 0 &&
(context.inputMap & ControllerPacket.RB_FLAG) == 0)) (context.inputMap & ControllerPacket.RB_FLAG) == 0))
{ {
context.inputMap &= ~ControllerPacket.SPECIAL_BUTTON_FLAG; context.inputMap &= ~ControllerPacket.SPECIAL_BUTTON_FLAG;
context.emulatingButtonFlags &= ~ControllerHandler.EMULATING_SPECIAL; context.emulatingButtonFlags &= ~ControllerHandler.EMULATING_SPECIAL;
try { try {
Thread.sleep(EMULATED_SPECIAL_UP_DELAY_MS); Thread.sleep(EMULATED_SPECIAL_UP_DELAY_MS);
} catch (InterruptedException ignored) {} } catch (InterruptedException ignored) {}
} }
} }
sendControllerInputPacket(context); sendControllerInputPacket(context);
return true; return true;
} }
public boolean handleButtonDown(KeyEvent event) { public boolean handleButtonDown(KeyEvent event) {
ControllerContext context = getContextForDevice(event.getDevice()); ControllerContext context = getContextForDevice(event.getDevice());
int keyCode = handleRemapping(context, event); int keyCode = handleRemapping(context, event);
if (keyCode == 0) { if (keyCode == 0) {
return true; return true;
} }
switch (keyCode) { switch (keyCode) {
case KeyEvent.KEYCODE_BUTTON_MODE: case KeyEvent.KEYCODE_BUTTON_MODE:
context.inputMap |= ControllerPacket.SPECIAL_BUTTON_FLAG; context.inputMap |= ControllerPacket.SPECIAL_BUTTON_FLAG;
break; break;
case KeyEvent.KEYCODE_BUTTON_START: case KeyEvent.KEYCODE_BUTTON_START:
case KeyEvent.KEYCODE_MENU: case KeyEvent.KEYCODE_MENU:
if (event.getRepeatCount() == 0) { if (event.getRepeatCount() == 0) {
context.startDownTime = SystemClock.uptimeMillis(); context.startDownTime = SystemClock.uptimeMillis();
} }
context.inputMap |= ControllerPacket.PLAY_FLAG; context.inputMap |= ControllerPacket.PLAY_FLAG;
break; break;
case KeyEvent.KEYCODE_BACK: case KeyEvent.KEYCODE_BACK:
case KeyEvent.KEYCODE_BUTTON_SELECT: case KeyEvent.KEYCODE_BUTTON_SELECT:
context.inputMap |= ControllerPacket.BACK_FLAG; context.inputMap |= ControllerPacket.BACK_FLAG;
break; break;
case KeyEvent.KEYCODE_DPAD_LEFT: case KeyEvent.KEYCODE_DPAD_LEFT:
context.inputMap |= ControllerPacket.LEFT_FLAG; context.inputMap |= ControllerPacket.LEFT_FLAG;
break; break;
case KeyEvent.KEYCODE_DPAD_RIGHT: case KeyEvent.KEYCODE_DPAD_RIGHT:
context.inputMap |= ControllerPacket.RIGHT_FLAG; context.inputMap |= ControllerPacket.RIGHT_FLAG;
break; break;
case KeyEvent.KEYCODE_DPAD_UP: case KeyEvent.KEYCODE_DPAD_UP:
context.inputMap |= ControllerPacket.UP_FLAG; context.inputMap |= ControllerPacket.UP_FLAG;
break; break;
case KeyEvent.KEYCODE_DPAD_DOWN: case KeyEvent.KEYCODE_DPAD_DOWN:
context.inputMap |= ControllerPacket.DOWN_FLAG; context.inputMap |= ControllerPacket.DOWN_FLAG;
break; break;
case KeyEvent.KEYCODE_BUTTON_B: case KeyEvent.KEYCODE_BUTTON_B:
context.inputMap |= ControllerPacket.B_FLAG; context.inputMap |= ControllerPacket.B_FLAG;
break; break;
case KeyEvent.KEYCODE_DPAD_CENTER: case KeyEvent.KEYCODE_DPAD_CENTER:
case KeyEvent.KEYCODE_BUTTON_A: case KeyEvent.KEYCODE_BUTTON_A:
context.inputMap |= ControllerPacket.A_FLAG; context.inputMap |= ControllerPacket.A_FLAG;
break; break;
case KeyEvent.KEYCODE_BUTTON_X: case KeyEvent.KEYCODE_BUTTON_X:
context.inputMap |= ControllerPacket.X_FLAG; context.inputMap |= ControllerPacket.X_FLAG;
break; break;
case KeyEvent.KEYCODE_BUTTON_Y: case KeyEvent.KEYCODE_BUTTON_Y:
context.inputMap |= ControllerPacket.Y_FLAG; context.inputMap |= ControllerPacket.Y_FLAG;
break; break;
case KeyEvent.KEYCODE_BUTTON_L1: case KeyEvent.KEYCODE_BUTTON_L1:
context.inputMap |= ControllerPacket.LB_FLAG; context.inputMap |= ControllerPacket.LB_FLAG;
break; break;
case KeyEvent.KEYCODE_BUTTON_R1: case KeyEvent.KEYCODE_BUTTON_R1:
context.inputMap |= ControllerPacket.RB_FLAG; context.inputMap |= ControllerPacket.RB_FLAG;
break; break;
case KeyEvent.KEYCODE_BUTTON_THUMBL: case KeyEvent.KEYCODE_BUTTON_THUMBL:
context.inputMap |= ControllerPacket.LS_CLK_FLAG; context.inputMap |= ControllerPacket.LS_CLK_FLAG;
break; break;
case KeyEvent.KEYCODE_BUTTON_THUMBR: case KeyEvent.KEYCODE_BUTTON_THUMBR:
context.inputMap |= ControllerPacket.RS_CLK_FLAG; context.inputMap |= ControllerPacket.RS_CLK_FLAG;
break; break;
case KeyEvent.KEYCODE_BUTTON_L2: case KeyEvent.KEYCODE_BUTTON_L2:
context.leftTrigger = (byte)0xFF; context.leftTrigger = (byte)0xFF;
break; break;
case KeyEvent.KEYCODE_BUTTON_R2: case KeyEvent.KEYCODE_BUTTON_R2:
context.rightTrigger = (byte)0xFF; context.rightTrigger = (byte)0xFF;
break; break;
default: default:
return false; return false;
} }
// Start+LB acts like select for controllers with one button // Start+LB acts like select for controllers with one button
if ((context.inputMap & ControllerPacket.PLAY_FLAG) != 0 && if ((context.inputMap & ControllerPacket.PLAY_FLAG) != 0 &&
((context.inputMap & ControllerPacket.LB_FLAG) != 0 || ((context.inputMap & ControllerPacket.LB_FLAG) != 0 ||
SystemClock.uptimeMillis() - context.lastLbUpTime <= MAXIMUM_BUMPER_UP_DELAY_MS)) SystemClock.uptimeMillis() - context.lastLbUpTime <= MAXIMUM_BUMPER_UP_DELAY_MS))
{ {
context.inputMap &= ~(ControllerPacket.PLAY_FLAG | ControllerPacket.LB_FLAG); context.inputMap &= ~(ControllerPacket.PLAY_FLAG | ControllerPacket.LB_FLAG);
context.inputMap |= ControllerPacket.BACK_FLAG; context.inputMap |= ControllerPacket.BACK_FLAG;
context.emulatingButtonFlags |= ControllerHandler.EMULATING_SELECT; context.emulatingButtonFlags |= ControllerHandler.EMULATING_SELECT;
} }
// We detect select+start or start+RB as the special button combo // We detect select+start or start+RB as the special button combo
if (((context.inputMap & ControllerPacket.RB_FLAG) != 0 || if (((context.inputMap & ControllerPacket.RB_FLAG) != 0 ||
(SystemClock.uptimeMillis() - context.lastRbUpTime <= MAXIMUM_BUMPER_UP_DELAY_MS) || (SystemClock.uptimeMillis() - context.lastRbUpTime <= MAXIMUM_BUMPER_UP_DELAY_MS) ||
(context.inputMap & ControllerPacket.BACK_FLAG) != 0) && (context.inputMap & ControllerPacket.BACK_FLAG) != 0) &&
(context.inputMap & ControllerPacket.PLAY_FLAG) != 0) (context.inputMap & ControllerPacket.PLAY_FLAG) != 0)
{ {
context.inputMap &= ~(ControllerPacket.BACK_FLAG | ControllerPacket.PLAY_FLAG | ControllerPacket.RB_FLAG); context.inputMap &= ~(ControllerPacket.BACK_FLAG | ControllerPacket.PLAY_FLAG | ControllerPacket.RB_FLAG);
context.inputMap |= ControllerPacket.SPECIAL_BUTTON_FLAG; context.inputMap |= ControllerPacket.SPECIAL_BUTTON_FLAG;
context.emulatingButtonFlags |= ControllerHandler.EMULATING_SPECIAL; context.emulatingButtonFlags |= ControllerHandler.EMULATING_SPECIAL;
} }
// Send a new input packet if this is the first instance of a button down event // Send a new input packet if this is the first instance of a button down event
// or anytime if we're emulating a button // or anytime if we're emulating a button
if (event.getRepeatCount() == 0 || context.emulatingButtonFlags != 0) { if (event.getRepeatCount() == 0 || context.emulatingButtonFlags != 0) {
sendControllerInputPacket(context); sendControllerInputPacket(context);
} }
return true; return true;
} }
class ControllerContext { class ControllerContext {
public String name; public String name;
public int id; public int id;
public int leftStickXAxis = -1; public int leftStickXAxis = -1;
public int leftStickYAxis = -1; public int leftStickYAxis = -1;
public float leftStickDeadzoneRadius; public float leftStickDeadzoneRadius;
public int rightStickXAxis = -1; public int rightStickXAxis = -1;
public int rightStickYAxis = -1; public int rightStickYAxis = -1;
public float rightStickDeadzoneRadius; public float rightStickDeadzoneRadius;
public int leftTriggerAxis = -1; public int leftTriggerAxis = -1;
public int rightTriggerAxis = -1; public int rightTriggerAxis = -1;
public boolean triggersIdleNegative; public boolean triggersIdleNegative;
public float triggerDeadzone; public float triggerDeadzone;
public int hatXAxis = -1; public int hatXAxis = -1;
public int hatYAxis = -1; public int hatYAxis = -1;
public boolean isDualShock4; public boolean isDualShock4;
public boolean isXboxController; public boolean isXboxController;
public boolean backIsStart; public boolean backIsStart;
public boolean modeIsSelect; public boolean modeIsSelect;
public boolean isRemote; public boolean isRemote;
@ -822,5 +822,5 @@ public class ControllerHandler implements InputManager.InputDeviceListener {
public long lastRbUpTime = 0; public long lastRbUpTime = 0;
public long startDownTime = 0; public long startDownTime = 0;
} }
} }

View File

@ -23,8 +23,8 @@ public class KeyboardTranslator extends KeycodeTranslator {
public static final int VK_Z = 90; public static final int VK_Z = 90;
public static final int VK_ALT = 18; public static final int VK_ALT = 18;
public static final int VK_NUMPAD0 = 96; public static final int VK_NUMPAD0 = 96;
public static final int VK_BACK_SLASH = 92; public static final int VK_BACK_SLASH = 92;
public static final int VK_CAPS_LOCK = 20; public static final int VK_CAPS_LOCK = 20;
public static final int VK_CLEAR = 12; public static final int VK_CLEAR = 12;
public static final int VK_COMMA = 44; public static final int VK_COMMA = 44;
public static final int VK_CONTROL = 17; public static final int VK_CONTROL = 17;

View File

@ -4,95 +4,95 @@ import com.limelight.nvstream.NvConnection;
import com.limelight.nvstream.input.MouseButtonPacket; import com.limelight.nvstream.input.MouseButtonPacket;
public class TouchContext { public class TouchContext {
private int lastTouchX = 0; private int lastTouchX = 0;
private int lastTouchY = 0; private int lastTouchY = 0;
private int originalTouchX = 0; private int originalTouchX = 0;
private int originalTouchY = 0; private int originalTouchY = 0;
private long originalTouchTime = 0; private long originalTouchTime = 0;
private boolean cancelled; private boolean cancelled;
private final NvConnection conn; private final NvConnection conn;
private final int actionIndex; private final int actionIndex;
private final double xFactor; private final double xFactor;
private final double yFactor; private final double yFactor;
private static final int TAP_MOVEMENT_THRESHOLD = 10; private static final int TAP_MOVEMENT_THRESHOLD = 10;
private static final int TAP_TIME_THRESHOLD = 250; private static final int TAP_TIME_THRESHOLD = 250;
public TouchContext(NvConnection conn, int actionIndex, double xFactor, double yFactor) public TouchContext(NvConnection conn, int actionIndex, double xFactor, double yFactor)
{ {
this.conn = conn; this.conn = conn;
this.actionIndex = actionIndex; this.actionIndex = actionIndex;
this.xFactor = xFactor; this.xFactor = xFactor;
this.yFactor = yFactor; this.yFactor = yFactor;
} }
public int getActionIndex() public int getActionIndex()
{ {
return actionIndex; return actionIndex;
} }
private boolean isTap() private boolean isTap()
{ {
int xDelta = Math.abs(lastTouchX - originalTouchX); int xDelta = Math.abs(lastTouchX - originalTouchX);
int yDelta = Math.abs(lastTouchY - originalTouchY); int yDelta = Math.abs(lastTouchY - originalTouchY);
long timeDelta = System.currentTimeMillis() - originalTouchTime; long timeDelta = System.currentTimeMillis() - originalTouchTime;
return xDelta <= TAP_MOVEMENT_THRESHOLD && return xDelta <= TAP_MOVEMENT_THRESHOLD &&
yDelta <= TAP_MOVEMENT_THRESHOLD && yDelta <= TAP_MOVEMENT_THRESHOLD &&
timeDelta <= TAP_TIME_THRESHOLD; timeDelta <= TAP_TIME_THRESHOLD;
} }
private byte getMouseButtonIndex() private byte getMouseButtonIndex()
{ {
if (actionIndex == 1) { if (actionIndex == 1) {
return MouseButtonPacket.BUTTON_RIGHT; return MouseButtonPacket.BUTTON_RIGHT;
} }
else { else {
return MouseButtonPacket.BUTTON_LEFT; return MouseButtonPacket.BUTTON_LEFT;
} }
} }
public boolean touchDownEvent(int eventX, int eventY) public boolean touchDownEvent(int eventX, int eventY)
{ {
originalTouchX = lastTouchX = eventX; originalTouchX = lastTouchX = eventX;
originalTouchY = lastTouchY = eventY; originalTouchY = lastTouchY = eventY;
originalTouchTime = System.currentTimeMillis(); originalTouchTime = System.currentTimeMillis();
cancelled = false; cancelled = false;
return true; return true;
} }
public void touchUpEvent(int eventX, int eventY) public void touchUpEvent(int eventX, int eventY)
{ {
if (cancelled) { if (cancelled) {
return; return;
} }
if (isTap()) if (isTap())
{ {
byte buttonIndex = getMouseButtonIndex(); byte buttonIndex = getMouseButtonIndex();
// Lower the mouse button // Lower the mouse button
conn.sendMouseButtonDown(buttonIndex); conn.sendMouseButtonDown(buttonIndex);
// We need to sleep a bit here because some games // We need to sleep a bit here because some games
// do input detection by polling // do input detection by polling
try { try {
Thread.sleep(100); Thread.sleep(100);
} catch (InterruptedException ignored) {} } catch (InterruptedException ignored) {}
// Raise the mouse button // Raise the mouse button
conn.sendMouseButtonUp(buttonIndex); conn.sendMouseButtonUp(buttonIndex);
} }
} }
public boolean touchMoveEvent(int eventX, int eventY) public boolean touchMoveEvent(int eventX, int eventY)
{ {
if (eventX != lastTouchX || eventY != lastTouchY) if (eventX != lastTouchX || eventY != lastTouchY)
{ {
// We only send moves for the primary touch point // We only send moves for the primary touch point
if (actionIndex == 0) { if (actionIndex == 0) {
int deltaX = eventX - lastTouchX; int deltaX = eventX - lastTouchX;
int deltaY = eventY - lastTouchY; int deltaY = eventY - lastTouchY;
@ -100,15 +100,15 @@ public class TouchContext {
deltaX = (int)Math.round((double)deltaX * xFactor); deltaX = (int)Math.round((double)deltaX * xFactor);
deltaY = (int)Math.round((double)deltaY * yFactor); deltaY = (int)Math.round((double)deltaY * yFactor);
conn.sendMouseMove((short)deltaX, (short)deltaY); conn.sendMouseMove((short)deltaX, (short)deltaY);
} }
lastTouchX = eventX; lastTouchX = eventX;
lastTouchY = eventY; lastTouchY = eventY;
} }
return true; return true;
} }
public void cancelTouch() { public void cancelTouch() {
cancelled = true; cancelled = true;

View File

@ -1,41 +1,41 @@
package com.limelight.binding.input.evdev; package com.limelight.binding.input.evdev;
public class EvdevEvent { public class EvdevEvent {
public static final int EVDEV_MIN_EVENT_SIZE = 16; public static final int EVDEV_MIN_EVENT_SIZE = 16;
public static final int EVDEV_MAX_EVENT_SIZE = 24; public static final int EVDEV_MAX_EVENT_SIZE = 24;
/* Event types */ /* Event types */
public static final short EV_SYN = 0x00; public static final short EV_SYN = 0x00;
public static final short EV_KEY = 0x01; public static final short EV_KEY = 0x01;
public static final short EV_REL = 0x02; public static final short EV_REL = 0x02;
public static final short EV_MSC = 0x04; public static final short EV_MSC = 0x04;
/* Relative axes */ /* Relative axes */
public static final short REL_X = 0x00; public static final short REL_X = 0x00;
public static final short REL_Y = 0x01; public static final short REL_Y = 0x01;
public static final short REL_WHEEL = 0x08; public static final short REL_WHEEL = 0x08;
/* Buttons */ /* Buttons */
public static final short BTN_LEFT = 0x110; public static final short BTN_LEFT = 0x110;
public static final short BTN_RIGHT = 0x111; public static final short BTN_RIGHT = 0x111;
public static final short BTN_MIDDLE = 0x112; public static final short BTN_MIDDLE = 0x112;
public static final short BTN_SIDE = 0x113; public static final short BTN_SIDE = 0x113;
public static final short BTN_EXTRA = 0x114; public static final short BTN_EXTRA = 0x114;
public static final short BTN_FORWARD = 0x115; public static final short BTN_FORWARD = 0x115;
public static final short BTN_BACK = 0x116; public static final short BTN_BACK = 0x116;
public static final short BTN_TASK = 0x117; public static final short BTN_TASK = 0x117;
public static final short BTN_GAMEPAD = 0x130; public static final short BTN_GAMEPAD = 0x130;
/* Keys */ /* Keys */
public static final short KEY_Q = 16; public static final short KEY_Q = 16;
public final short type; public final short type;
public final short code; public final short code;
public final int value; public final int value;
public EvdevEvent(short type, short code, int value) { public EvdevEvent(short type, short code, int value) {
this.type = type; this.type = type;
this.code = code; this.code = code;
this.value = value; this.value = value;
} }
} }

View File

@ -7,161 +7,161 @@ import com.limelight.LimeLog;
public class EvdevHandler { public class EvdevHandler {
private final String absolutePath; private final String absolutePath;
private final EvdevListener listener; private final EvdevListener listener;
private boolean shutdown = false; private boolean shutdown = false;
private int fd = -1; private int fd = -1;
private final Thread handlerThread = new Thread() { private final Thread handlerThread = new Thread() {
@Override @Override
public void run() { public void run() {
// All the finally blocks here make this code look like a mess // All the finally blocks here make this code look like a mess
// but it's important that we get this right to avoid causing // but it's important that we get this right to avoid causing
// system-wide input problems. // system-wide input problems.
// Open the /dev/input/eventX file // Open the /dev/input/eventX file
fd = EvdevReader.open(absolutePath); fd = EvdevReader.open(absolutePath);
if (fd == -1) { if (fd == -1) {
LimeLog.warning("Unable to open "+absolutePath); LimeLog.warning("Unable to open "+absolutePath);
return; return;
} }
try { try {
// Check if it's a mouse or keyboard, but not a gamepad // Check if it's a mouse or keyboard, but not a gamepad
if ((!EvdevReader.isMouse(fd) && !EvdevReader.isAlphaKeyboard(fd)) || if ((!EvdevReader.isMouse(fd) && !EvdevReader.isAlphaKeyboard(fd)) ||
EvdevReader.isGamepad(fd)) { EvdevReader.isGamepad(fd)) {
// We only handle keyboards and mice // We only handle keyboards and mice
return; return;
} }
// Grab it for ourselves // Grab it for ourselves
if (!EvdevReader.grab(fd)) { if (!EvdevReader.grab(fd)) {
LimeLog.warning("Unable to grab "+absolutePath); LimeLog.warning("Unable to grab "+absolutePath);
return; return;
} }
LimeLog.info("Grabbed device for raw keyboard/mouse input: "+absolutePath); LimeLog.info("Grabbed device for raw keyboard/mouse input: "+absolutePath);
ByteBuffer buffer = ByteBuffer.allocate(EvdevEvent.EVDEV_MAX_EVENT_SIZE).order(ByteOrder.nativeOrder()); ByteBuffer buffer = ByteBuffer.allocate(EvdevEvent.EVDEV_MAX_EVENT_SIZE).order(ByteOrder.nativeOrder());
try { try {
int deltaX = 0; int deltaX = 0;
int deltaY = 0; int deltaY = 0;
byte deltaScroll = 0; byte deltaScroll = 0;
while (!isInterrupted() && !shutdown) { while (!isInterrupted() && !shutdown) {
EvdevEvent event = EvdevReader.read(fd, buffer); EvdevEvent event = EvdevReader.read(fd, buffer);
if (event == null) { if (event == null) {
return; return;
} }
switch (event.type) switch (event.type)
{ {
case EvdevEvent.EV_SYN: case EvdevEvent.EV_SYN:
if (deltaX != 0 || deltaY != 0) { if (deltaX != 0 || deltaY != 0) {
listener.mouseMove(deltaX, deltaY); listener.mouseMove(deltaX, deltaY);
deltaX = deltaY = 0; deltaX = deltaY = 0;
} }
if (deltaScroll != 0) { if (deltaScroll != 0) {
listener.mouseScroll(deltaScroll); listener.mouseScroll(deltaScroll);
deltaScroll = 0; deltaScroll = 0;
} }
break; break;
case EvdevEvent.EV_REL: case EvdevEvent.EV_REL:
switch (event.code) switch (event.code)
{ {
case EvdevEvent.REL_X: case EvdevEvent.REL_X:
deltaX = event.value; deltaX = event.value;
break; break;
case EvdevEvent.REL_Y: case EvdevEvent.REL_Y:
deltaY = event.value; deltaY = event.value;
break; break;
case EvdevEvent.REL_WHEEL: case EvdevEvent.REL_WHEEL:
deltaScroll = (byte) event.value; deltaScroll = (byte) event.value;
break; break;
} }
break; break;
case EvdevEvent.EV_KEY: case EvdevEvent.EV_KEY:
switch (event.code) switch (event.code)
{ {
case EvdevEvent.BTN_LEFT: case EvdevEvent.BTN_LEFT:
listener.mouseButtonEvent(EvdevListener.BUTTON_LEFT, listener.mouseButtonEvent(EvdevListener.BUTTON_LEFT,
event.value != 0); event.value != 0);
break; break;
case EvdevEvent.BTN_MIDDLE: case EvdevEvent.BTN_MIDDLE:
listener.mouseButtonEvent(EvdevListener.BUTTON_MIDDLE, listener.mouseButtonEvent(EvdevListener.BUTTON_MIDDLE,
event.value != 0); event.value != 0);
break; break;
case EvdevEvent.BTN_RIGHT: case EvdevEvent.BTN_RIGHT:
listener.mouseButtonEvent(EvdevListener.BUTTON_RIGHT, listener.mouseButtonEvent(EvdevListener.BUTTON_RIGHT,
event.value != 0); event.value != 0);
break; break;
case EvdevEvent.BTN_SIDE: case EvdevEvent.BTN_SIDE:
case EvdevEvent.BTN_EXTRA: case EvdevEvent.BTN_EXTRA:
case EvdevEvent.BTN_FORWARD: case EvdevEvent.BTN_FORWARD:
case EvdevEvent.BTN_BACK: case EvdevEvent.BTN_BACK:
case EvdevEvent.BTN_TASK: case EvdevEvent.BTN_TASK:
// Other unhandled mouse buttons // Other unhandled mouse buttons
break; break;
default: default:
// We got some unrecognized button. This means // We got some unrecognized button. This means
// someone is trying to use the other device in this // someone is trying to use the other device in this
// "combination" input device. We'll try to handle // "combination" input device. We'll try to handle
// it via keyboard, but we're not going to disconnect // it via keyboard, but we're not going to disconnect
// if we can't // if we can't
short keyCode = EvdevTranslator.translateEvdevKeyCode(event.code); short keyCode = EvdevTranslator.translateEvdevKeyCode(event.code);
if (keyCode != 0) { if (keyCode != 0) {
listener.keyboardEvent(event.value != 0, keyCode); listener.keyboardEvent(event.value != 0, keyCode);
} }
break; break;
} }
break; break;
case EvdevEvent.EV_MSC: case EvdevEvent.EV_MSC:
break; break;
} }
} }
} finally { } finally {
// Release our grab // Release our grab
EvdevReader.ungrab(fd); EvdevReader.ungrab(fd);
} }
} finally { } finally {
// Close the file // Close the file
EvdevReader.close(fd); EvdevReader.close(fd);
} }
} }
}; };
public EvdevHandler(String absolutePath, EvdevListener listener) { public EvdevHandler(String absolutePath, EvdevListener listener) {
this.absolutePath = absolutePath; this.absolutePath = absolutePath;
this.listener = listener; this.listener = listener;
} }
public void start() { public void start() {
handlerThread.start(); handlerThread.start();
} }
public void stop() { public void stop() {
// Close the fd. It doesn't matter if this races // Close the fd. It doesn't matter if this races
// with the handler thread. We'll close this out from // with the handler thread. We'll close this out from
// under the thread to wake it up // under the thread to wake it up
if (fd != -1) { if (fd != -1) {
EvdevReader.close(fd); EvdevReader.close(fd);
} }
shutdown = true; shutdown = true;
handlerThread.interrupt(); handlerThread.interrupt();
try { try {
handlerThread.join(); handlerThread.join();
} catch (InterruptedException ignored) {} } catch (InterruptedException ignored) {}
} }
public void notifyDeleted() { public void notifyDeleted() {
stop(); stop();
} }
} }

View File

@ -1,12 +1,12 @@
package com.limelight.binding.input.evdev; package com.limelight.binding.input.evdev;
public interface EvdevListener { public interface EvdevListener {
public static final int BUTTON_LEFT = 1; public static final int BUTTON_LEFT = 1;
public static final int BUTTON_MIDDLE = 2; public static final int BUTTON_MIDDLE = 2;
public static final int BUTTON_RIGHT = 3; public static final int BUTTON_RIGHT = 3;
public void mouseMove(int deltaX, int deltaY); public void mouseMove(int deltaX, int deltaY);
public void mouseButtonEvent(int buttonId, boolean down); public void mouseButtonEvent(int buttonId, boolean down);
public void mouseScroll(byte amount); public void mouseScroll(byte amount);
public void keyboardEvent(boolean buttonDown, short keyCode); public void keyboardEvent(boolean buttonDown, short keyCode);
} }

View File

@ -8,9 +8,9 @@ import java.util.Locale;
import com.limelight.LimeLog; import com.limelight.LimeLog;
public class EvdevReader { public class EvdevReader {
static { static {
System.loadLibrary("evdev_reader"); System.loadLibrary("evdev_reader");
} }
public static void patchSeLinuxPolicies() { public static void patchSeLinuxPolicies() {
// //
@ -31,75 +31,75 @@ public class EvdevReader {
} }
} }
// Requires root to chmod /dev/input/eventX // Requires root to chmod /dev/input/eventX
public static void setPermissions(String[] files, int octalPermissions) { public static void setPermissions(String[] files, int octalPermissions) {
EvdevShell shell = EvdevShell.getInstance(); EvdevShell shell = EvdevShell.getInstance();
for (String file : files) { for (String file : files) {
shell.runCommand(String.format((Locale)null, "chmod %o %s", octalPermissions, file)); shell.runCommand(String.format((Locale)null, "chmod %o %s", octalPermissions, file));
} }
} }
// Returns the fd to be passed to other function or -1 on error // Returns the fd to be passed to other function or -1 on error
public static native int open(String fileName); public static native int open(String fileName);
// Prevent other apps (including Android itself) from using the device while "grabbed" // Prevent other apps (including Android itself) from using the device while "grabbed"
public static native boolean grab(int fd); public static native boolean grab(int fd);
public static native boolean ungrab(int fd); public static native boolean ungrab(int fd);
// Used for checking device capabilities // Used for checking device capabilities
public static native boolean hasRelAxis(int fd, short axis); public static native boolean hasRelAxis(int fd, short axis);
public static native boolean hasAbsAxis(int fd, short axis); public static native boolean hasAbsAxis(int fd, short axis);
public static native boolean hasKey(int fd, short key); public static native boolean hasKey(int fd, short key);
public static boolean isMouse(int fd) { public static boolean isMouse(int fd) {
// This is the same check that Android does in EventHub.cpp // This is the same check that Android does in EventHub.cpp
return hasRelAxis(fd, EvdevEvent.REL_X) && return hasRelAxis(fd, EvdevEvent.REL_X) &&
hasRelAxis(fd, EvdevEvent.REL_Y) && hasRelAxis(fd, EvdevEvent.REL_Y) &&
hasKey(fd, EvdevEvent.BTN_LEFT); hasKey(fd, EvdevEvent.BTN_LEFT);
} }
public static boolean isAlphaKeyboard(int fd) { public static boolean isAlphaKeyboard(int fd) {
// This is the same check that Android does in EventHub.cpp // This is the same check that Android does in EventHub.cpp
return hasKey(fd, EvdevEvent.KEY_Q); return hasKey(fd, EvdevEvent.KEY_Q);
} }
public static boolean isGamepad(int fd) { public static boolean isGamepad(int fd) {
return hasKey(fd, EvdevEvent.BTN_GAMEPAD); return hasKey(fd, EvdevEvent.BTN_GAMEPAD);
} }
// Returns the bytes read or -1 on error // Returns the bytes read or -1 on error
private static native int read(int fd, byte[] buffer); private static native int read(int fd, byte[] buffer);
// Takes a byte buffer to use to read the output into. // Takes a byte buffer to use to read the output into.
// This buffer MUST be in native byte order and at least // This buffer MUST be in native byte order and at least
// EVDEV_MAX_EVENT_SIZE bytes long. // EVDEV_MAX_EVENT_SIZE bytes long.
public static EvdevEvent read(int fd, ByteBuffer buffer) { public static EvdevEvent read(int fd, ByteBuffer buffer) {
int bytesRead = read(fd, buffer.array()); int bytesRead = read(fd, buffer.array());
if (bytesRead < 0) { if (bytesRead < 0) {
LimeLog.warning("Failed to read: "+bytesRead); LimeLog.warning("Failed to read: "+bytesRead);
return null; return null;
} }
else if (bytesRead < EvdevEvent.EVDEV_MIN_EVENT_SIZE) { else if (bytesRead < EvdevEvent.EVDEV_MIN_EVENT_SIZE) {
LimeLog.warning("Short read: "+bytesRead); LimeLog.warning("Short read: "+bytesRead);
return null; return null;
} }
buffer.limit(bytesRead); buffer.limit(bytesRead);
buffer.rewind(); buffer.rewind();
// Throw away the time stamp // Throw away the time stamp
if (bytesRead == EvdevEvent.EVDEV_MAX_EVENT_SIZE) { if (bytesRead == EvdevEvent.EVDEV_MAX_EVENT_SIZE) {
buffer.getLong(); buffer.getLong();
buffer.getLong(); buffer.getLong();
} else { } else {
buffer.getInt(); buffer.getInt();
buffer.getInt(); buffer.getInt();
} }
return new EvdevEvent(buffer.getShort(), buffer.getShort(), buffer.getInt()); return new EvdevEvent(buffer.getShort(), buffer.getShort(), buffer.getInt());
} }
// Closes the fd from open() // Closes the fd from open()
public static native int close(int fd); public static native int close(int fd);
} }

View File

@ -4,136 +4,136 @@ import android.view.KeyEvent;
public class EvdevTranslator { public class EvdevTranslator {
private static final short[] EVDEV_KEY_CODES = { private static final short[] EVDEV_KEY_CODES = {
0, //KeyEvent.VK_RESERVED 0, //KeyEvent.VK_RESERVED
KeyEvent.KEYCODE_ESCAPE, KeyEvent.KEYCODE_ESCAPE,
KeyEvent.KEYCODE_1, KeyEvent.KEYCODE_1,
KeyEvent.KEYCODE_2, KeyEvent.KEYCODE_2,
KeyEvent.KEYCODE_3, KeyEvent.KEYCODE_3,
KeyEvent.KEYCODE_4, KeyEvent.KEYCODE_4,
KeyEvent.KEYCODE_5, KeyEvent.KEYCODE_5,
KeyEvent.KEYCODE_6, KeyEvent.KEYCODE_6,
KeyEvent.KEYCODE_7, KeyEvent.KEYCODE_7,
KeyEvent.KEYCODE_8, KeyEvent.KEYCODE_8,
KeyEvent.KEYCODE_9, KeyEvent.KEYCODE_9,
KeyEvent.KEYCODE_0, KeyEvent.KEYCODE_0,
KeyEvent.KEYCODE_MINUS, KeyEvent.KEYCODE_MINUS,
KeyEvent.KEYCODE_EQUALS, KeyEvent.KEYCODE_EQUALS,
KeyEvent.KEYCODE_DEL, KeyEvent.KEYCODE_DEL,
KeyEvent.KEYCODE_TAB, KeyEvent.KEYCODE_TAB,
KeyEvent.KEYCODE_Q, KeyEvent.KEYCODE_Q,
KeyEvent.KEYCODE_W, KeyEvent.KEYCODE_W,
KeyEvent.KEYCODE_E, KeyEvent.KEYCODE_E,
KeyEvent.KEYCODE_R, KeyEvent.KEYCODE_R,
KeyEvent.KEYCODE_T, KeyEvent.KEYCODE_T,
KeyEvent.KEYCODE_Y, KeyEvent.KEYCODE_Y,
KeyEvent.KEYCODE_U, KeyEvent.KEYCODE_U,
KeyEvent.KEYCODE_I, KeyEvent.KEYCODE_I,
KeyEvent.KEYCODE_O, KeyEvent.KEYCODE_O,
KeyEvent.KEYCODE_P, KeyEvent.KEYCODE_P,
KeyEvent.KEYCODE_LEFT_BRACKET, KeyEvent.KEYCODE_LEFT_BRACKET,
KeyEvent.KEYCODE_RIGHT_BRACKET, KeyEvent.KEYCODE_RIGHT_BRACKET,
KeyEvent.KEYCODE_ENTER, KeyEvent.KEYCODE_ENTER,
KeyEvent.KEYCODE_CTRL_LEFT, KeyEvent.KEYCODE_CTRL_LEFT,
KeyEvent.KEYCODE_A, KeyEvent.KEYCODE_A,
KeyEvent.KEYCODE_S, KeyEvent.KEYCODE_S,
KeyEvent.KEYCODE_D, KeyEvent.KEYCODE_D,
KeyEvent.KEYCODE_F, KeyEvent.KEYCODE_F,
KeyEvent.KEYCODE_G, KeyEvent.KEYCODE_G,
KeyEvent.KEYCODE_H, KeyEvent.KEYCODE_H,
KeyEvent.KEYCODE_J, KeyEvent.KEYCODE_J,
KeyEvent.KEYCODE_K, KeyEvent.KEYCODE_K,
KeyEvent.KEYCODE_L, KeyEvent.KEYCODE_L,
KeyEvent.KEYCODE_SEMICOLON, KeyEvent.KEYCODE_SEMICOLON,
KeyEvent.KEYCODE_APOSTROPHE, KeyEvent.KEYCODE_APOSTROPHE,
KeyEvent.KEYCODE_GRAVE, KeyEvent.KEYCODE_GRAVE,
KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_SHIFT_LEFT,
KeyEvent.KEYCODE_BACKSLASH, KeyEvent.KEYCODE_BACKSLASH,
KeyEvent.KEYCODE_Z, KeyEvent.KEYCODE_Z,
KeyEvent.KEYCODE_X, KeyEvent.KEYCODE_X,
KeyEvent.KEYCODE_C, KeyEvent.KEYCODE_C,
KeyEvent.KEYCODE_V, KeyEvent.KEYCODE_V,
KeyEvent.KEYCODE_B, KeyEvent.KEYCODE_B,
KeyEvent.KEYCODE_N, KeyEvent.KEYCODE_N,
KeyEvent.KEYCODE_M, KeyEvent.KEYCODE_M,
KeyEvent.KEYCODE_COMMA, KeyEvent.KEYCODE_COMMA,
KeyEvent.KEYCODE_PERIOD, KeyEvent.KEYCODE_PERIOD,
KeyEvent.KEYCODE_SLASH, KeyEvent.KEYCODE_SLASH,
KeyEvent.KEYCODE_SHIFT_RIGHT, KeyEvent.KEYCODE_SHIFT_RIGHT,
KeyEvent.KEYCODE_NUMPAD_MULTIPLY, KeyEvent.KEYCODE_NUMPAD_MULTIPLY,
KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_ALT_LEFT,
KeyEvent.KEYCODE_SPACE, KeyEvent.KEYCODE_SPACE,
KeyEvent.KEYCODE_CAPS_LOCK, KeyEvent.KEYCODE_CAPS_LOCK,
KeyEvent.KEYCODE_F1, KeyEvent.KEYCODE_F1,
KeyEvent.KEYCODE_F2, KeyEvent.KEYCODE_F2,
KeyEvent.KEYCODE_F3, KeyEvent.KEYCODE_F3,
KeyEvent.KEYCODE_F4, KeyEvent.KEYCODE_F4,
KeyEvent.KEYCODE_F5, KeyEvent.KEYCODE_F5,
KeyEvent.KEYCODE_F6, KeyEvent.KEYCODE_F6,
KeyEvent.KEYCODE_F7, KeyEvent.KEYCODE_F7,
KeyEvent.KEYCODE_F8, KeyEvent.KEYCODE_F8,
KeyEvent.KEYCODE_F9, KeyEvent.KEYCODE_F9,
KeyEvent.KEYCODE_F10, KeyEvent.KEYCODE_F10,
KeyEvent.KEYCODE_NUM_LOCK, KeyEvent.KEYCODE_NUM_LOCK,
KeyEvent.KEYCODE_SCROLL_LOCK, KeyEvent.KEYCODE_SCROLL_LOCK,
KeyEvent.KEYCODE_NUMPAD_7, KeyEvent.KEYCODE_NUMPAD_7,
KeyEvent.KEYCODE_NUMPAD_8, KeyEvent.KEYCODE_NUMPAD_8,
KeyEvent.KEYCODE_NUMPAD_9, KeyEvent.KEYCODE_NUMPAD_9,
KeyEvent.KEYCODE_NUMPAD_SUBTRACT, KeyEvent.KEYCODE_NUMPAD_SUBTRACT,
KeyEvent.KEYCODE_NUMPAD_4, KeyEvent.KEYCODE_NUMPAD_4,
KeyEvent.KEYCODE_NUMPAD_5, KeyEvent.KEYCODE_NUMPAD_5,
KeyEvent.KEYCODE_NUMPAD_6, KeyEvent.KEYCODE_NUMPAD_6,
KeyEvent.KEYCODE_NUMPAD_ADD, KeyEvent.KEYCODE_NUMPAD_ADD,
KeyEvent.KEYCODE_NUMPAD_1, KeyEvent.KEYCODE_NUMPAD_1,
KeyEvent.KEYCODE_NUMPAD_2, KeyEvent.KEYCODE_NUMPAD_2,
KeyEvent.KEYCODE_NUMPAD_3, KeyEvent.KEYCODE_NUMPAD_3,
KeyEvent.KEYCODE_NUMPAD_0, KeyEvent.KEYCODE_NUMPAD_0,
KeyEvent.KEYCODE_NUMPAD_DOT, KeyEvent.KEYCODE_NUMPAD_DOT,
0, 0,
0, //KeyEvent.VK_ZENKAKUHANKAKU, 0, //KeyEvent.VK_ZENKAKUHANKAKU,
0, //KeyEvent.VK_102ND, 0, //KeyEvent.VK_102ND,
KeyEvent.KEYCODE_F11, KeyEvent.KEYCODE_F11,
KeyEvent.KEYCODE_F12, KeyEvent.KEYCODE_F12,
0, //KeyEvent.VK_RO, 0, //KeyEvent.VK_RO,
0, //KeyEvent.VK_KATAKANA, 0, //KeyEvent.VK_KATAKANA,
0, //KeyEvent.VK_HIRAGANA, 0, //KeyEvent.VK_HIRAGANA,
0, //KeyEvent.VK_HENKAN, 0, //KeyEvent.VK_HENKAN,
0, //KeyEvent.VK_KATAKANAHIRAGANA, 0, //KeyEvent.VK_KATAKANAHIRAGANA,
0, //KeyEvent.VK_MUHENKAN, 0, //KeyEvent.VK_MUHENKAN,
0, //KeyEvent.VK_KPJPCOMMA, 0, //KeyEvent.VK_KPJPCOMMA,
KeyEvent.KEYCODE_NUMPAD_ENTER, KeyEvent.KEYCODE_NUMPAD_ENTER,
KeyEvent.KEYCODE_CTRL_RIGHT, KeyEvent.KEYCODE_CTRL_RIGHT,
KeyEvent.KEYCODE_NUMPAD_DIVIDE, KeyEvent.KEYCODE_NUMPAD_DIVIDE,
KeyEvent.KEYCODE_SYSRQ, KeyEvent.KEYCODE_SYSRQ,
KeyEvent.KEYCODE_ALT_RIGHT, KeyEvent.KEYCODE_ALT_RIGHT,
0, //KeyEvent.VK_LINEFEED, 0, //KeyEvent.VK_LINEFEED,
KeyEvent.KEYCODE_HOME, KeyEvent.KEYCODE_HOME,
KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_UP,
KeyEvent.KEYCODE_PAGE_UP, KeyEvent.KEYCODE_PAGE_UP,
KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_LEFT,
KeyEvent.KEYCODE_DPAD_RIGHT, KeyEvent.KEYCODE_DPAD_RIGHT,
KeyEvent.KEYCODE_MOVE_END, KeyEvent.KEYCODE_MOVE_END,
KeyEvent.KEYCODE_DPAD_DOWN, KeyEvent.KEYCODE_DPAD_DOWN,
KeyEvent.KEYCODE_PAGE_DOWN, KeyEvent.KEYCODE_PAGE_DOWN,
KeyEvent.KEYCODE_INSERT, KeyEvent.KEYCODE_INSERT,
KeyEvent.KEYCODE_FORWARD_DEL, KeyEvent.KEYCODE_FORWARD_DEL,
0, //KeyEvent.VK_MACRO, 0, //KeyEvent.VK_MACRO,
0, //KeyEvent.VK_MUTE, 0, //KeyEvent.VK_MUTE,
0, //KeyEvent.VK_VOLUMEDOWN, 0, //KeyEvent.VK_VOLUMEDOWN,
0, //KeyEvent.VK_VOLUMEUP, 0, //KeyEvent.VK_VOLUMEUP,
0, //KeyEvent.VK_POWER, /* SC System Power Down */ 0, //KeyEvent.VK_POWER, /* SC System Power Down */
KeyEvent.KEYCODE_NUMPAD_EQUALS, KeyEvent.KEYCODE_NUMPAD_EQUALS,
0, //KeyEvent.VK_KPPLUSMINUS, 0, //KeyEvent.VK_KPPLUSMINUS,
KeyEvent.KEYCODE_BREAK, KeyEvent.KEYCODE_BREAK,
0, //KeyEvent.VK_SCALE, /* AL Compiz Scale (Expose) */ 0, //KeyEvent.VK_SCALE, /* AL Compiz Scale (Expose) */
}; };
public static short translateEvdevKeyCode(short evdevKeyCode) { public static short translateEvdevKeyCode(short evdevKeyCode) {
if (evdevKeyCode < EVDEV_KEY_CODES.length) { if (evdevKeyCode < EVDEV_KEY_CODES.length) {
return EVDEV_KEY_CODES[evdevKeyCode]; return EVDEV_KEY_CODES[evdevKeyCode];
} }
return 0; return 0;
} }
} }

View File

@ -10,115 +10,115 @@ import android.os.FileObserver;
@SuppressWarnings("ALL") @SuppressWarnings("ALL")
public class EvdevWatcher { public class EvdevWatcher {
private static final String PATH = "/dev/input"; private static final String PATH = "/dev/input";
private static final String REQUIRED_FILE_PREFIX = "event"; private static final String REQUIRED_FILE_PREFIX = "event";
private final HashMap<String, EvdevHandler> handlers = new HashMap<String, EvdevHandler>(); private final HashMap<String, EvdevHandler> handlers = new HashMap<String, EvdevHandler>();
private boolean shutdown = false; private boolean shutdown = false;
private boolean init = false; private boolean init = false;
private boolean ungrabbed = false; private boolean ungrabbed = false;
private EvdevListener listener; private EvdevListener listener;
private Thread startThread; private Thread startThread;
private static boolean patchedSeLinuxPolicies = false; private static boolean patchedSeLinuxPolicies = false;
private FileObserver observer = new FileObserver(PATH, FileObserver.CREATE | FileObserver.DELETE) { private FileObserver observer = new FileObserver(PATH, FileObserver.CREATE | FileObserver.DELETE) {
@Override @Override
public void onEvent(int event, String fileName) { public void onEvent(int event, String fileName) {
if (fileName == null) { if (fileName == null) {
return; return;
} }
if (!fileName.startsWith(REQUIRED_FILE_PREFIX)) { if (!fileName.startsWith(REQUIRED_FILE_PREFIX)) {
return; return;
} }
synchronized (handlers) { synchronized (handlers) {
if (shutdown) { if (shutdown) {
return; return;
} }
if ((event & FileObserver.CREATE) != 0) { if ((event & FileObserver.CREATE) != 0) {
LimeLog.info("Starting evdev handler for "+fileName); LimeLog.info("Starting evdev handler for "+fileName);
if (!init) { if (!init) {
// If this a real new device, update permissions again so we can read it // If this a real new device, update permissions again so we can read it
EvdevReader.setPermissions(new String[]{PATH + "/" + fileName}, 0666); EvdevReader.setPermissions(new String[]{PATH + "/" + fileName}, 0666);
} }
EvdevHandler handler = new EvdevHandler(PATH + "/" + fileName, listener); EvdevHandler handler = new EvdevHandler(PATH + "/" + fileName, listener);
// If we're ungrabbed now, don't start the handler // If we're ungrabbed now, don't start the handler
if (!ungrabbed) { if (!ungrabbed) {
handler.start(); handler.start();
} }
handlers.put(fileName, handler); handlers.put(fileName, handler);
} }
if ((event & FileObserver.DELETE) != 0) { if ((event & FileObserver.DELETE) != 0) {
LimeLog.info("Halting evdev handler for "+fileName); LimeLog.info("Halting evdev handler for "+fileName);
EvdevHandler handler = handlers.remove(fileName); EvdevHandler handler = handlers.remove(fileName);
if (handler != null) { if (handler != null) {
handler.notifyDeleted(); handler.notifyDeleted();
} }
} }
} }
} }
}; };
public EvdevWatcher(EvdevListener listener) { public EvdevWatcher(EvdevListener listener) {
this.listener = listener; this.listener = listener;
} }
private File[] rundownWithPermissionsChange(int newPermissions) { private File[] rundownWithPermissionsChange(int newPermissions) {
// Rundown existing files // Rundown existing files
File devInputDir = new File(PATH); File devInputDir = new File(PATH);
File[] files = devInputDir.listFiles(); File[] files = devInputDir.listFiles();
if (files == null) { if (files == null) {
return new File[0]; return new File[0];
} }
// Set desired permissions // Set desired permissions
String[] filePaths = new String[files.length]; String[] filePaths = new String[files.length];
for (int i = 0; i < files.length; i++) { for (int i = 0; i < files.length; i++) {
filePaths[i] = files[i].getAbsolutePath(); filePaths[i] = files[i].getAbsolutePath();
} }
EvdevReader.setPermissions(filePaths, newPermissions); EvdevReader.setPermissions(filePaths, newPermissions);
return files; return files;
} }
public void ungrabAll() { public void ungrabAll() {
synchronized (handlers) { synchronized (handlers) {
// Note that we're ungrabbed for now // Note that we're ungrabbed for now
ungrabbed = true; ungrabbed = true;
// Stop all handlers // Stop all handlers
for (EvdevHandler handler : handlers.values()) { for (EvdevHandler handler : handlers.values()) {
handler.stop(); handler.stop();
} }
} }
} }
public void regrabAll() { public void regrabAll() {
synchronized (handlers) { synchronized (handlers) {
// We're regrabbing everything now // We're regrabbing everything now
ungrabbed = false; ungrabbed = false;
for (Map.Entry<String, EvdevHandler> entry : handlers.entrySet()) { for (Map.Entry<String, EvdevHandler> entry : handlers.entrySet()) {
// We need to recreate each entry since we can't reuse a stopped one // We need to recreate each entry since we can't reuse a stopped one
entry.setValue(new EvdevHandler(PATH + "/" + entry.getKey(), listener)); entry.setValue(new EvdevHandler(PATH + "/" + entry.getKey(), listener));
entry.getValue().start(); entry.getValue().start();
} }
} }
} }
public void start() { public void start() {
startThread = new Thread() { startThread = new Thread() {
@Override @Override
public void run() { public void run() {
// Initialize the root shell // Initialize the root shell
EvdevShell.getInstance().startShell(); EvdevShell.getInstance().startShell();
@ -128,61 +128,61 @@ public class EvdevWatcher {
patchedSeLinuxPolicies = true; patchedSeLinuxPolicies = true;
} }
// List all files and allow us access // List all files and allow us access
File[] files = rundownWithPermissionsChange(0666); File[] files = rundownWithPermissionsChange(0666);
init = true; init = true;
for (File f : files) { for (File f : files) {
observer.onEvent(FileObserver.CREATE, f.getName()); observer.onEvent(FileObserver.CREATE, f.getName());
} }
// Done with initial onEvent calls // Done with initial onEvent calls
init = false; init = false;
// Start watching for new files // Start watching for new files
observer.startWatching(); observer.startWatching();
synchronized (startThread) { synchronized (startThread) {
// Wait to be awoken again by shutdown() // Wait to be awoken again by shutdown()
try { try {
startThread.wait(); startThread.wait();
} catch (InterruptedException e) {} } catch (InterruptedException e) {}
} }
// Giveup eventX permissions // Giveup eventX permissions
rundownWithPermissionsChange(0660); rundownWithPermissionsChange(0660);
// Kill the root shell // Kill the root shell
try { try {
EvdevShell.getInstance().stopShell(); EvdevShell.getInstance().stopShell();
} catch (InterruptedException e) {} } catch (InterruptedException e) {}
} }
}; };
startThread.start(); startThread.start();
} }
public void shutdown() { public void shutdown() {
// Let start thread cleanup on it's own sweet time // Let start thread cleanup on it's own sweet time
synchronized (startThread) { synchronized (startThread) {
startThread.notify(); startThread.notify();
} }
// Stop the observer // Stop the observer
observer.stopWatching(); observer.stopWatching();
synchronized (handlers) { synchronized (handlers) {
// Stop creating new handlers // Stop creating new handlers
shutdown = true; shutdown = true;
// If we've already ungrabbed, there's nothing else to do // If we've already ungrabbed, there's nothing else to do
if (ungrabbed) { if (ungrabbed) {
return; return;
} }
// Stop all handlers // Stop all handlers
for (EvdevHandler handler : handlers.values()) { for (EvdevHandler handler : handlers.values()) {
handler.stop(); handler.stop();
} }
} }
} }
} }

View File

@ -20,140 +20,140 @@ import com.limelight.nvstream.av.video.cpu.AvcDecoder;
@SuppressWarnings("EmptyCatchBlock") @SuppressWarnings("EmptyCatchBlock")
public class AndroidCpuDecoderRenderer extends EnhancedDecoderRenderer { public class AndroidCpuDecoderRenderer extends EnhancedDecoderRenderer {
private Thread rendererThread, decoderThread; private Thread rendererThread, decoderThread;
private int targetFps; private int targetFps;
private static final int DECODER_BUFFER_SIZE = 92*1024; private static final int DECODER_BUFFER_SIZE = 92*1024;
private ByteBuffer decoderBuffer; private ByteBuffer decoderBuffer;
// Only sleep if the difference is above this value // Only sleep if the difference is above this value
private static final int WAIT_CEILING_MS = 5; private static final int WAIT_CEILING_MS = 5;
private static final int LOW_PERF = 1; private static final int LOW_PERF = 1;
private static final int MED_PERF = 2; private static final int MED_PERF = 2;
private static final int HIGH_PERF = 3; private static final int HIGH_PERF = 3;
private int totalFrames; private int totalFrames;
private long totalTimeMs; private long totalTimeMs;
private final int cpuCount = Runtime.getRuntime().availableProcessors(); private final int cpuCount = Runtime.getRuntime().availableProcessors();
@SuppressWarnings("unused") @SuppressWarnings("unused")
private int findOptimalPerformanceLevel() { private int findOptimalPerformanceLevel() {
StringBuilder cpuInfo = new StringBuilder(); StringBuilder cpuInfo = new StringBuilder();
BufferedReader br = null; BufferedReader br = null;
try { try {
br = new BufferedReader(new FileReader(new File("/proc/cpuinfo"))); br = new BufferedReader(new FileReader(new File("/proc/cpuinfo")));
for (;;) { for (;;) {
int ch = br.read(); int ch = br.read();
if (ch == -1) if (ch == -1)
break; break;
cpuInfo.append((char)ch); cpuInfo.append((char)ch);
} }
// Here we're doing very simple heuristics based on CPU model // Here we're doing very simple heuristics based on CPU model
String cpuInfoStr = cpuInfo.toString(); String cpuInfoStr = cpuInfo.toString();
// We order them from greatest to least for proper detection // We order them from greatest to least for proper detection
// of devices with multiple sets of cores (like Exynos 5 Octa) // of devices with multiple sets of cores (like Exynos 5 Octa)
// TODO Make this better (only even kind of works on ARM) // TODO Make this better (only even kind of works on ARM)
if (Build.FINGERPRINT.contains("generic")) { if (Build.FINGERPRINT.contains("generic")) {
// Emulator // Emulator
return LOW_PERF; return LOW_PERF;
} }
else if (cpuInfoStr.contains("0xc0f")) { else if (cpuInfoStr.contains("0xc0f")) {
// Cortex-A15 // Cortex-A15
return MED_PERF; return MED_PERF;
} }
else if (cpuInfoStr.contains("0xc09")) { else if (cpuInfoStr.contains("0xc09")) {
// Cortex-A9 // Cortex-A9
return LOW_PERF; return LOW_PERF;
} }
else if (cpuInfoStr.contains("0xc07")) { else if (cpuInfoStr.contains("0xc07")) {
// Cortex-A7 // Cortex-A7
return LOW_PERF; return LOW_PERF;
} }
else { else {
// Didn't have anything we're looking for // Didn't have anything we're looking for
return MED_PERF; return MED_PERF;
} }
} catch (IOException e) { } catch (IOException e) {
} finally { } finally {
if (br != null) { if (br != null) {
try { try {
br.close(); br.close();
} catch (IOException e) {} } catch (IOException e) {}
} }
} }
// Couldn't read cpuinfo, so assume medium // Couldn't read cpuinfo, so assume medium
return MED_PERF; return MED_PERF;
} }
@Override @Override
public boolean setup(int width, int height, int redrawRate, Object renderTarget, int drFlags) { public boolean setup(int width, int height, int redrawRate, Object renderTarget, int drFlags) {
this.targetFps = redrawRate; this.targetFps = redrawRate;
int perfLevel = LOW_PERF; //findOptimalPerformanceLevel(); int perfLevel = LOW_PERF; //findOptimalPerformanceLevel();
int threadCount; int threadCount;
int avcFlags = 0; int avcFlags = 0;
switch (perfLevel) { switch (perfLevel) {
case HIGH_PERF: case HIGH_PERF:
// Single threaded low latency decode is ideal but hard to acheive // Single threaded low latency decode is ideal but hard to acheive
avcFlags = AvcDecoder.LOW_LATENCY_DECODE; avcFlags = AvcDecoder.LOW_LATENCY_DECODE;
threadCount = 1; threadCount = 1;
break; break;
case LOW_PERF: case LOW_PERF:
// Disable the loop filter for performance reasons // Disable the loop filter for performance reasons
avcFlags = AvcDecoder.FAST_BILINEAR_FILTERING; avcFlags = AvcDecoder.FAST_BILINEAR_FILTERING;
// Use plenty of threads to try to utilize the CPU as best we can // Use plenty of threads to try to utilize the CPU as best we can
threadCount = cpuCount - 1; threadCount = cpuCount - 1;
break; break;
default: default:
case MED_PERF: case MED_PERF:
avcFlags = AvcDecoder.BILINEAR_FILTERING; avcFlags = AvcDecoder.BILINEAR_FILTERING;
// Only use 2 threads to minimize frame processing latency // Only use 2 threads to minimize frame processing latency
threadCount = 2; threadCount = 2;
break; break;
} }
// If the user wants quality, we'll remove the low IQ flags // If the user wants quality, we'll remove the low IQ flags
if ((drFlags & VideoDecoderRenderer.FLAG_PREFER_QUALITY) != 0) { if ((drFlags & VideoDecoderRenderer.FLAG_PREFER_QUALITY) != 0) {
// Make sure the loop filter is enabled // Make sure the loop filter is enabled
avcFlags &= ~AvcDecoder.DISABLE_LOOP_FILTER; avcFlags &= ~AvcDecoder.DISABLE_LOOP_FILTER;
// Disable the non-compliant speed optimizations // Disable the non-compliant speed optimizations
avcFlags &= ~AvcDecoder.FAST_DECODE; avcFlags &= ~AvcDecoder.FAST_DECODE;
LimeLog.info("Using high quality decoding"); LimeLog.info("Using high quality decoding");
} }
SurfaceHolder sh = (SurfaceHolder)renderTarget; SurfaceHolder sh = (SurfaceHolder)renderTarget;
sh.setFormat(PixelFormat.RGBX_8888); sh.setFormat(PixelFormat.RGBX_8888);
int err = AvcDecoder.init(width, height, avcFlags, threadCount); int err = AvcDecoder.init(width, height, avcFlags, threadCount);
if (err != 0) { if (err != 0) {
throw new IllegalStateException("AVC decoder initialization failure: "+err); throw new IllegalStateException("AVC decoder initialization failure: "+err);
} }
if (!AvcDecoder.setRenderTarget(sh.getSurface())) { if (!AvcDecoder.setRenderTarget(sh.getSurface())) {
return false; return false;
} }
decoderBuffer = ByteBuffer.allocate(DECODER_BUFFER_SIZE + AvcDecoder.getInputPaddingSize()); decoderBuffer = ByteBuffer.allocate(DECODER_BUFFER_SIZE + AvcDecoder.getInputPaddingSize());
LimeLog.info("Using software decoding (performance level: "+perfLevel+")"); LimeLog.info("Using software decoding (performance level: "+perfLevel+")");
return true; return true;
} }
@Override @Override
public boolean start(final VideoDepacketizer depacketizer) { public boolean start(final VideoDepacketizer depacketizer) {
decoderThread = new Thread() { decoderThread = new Thread() {
@Override @Override
public void run() { public void run() {
@ -174,112 +174,112 @@ public class AndroidCpuDecoderRenderer extends EnhancedDecoderRenderer {
decoderThread.setPriority(Thread.MAX_PRIORITY - 1); decoderThread.setPriority(Thread.MAX_PRIORITY - 1);
decoderThread.start(); decoderThread.start();
rendererThread = new Thread() { rendererThread = new Thread() {
@Override @Override
public void run() { public void run() {
long nextFrameTime = System.currentTimeMillis(); long nextFrameTime = System.currentTimeMillis();
DecodeUnit du; DecodeUnit du;
while (!isInterrupted()) while (!isInterrupted())
{ {
long diff = nextFrameTime - System.currentTimeMillis(); long diff = nextFrameTime - System.currentTimeMillis();
if (diff > WAIT_CEILING_MS) { if (diff > WAIT_CEILING_MS) {
try { try {
Thread.sleep(diff - WAIT_CEILING_MS); Thread.sleep(diff - WAIT_CEILING_MS);
} catch (InterruptedException e) { } catch (InterruptedException e) {
return; return;
} }
continue; continue;
} }
nextFrameTime = computePresentationTimeMs(targetFps); nextFrameTime = computePresentationTimeMs(targetFps);
AvcDecoder.redraw(); AvcDecoder.redraw();
} }
} }
}; };
rendererThread.setName("Video - Renderer (CPU)"); rendererThread.setName("Video - Renderer (CPU)");
rendererThread.setPriority(Thread.MAX_PRIORITY); rendererThread.setPriority(Thread.MAX_PRIORITY);
rendererThread.start(); rendererThread.start();
return true; return true;
} }
private long computePresentationTimeMs(int frameRate) { private long computePresentationTimeMs(int frameRate) {
return System.currentTimeMillis() + (1000 / frameRate); return System.currentTimeMillis() + (1000 / frameRate);
} }
@Override @Override
public void stop() { public void stop() {
rendererThread.interrupt(); rendererThread.interrupt();
decoderThread.interrupt(); decoderThread.interrupt();
try { try {
rendererThread.join(); rendererThread.join();
} catch (InterruptedException e) { } } catch (InterruptedException e) { }
try { try {
decoderThread.join(); decoderThread.join();
} catch (InterruptedException e) { } } catch (InterruptedException e) { }
} }
@Override @Override
public void release() { public void release() {
AvcDecoder.destroy(); AvcDecoder.destroy();
} }
private boolean submitDecodeUnit(DecodeUnit decodeUnit) { private boolean submitDecodeUnit(DecodeUnit decodeUnit) {
byte[] data; byte[] data;
// Use the reserved decoder buffer if this decode unit will fit // Use the reserved decoder buffer if this decode unit will fit
if (decodeUnit.getDataLength() <= DECODER_BUFFER_SIZE) { if (decodeUnit.getDataLength() <= DECODER_BUFFER_SIZE) {
decoderBuffer.clear(); decoderBuffer.clear();
for (ByteBufferDescriptor bbd : decodeUnit.getBufferList()) { for (ByteBufferDescriptor bbd : decodeUnit.getBufferList()) {
decoderBuffer.put(bbd.data, bbd.offset, bbd.length); decoderBuffer.put(bbd.data, bbd.offset, bbd.length);
} }
data = decoderBuffer.array(); data = decoderBuffer.array();
} }
else { else {
data = new byte[decodeUnit.getDataLength()+AvcDecoder.getInputPaddingSize()]; data = new byte[decodeUnit.getDataLength()+AvcDecoder.getInputPaddingSize()];
int offset = 0; int offset = 0;
for (ByteBufferDescriptor bbd : decodeUnit.getBufferList()) { for (ByteBufferDescriptor bbd : decodeUnit.getBufferList()) {
System.arraycopy(bbd.data, bbd.offset, data, offset, bbd.length); System.arraycopy(bbd.data, bbd.offset, data, offset, bbd.length);
offset += bbd.length; offset += bbd.length;
} }
} }
boolean success = (AvcDecoder.decode(data, 0, decodeUnit.getDataLength()) == 0); boolean success = (AvcDecoder.decode(data, 0, decodeUnit.getDataLength()) == 0);
if (success) { if (success) {
long timeAfterDecode = System.currentTimeMillis(); long timeAfterDecode = System.currentTimeMillis();
// Add delta time to the totals (excluding probable outliers) // Add delta time to the totals (excluding probable outliers)
long delta = timeAfterDecode - decodeUnit.getReceiveTimestamp(); long delta = timeAfterDecode - decodeUnit.getReceiveTimestamp();
if (delta >= 0 && delta < 1000) { if (delta >= 0 && delta < 1000) {
totalTimeMs += delta; totalTimeMs += delta;
totalFrames++; totalFrames++;
} }
} }
return success; return success;
} }
@Override @Override
public int getCapabilities() { public int getCapabilities() {
return 0; return 0;
} }
@Override @Override
public int getAverageDecoderLatency() { public int getAverageDecoderLatency() {
return 0; return 0;
} }
@Override @Override
public int getAverageEndToEndLatency() { public int getAverageEndToEndLatency() {
if (totalFrames == 0) { if (totalFrames == 0) {
return 0; return 0;
} }
return (int)(totalTimeMs / totalFrames); return (int)(totalTimeMs / totalFrames);
} }
@Override @Override
public String getDecoderName() { public String getDecoderName() {

View File

@ -5,75 +5,75 @@ import com.limelight.nvstream.av.video.VideoDepacketizer;
public class ConfigurableDecoderRenderer extends EnhancedDecoderRenderer { public class ConfigurableDecoderRenderer extends EnhancedDecoderRenderer {
private EnhancedDecoderRenderer decoderRenderer; private EnhancedDecoderRenderer decoderRenderer;
@Override @Override
public void release() { public void release() {
if (decoderRenderer != null) { if (decoderRenderer != null) {
decoderRenderer.release(); decoderRenderer.release();
} }
} }
@Override @Override
public boolean setup(int width, int height, int redrawRate, Object renderTarget, int drFlags) { public boolean setup(int width, int height, int redrawRate, Object renderTarget, int drFlags) {
if (decoderRenderer == null) { if (decoderRenderer == null) {
throw new IllegalStateException("ConfigurableDecoderRenderer not initialized"); throw new IllegalStateException("ConfigurableDecoderRenderer not initialized");
} }
return decoderRenderer.setup(width, height, redrawRate, renderTarget, drFlags); return decoderRenderer.setup(width, height, redrawRate, renderTarget, drFlags);
} }
public void initializeWithFlags(int drFlags) { public void initializeWithFlags(int drFlags) {
if ((drFlags & VideoDecoderRenderer.FLAG_FORCE_HARDWARE_DECODING) != 0 || if ((drFlags & VideoDecoderRenderer.FLAG_FORCE_HARDWARE_DECODING) != 0 ||
((drFlags & VideoDecoderRenderer.FLAG_FORCE_SOFTWARE_DECODING) == 0 && ((drFlags & VideoDecoderRenderer.FLAG_FORCE_SOFTWARE_DECODING) == 0 &&
MediaCodecHelper.findProbableSafeDecoder() != null)) { MediaCodecHelper.findProbableSafeDecoder() != null)) {
decoderRenderer = new MediaCodecDecoderRenderer(); decoderRenderer = new MediaCodecDecoderRenderer();
} }
else { else {
decoderRenderer = new AndroidCpuDecoderRenderer(); decoderRenderer = new AndroidCpuDecoderRenderer();
} }
} }
public boolean isHardwareAccelerated() { public boolean isHardwareAccelerated() {
if (decoderRenderer == null) { if (decoderRenderer == null) {
throw new IllegalStateException("ConfigurableDecoderRenderer not initialized"); throw new IllegalStateException("ConfigurableDecoderRenderer not initialized");
} }
return (decoderRenderer instanceof MediaCodecDecoderRenderer); return (decoderRenderer instanceof MediaCodecDecoderRenderer);
} }
@Override @Override
public boolean start(VideoDepacketizer depacketizer) { public boolean start(VideoDepacketizer depacketizer) {
return decoderRenderer.start(depacketizer); return decoderRenderer.start(depacketizer);
} }
@Override @Override
public void stop() { public void stop() {
decoderRenderer.stop(); decoderRenderer.stop();
} }
@Override @Override
public int getCapabilities() { public int getCapabilities() {
return decoderRenderer.getCapabilities(); return decoderRenderer.getCapabilities();
} }
@Override @Override
public int getAverageDecoderLatency() { public int getAverageDecoderLatency() {
if (decoderRenderer != null) { if (decoderRenderer != null) {
return decoderRenderer.getAverageDecoderLatency(); return decoderRenderer.getAverageDecoderLatency();
} }
else { else {
return 0; return 0;
} }
} }
@Override @Override
public int getAverageEndToEndLatency() { public int getAverageEndToEndLatency() {
if (decoderRenderer != null) { if (decoderRenderer != null) {
return decoderRenderer.getAverageEndToEndLatency(); return decoderRenderer.getAverageEndToEndLatency();
} }
else { else {
return 0; return 0;
} }
} }
@Override @Override
public String getDecoderName() { public String getDecoderName() {

View File

@ -25,391 +25,391 @@ import android.view.SurfaceHolder;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer { public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
private ByteBuffer[] videoDecoderInputBuffers; private ByteBuffer[] videoDecoderInputBuffers;
private MediaCodec videoDecoder; private MediaCodec videoDecoder;
private Thread rendererThread; private Thread rendererThread;
private boolean needsSpsBitstreamFixup, isExynos4; private boolean needsSpsBitstreamFixup, isExynos4;
private VideoDepacketizer depacketizer; private VideoDepacketizer depacketizer;
private boolean adaptivePlayback; private boolean adaptivePlayback;
private int initialWidth, initialHeight; private int initialWidth, initialHeight;
private boolean needsBaselineSpsHack; private boolean needsBaselineSpsHack;
private SeqParameterSet savedSps; private SeqParameterSet savedSps;
private long lastTimestampUs; private long lastTimestampUs;
private long totalTimeMs; private long totalTimeMs;
private long decoderTimeMs; private long decoderTimeMs;
private int totalFrames; private int totalFrames;
private String decoderName; private String decoderName;
private int numSpsIn; private int numSpsIn;
private int numPpsIn; private int numPpsIn;
private int numIframeIn; private int numIframeIn;
private static final boolean ENABLE_ASYNC_RENDERER = false; private static final boolean ENABLE_ASYNC_RENDERER = false;
@TargetApi(Build.VERSION_CODES.KITKAT) @TargetApi(Build.VERSION_CODES.KITKAT)
public MediaCodecDecoderRenderer() { public MediaCodecDecoderRenderer() {
//dumpDecoders(); //dumpDecoders();
MediaCodecInfo decoder = MediaCodecHelper.findProbableSafeDecoder(); MediaCodecInfo decoder = MediaCodecHelper.findProbableSafeDecoder();
if (decoder == null) { if (decoder == null) {
decoder = MediaCodecHelper.findFirstDecoder(); decoder = MediaCodecHelper.findFirstDecoder();
} }
if (decoder == null) { if (decoder == null) {
// This case is handled later in setup() // This case is handled later in setup()
return; return;
} }
decoderName = decoder.getName(); decoderName = decoder.getName();
// Set decoder-specific attributes // Set decoder-specific attributes
adaptivePlayback = MediaCodecHelper.decoderSupportsAdaptivePlayback(decoderName, decoder); adaptivePlayback = MediaCodecHelper.decoderSupportsAdaptivePlayback(decoderName, decoder);
needsSpsBitstreamFixup = MediaCodecHelper.decoderNeedsSpsBitstreamRestrictions(decoderName, decoder); needsSpsBitstreamFixup = MediaCodecHelper.decoderNeedsSpsBitstreamRestrictions(decoderName, decoder);
needsBaselineSpsHack = MediaCodecHelper.decoderNeedsBaselineSpsHack(decoderName, decoder); needsBaselineSpsHack = MediaCodecHelper.decoderNeedsBaselineSpsHack(decoderName, decoder);
isExynos4 = MediaCodecHelper.isExynos4Device(); isExynos4 = MediaCodecHelper.isExynos4Device();
if (needsSpsBitstreamFixup) { if (needsSpsBitstreamFixup) {
LimeLog.info("Decoder "+decoderName+" needs SPS bitstream restrictions fixup"); LimeLog.info("Decoder "+decoderName+" needs SPS bitstream restrictions fixup");
} }
if (needsBaselineSpsHack) { if (needsBaselineSpsHack) {
LimeLog.info("Decoder "+decoderName+" needs baseline SPS hack"); LimeLog.info("Decoder "+decoderName+" needs baseline SPS hack");
} }
if (isExynos4) { if (isExynos4) {
LimeLog.info("Decoder "+decoderName+" is on Exynos 4"); LimeLog.info("Decoder "+decoderName+" is on Exynos 4");
} }
} }
@TargetApi(Build.VERSION_CODES.LOLLIPOP) @TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override @Override
public boolean setup(int width, int height, int redrawRate, Object renderTarget, int drFlags) { public boolean setup(int width, int height, int redrawRate, Object renderTarget, int drFlags) {
this.initialWidth = width; this.initialWidth = width;
this.initialHeight = height; this.initialHeight = height;
if (decoderName == null) { if (decoderName == null) {
LimeLog.severe("No available hardware decoder!"); LimeLog.severe("No available hardware decoder!");
return false; return false;
} }
// Codecs have been known to throw all sorts of crazy runtime exceptions // Codecs have been known to throw all sorts of crazy runtime exceptions
// due to implementation problems // due to implementation problems
try { try {
videoDecoder = MediaCodec.createByCodecName(decoderName); videoDecoder = MediaCodec.createByCodecName(decoderName);
} catch (Exception e) { } catch (Exception e) {
return false; return false;
} }
MediaFormat videoFormat = MediaFormat.createVideoFormat("video/avc", width, height); MediaFormat videoFormat = MediaFormat.createVideoFormat("video/avc", width, height);
// Adaptive playback can also be enabled by the whitelist on pre-KitKat devices // Adaptive playback can also be enabled by the whitelist on pre-KitKat devices
// so we don't fill these pre-KitKat // so we don't fill these pre-KitKat
if (adaptivePlayback && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { if (adaptivePlayback && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
videoFormat.setInteger(MediaFormat.KEY_MAX_WIDTH, width); videoFormat.setInteger(MediaFormat.KEY_MAX_WIDTH, width);
videoFormat.setInteger(MediaFormat.KEY_MAX_HEIGHT, height); videoFormat.setInteger(MediaFormat.KEY_MAX_HEIGHT, height);
} }
// On Lollipop, we use asynchronous mode to avoid having a busy looping renderer thread // 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) { if (ENABLE_ASYNC_RENDERER && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
videoDecoder.setCallback(new MediaCodec.Callback() { videoDecoder.setCallback(new MediaCodec.Callback() {
@Override @Override
public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) { public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
LimeLog.info("Output format changed"); LimeLog.info("Output format changed");
LimeLog.info("New output Format: " + format); LimeLog.info("New output Format: " + format);
} }
@Override @Override
public void onOutputBufferAvailable(MediaCodec codec, int index, public void onOutputBufferAvailable(MediaCodec codec, int index,
BufferInfo info) { BufferInfo info) {
try { try {
// FIXME: It looks like we can't frameskip here // FIXME: It looks like we can't frameskip here
codec.releaseOutputBuffer(index, true); codec.releaseOutputBuffer(index, true);
} catch (Exception e) { } catch (Exception e) {
handleDecoderException(MediaCodecDecoderRenderer.this, e, null, 0); handleDecoderException(MediaCodecDecoderRenderer.this, e, null, 0);
} }
} }
@Override @Override
public void onInputBufferAvailable(MediaCodec codec, int index) { public void onInputBufferAvailable(MediaCodec codec, int index) {
try { try {
submitDecodeUnit(depacketizer.takeNextDecodeUnit(), codec.getInputBuffer(index), index); submitDecodeUnit(depacketizer.takeNextDecodeUnit(), codec.getInputBuffer(index), index);
} catch (InterruptedException e) { } catch (InterruptedException e) {
// What do we do here? // What do we do here?
e.printStackTrace(); e.printStackTrace();
} catch (Exception e) { } catch (Exception e) {
handleDecoderException(MediaCodecDecoderRenderer.this, e, null, 0); handleDecoderException(MediaCodecDecoderRenderer.this, e, null, 0);
} }
} }
@Override @Override
public void onError(MediaCodec codec, CodecException e) { public void onError(MediaCodec codec, CodecException e) {
if (e.isTransient()) { if (e.isTransient()) {
LimeLog.warning(e.getDiagnosticInfo()); LimeLog.warning(e.getDiagnosticInfo());
e.printStackTrace(); e.printStackTrace();
} }
else { else {
LimeLog.severe(e.getDiagnosticInfo()); LimeLog.severe(e.getDiagnosticInfo());
e.printStackTrace(); e.printStackTrace();
} }
} }
}); });
} }
videoDecoder.configure(videoFormat, ((SurfaceHolder)renderTarget).getSurface(), null, 0); videoDecoder.configure(videoFormat, ((SurfaceHolder)renderTarget).getSurface(), null, 0);
videoDecoder.setVideoScalingMode(MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT); videoDecoder.setVideoScalingMode(MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT);
LimeLog.info("Using hardware decoding"); LimeLog.info("Using hardware decoding");
return true; return true;
} }
@TargetApi(Build.VERSION_CODES.LOLLIPOP) @TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void handleDecoderException(MediaCodecDecoderRenderer dr, Exception e, ByteBuffer buf, int codecFlags) { private void handleDecoderException(MediaCodecDecoderRenderer dr, Exception e, ByteBuffer buf, int codecFlags) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (e instanceof CodecException) { if (e instanceof CodecException) {
CodecException codecExc = (CodecException) e; CodecException codecExc = (CodecException) e;
if (codecExc.isTransient()) { if (codecExc.isTransient()) {
// We'll let transient exceptions go // We'll let transient exceptions go
LimeLog.warning(codecExc.getDiagnosticInfo()); LimeLog.warning(codecExc.getDiagnosticInfo());
return; return;
} }
LimeLog.severe(codecExc.getDiagnosticInfo()); LimeLog.severe(codecExc.getDiagnosticInfo());
} }
} }
if (buf != null || codecFlags != 0) { if (buf != null || codecFlags != 0) {
throw new RendererException(dr, e, buf, codecFlags); throw new RendererException(dr, e, buf, codecFlags);
} }
else { else {
throw new RendererException(dr, e); throw new RendererException(dr, e);
} }
} }
private void startRendererThread() private void startRendererThread()
{ {
rendererThread = new Thread() { rendererThread = new Thread() {
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@Override @Override
public void run() { public void run() {
BufferInfo info = new BufferInfo(); BufferInfo info = new BufferInfo();
DecodeUnit du = null; DecodeUnit du = null;
int inputIndex = -1; int inputIndex = -1;
while (!isInterrupted()) while (!isInterrupted())
{ {
// In order to get as much data to the decoder as early as possible, // 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. // try to submit up to 5 decode units at once without blocking.
if (inputIndex == -1 && du == null) { if (inputIndex == -1 && du == null) {
try { try {
for (int i = 0; i < 5; i++) { for (int i = 0; i < 5; i++) {
inputIndex = videoDecoder.dequeueInputBuffer(0); inputIndex = videoDecoder.dequeueInputBuffer(0);
du = depacketizer.pollNextDecodeUnit(); du = depacketizer.pollNextDecodeUnit();
// Stop if we can't get a DU or input buffer // Stop if we can't get a DU or input buffer
if (du == null || inputIndex == -1) { if (du == null || inputIndex == -1) {
break; break;
} }
submitDecodeUnit(du, videoDecoderInputBuffers[inputIndex], inputIndex); submitDecodeUnit(du, videoDecoderInputBuffers[inputIndex], inputIndex);
du = null; du = null;
inputIndex = -1; inputIndex = -1;
} }
} catch (Exception e) { } catch (Exception e) {
inputIndex = -1; inputIndex = -1;
handleDecoderException(MediaCodecDecoderRenderer.this, e, null, 0); handleDecoderException(MediaCodecDecoderRenderer.this, e, null, 0);
} }
} }
// Grab an input buffer if we don't have one already. // Grab an input buffer if we don't have one already.
// This way we can have one ready hopefully by the time // This way we can have one ready hopefully by the time
// the depacketizer is done with this frame. It's important // the depacketizer is done with this frame. It's important
// that this can timeout because it's possible that we could exhaust // that this can timeout because it's possible that we could exhaust
// the decoder's input buffers and deadlocks because aren't pulling // the decoder's input buffers and deadlocks because aren't pulling
// frames out of the other end. // frames out of the other end.
if (inputIndex == -1) { if (inputIndex == -1) {
try { try {
// If we've got a DU waiting to be given to the decoder, // If we've got a DU waiting to be given to the decoder,
// wait a full 3 ms for an input buffer. Otherwise // wait a full 3 ms for an input buffer. Otherwise
// just see if we can get one immediately. // just see if we can get one immediately.
inputIndex = videoDecoder.dequeueInputBuffer(du != null ? 3000 : 0); inputIndex = videoDecoder.dequeueInputBuffer(du != null ? 3000 : 0);
} catch (Exception e) { } catch (Exception e) {
inputIndex = -1; inputIndex = -1;
handleDecoderException(MediaCodecDecoderRenderer.this, e, null, 0); handleDecoderException(MediaCodecDecoderRenderer.this, e, null, 0);
} }
} }
// Grab a decode unit if we don't have one already // Grab a decode unit if we don't have one already
if (du == null) { if (du == null) {
du = depacketizer.pollNextDecodeUnit(); du = depacketizer.pollNextDecodeUnit();
} }
// If we've got both a decode unit and an input buffer, we'll // If we've got both a decode unit and an input buffer, we'll
// submit now. Otherwise, we wait until we have one. // submit now. Otherwise, we wait until we have one.
if (du != null && inputIndex >= 0) { if (du != null && inputIndex >= 0) {
submitDecodeUnit(du, videoDecoderInputBuffers[inputIndex], inputIndex); submitDecodeUnit(du, videoDecoderInputBuffers[inputIndex], inputIndex);
// DU and input buffer have both been consumed // DU and input buffer have both been consumed
du = null; du = null;
inputIndex = -1; inputIndex = -1;
} }
// Try to output a frame // Try to output a frame
try { try {
int outIndex = videoDecoder.dequeueOutputBuffer(info, 0); int outIndex = videoDecoder.dequeueOutputBuffer(info, 0);
if (outIndex >= 0) { if (outIndex >= 0) {
long presentationTimeUs = info.presentationTimeUs; long presentationTimeUs = info.presentationTimeUs;
int lastIndex = outIndex; int lastIndex = outIndex;
// Get the last output buffer in the queue // Get the last output buffer in the queue
while ((outIndex = videoDecoder.dequeueOutputBuffer(info, 0)) >= 0) { while ((outIndex = videoDecoder.dequeueOutputBuffer(info, 0)) >= 0) {
videoDecoder.releaseOutputBuffer(lastIndex, false); videoDecoder.releaseOutputBuffer(lastIndex, false);
lastIndex = outIndex; lastIndex = outIndex;
presentationTimeUs = info.presentationTimeUs; presentationTimeUs = info.presentationTimeUs;
} }
// Render the last buffer // Render the last buffer
videoDecoder.releaseOutputBuffer(lastIndex, true); videoDecoder.releaseOutputBuffer(lastIndex, true);
// Add delta time to the totals (excluding probable outliers) // Add delta time to the totals (excluding probable outliers)
long delta = System.currentTimeMillis()-(presentationTimeUs/1000); long delta = System.currentTimeMillis()-(presentationTimeUs/1000);
if (delta >= 0 && delta < 1000) { if (delta >= 0 && delta < 1000) {
decoderTimeMs += delta; decoderTimeMs += delta;
totalTimeMs += delta; totalTimeMs += delta;
} }
} else { } else {
switch (outIndex) { switch (outIndex) {
case MediaCodec.INFO_TRY_AGAIN_LATER: case MediaCodec.INFO_TRY_AGAIN_LATER:
// Getting an input buffer may already block // Getting an input buffer may already block
// so don't park if we still need to do that // so don't park if we still need to do that
if (inputIndex >= 0) { if (inputIndex >= 0) {
LockSupport.parkNanos(1); LockSupport.parkNanos(1);
} }
break; break;
case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED: case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
LimeLog.info("Output buffers changed"); LimeLog.info("Output buffers changed");
break; break;
case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED: case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
LimeLog.info("Output format changed"); LimeLog.info("Output format changed");
LimeLog.info("New output Format: " + videoDecoder.getOutputFormat()); LimeLog.info("New output Format: " + videoDecoder.getOutputFormat());
break; break;
default: default:
break; break;
} }
} }
} catch (Exception e) { } catch (Exception e) {
handleDecoderException(MediaCodecDecoderRenderer.this, e, null, 0); handleDecoderException(MediaCodecDecoderRenderer.this, e, null, 0);
} }
} }
} }
}; };
rendererThread.setName("Video - Renderer (MediaCodec)"); rendererThread.setName("Video - Renderer (MediaCodec)");
rendererThread.setPriority(Thread.MAX_PRIORITY); rendererThread.setPriority(Thread.MAX_PRIORITY);
rendererThread.start(); rendererThread.start();
} }
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@Override @Override
public boolean start(VideoDepacketizer depacketizer) { public boolean start(VideoDepacketizer depacketizer) {
this.depacketizer = depacketizer; this.depacketizer = depacketizer;
// Start the decoder // Start the decoder
videoDecoder.start(); videoDecoder.start();
// On devices pre-Lollipop, we'll use a rendering thread // On devices pre-Lollipop, we'll use a rendering thread
if (!ENABLE_ASYNC_RENDERER || Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { if (!ENABLE_ASYNC_RENDERER || Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
videoDecoderInputBuffers = videoDecoder.getInputBuffers(); videoDecoderInputBuffers = videoDecoder.getInputBuffers();
startRendererThread(); startRendererThread();
} }
return true; return true;
} }
@Override @Override
public void stop() { public void stop() {
if (rendererThread != null) { if (rendererThread != null) {
// Halt the rendering thread // Halt the rendering thread
rendererThread.interrupt(); rendererThread.interrupt();
try { try {
rendererThread.join(); rendererThread.join();
} catch (InterruptedException ignored) { } } catch (InterruptedException ignored) { }
} }
// Stop the decoder // Stop the decoder
videoDecoder.stop(); videoDecoder.stop();
} }
@Override @Override
public void release() { public void release() {
if (videoDecoder != null) { if (videoDecoder != null) {
videoDecoder.release(); videoDecoder.release();
} }
} }
private void queueInputBuffer(int inputBufferIndex, int offset, int length, long timestampUs, int codecFlags) { 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 // Try 25 times to submit the input buffer before throwing a real exception
int i; int i;
Exception lastException = null; Exception lastException = null;
for (i = 0; i < 25; i++) { for (i = 0; i < 25; i++) {
try { try {
videoDecoder.queueInputBuffer(inputBufferIndex, videoDecoder.queueInputBuffer(inputBufferIndex,
0, length, 0, length,
timestampUs, codecFlags); timestampUs, codecFlags);
break; break;
} catch (Exception e) { } catch (Exception e) {
handleDecoderException(this, e, null, codecFlags); handleDecoderException(this, e, null, codecFlags);
lastException = e; lastException = e;
} }
} }
if (i == 25) { if (i == 25) {
throw new RendererException(this, lastException, null, codecFlags); throw new RendererException(this, lastException, null, codecFlags);
} }
} }
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
private void submitDecodeUnit(DecodeUnit decodeUnit, ByteBuffer buf, int inputBufferIndex) { private void submitDecodeUnit(DecodeUnit decodeUnit, ByteBuffer buf, int inputBufferIndex) {
long currentTime = System.currentTimeMillis(); long currentTime = System.currentTimeMillis();
long delta = currentTime-decodeUnit.getReceiveTimestamp(); long delta = currentTime-decodeUnit.getReceiveTimestamp();
if (delta >= 0 && delta < 1000) { if (delta >= 0 && delta < 1000) {
totalTimeMs += currentTime-decodeUnit.getReceiveTimestamp(); totalTimeMs += currentTime-decodeUnit.getReceiveTimestamp();
totalFrames++; totalFrames++;
} }
long timestampUs = currentTime * 1000; long timestampUs = currentTime * 1000;
if (timestampUs <= lastTimestampUs) { if (timestampUs <= lastTimestampUs) {
// We can't submit multiple buffers with the same timestamp // We can't submit multiple buffers with the same timestamp
// so bump it up by one before queuing // so bump it up by one before queuing
timestampUs = lastTimestampUs + 1; timestampUs = lastTimestampUs + 1;
} }
lastTimestampUs = timestampUs; lastTimestampUs = timestampUs;
// Clear old input data // Clear old input data
buf.clear(); buf.clear();
int codecFlags = 0; int codecFlags = 0;
int decodeUnitFlags = decodeUnit.getFlags(); int decodeUnitFlags = decodeUnit.getFlags();
if ((decodeUnitFlags & DecodeUnit.DU_FLAG_CODEC_CONFIG) != 0) { if ((decodeUnitFlags & DecodeUnit.DU_FLAG_CODEC_CONFIG) != 0) {
codecFlags |= MediaCodec.BUFFER_FLAG_CODEC_CONFIG; codecFlags |= MediaCodec.BUFFER_FLAG_CODEC_CONFIG;
} }
if ((decodeUnitFlags & DecodeUnit.DU_FLAG_SYNC_FRAME) != 0) { if ((decodeUnitFlags & DecodeUnit.DU_FLAG_SYNC_FRAME) != 0) {
codecFlags |= MediaCodec.BUFFER_FLAG_SYNC_FRAME; codecFlags |= MediaCodec.BUFFER_FLAG_SYNC_FRAME;
numIframeIn++; numIframeIn++;
} }
boolean needsSpsReplay = false; boolean needsSpsReplay = false;
if ((decodeUnitFlags & DecodeUnit.DU_FLAG_CODEC_CONFIG) != 0) { if ((decodeUnitFlags & DecodeUnit.DU_FLAG_CODEC_CONFIG) != 0) {
ByteBufferDescriptor header = decodeUnit.getBufferList().get(0); ByteBufferDescriptor header = decodeUnit.getBufferList().get(0);
if (header.data[header.offset+4] == 0x67) { if (header.data[header.offset+4] == 0x67) {
numSpsIn++; numSpsIn++;
ByteBuffer spsBuf = ByteBuffer.wrap(header.data); ByteBuffer spsBuf = ByteBuffer.wrap(header.data);
// Skip to the start of the NALU data // Skip to the start of the NALU data
spsBuf.position(header.offset+5); spsBuf.position(header.offset+5);
SeqParameterSet sps = SeqParameterSet.read(spsBuf); SeqParameterSet sps = SeqParameterSet.read(spsBuf);
// Some decoders rely on H264 level to decide how many buffers are needed // 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 // Since we only need one frame buffered, we'll set the level as low as we can
@ -428,29 +428,29 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
// Leave the profile alone (currently 5.0) // Leave the profile alone (currently 5.0)
} }
// TI OMAP4 requires a reference frame count of 1 to decode successfully. Exynos 4 // TI OMAP4 requires a reference frame count of 1 to decode successfully. Exynos 4
// also requires this fixup. // also requires this fixup.
// //
// I'm doing this fixup for all devices because I haven't seen any devices that // 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 // this causes issues for. At worst, it seems to do nothing and at best it fixes
// issues with video lag, hangs, and crashes. // issues with video lag, hangs, and crashes.
LimeLog.info("Patching num_ref_frames in SPS"); LimeLog.info("Patching num_ref_frames in SPS");
sps.num_ref_frames = 1; sps.num_ref_frames = 1;
if (needsSpsBitstreamFixup || isExynos4) { if (needsSpsBitstreamFixup || isExynos4) {
// The SPS that comes in the current H264 bytestream doesn't set bitstream_restriction_flag // 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. // or max_dec_frame_buffering which increases decoding latency on Tegra.
LimeLog.info("Adding bitstream restrictions"); LimeLog.info("Adding bitstream restrictions");
sps.vuiParams.bitstreamRestriction = new VUIParameters.BitstreamRestriction(); sps.vuiParams.bitstreamRestriction = new VUIParameters.BitstreamRestriction();
sps.vuiParams.bitstreamRestriction.motion_vectors_over_pic_boundaries_flag = true; sps.vuiParams.bitstreamRestriction.motion_vectors_over_pic_boundaries_flag = true;
sps.vuiParams.bitstreamRestriction.max_bytes_per_pic_denom = 2; sps.vuiParams.bitstreamRestriction.max_bytes_per_pic_denom = 2;
sps.vuiParams.bitstreamRestriction.max_bits_per_mb_denom = 1; 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_horizontal = 16;
sps.vuiParams.bitstreamRestriction.log2_max_mv_length_vertical = 16; sps.vuiParams.bitstreamRestriction.log2_max_mv_length_vertical = 16;
sps.vuiParams.bitstreamRestriction.num_reorder_frames = 0; sps.vuiParams.bitstreamRestriction.num_reorder_frames = 0;
sps.vuiParams.bitstreamRestriction.max_dec_frame_buffering = 1; sps.vuiParams.bitstreamRestriction.max_dec_frame_buffering = 1;
} }
// If we need to hack this SPS to say we're baseline, do so now // If we need to hack this SPS to say we're baseline, do so now
if (needsBaselineSpsHack) { if (needsBaselineSpsHack) {
@ -459,20 +459,20 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
savedSps = sps; savedSps = sps;
} }
// Write the annex B header // Write the annex B header
buf.put(header.data, header.offset, 5); buf.put(header.data, header.offset, 5);
// Write the modified SPS to the input buffer // Write the modified SPS to the input buffer
sps.write(buf); sps.write(buf);
queueInputBuffer(inputBufferIndex, queueInputBuffer(inputBufferIndex,
0, buf.position(), 0, buf.position(),
timestampUs, codecFlags); timestampUs, codecFlags);
depacketizer.freeDecodeUnit(decodeUnit); depacketizer.freeDecodeUnit(decodeUnit);
return; return;
} else if (header.data[header.offset+4] == 0x68) { } else if (header.data[header.offset+4] == 0x68) {
numPpsIn++; numPpsIn++;
if (needsBaselineSpsHack) { if (needsBaselineSpsHack) {
LimeLog.info("Saw PPS; disabling SPS hack"); 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 // Give the decoder the SPS again with the proper profile now
needsSpsReplay = true; needsSpsReplay = true;
} }
} }
} }
// Copy data from our buffer list into the input buffer // Copy data from our buffer list into the input buffer
for (ByteBufferDescriptor desc : decodeUnit.getBufferList()) for (ByteBufferDescriptor desc : decodeUnit.getBufferList())
{ {
buf.put(desc.data, desc.offset, desc.length); buf.put(desc.data, desc.offset, desc.length);
} }
queueInputBuffer(inputBufferIndex, queueInputBuffer(inputBufferIndex,
0, decodeUnit.getDataLength(), 0, decodeUnit.getDataLength(),
timestampUs, codecFlags); timestampUs, codecFlags);
depacketizer.freeDecodeUnit(decodeUnit); depacketizer.freeDecodeUnit(decodeUnit);
if (needsSpsReplay) { if (needsSpsReplay) {
replaySps(); replaySps();
} }
} }
private void replaySps() { private void replaySps() {
int inputIndex = videoDecoder.dequeueInputBuffer(-1); int inputIndex = videoDecoder.dequeueInputBuffer(-1);
@ -528,27 +528,27 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
LimeLog.info("SPS replay complete"); LimeLog.info("SPS replay complete");
} }
@Override @Override
public int getCapabilities() { public int getCapabilities() {
return adaptivePlayback ? return adaptivePlayback ?
VideoDecoderRenderer.CAPABILITY_ADAPTIVE_RESOLUTION : 0; VideoDecoderRenderer.CAPABILITY_ADAPTIVE_RESOLUTION : 0;
} }
@Override @Override
public int getAverageDecoderLatency() { public int getAverageDecoderLatency() {
if (totalFrames == 0) { if (totalFrames == 0) {
return 0; return 0;
} }
return (int)(decoderTimeMs / totalFrames); return (int)(decoderTimeMs / totalFrames);
} }
@Override @Override
public int getAverageEndToEndLatency() { public int getAverageEndToEndLatency() {
if (totalFrames == 0) { if (totalFrames == 0) {
return 0; return 0;
} }
return (int)(totalTimeMs / totalFrames); return (int)(totalTimeMs / totalFrames);
} }
@Override @Override
public String getDecoderName() { public String getDecoderName() {
@ -556,62 +556,62 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
} }
public class RendererException extends RuntimeException { public class RendererException extends RuntimeException {
private static final long serialVersionUID = 8985937536997012406L; private static final long serialVersionUID = 8985937536997012406L;
private final Exception originalException; private final Exception originalException;
private final MediaCodecDecoderRenderer renderer; private final MediaCodecDecoderRenderer renderer;
private ByteBuffer currentBuffer; private ByteBuffer currentBuffer;
private int currentCodecFlags; private int currentCodecFlags;
public RendererException(MediaCodecDecoderRenderer renderer, Exception e) { public RendererException(MediaCodecDecoderRenderer renderer, Exception e) {
this.renderer = renderer; this.renderer = renderer;
this.originalException = e; this.originalException = e;
} }
public RendererException(MediaCodecDecoderRenderer renderer, Exception e, ByteBuffer currentBuffer, int currentCodecFlags) { public RendererException(MediaCodecDecoderRenderer renderer, Exception e, ByteBuffer currentBuffer, int currentCodecFlags) {
this.renderer = renderer; this.renderer = renderer;
this.originalException = e; this.originalException = e;
this.currentBuffer = currentBuffer; this.currentBuffer = currentBuffer;
this.currentCodecFlags = currentCodecFlags; this.currentCodecFlags = currentCodecFlags;
} }
public String toString() { public String toString() {
String str = ""; String str = "";
str += "Decoder: "+renderer.decoderName+"\n"; str += "Decoder: "+renderer.decoderName+"\n";
str += "Initial video dimensions: "+renderer.initialWidth+"x"+renderer.initialHeight+"\n"; str += "Initial video dimensions: "+renderer.initialWidth+"x"+renderer.initialHeight+"\n";
str += "In stats: "+renderer.numSpsIn+", "+renderer.numPpsIn+", "+renderer.numIframeIn+"\n"; str += "In stats: "+renderer.numSpsIn+", "+renderer.numPpsIn+", "+renderer.numIframeIn+"\n";
str += "Total frames: "+renderer.totalFrames+"\n"; str += "Total frames: "+renderer.totalFrames+"\n";
if (currentBuffer != null) { if (currentBuffer != null) {
str += "Current buffer: "; str += "Current buffer: ";
currentBuffer.flip(); currentBuffer.flip();
while (currentBuffer.hasRemaining() && currentBuffer.position() < 10) { while (currentBuffer.hasRemaining() && currentBuffer.position() < 10) {
str += String.format((Locale)null, "%02x ", currentBuffer.get()); str += String.format((Locale)null, "%02x ", currentBuffer.get());
} }
str += "\n"; str += "\n";
str += "Buffer codec flags: "+currentCodecFlags+"\n"; str += "Buffer codec flags: "+currentCodecFlags+"\n";
} }
str += "Is Exynos 4: "+renderer.isExynos4+"\n"; str += "Is Exynos 4: "+renderer.isExynos4+"\n";
str += "/proc/cpuinfo:\n"; str += "/proc/cpuinfo:\n";
try { try {
str += MediaCodecHelper.readCpuinfo(); str += MediaCodecHelper.readCpuinfo();
} catch (Exception e) { } catch (Exception e) {
str += e.getMessage(); str += e.getMessage();
} }
str += "Full decoder dump:\n"; str += "Full decoder dump:\n";
try { try {
str += MediaCodecHelper.dumpDecoders(); str += MediaCodecHelper.dumpDecoders();
} catch (Exception e) { } catch (Exception e) {
str += e.getMessage(); str += e.getMessage();
} }
str += originalException.toString(); str += originalException.toString();
return str; return str;
} }
} }
} }

View File

@ -72,8 +72,8 @@ public class MediaCodecHelper {
@TargetApi(Build.VERSION_CODES.KITKAT) @TargetApi(Build.VERSION_CODES.KITKAT)
public static boolean decoderSupportsAdaptivePlayback(String decoderName, MediaCodecInfo decoderInfo) { 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 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 so we'll keep it off for now, since we don't know whether other devices also do the same
if (isDecoderInList(whitelistedAdaptiveResolutionPrefixes, decoderName)) { if (isDecoderInList(whitelistedAdaptiveResolutionPrefixes, decoderName)) {
LimeLog.info("Adaptive playback supported (whitelist)"); LimeLog.info("Adaptive playback supported (whitelist)");

View File

@ -17,153 +17,153 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteException;
public class ComputerDatabaseManager { public class ComputerDatabaseManager {
private static final String COMPUTER_DB_NAME = "computers.db"; private static final String COMPUTER_DB_NAME = "computers.db";
private static final String COMPUTER_TABLE_NAME = "Computers"; private static final String COMPUTER_TABLE_NAME = "Computers";
private static final String COMPUTER_NAME_COLUMN_NAME = "ComputerName"; private static final String COMPUTER_NAME_COLUMN_NAME = "ComputerName";
private static final String COMPUTER_UUID_COLUMN_NAME = "UUID"; private static final String COMPUTER_UUID_COLUMN_NAME = "UUID";
private static final String LOCAL_IP_COLUMN_NAME = "LocalIp"; private static final String LOCAL_IP_COLUMN_NAME = "LocalIp";
private static final String REMOTE_IP_COLUMN_NAME = "RemoteIp"; private static final String REMOTE_IP_COLUMN_NAME = "RemoteIp";
private static final String MAC_COLUMN_NAME = "Mac"; private static final String MAC_COLUMN_NAME = "Mac";
private SQLiteDatabase computerDb; private SQLiteDatabase computerDb;
public ComputerDatabaseManager(Context c) { public ComputerDatabaseManager(Context c) {
try { try {
// Create or open an existing DB // Create or open an existing DB
computerDb = c.openOrCreateDatabase(COMPUTER_DB_NAME, 0, null); computerDb = c.openOrCreateDatabase(COMPUTER_DB_NAME, 0, null);
} catch (SQLiteException e) { } catch (SQLiteException e) {
// Delete the DB and try again // Delete the DB and try again
c.deleteDatabase(COMPUTER_DB_NAME); c.deleteDatabase(COMPUTER_DB_NAME);
computerDb = c.openOrCreateDatabase(COMPUTER_DB_NAME, 0, null); computerDb = c.openOrCreateDatabase(COMPUTER_DB_NAME, 0, null);
} }
initializeDb(); initializeDb();
} }
public void close() { public void close() {
computerDb.close(); computerDb.close();
} }
private void initializeDb() { private void initializeDb() {
// Create tables if they aren't already there // Create tables if they aren't already there
computerDb.execSQL(String.format((Locale)null, "CREATE TABLE IF NOT EXISTS %s(%s TEXT PRIMARY KEY," + 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)", " %s TEXT NOT NULL, %s TEXT NOT NULL, %s TEXT NOT NULL, %s TEXT NOT NULL)",
COMPUTER_TABLE_NAME, COMPUTER_TABLE_NAME,
COMPUTER_NAME_COLUMN_NAME, COMPUTER_UUID_COLUMN_NAME, LOCAL_IP_COLUMN_NAME, COMPUTER_NAME_COLUMN_NAME, COMPUTER_UUID_COLUMN_NAME, LOCAL_IP_COLUMN_NAME,
REMOTE_IP_COLUMN_NAME, MAC_COLUMN_NAME)); REMOTE_IP_COLUMN_NAME, MAC_COLUMN_NAME));
} }
public void deleteComputer(String name) { public void deleteComputer(String name) {
computerDb.delete(COMPUTER_TABLE_NAME, COMPUTER_NAME_COLUMN_NAME+"='"+name+"'", null); computerDb.delete(COMPUTER_TABLE_NAME, COMPUTER_NAME_COLUMN_NAME+"='"+name+"'", null);
} }
public boolean updateComputer(ComputerDetails details) { public boolean updateComputer(ComputerDetails details) {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(COMPUTER_NAME_COLUMN_NAME, details.name); values.put(COMPUTER_NAME_COLUMN_NAME, details.name);
values.put(COMPUTER_UUID_COLUMN_NAME, details.uuid.toString()); values.put(COMPUTER_UUID_COLUMN_NAME, details.uuid.toString());
values.put(LOCAL_IP_COLUMN_NAME, details.localIp.getAddress()); values.put(LOCAL_IP_COLUMN_NAME, details.localIp.getAddress());
values.put(REMOTE_IP_COLUMN_NAME, details.remoteIp.getAddress()); values.put(REMOTE_IP_COLUMN_NAME, details.remoteIp.getAddress());
values.put(MAC_COLUMN_NAME, details.macAddress); values.put(MAC_COLUMN_NAME, details.macAddress);
return -1 != computerDb.insertWithOnConflict(COMPUTER_TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_REPLACE); return -1 != computerDb.insertWithOnConflict(COMPUTER_TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_REPLACE);
} }
public List<ComputerDetails> getAllComputers() { public List<ComputerDetails> getAllComputers() {
Cursor c = computerDb.rawQuery("SELECT * FROM "+COMPUTER_TABLE_NAME, null); Cursor c = computerDb.rawQuery("SELECT * FROM "+COMPUTER_TABLE_NAME, null);
LinkedList<ComputerDetails> computerList = new LinkedList<ComputerDetails>(); LinkedList<ComputerDetails> computerList = new LinkedList<ComputerDetails>();
while (c.moveToNext()) { while (c.moveToNext()) {
ComputerDetails details = new ComputerDetails(); ComputerDetails details = new ComputerDetails();
details.name = c.getString(0); details.name = c.getString(0);
String uuidStr = c.getString(1); String uuidStr = c.getString(1);
try { try {
details.uuid = UUID.fromString(uuidStr); details.uuid = UUID.fromString(uuidStr);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
// We'll delete this entry // We'll delete this entry
LimeLog.severe("DB: Corrupted UUID for "+details.name); LimeLog.severe("DB: Corrupted UUID for "+details.name);
} }
try { try {
details.localIp = InetAddress.getByAddress(c.getBlob(2)); details.localIp = InetAddress.getByAddress(c.getBlob(2));
} catch (UnknownHostException e) { } catch (UnknownHostException e) {
// We'll delete this entry // We'll delete this entry
LimeLog.severe("DB: Corrupted local IP for "+details.name); LimeLog.severe("DB: Corrupted local IP for "+details.name);
} }
try { try {
details.remoteIp = InetAddress.getByAddress(c.getBlob(3)); details.remoteIp = InetAddress.getByAddress(c.getBlob(3));
} catch (UnknownHostException e) { } catch (UnknownHostException e) {
// We'll delete this entry // We'll delete this entry
LimeLog.severe("DB: Corrupted remote IP for "+details.name); LimeLog.severe("DB: Corrupted remote IP for "+details.name);
} }
details.macAddress = c.getString(4); details.macAddress = c.getString(4);
// This signifies we don't have dynamic state (like pair state) // This signifies we don't have dynamic state (like pair state)
details.state = ComputerDetails.State.UNKNOWN; details.state = ComputerDetails.State.UNKNOWN;
details.reachability = ComputerDetails.Reachability.UNKNOWN; details.reachability = ComputerDetails.Reachability.UNKNOWN;
// If a field is corrupt or missing, skip the database entry // If a field is corrupt or missing, skip the database entry
if (details.uuid == null || details.localIp == null || details.remoteIp == null || if (details.uuid == null || details.localIp == null || details.remoteIp == null ||
details.macAddress == null) { details.macAddress == null) {
continue; continue;
} }
computerList.add(details); computerList.add(details);
} }
c.close(); c.close();
return computerList; return computerList;
} }
public ComputerDetails getComputerByName(String name) { public ComputerDetails getComputerByName(String name) {
Cursor c = computerDb.rawQuery("SELECT * FROM "+COMPUTER_TABLE_NAME+" WHERE "+COMPUTER_NAME_COLUMN_NAME+"='"+name+"'", null); Cursor c = computerDb.rawQuery("SELECT * FROM "+COMPUTER_TABLE_NAME+" WHERE "+COMPUTER_NAME_COLUMN_NAME+"='"+name+"'", null);
ComputerDetails details = new ComputerDetails(); ComputerDetails details = new ComputerDetails();
if (!c.moveToFirst()) { if (!c.moveToFirst()) {
// No matching computer // No matching computer
c.close(); c.close();
return null; return null;
} }
details.name = c.getString(0); details.name = c.getString(0);
String uuidStr = c.getString(1); String uuidStr = c.getString(1);
try { try {
details.uuid = UUID.fromString(uuidStr); details.uuid = UUID.fromString(uuidStr);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
// We'll delete this entry // We'll delete this entry
LimeLog.severe("DB: Corrupted UUID for "+details.name); LimeLog.severe("DB: Corrupted UUID for "+details.name);
} }
try { try {
details.localIp = InetAddress.getByAddress(c.getBlob(2)); details.localIp = InetAddress.getByAddress(c.getBlob(2));
} catch (UnknownHostException e) { } catch (UnknownHostException e) {
// We'll delete this entry // We'll delete this entry
LimeLog.severe("DB: Corrupted local IP for "+details.name); LimeLog.severe("DB: Corrupted local IP for "+details.name);
} }
try { try {
details.remoteIp = InetAddress.getByAddress(c.getBlob(3)); details.remoteIp = InetAddress.getByAddress(c.getBlob(3));
} catch (UnknownHostException e) { } catch (UnknownHostException e) {
// We'll delete this entry // We'll delete this entry
LimeLog.severe("DB: Corrupted remote IP for "+details.name); LimeLog.severe("DB: Corrupted remote IP for "+details.name);
} }
details.macAddress = c.getString(4); details.macAddress = c.getString(4);
c.close(); c.close();
details.state = ComputerDetails.State.UNKNOWN; details.state = ComputerDetails.State.UNKNOWN;
details.reachability = ComputerDetails.Reachability.UNKNOWN; details.reachability = ComputerDetails.Reachability.UNKNOWN;
// If a field is corrupt or missing, delete the database entry // If a field is corrupt or missing, delete the database entry
if (details.uuid == null || details.localIp == null || details.remoteIp == null || if (details.uuid == null || details.localIp == null || details.remoteIp == null ||
details.macAddress == null) { details.macAddress == null) {
deleteComputer(details.name); deleteComputer(details.name);
return null; return null;
} }
return details; return details;
} }
} }

View File

@ -3,5 +3,5 @@ package com.limelight.computers;
import com.limelight.nvstream.http.ComputerDetails; import com.limelight.nvstream.http.ComputerDetails;
public interface ComputerManagerListener { public interface ComputerManagerListener {
public void notifyComputerUpdated(ComputerDetails details); public void notifyComputerUpdated(ComputerDetails details);
} }

View File

@ -29,39 +29,39 @@ import android.os.IBinder;
import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserException;
public class ComputerManagerService extends Service { public class ComputerManagerService extends Service {
private static final int POLLING_PERIOD_MS = 3000; private static final int POLLING_PERIOD_MS = 3000;
private static final int MDNS_QUERY_PERIOD_MS = 1000; private static final int MDNS_QUERY_PERIOD_MS = 1000;
private final ComputerManagerBinder binder = new ComputerManagerBinder(); private final ComputerManagerBinder binder = new ComputerManagerBinder();
private ComputerDatabaseManager dbManager; private ComputerDatabaseManager dbManager;
private final AtomicInteger dbRefCount = new AtomicInteger(0); private final AtomicInteger dbRefCount = new AtomicInteger(0);
private IdentityManager idManager; private IdentityManager idManager;
private final LinkedList<PollingTuple> pollingTuples = new LinkedList<PollingTuple>(); private final LinkedList<PollingTuple> pollingTuples = new LinkedList<PollingTuple>();
private ComputerManagerListener listener = null; private ComputerManagerListener listener = null;
private final AtomicInteger activePolls = new AtomicInteger(0); private final AtomicInteger activePolls = new AtomicInteger(0);
private boolean pollingActive = false; private boolean pollingActive = false;
private DiscoveryService.DiscoveryBinder discoveryBinder; private DiscoveryService.DiscoveryBinder discoveryBinder;
private final ServiceConnection discoveryServiceConnection = new ServiceConnection() { private final ServiceConnection discoveryServiceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder binder) { public void onServiceConnected(ComponentName className, IBinder binder) {
synchronized (discoveryServiceConnection) { synchronized (discoveryServiceConnection) {
DiscoveryService.DiscoveryBinder privateBinder = ((DiscoveryService.DiscoveryBinder)binder); DiscoveryService.DiscoveryBinder privateBinder = ((DiscoveryService.DiscoveryBinder)binder);
// Set us as the event listener // Set us as the event listener
privateBinder.setListener(createDiscoveryListener()); privateBinder.setListener(createDiscoveryListener());
// Signal a possible waiter that we're all setup // Signal a possible waiter that we're all setup
discoveryBinder = privateBinder; discoveryBinder = privateBinder;
discoveryServiceConnection.notifyAll(); discoveryServiceConnection.notifyAll();
} }
} }
public void onServiceDisconnected(ComponentName className) { public void onServiceDisconnected(ComponentName className) {
discoveryBinder = null; discoveryBinder = null;
} }
}; };
// Returns true if the details object was modified // Returns true if the details object was modified
private boolean runPoll(ComputerDetails details, boolean newPc) private boolean runPoll(ComputerDetails details, boolean newPc)
@ -125,16 +125,16 @@ public class ComputerManagerService extends Service {
return t; return t;
} }
public class ComputerManagerBinder extends Binder { public class ComputerManagerBinder extends Binder {
public void startPolling(ComputerManagerListener listener) { public void startPolling(ComputerManagerListener listener) {
// Polling is active // Polling is active
pollingActive = true; pollingActive = true;
// Set the listener // Set the listener
ComputerManagerService.this.listener = listener; ComputerManagerService.this.listener = listener;
// Start mDNS autodiscovery too // Start mDNS autodiscovery too
discoveryBinder.startDiscovery(MDNS_QUERY_PERIOD_MS); discoveryBinder.startDiscovery(MDNS_QUERY_PERIOD_MS);
synchronized (pollingTuples) { synchronized (pollingTuples) {
for (PollingTuple tuple : pollingTuples) { for (PollingTuple tuple : pollingTuples) {
@ -148,48 +148,48 @@ public class ComputerManagerService extends Service {
} }
} }
} }
} }
public void waitForReady() { public void waitForReady() {
synchronized (discoveryServiceConnection) { synchronized (discoveryServiceConnection) {
try { try {
while (discoveryBinder == null) { while (discoveryBinder == null) {
// Wait for the bind notification // Wait for the bind notification
discoveryServiceConnection.wait(1000); discoveryServiceConnection.wait(1000);
} }
} catch (InterruptedException ignored) { } catch (InterruptedException ignored) {
} }
} }
} }
public void waitForPollingStopped() { public void waitForPollingStopped() {
while (activePolls.get() != 0) { while (activePolls.get() != 0) {
try { try {
Thread.sleep(250); Thread.sleep(250);
} catch (InterruptedException ignored) {} } catch (InterruptedException ignored) {}
} }
} }
public boolean addComputerBlocking(InetAddress addr) { public boolean addComputerBlocking(InetAddress addr) {
return ComputerManagerService.this.addComputerBlocking(addr); return ComputerManagerService.this.addComputerBlocking(addr);
} }
public void removeComputer(String name) { public void removeComputer(String name) {
ComputerManagerService.this.removeComputer(name); ComputerManagerService.this.removeComputer(name);
} }
public void stopPolling() { public void stopPolling() {
// Just call the unbind handler to cleanup // Just call the unbind handler to cleanup
ComputerManagerService.this.onUnbind(null); ComputerManagerService.this.onUnbind(null);
} }
public ApplistPoller createAppListPoller(ComputerDetails computer) { public ApplistPoller createAppListPoller(ComputerDetails computer) {
return new ApplistPoller(computer); return new ApplistPoller(computer);
} }
public String getUniqueId() { public String getUniqueId() {
return idManager.getUniqueId(); return idManager.getUniqueId();
} }
public ComputerDetails getComputer(UUID uuid) { public ComputerDetails getComputer(UUID uuid) {
synchronized (pollingTuples) { synchronized (pollingTuples) {
@ -202,14 +202,14 @@ public class ComputerManagerService extends Service {
return null; return null;
} }
} }
@Override @Override
public boolean onUnbind(Intent intent) { public boolean onUnbind(Intent intent) {
// Stop mDNS autodiscovery // Stop mDNS autodiscovery
discoveryBinder.stopDiscovery(); discoveryBinder.stopDiscovery();
// Stop polling // Stop polling
pollingActive = false; pollingActive = false;
synchronized (pollingTuples) { synchronized (pollingTuples) {
for (PollingTuple tuple : pollingTuples) { for (PollingTuple tuple : pollingTuples) {
@ -221,32 +221,32 @@ public class ComputerManagerService extends Service {
} }
} }
// Remove the listener // Remove the listener
listener = null; listener = null;
return false; return false;
} }
private MdnsDiscoveryListener createDiscoveryListener() { private MdnsDiscoveryListener createDiscoveryListener() {
return new MdnsDiscoveryListener() { return new MdnsDiscoveryListener() {
@Override @Override
public void notifyComputerAdded(MdnsComputer computer) { public void notifyComputerAdded(MdnsComputer computer) {
// Kick off a serverinfo poll on this machine // Kick off a serverinfo poll on this machine
addComputerBlocking(computer.getAddress()); addComputerBlocking(computer.getAddress());
} }
@Override @Override
public void notifyComputerRemoved(MdnsComputer computer) { public void notifyComputerRemoved(MdnsComputer computer) {
// Nothing to do here // Nothing to do here
} }
@Override @Override
public void notifyDiscoveryFailure(Exception e) { public void notifyDiscoveryFailure(Exception e) {
LimeLog.severe("mDNS discovery failed"); LimeLog.severe("mDNS discovery failed");
e.printStackTrace(); e.printStackTrace();
} }
}; };
} }
private void addTuple(ComputerDetails details) { private void addTuple(ComputerDetails details) {
synchronized (pollingTuples) { synchronized (pollingTuples) {
@ -278,17 +278,17 @@ public class ComputerManagerService extends Service {
} }
} }
public boolean addComputerBlocking(InetAddress addr) { public boolean addComputerBlocking(InetAddress addr) {
// Setup a placeholder // Setup a placeholder
ComputerDetails fakeDetails = new ComputerDetails(); ComputerDetails fakeDetails = new ComputerDetails();
fakeDetails.localIp = addr; fakeDetails.localIp = addr;
fakeDetails.remoteIp = addr; fakeDetails.remoteIp = addr;
// Block while we try to fill the details // Block while we try to fill the details
runPoll(fakeDetails, true); runPoll(fakeDetails, true);
// If the machine is reachable, it was successful // If the machine is reachable, it was successful
if (fakeDetails.state == ComputerDetails.State.ONLINE) { if (fakeDetails.state == ComputerDetails.State.ONLINE) {
LimeLog.info("New PC ("+fakeDetails.name+") is UUID "+fakeDetails.uuid); LimeLog.info("New PC ("+fakeDetails.name+") is UUID "+fakeDetails.uuid);
// Start a polling thread for this machine // Start a polling thread for this machine
@ -298,15 +298,15 @@ public class ComputerManagerService extends Service {
else { else {
return false; return false;
} }
} }
public void removeComputer(String name) { public void removeComputer(String name) {
if (!getLocalDatabaseReference()) { if (!getLocalDatabaseReference()) {
return; return;
} }
// Remove it from the database // Remove it from the database
dbManager.deleteComputer(name); dbManager.deleteComputer(name);
synchronized (pollingTuples) { synchronized (pollingTuples) {
// Remove the computer from the computer list // Remove the computer from the computer list
@ -322,30 +322,30 @@ public class ComputerManagerService extends Service {
} }
} }
releaseLocalDatabaseReference(); releaseLocalDatabaseReference();
} }
private boolean getLocalDatabaseReference() { private boolean getLocalDatabaseReference() {
if (dbRefCount.get() == 0) { if (dbRefCount.get() == 0) {
return false; return false;
} }
dbRefCount.incrementAndGet(); dbRefCount.incrementAndGet();
return true; return true;
} }
private void releaseLocalDatabaseReference() { private void releaseLocalDatabaseReference() {
if (dbRefCount.decrementAndGet() == 0) { if (dbRefCount.decrementAndGet() == 0) {
dbManager.close(); dbManager.close();
} }
} }
private ComputerDetails tryPollIp(ComputerDetails details, InetAddress ipAddr) { private ComputerDetails tryPollIp(ComputerDetails details, InetAddress ipAddr) {
try { try {
NvHTTP http = new NvHTTP(ipAddr, idManager.getUniqueId(), NvHTTP http = new NvHTTP(ipAddr, idManager.getUniqueId(),
null, PlatformBinding.getCryptoProvider(ComputerManagerService.this)); null, PlatformBinding.getCryptoProvider(ComputerManagerService.this));
ComputerDetails newDetails = http.getComputerDetails(); ComputerDetails newDetails = http.getComputerDetails();
// Check if this is the PC we expected // Check if this is the PC we expected
if (details.uuid != null && newDetails.uuid != null && if (details.uuid != null && newDetails.uuid != null &&
@ -356,13 +356,13 @@ public class ComputerManagerService extends Service {
} }
return newDetails; return newDetails;
} catch (Exception e) { } catch (Exception e) {
return null; return null;
} }
} }
private boolean pollComputer(ComputerDetails details, boolean localFirst) { private boolean pollComputer(ComputerDetails details, boolean localFirst) {
ComputerDetails polledDetails; ComputerDetails polledDetails;
// If the local address is routable across the Internet, // If the local address is routable across the Internet,
// always consider this PC remote to be conservative // always consider this PC remote to be conservative
@ -370,44 +370,44 @@ public class ComputerManagerService extends Service {
localFirst = false; localFirst = false;
} }
if (localFirst) { if (localFirst) {
polledDetails = tryPollIp(details, details.localIp); polledDetails = tryPollIp(details, details.localIp);
} }
else { else {
polledDetails = tryPollIp(details, details.remoteIp); polledDetails = tryPollIp(details, details.remoteIp);
} }
if (polledDetails == null && !details.localIp.equals(details.remoteIp)) { if (polledDetails == null && !details.localIp.equals(details.remoteIp)) {
// Failed, so let's try the fallback // Failed, so let's try the fallback
if (!localFirst) { if (!localFirst) {
polledDetails = tryPollIp(details, details.localIp); polledDetails = tryPollIp(details, details.localIp);
} }
else { else {
polledDetails = tryPollIp(details, details.remoteIp); polledDetails = tryPollIp(details, details.remoteIp);
} }
// The fallback poll worked // The fallback poll worked
if (polledDetails != null) { if (polledDetails != null) {
polledDetails.reachability = !localFirst ? ComputerDetails.Reachability.LOCAL : polledDetails.reachability = !localFirst ? ComputerDetails.Reachability.LOCAL :
ComputerDetails.Reachability.REMOTE; ComputerDetails.Reachability.REMOTE;
} }
} }
else if (polledDetails != null) { else if (polledDetails != null) {
polledDetails.reachability = localFirst ? ComputerDetails.Reachability.LOCAL : polledDetails.reachability = localFirst ? ComputerDetails.Reachability.LOCAL :
ComputerDetails.Reachability.REMOTE; ComputerDetails.Reachability.REMOTE;
} }
// Machine was unreachable both tries // Machine was unreachable both tries
if (polledDetails == null) { if (polledDetails == null) {
return false; return false;
} }
// If we got here, it's reachable // If we got here, it's reachable
details.update(polledDetails); details.update(polledDetails);
return true; return true;
} }
private boolean doPollMachine(ComputerDetails details) { private boolean doPollMachine(ComputerDetails details) {
if (details.reachability == ComputerDetails.Reachability.UNKNOWN || if (details.reachability == ComputerDetails.Reachability.UNKNOWN ||
details.reachability == ComputerDetails.Reachability.OFFLINE) { details.reachability == ComputerDetails.Reachability.OFFLINE) {
// Always try local first to avoid potential UDP issues when // Always try local first to avoid potential UDP issues when
@ -420,20 +420,20 @@ public class ComputerManagerService extends Service {
// always try that one first // always try that one first
return pollComputer(details, details.reachability == ComputerDetails.Reachability.LOCAL); return pollComputer(details, details.reachability == ComputerDetails.Reachability.LOCAL);
} }
} }
@Override @Override
public void onCreate() { public void onCreate() {
// Bind to the discovery service // Bind to the discovery service
bindService(new Intent(this, DiscoveryService.class), bindService(new Intent(this, DiscoveryService.class),
discoveryServiceConnection, Service.BIND_AUTO_CREATE); discoveryServiceConnection, Service.BIND_AUTO_CREATE);
// Lookup or generate this device's UID // Lookup or generate this device's UID
idManager = new IdentityManager(this); idManager = new IdentityManager(this);
// Initialize the DB // Initialize the DB
dbManager = new ComputerDatabaseManager(this); dbManager = new ComputerDatabaseManager(this);
dbRefCount.set(1); dbRefCount.set(1);
// Grab known machines into our computer list // Grab known machines into our computer list
if (!getLocalDatabaseReference()) { if (!getLocalDatabaseReference()) {
@ -446,25 +446,25 @@ public class ComputerManagerService extends Service {
} }
releaseLocalDatabaseReference(); releaseLocalDatabaseReference();
} }
@Override @Override
public void onDestroy() { public void onDestroy() {
if (discoveryBinder != null) { if (discoveryBinder != null) {
// Unbind from the discovery service // Unbind from the discovery service
unbindService(discoveryServiceConnection); unbindService(discoveryServiceConnection);
} }
// FIXME: Should await termination here but we have timeout issues in HttpURLConnection // FIXME: Should await termination here but we have timeout issues in HttpURLConnection
// Remove the initial DB reference // Remove the initial DB reference
releaseLocalDatabaseReference(); releaseLocalDatabaseReference();
} }
@Override @Override
public IBinder onBind(Intent intent) { public IBinder onBind(Intent intent) {
return binder; return binder;
} }
public class ApplistPoller { public class ApplistPoller {
private Thread thread; private Thread thread;

View File

@ -12,75 +12,75 @@ import com.limelight.LimeLog;
import android.content.Context; import android.content.Context;
public class IdentityManager { public class IdentityManager {
private static final String UNIQUE_ID_FILE_NAME = "uniqueid"; private static final String UNIQUE_ID_FILE_NAME = "uniqueid";
private static final int UID_SIZE_IN_BYTES = 8; private static final int UID_SIZE_IN_BYTES = 8;
private String uniqueId; private String uniqueId;
public IdentityManager(Context c) { public IdentityManager(Context c) {
uniqueId = loadUniqueId(c); uniqueId = loadUniqueId(c);
if (uniqueId == null) { if (uniqueId == null) {
uniqueId = generateNewUniqueId(c); uniqueId = generateNewUniqueId(c);
} }
LimeLog.info("UID is now: "+uniqueId); LimeLog.info("UID is now: "+uniqueId);
} }
public String getUniqueId() { public String getUniqueId() {
return uniqueId; return uniqueId;
} }
private static String loadUniqueId(Context c) { private static String loadUniqueId(Context c) {
// 2 Hex digits per byte // 2 Hex digits per byte
char[] uid = new char[UID_SIZE_IN_BYTES * 2]; char[] uid = new char[UID_SIZE_IN_BYTES * 2];
InputStreamReader reader = null; InputStreamReader reader = null;
LimeLog.info("Reading UID from disk"); LimeLog.info("Reading UID from disk");
try { try {
reader = new InputStreamReader(c.openFileInput(UNIQUE_ID_FILE_NAME)); reader = new InputStreamReader(c.openFileInput(UNIQUE_ID_FILE_NAME));
if (reader.read(uid) != UID_SIZE_IN_BYTES * 2) if (reader.read(uid) != UID_SIZE_IN_BYTES * 2)
{ {
LimeLog.severe("UID file data is truncated"); LimeLog.severe("UID file data is truncated");
return null; return null;
} }
return new String(uid); return new String(uid);
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
LimeLog.info("No UID file found"); LimeLog.info("No UID file found");
return null; return null;
} catch (IOException e) { } catch (IOException e) {
LimeLog.severe("Error while reading UID file"); LimeLog.severe("Error while reading UID file");
e.printStackTrace(); e.printStackTrace();
return null; return null;
} finally { } finally {
if (reader != null) { if (reader != null) {
try { try {
reader.close(); reader.close();
} catch (IOException ignored) {} } catch (IOException ignored) {}
} }
} }
} }
private static String generateNewUniqueId(Context c) { private static String generateNewUniqueId(Context c) {
// Generate a new UID hex string // Generate a new UID hex string
LimeLog.info("Generating new UID"); LimeLog.info("Generating new UID");
String uidStr = String.format((Locale)null, "%016x", new Random().nextLong()); String uidStr = String.format((Locale)null, "%016x", new Random().nextLong());
OutputStreamWriter writer = null; OutputStreamWriter writer = null;
try { try {
writer = new OutputStreamWriter(c.openFileOutput(UNIQUE_ID_FILE_NAME, 0)); writer = new OutputStreamWriter(c.openFileOutput(UNIQUE_ID_FILE_NAME, 0));
writer.write(uidStr); writer.write(uidStr);
LimeLog.info("UID written to disk"); LimeLog.info("UID written to disk");
} catch (IOException e) { } catch (IOException e) {
LimeLog.severe("Error while writing UID file"); LimeLog.severe("Error while writing UID file");
e.printStackTrace(); e.printStackTrace();
} finally { } finally {
if (writer != null) { if (writer != null) {
try { try {
writer.close(); writer.close();
} catch (IOException ignored) {} } catch (IOException ignored) {}
} }
} }
// We can return a UID even if I/O fails // We can return a UID even if I/O fails
return uidStr; return uidStr;
} }
} }

View File

@ -16,75 +16,75 @@ import android.os.IBinder;
public class DiscoveryService extends Service { public class DiscoveryService extends Service {
private MdnsDiscoveryAgent discoveryAgent; private MdnsDiscoveryAgent discoveryAgent;
private MdnsDiscoveryListener boundListener; private MdnsDiscoveryListener boundListener;
private MulticastLock multicastLock; private MulticastLock multicastLock;
public class DiscoveryBinder extends Binder { public class DiscoveryBinder extends Binder {
public void setListener(MdnsDiscoveryListener listener) { public void setListener(MdnsDiscoveryListener listener) {
boundListener = listener; boundListener = listener;
} }
public void startDiscovery(int queryIntervalMs) { public void startDiscovery(int queryIntervalMs) {
multicastLock.acquire(); multicastLock.acquire();
discoveryAgent.startDiscovery(queryIntervalMs); discoveryAgent.startDiscovery(queryIntervalMs);
} }
public void stopDiscovery() { public void stopDiscovery() {
discoveryAgent.stopDiscovery(); discoveryAgent.stopDiscovery();
multicastLock.release(); multicastLock.release();
} }
public List<MdnsComputer> getComputerSet() { public List<MdnsComputer> getComputerSet() {
return discoveryAgent.getComputerSet(); return discoveryAgent.getComputerSet();
} }
} }
@Override @Override
public void onCreate() { public void onCreate() {
WifiManager wifiMgr = (WifiManager) getSystemService(Context.WIFI_SERVICE); WifiManager wifiMgr = (WifiManager) getSystemService(Context.WIFI_SERVICE);
multicastLock = wifiMgr.createMulticastLock("Limelight mDNS"); multicastLock = wifiMgr.createMulticastLock("Limelight mDNS");
multicastLock.setReferenceCounted(false); multicastLock.setReferenceCounted(false);
discoveryAgent = new MdnsDiscoveryAgent(new MdnsDiscoveryListener() { discoveryAgent = new MdnsDiscoveryAgent(new MdnsDiscoveryListener() {
@Override @Override
public void notifyComputerAdded(MdnsComputer computer) { public void notifyComputerAdded(MdnsComputer computer) {
if (boundListener != null) { if (boundListener != null) {
boundListener.notifyComputerAdded(computer); boundListener.notifyComputerAdded(computer);
} }
} }
@Override @Override
public void notifyComputerRemoved(MdnsComputer computer) { public void notifyComputerRemoved(MdnsComputer computer) {
if (boundListener != null) { if (boundListener != null) {
boundListener.notifyComputerRemoved(computer); boundListener.notifyComputerRemoved(computer);
} }
} }
@Override @Override
public void notifyDiscoveryFailure(Exception e) { public void notifyDiscoveryFailure(Exception e) {
if (boundListener != null) { if (boundListener != null) {
boundListener.notifyDiscoveryFailure(e); boundListener.notifyDiscoveryFailure(e);
} }
} }
}); });
} }
private final DiscoveryBinder binder = new DiscoveryBinder(); private final DiscoveryBinder binder = new DiscoveryBinder();
@Override @Override
public IBinder onBind(Intent intent) { public IBinder onBind(Intent intent) {
return binder; return binder;
} }
@Override @Override
public boolean onUnbind(Intent intent) { public boolean onUnbind(Intent intent) {
// Stop any discovery session // Stop any discovery session
discoveryAgent.stopDiscovery(); discoveryAgent.stopDiscovery();
multicastLock.release(); multicastLock.release();
// Unbind the listener // Unbind the listener
boundListener = null; boundListener = null;
return false; return false;
} }
} }

View File

@ -1,44 +1,44 @@
package com.limelight.nvstream.av.video.cpu; package com.limelight.nvstream.av.video.cpu;
public class AvcDecoder { public class AvcDecoder {
static { static {
// FFMPEG dependencies // FFMPEG dependencies
System.loadLibrary("avutil-52"); System.loadLibrary("avutil-52");
System.loadLibrary("swresample-0"); System.loadLibrary("swresample-0");
System.loadLibrary("swscale-2"); System.loadLibrary("swscale-2");
System.loadLibrary("avcodec-55"); System.loadLibrary("avcodec-55");
System.loadLibrary("avformat-55"); System.loadLibrary("avformat-55");
System.loadLibrary("nv_avc_dec"); System.loadLibrary("nv_avc_dec");
} }
/** Disables the deblocking filter at the cost of image quality */ /** Disables the deblocking filter at the cost of image quality */
public static final int DISABLE_LOOP_FILTER = 0x1; public static final int DISABLE_LOOP_FILTER = 0x1;
/** Uses the low latency decode flag (disables multithreading) */ /** Uses the low latency decode flag (disables multithreading) */
public static final int LOW_LATENCY_DECODE = 0x2; public static final int LOW_LATENCY_DECODE = 0x2;
/** Threads process each slice, rather than each frame */ /** Threads process each slice, rather than each frame */
public static final int SLICE_THREADING = 0x4; public static final int SLICE_THREADING = 0x4;
/** Uses nonstandard speedup tricks */ /** Uses nonstandard speedup tricks */
public static final int FAST_DECODE = 0x8; public static final int FAST_DECODE = 0x8;
/** Uses bilinear filtering instead of bicubic */ /** Uses bilinear filtering instead of bicubic */
public static final int BILINEAR_FILTERING = 0x10; public static final int BILINEAR_FILTERING = 0x10;
/** Uses a faster bilinear filtering with lower image quality */ /** Uses a faster bilinear filtering with lower image quality */
public static final int FAST_BILINEAR_FILTERING = 0x20; public static final int FAST_BILINEAR_FILTERING = 0x20;
/** Disables color conversion (output is NV21) */ /** Disables color conversion (output is NV21) */
public static final int NO_COLOR_CONVERSION = 0x40; public static final int NO_COLOR_CONVERSION = 0x40;
public static native int init(int width, int height, int perflvl, int threadcount); public static native int init(int width, int height, int perflvl, int threadcount);
public static native void destroy(); public static native void destroy();
// Rendering API when NO_COLOR_CONVERSION == 0 // Rendering API when NO_COLOR_CONVERSION == 0
public static native boolean setRenderTarget(Object androidSurface); public static native boolean setRenderTarget(Object androidSurface);
public static native boolean getRgbFrameInt(int[] rgbFrame, int bufferSize); public static native boolean getRgbFrameInt(int[] rgbFrame, int bufferSize);
public static native boolean getRgbFrame(byte[] rgbFrame, int bufferSize); public static native boolean getRgbFrame(byte[] rgbFrame, int bufferSize);
public static native boolean redraw(); public static native boolean redraw();
// Rendering API when NO_COLOR_CONVERSION == 1 // Rendering API when NO_COLOR_CONVERSION == 1
public static native boolean getRawFrame(byte[] yuvFrame, int bufferSize); public static native boolean getRawFrame(byte[] yuvFrame, int bufferSize);
public static native int getInputPaddingSize(); public static native int getInputPaddingSize();
public static native int decode(byte[] indata, int inoff, int inlen); public static native int decode(byte[] indata, int inoff, int inlen);
} }

View File

@ -25,127 +25,127 @@ import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
public class AddComputerManually extends Activity { public class AddComputerManually extends Activity {
private TextView hostText; private TextView hostText;
private ComputerManagerService.ComputerManagerBinder managerBinder; private ComputerManagerService.ComputerManagerBinder managerBinder;
private final LinkedBlockingQueue<String> computersToAdd = new LinkedBlockingQueue<String>(); private final LinkedBlockingQueue<String> computersToAdd = new LinkedBlockingQueue<String>();
private Thread addThread; private Thread addThread;
private final ServiceConnection serviceConnection = new ServiceConnection() { private final ServiceConnection serviceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, final IBinder binder) { public void onServiceConnected(ComponentName className, final IBinder binder) {
managerBinder = ((ComputerManagerService.ComputerManagerBinder)binder); managerBinder = ((ComputerManagerService.ComputerManagerBinder)binder);
startAddThread(); startAddThread();
} }
public void onServiceDisconnected(ComponentName className) { public void onServiceDisconnected(ComponentName className) {
joinAddThread(); joinAddThread();
managerBinder = null; managerBinder = null;
} }
}; };
private void doAddPc(String host) { private void doAddPc(String host) {
String msg; String msg;
boolean finish = false; boolean finish = false;
SpinnerDialog dialog = SpinnerDialog.displayDialog(this, getResources().getString(R.string.title_add_pc), 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 { try {
InetAddress addr = InetAddress.getByName(host); InetAddress addr = InetAddress.getByName(host);
if (!managerBinder.addComputerBlocking(addr)){ if (!managerBinder.addComputerBlocking(addr)){
msg = getResources().getString(R.string.addpc_fail); msg = getResources().getString(R.string.addpc_fail);
} }
else { else {
msg = getResources().getString(R.string.addpc_success); msg = getResources().getString(R.string.addpc_success);
finish = true; finish = true;
} }
} catch (UnknownHostException e) { } catch (UnknownHostException e) {
msg = getResources().getString(R.string.addpc_unknown_host); msg = getResources().getString(R.string.addpc_unknown_host);
} }
dialog.dismiss(); dialog.dismiss();
final boolean toastFinish = finish; final boolean toastFinish = finish;
final String toastMsg = msg; final String toastMsg = msg;
AddComputerManually.this.runOnUiThread(new Runnable() { AddComputerManually.this.runOnUiThread(new Runnable() {
@Override @Override
public void run() { public void run() {
Toast.makeText(AddComputerManually.this, toastMsg, Toast.LENGTH_LONG).show(); Toast.makeText(AddComputerManually.this, toastMsg, Toast.LENGTH_LONG).show();
if (toastFinish && !isFinishing()) { if (toastFinish && !isFinishing()) {
// Close the activity // Close the activity
AddComputerManually.this.finish(); AddComputerManually.this.finish();
} }
} }
}); });
} }
private void startAddThread() { private void startAddThread() {
addThread = new Thread() { addThread = new Thread() {
@Override @Override
public void run() { public void run() {
while (!isInterrupted()) { while (!isInterrupted()) {
String computer; String computer;
try { try {
computer = computersToAdd.take(); computer = computersToAdd.take();
} catch (InterruptedException e) { } catch (InterruptedException e) {
return; return;
} }
doAddPc(computer); doAddPc(computer);
} }
} }
}; };
addThread.setName("UI - AddComputerManually"); addThread.setName("UI - AddComputerManually");
addThread.start(); addThread.start();
} }
private void joinAddThread() { private void joinAddThread() {
if (addThread != null) { if (addThread != null) {
addThread.interrupt(); addThread.interrupt();
try { try {
addThread.join(); addThread.join();
} catch (InterruptedException ignored) {} } catch (InterruptedException ignored) {}
addThread = null; addThread = null;
} }
} }
@Override @Override
protected void onStop() { protected void onStop() {
super.onStop(); super.onStop();
Dialog.closeDialogs(); Dialog.closeDialogs();
SpinnerDialog.closeDialogs(this); SpinnerDialog.closeDialogs(this);
} }
@Override @Override
protected void onDestroy() { protected void onDestroy() {
super.onDestroy(); super.onDestroy();
if (managerBinder != null) { if (managerBinder != null) {
joinAddThread(); joinAddThread();
unbindService(serviceConnection); unbindService(serviceConnection);
} }
} }
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
String locale = PreferenceConfiguration.readPreferences(this).language; String locale = PreferenceConfiguration.readPreferences(this).language;
if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) { if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
Configuration config = new Configuration(getResources().getConfiguration()); Configuration config = new Configuration(getResources().getConfiguration());
config.locale = new Locale(locale); config.locale = new Locale(locale);
getResources().updateConfiguration(config, getResources().getDisplayMetrics()); getResources().updateConfiguration(config, getResources().getDisplayMetrics());
} }
setContentView(R.layout.activity_add_computer_manually); setContentView(R.layout.activity_add_computer_manually);
UiHelper.notifyNewRootView(this); UiHelper.notifyNewRootView(this);
this.hostText = (TextView) findViewById(R.id.hostTextView); this.hostText = (TextView) findViewById(R.id.hostTextView);
hostText.setImeOptions(EditorInfo.IME_ACTION_DONE); hostText.setImeOptions(EditorInfo.IME_ACTION_DONE);
hostText.setOnEditorActionListener(new TextView.OnEditorActionListener() { hostText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override @Override
@ -166,8 +166,8 @@ public class AddComputerManually extends Activity {
} }
}); });
// Bind to the ComputerManager service // Bind to the ComputerManager service
bindService(new Intent(AddComputerManually.this, bindService(new Intent(AddComputerManually.this,
ComputerManagerService.class), serviceConnection, Service.BIND_AUTO_CREATE); ComputerManagerService.class), serviceConnection, Service.BIND_AUTO_CREATE);
} }
} }

View File

@ -7,71 +7,71 @@ import android.app.AlertDialog;
import android.content.DialogInterface; import android.content.DialogInterface;
public class Dialog implements Runnable { public class Dialog implements Runnable {
private final String title; private final String title;
private final String message; private final String message;
private final Activity activity; private final Activity activity;
private final boolean endAfterDismiss; private final boolean endAfterDismiss;
private AlertDialog alert; private AlertDialog alert;
private static final ArrayList<Dialog> rundownDialogs = new ArrayList<Dialog>(); private static final ArrayList<Dialog> rundownDialogs = new ArrayList<Dialog>();
private Dialog(Activity activity, String title, String message, boolean endAfterDismiss) private Dialog(Activity activity, String title, String message, boolean endAfterDismiss)
{ {
this.activity = activity; this.activity = activity;
this.title = title; this.title = title;
this.message = message; this.message = message;
this.endAfterDismiss = endAfterDismiss; this.endAfterDismiss = endAfterDismiss;
} }
public static void closeDialogs() public static void closeDialogs()
{ {
synchronized (rundownDialogs) { synchronized (rundownDialogs) {
for (Dialog d : rundownDialogs) { for (Dialog d : rundownDialogs) {
if (d.alert.isShowing()) { if (d.alert.isShowing()) {
d.alert.dismiss(); d.alert.dismiss();
} }
} }
rundownDialogs.clear(); rundownDialogs.clear();
} }
} }
public static void displayDialog(Activity activity, String title, String message, boolean endAfterDismiss) public static void displayDialog(Activity activity, String title, String message, boolean endAfterDismiss)
{ {
activity.runOnUiThread(new Dialog(activity, title, message, endAfterDismiss)); activity.runOnUiThread(new Dialog(activity, title, message, endAfterDismiss));
} }
@Override @Override
public void run() { public void run() {
// If we're dying, don't bother creating a dialog // If we're dying, don't bother creating a dialog
if (activity.isFinishing()) if (activity.isFinishing())
return; return;
alert = new AlertDialog.Builder(activity).create(); alert = new AlertDialog.Builder(activity).create();
alert.setTitle(title); alert.setTitle(title);
alert.setMessage(message); alert.setMessage(message);
alert.setCancelable(false); alert.setCancelable(false);
alert.setCanceledOnTouchOutside(false); alert.setCanceledOnTouchOutside(false);
alert.setButton(AlertDialog.BUTTON_NEUTRAL, "OK", new DialogInterface.OnClickListener() { alert.setButton(AlertDialog.BUTTON_NEUTRAL, "OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
synchronized (rundownDialogs) { synchronized (rundownDialogs) {
rundownDialogs.remove(Dialog.this); rundownDialogs.remove(Dialog.this);
alert.dismiss(); alert.dismiss();
} }
if (endAfterDismiss) { if (endAfterDismiss) {
activity.finish(); activity.finish();
} }
} }
}); });
synchronized (rundownDialogs) { synchronized (rundownDialogs) {
rundownDialogs.add(this); rundownDialogs.add(this);
alert.show(); alert.show();
} }
} }
} }

View File

@ -9,112 +9,112 @@ import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener; import android.content.DialogInterface.OnCancelListener;
public class SpinnerDialog implements Runnable,OnCancelListener { public class SpinnerDialog implements Runnable,OnCancelListener {
private final String title; private final String title;
private final String message; private final String message;
private final Activity activity; private final Activity activity;
private ProgressDialog progress; private ProgressDialog progress;
private final boolean finish; private final boolean finish;
private static final ArrayList<SpinnerDialog> rundownDialogs = new ArrayList<SpinnerDialog>(); private static final ArrayList<SpinnerDialog> rundownDialogs = new ArrayList<SpinnerDialog>();
private SpinnerDialog(Activity activity, String title, String message, boolean finish) private SpinnerDialog(Activity activity, String title, String message, boolean finish)
{ {
this.activity = activity; this.activity = activity;
this.title = title; this.title = title;
this.message = message; this.message = message;
this.progress = null; this.progress = null;
this.finish = finish; this.finish = finish;
} }
public static SpinnerDialog displayDialog(Activity activity, String title, String message, boolean finish) public static SpinnerDialog displayDialog(Activity activity, String title, String message, boolean finish)
{ {
SpinnerDialog spinner = new SpinnerDialog(activity, title, message, finish); SpinnerDialog spinner = new SpinnerDialog(activity, title, message, finish);
activity.runOnUiThread(spinner); activity.runOnUiThread(spinner);
return spinner; return spinner;
} }
public static void closeDialogs(Activity activity) public static void closeDialogs(Activity activity)
{ {
synchronized (rundownDialogs) { synchronized (rundownDialogs) {
Iterator<SpinnerDialog> i = rundownDialogs.iterator(); Iterator<SpinnerDialog> i = rundownDialogs.iterator();
while (i.hasNext()) { while (i.hasNext()) {
SpinnerDialog dialog = i.next(); SpinnerDialog dialog = i.next();
if (dialog.activity == activity) { if (dialog.activity == activity) {
i.remove(); i.remove();
if (dialog.progress.isShowing()) { if (dialog.progress.isShowing()) {
dialog.progress.dismiss(); dialog.progress.dismiss();
} }
} }
} }
} }
} }
public void dismiss() public void dismiss()
{ {
// Running again with progress != null will destroy it // Running again with progress != null will destroy it
activity.runOnUiThread(this); activity.runOnUiThread(this);
} }
public void setMessage(final String message) public void setMessage(final String message)
{ {
activity.runOnUiThread(new Runnable() { activity.runOnUiThread(new Runnable() {
@Override @Override
public void run() { public void run() {
progress.setMessage(message); progress.setMessage(message);
} }
}); });
} }
@Override @Override
public void run() { public void run() {
// If we're dying, don't bother doing anything // If we're dying, don't bother doing anything
if (activity.isFinishing()) { if (activity.isFinishing()) {
return; return;
} }
if (progress == null) if (progress == null)
{ {
progress = new ProgressDialog(activity); progress = new ProgressDialog(activity);
progress.setTitle(title); progress.setTitle(title);
progress.setMessage(message); progress.setMessage(message);
progress.setProgressStyle(ProgressDialog.STYLE_SPINNER); progress.setProgressStyle(ProgressDialog.STYLE_SPINNER);
progress.setOnCancelListener(this); progress.setOnCancelListener(this);
// If we want to finish the activity when this is killed, make it cancellable // If we want to finish the activity when this is killed, make it cancellable
if (finish) if (finish)
{ {
progress.setCancelable(true); progress.setCancelable(true);
progress.setCanceledOnTouchOutside(false); progress.setCanceledOnTouchOutside(false);
} }
else else
{ {
progress.setCancelable(false); progress.setCancelable(false);
} }
synchronized (rundownDialogs) { synchronized (rundownDialogs) {
rundownDialogs.add(this); rundownDialogs.add(this);
progress.show(); progress.show();
} }
} }
else else
{ {
synchronized (rundownDialogs) { synchronized (rundownDialogs) {
if (rundownDialogs.remove(this) && progress.isShowing()) { if (rundownDialogs.remove(this) && progress.isShowing()) {
progress.dismiss(); progress.dismiss();
} }
} }
} }
} }
@Override @Override
public void onCancel(DialogInterface dialog) { public void onCancel(DialogInterface dialog) {
synchronized (rundownDialogs) { synchronized (rundownDialogs) {
rundownDialogs.remove(this); rundownDialogs.remove(this);
} }
// This will only be called if finish was true, so we don't need to check again // This will only be called if finish was true, so we don't need to check again
activity.finish(); activity.finish();
} }
} }