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() {
@ -183,33 +183,33 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
managerBinder.stopPolling(); managerBinder.stopPolling();
} }
} }
@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 {
@ -233,25 +233,25 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
blockingLoadSpinner = SpinnerDialog.displayDialog(this, getResources().getString(R.string.applist_refresh_title), blockingLoadSpinner = SpinnerDialog.displayDialog(this, getResources().getString(R.string.applist_refresh_title),
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() {
@ -259,49 +259,49 @@ 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
new Thread() {
@Override
public void run() {
// Wait for the binder to be ready
localBinder.waitForReady();
// Now make the binder visible
managerBinder = localBinder;
// Start updates
startComputerUpdates();
// Force a keypair to be generated early to avoid discovery delays
new AndroidCryptoProvider(PcView.this).getClientCertificate();
}
}.start();
}
public void onServiceDisconnected(ComponentName className) { // Wait in a separate thread to avoid stalling the UI
managerBinder = null; new Thread() {
} @Override
}; public void run() {
// Wait for the binder to be ready
public void onConfigurationChanged(Configuration newConfig) { localBinder.waitForReady();
super.onConfigurationChanged(newConfig);
// Now make the binder visible
// Reinitialize views just in case orientation changed managerBinder = localBinder;
initializeViews();
} // Start updates
startComputerUpdates();
private final static int APP_LIST_ID = 1;
private final static int PAIR_ID = 2; // Force a keypair to be generated early to avoid discovery delays
private final static int UNPAIR_ID = 3; new AndroidCryptoProvider(PcView.this).getClientCertificate();
private final static int WOL_ID = 4; }
private final static int DELETE_ID = 5; }.start();
}
private void initializeViews() {
setContentView(R.layout.activity_pc_view); public void onServiceDisconnected(ComponentName className) {
managerBinder = null;
}
};
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// Reinitialize views just in case orientation changed
initializeViews();
}
private final static int APP_LIST_ID = 1;
private final static int PAIR_ID = 2;
private final static int UNPAIR_ID = 3;
private final static int WOL_ID = 4;
private final static int DELETE_ID = 5;
private void initializeViews() {
setContentView(R.layout.activity_pc_view);
UiHelper.notifyNewRootView(this); 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);
@ -133,114 +133,114 @@ 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
@ -260,94 +260,94 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
menu.add(Menu.NONE, DELETE_ID, 2, getResources().getString(R.string.pcview_menu_delete_pc)); menu.add(Menu.NONE, DELETE_ID, 2, getResources().getString(R.string.pcview_menu_delete_pc));
} }
} }
@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,75 +482,75 @@ 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);
} }
} }
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;
@Override private AudioTrack track;
public boolean streamInitialized(int channelCount, int sampleRate) {
int channelConfig;
int bufferSize;
switch (channelCount) @Override
{ public boolean streamInitialized(int channelCount, int sampleRate) {
case 1: int channelConfig;
channelConfig = AudioFormat.CHANNEL_OUT_MONO; int bufferSize;
break;
case 2: switch (channelCount)
channelConfig = AudioFormat.CHANNEL_OUT_STEREO; {
break; case 1:
default: channelConfig = AudioFormat.CHANNEL_OUT_MONO;
LimeLog.severe("Decoder returned unhandled channel count"); break;
return false; case 2:
} channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
break;
default:
LimeLog.severe("Decoder returned unhandled channel count");
return false;
}
// We're not supposed to request less than the minimum // 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
@ -72,26 +72,26 @@ public class AndroidAudioRenderer implements AudioRenderer {
AudioTrack.MODE_STREAM); AudioTrack.MODE_STREAM);
track.play(); track.play();
} }
LimeLog.info("Audio track buffer size: "+bufferSize);
return true; LimeLog.info("Audio track buffer size: "+bufferSize);
}
@Override return true;
public void playDecodedAudio(byte[] audioData, int offset, int length) { }
track.write(audioData, offset, length);
}
@Override @Override
public void streamClosing() { public void playDecodedAudio(byte[] audioData, int offset, int length) {
if (track != null) { track.write(audioData, offset, length);
track.release(); }
}
}
@Override @Override
public int getCapabilities() { public void streamClosing() {
return 0; if (track != null) {
} track.release();
}
}
@Override
public int getCapabilities() {
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() {
byte[] certBytes = loadFileToBytes(certFile);
byte[] keyBytes = loadFileToBytes(keyFile);
// If either file was missing, we definitely can't succeed
if (certBytes == null || keyBytes == null) {
LimeLog.info("Missing cert or key; need to generate a new one");
return false;
}
try {
CertificateFactory certFactory = CertificateFactory.getInstance("X.509", "BC");
cert = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(certBytes));
pemCertBytes = certBytes;
KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC");
key = (RSAPrivateKey) keyFactory.generatePrivate(new PKCS8EncodedKeySpec(keyBytes));
} catch (CertificateException e) {
// May happen if the cert is corrupt
LimeLog.warning("Corrupted certificate");
return false;
} catch (NoSuchAlgorithmException e) {
// Should never happen
e.printStackTrace();
return false;
} catch (InvalidKeySpecException e) {
// May happen if the key is corrupt
LimeLog.warning("Corrupted key");
return false;
} catch (NoSuchProviderException e) {
// Should never happen
e.printStackTrace();
return false;
}
return true;
}
@SuppressLint("TrulyRandom")
private boolean generateCertKeyPair() {
byte[] snBytes = new byte[8];
new SecureRandom().nextBytes(snBytes);
KeyPair keyPair;
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC");
keyPairGenerator.initialize(2048);
keyPair = keyPairGenerator.generateKeyPair();
} catch (NoSuchAlgorithmException e1) {
// Should never happen
e1.printStackTrace();
return false;
} catch (NoSuchProviderException e) {
// Should never happen
e.printStackTrace();
return false;
}
Date now = new Date();
// Expires in 20 years
Calendar calendar = Calendar.getInstance();
calendar.setTime(now);
calendar.add(Calendar.YEAR, 20);
Date expirationDate = calendar.getTime();
BigInteger serial = new BigInteger(snBytes).abs();
X500NameBuilder nameBuilder = new X500NameBuilder(BCStyle.INSTANCE);
nameBuilder.addRDN(BCStyle.CN, "NVIDIA GameStream Client");
X500Name name = nameBuilder.build();
X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder(name, serial, now, expirationDate, Locale.ENGLISH, name,
SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded()));
try {
ContentSigner sigGen = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BouncyCastleProvider.PROVIDER_NAME).build(keyPair.getPrivate());
cert = new JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME).getCertificate(certBuilder.build(sigGen));
key = (RSAPrivateKey) keyPair.getPrivate();
} catch (Exception e) {
// Nothing should go wrong here
e.printStackTrace();
return false;
}
LimeLog.info("Generated a new key pair");
// Save the resulting pair
saveCertKeyPair();
return true;
}
private void saveCertKeyPair() {
try {
FileOutputStream certOut = new FileOutputStream(certFile);
FileOutputStream keyOut = new FileOutputStream(keyFile);
// Write the certificate in OpenSSL PEM format (important for the server)
StringWriter strWriter = new StringWriter();
JcaPEMWriter pemWriter = new JcaPEMWriter(strWriter);
pemWriter.writeObject(cert);
pemWriter.close();
// Line endings MUST be UNIX for the PC to accept the cert properly
OutputStreamWriter certWriter = new OutputStreamWriter(certOut);
String pemStr = strWriter.getBuffer().toString();
for (int i = 0; i < pemStr.length(); i++) {
char c = pemStr.charAt(i);
if (c != '\r')
certWriter.append(c);
}
certWriter.close();
// Write the private out in PKCS8 format
keyOut.write(key.getEncoded());
certOut.close();
keyOut.close();
LimeLog.info("Saved generated key pair to disk");
} catch (IOException e) {
// This isn't good because it means we'll have
// to re-pair next time
e.printStackTrace();
}
}
public X509Certificate getClientCertificate() {
// Use a lock here to ensure only one guy will be generating or loading
// the certificate and key at a time
synchronized (globalCryptoLock) {
// Return a loaded cert if we have one
if (cert != null) {
return cert;
}
// No loaded cert yet, let's see if we have one on disk
if (loadCertKeyPair()) {
// Got one
return cert;
}
// Try to generate a new key pair
if (!generateCertKeyPair()) {
// Failed
return null;
}
// Load the generated pair
loadCertKeyPair();
return cert;
}
}
public RSAPrivateKey getClientPrivateKey() { private boolean loadCertKeyPair() {
// Use a lock here to ensure only one guy will be generating or loading byte[] certBytes = loadFileToBytes(certFile);
// the certificate and key at a time byte[] keyBytes = loadFileToBytes(keyFile);
synchronized (globalCryptoLock) {
// Return a loaded key if we have one
if (key != null) {
return key;
}
// No loaded key yet, let's see if we have one on disk
if (loadCertKeyPair()) {
// Got one
return key;
}
// Try to generate a new key pair
if (!generateCertKeyPair()) {
// Failed
return null;
}
// Load the generated pair
loadCertKeyPair();
return key;
}
}
public byte[] getPemEncodedClientCertificate() {
synchronized (globalCryptoLock) {
// Call our helper function to do the cert loading/generation for us
getClientCertificate();
// Return a cached value if we have it
return pemCertBytes;
}
}
@Override // If either file was missing, we definitely can't succeed
public String encodeBase64String(byte[] data) { if (certBytes == null || keyBytes == null) {
return Base64.encodeToString(data, Base64.NO_WRAP); LimeLog.info("Missing cert or key; need to generate a new one");
} return false;
}
try {
CertificateFactory certFactory = CertificateFactory.getInstance("X.509", "BC");
cert = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(certBytes));
pemCertBytes = certBytes;
KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC");
key = (RSAPrivateKey) keyFactory.generatePrivate(new PKCS8EncodedKeySpec(keyBytes));
} catch (CertificateException e) {
// May happen if the cert is corrupt
LimeLog.warning("Corrupted certificate");
return false;
} catch (NoSuchAlgorithmException e) {
// Should never happen
e.printStackTrace();
return false;
} catch (InvalidKeySpecException e) {
// May happen if the key is corrupt
LimeLog.warning("Corrupted key");
return false;
} catch (NoSuchProviderException e) {
// Should never happen
e.printStackTrace();
return false;
}
return true;
}
@SuppressLint("TrulyRandom")
private boolean generateCertKeyPair() {
byte[] snBytes = new byte[8];
new SecureRandom().nextBytes(snBytes);
KeyPair keyPair;
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC");
keyPairGenerator.initialize(2048);
keyPair = keyPairGenerator.generateKeyPair();
} catch (NoSuchAlgorithmException e1) {
// Should never happen
e1.printStackTrace();
return false;
} catch (NoSuchProviderException e) {
// Should never happen
e.printStackTrace();
return false;
}
Date now = new Date();
// Expires in 20 years
Calendar calendar = Calendar.getInstance();
calendar.setTime(now);
calendar.add(Calendar.YEAR, 20);
Date expirationDate = calendar.getTime();
BigInteger serial = new BigInteger(snBytes).abs();
X500NameBuilder nameBuilder = new X500NameBuilder(BCStyle.INSTANCE);
nameBuilder.addRDN(BCStyle.CN, "NVIDIA GameStream Client");
X500Name name = nameBuilder.build();
X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder(name, serial, now, expirationDate, Locale.ENGLISH, name,
SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded()));
try {
ContentSigner sigGen = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BouncyCastleProvider.PROVIDER_NAME).build(keyPair.getPrivate());
cert = new JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME).getCertificate(certBuilder.build(sigGen));
key = (RSAPrivateKey) keyPair.getPrivate();
} catch (Exception e) {
// Nothing should go wrong here
e.printStackTrace();
return false;
}
LimeLog.info("Generated a new key pair");
// Save the resulting pair
saveCertKeyPair();
return true;
}
private void saveCertKeyPair() {
try {
FileOutputStream certOut = new FileOutputStream(certFile);
FileOutputStream keyOut = new FileOutputStream(keyFile);
// Write the certificate in OpenSSL PEM format (important for the server)
StringWriter strWriter = new StringWriter();
JcaPEMWriter pemWriter = new JcaPEMWriter(strWriter);
pemWriter.writeObject(cert);
pemWriter.close();
// Line endings MUST be UNIX for the PC to accept the cert properly
OutputStreamWriter certWriter = new OutputStreamWriter(certOut);
String pemStr = strWriter.getBuffer().toString();
for (int i = 0; i < pemStr.length(); i++) {
char c = pemStr.charAt(i);
if (c != '\r')
certWriter.append(c);
}
certWriter.close();
// Write the private out in PKCS8 format
keyOut.write(key.getEncoded());
certOut.close();
keyOut.close();
LimeLog.info("Saved generated key pair to disk");
} catch (IOException e) {
// This isn't good because it means we'll have
// to re-pair next time
e.printStackTrace();
}
}
public X509Certificate getClientCertificate() {
// Use a lock here to ensure only one guy will be generating or loading
// the certificate and key at a time
synchronized (globalCryptoLock) {
// Return a loaded cert if we have one
if (cert != null) {
return cert;
}
// No loaded cert yet, let's see if we have one on disk
if (loadCertKeyPair()) {
// Got one
return cert;
}
// Try to generate a new key pair
if (!generateCertKeyPair()) {
// Failed
return null;
}
// Load the generated pair
loadCertKeyPair();
return cert;
}
}
public RSAPrivateKey getClientPrivateKey() {
// Use a lock here to ensure only one guy will be generating or loading
// the certificate and key at a time
synchronized (globalCryptoLock) {
// Return a loaded key if we have one
if (key != null) {
return key;
}
// No loaded key yet, let's see if we have one on disk
if (loadCertKeyPair()) {
// Got one
return key;
}
// Try to generate a new key pair
if (!generateCertKeyPair()) {
// Failed
return null;
}
// Load the generated pair
loadCertKeyPair();
return key;
}
}
public byte[] getPemEncodedClientCertificate() {
synchronized (globalCryptoLock) {
// Call our helper function to do the cert loading/generation for us
getClientCertificate();
// Return a cached value if we have it
return pemCertBytes;
}
}
@Override
public String encodeBase64String(byte[] data) {
return Base64.encodeToString(data, Base64.NO_WRAP);
}
} }

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

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

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();
return computerList;
}
public ComputerDetails getComputerByName(String name) {
Cursor c = computerDb.rawQuery("SELECT * FROM "+COMPUTER_TABLE_NAME+" WHERE "+COMPUTER_NAME_COLUMN_NAME+"='"+name+"'", null);
ComputerDetails details = new ComputerDetails();
if (!c.moveToFirst()) {
// No matching computer
c.close();
return null;
}
details.name = c.getString(0); c.close();
String uuidStr = c.getString(1); return computerList;
try { }
details.uuid = UUID.fromString(uuidStr);
} catch (IllegalArgumentException e) { public ComputerDetails getComputerByName(String name) {
// We'll delete this entry Cursor c = computerDb.rawQuery("SELECT * FROM "+COMPUTER_TABLE_NAME+" WHERE "+COMPUTER_NAME_COLUMN_NAME+"='"+name+"'", null);
LimeLog.severe("DB: Corrupted UUID for "+details.name); ComputerDetails details = new ComputerDetails();
} if (!c.moveToFirst()) {
// No matching computer
try { c.close();
details.localIp = InetAddress.getByAddress(c.getBlob(2)); return null;
} catch (UnknownHostException e) { }
// We'll delete this entry
LimeLog.severe("DB: Corrupted local IP for "+details.name); details.name = c.getString(0);
}
String uuidStr = c.getString(1);
try { try {
details.remoteIp = InetAddress.getByAddress(c.getBlob(3)); details.uuid = UUID.fromString(uuidStr);
} catch (UnknownHostException e) { } catch (IllegalArgumentException e) {
// We'll delete this entry // We'll delete this entry
LimeLog.severe("DB: Corrupted remote IP for "+details.name); LimeLog.severe("DB: Corrupted UUID for "+details.name);
} }
details.macAddress = c.getString(4); try {
details.localIp = InetAddress.getByAddress(c.getBlob(2));
c.close(); } catch (UnknownHostException e) {
// We'll delete this entry
LimeLog.severe("DB: Corrupted local IP for "+details.name);
}
try {
details.remoteIp = InetAddress.getByAddress(c.getBlob(3));
} catch (UnknownHostException e) {
// We'll delete this entry
LimeLog.severe("DB: Corrupted remote IP for "+details.name);
}
details.macAddress = c.getString(4);
c.close();
details.state = ComputerDetails.State.UNKNOWN; details.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
privateBinder.setListener(createDiscoveryListener());
// Signal a possible waiter that we're all setup
discoveryBinder = privateBinder;
discoveryServiceConnection.notifyAll();
}
}
public void onServiceDisconnected(ComponentName className) { // Set us as the event listener
discoveryBinder = null; privateBinder.setListener(createDiscoveryListener());
}
}; // Signal a possible waiter that we're all setup
discoveryBinder = privateBinder;
discoveryServiceConnection.notifyAll();
}
}
public void onServiceDisconnected(ComponentName className) {
discoveryBinder = null;
}
};
// Returns true if the details object was modified // Returns true if the details object was modified
private boolean runPoll(ComputerDetails details, boolean newPc) private boolean runPoll(ComputerDetails details, boolean newPc)
@ -124,17 +124,17 @@ public class ComputerManagerService extends Service {
t.setName("Polling thread for "+details.localIp.getHostAddress()); t.setName("Polling thread for "+details.localIp.getHostAddress());
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) {
@ -220,33 +220,33 @@ public class ComputerManagerService extends Service {
} }
} }
} }
// Remove the listener
listener = null;
return false;
}
private MdnsDiscoveryListener createDiscoveryListener() {
return new MdnsDiscoveryListener() {
@Override
public void notifyComputerAdded(MdnsComputer computer) {
// Kick off a serverinfo poll on this machine
addComputerBlocking(computer.getAddress());
}
@Override // Remove the listener
public void notifyComputerRemoved(MdnsComputer computer) { listener = null;
// Nothing to do here
}
@Override return false;
public void notifyDiscoveryFailure(Exception e) { }
LimeLog.severe("mDNS discovery failed");
e.printStackTrace(); private MdnsDiscoveryListener createDiscoveryListener() {
} return new MdnsDiscoveryListener() {
}; @Override
} public void notifyComputerAdded(MdnsComputer computer) {
// Kick off a serverinfo poll on this machine
addComputerBlocking(computer.getAddress());
}
@Override
public void notifyComputerRemoved(MdnsComputer computer) {
// Nothing to do here
}
@Override
public void notifyDiscoveryFailure(Exception e) {
LimeLog.severe("mDNS discovery failed");
e.printStackTrace();
}
};
}
private void addTuple(ComputerDetails details) { 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
@ -321,31 +321,31 @@ public class ComputerManagerService extends Service {
} }
} }
} }
releaseLocalDatabaseReference();
}
private boolean getLocalDatabaseReference() {
if (dbRefCount.get() == 0) {
return false;
}
dbRefCount.incrementAndGet();
return true;
}
private void releaseLocalDatabaseReference() {
if (dbRefCount.decrementAndGet() == 0) {
dbManager.close();
}
}
private ComputerDetails tryPollIp(ComputerDetails details, InetAddress ipAddr) {
try {
NvHTTP http = new NvHTTP(ipAddr, idManager.getUniqueId(),
null, PlatformBinding.getCryptoProvider(ComputerManagerService.this));
ComputerDetails newDetails = http.getComputerDetails(); releaseLocalDatabaseReference();
}
private boolean getLocalDatabaseReference() {
if (dbRefCount.get() == 0) {
return false;
}
dbRefCount.incrementAndGet();
return true;
}
private void releaseLocalDatabaseReference() {
if (dbRefCount.decrementAndGet() == 0) {
dbManager.close();
}
}
private ComputerDetails tryPollIp(ComputerDetails details, InetAddress ipAddr) {
try {
NvHTTP http = new NvHTTP(ipAddr, idManager.getUniqueId(),
null, PlatformBinding.getCryptoProvider(ComputerManagerService.this));
ComputerDetails newDetails = http.getComputerDetails();
// Check if this is the PC we expected // Check if this is the PC we expected
if (details.uuid != null && newDetails.uuid != null && if (details.uuid != null && newDetails.uuid != null &&
@ -356,58 +356,58 @@ 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
if (details.localIp.equals(details.remoteIp)) { if (details.localIp.equals(details.remoteIp)) {
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

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

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");
}
/** Disables the deblocking filter at the cost of image quality */
public static final int DISABLE_LOOP_FILTER = 0x1;
/** Uses the low latency decode flag (disables multithreading) */
public static final int LOW_LATENCY_DECODE = 0x2;
/** Threads process each slice, rather than each frame */
public static final int SLICE_THREADING = 0x4;
/** Uses nonstandard speedup tricks */
public static final int FAST_DECODE = 0x8;
/** Uses bilinear filtering instead of bicubic */
public static final int BILINEAR_FILTERING = 0x10;
/** Uses a faster bilinear filtering with lower image quality */
public static final int FAST_BILINEAR_FILTERING = 0x20;
/** Disables color conversion (output is NV21) */
public static final int NO_COLOR_CONVERSION = 0x40;
public static native int init(int width, int height, int perflvl, int threadcount);
public static native void destroy();
// Rendering API when NO_COLOR_CONVERSION == 0
public static native boolean setRenderTarget(Object androidSurface);
public static native boolean getRgbFrameInt(int[] rgbFrame, int bufferSize);
public static native boolean getRgbFrame(byte[] rgbFrame, int bufferSize);
public static native boolean redraw();
// Rendering API when NO_COLOR_CONVERSION == 1 System.loadLibrary("nv_avc_dec");
public static native boolean getRawFrame(byte[] yuvFrame, int bufferSize); }
public static native int getInputPaddingSize(); /** Disables the deblocking filter at the cost of image quality */
public static native int decode(byte[] indata, int inoff, int inlen); public static final int DISABLE_LOOP_FILTER = 0x1;
/** Uses the low latency decode flag (disables multithreading) */
public static final int LOW_LATENCY_DECODE = 0x2;
/** Threads process each slice, rather than each frame */
public static final int SLICE_THREADING = 0x4;
/** Uses nonstandard speedup tricks */
public static final int FAST_DECODE = 0x8;
/** Uses bilinear filtering instead of bicubic */
public static final int BILINEAR_FILTERING = 0x10;
/** Uses a faster bilinear filtering with lower image quality */
public static final int FAST_BILINEAR_FILTERING = 0x20;
/** Disables color conversion (output is NV21) */
public static final int NO_COLOR_CONVERSION = 0x40;
public static native int init(int width, int height, int perflvl, int threadcount);
public static native void destroy();
// Rendering API when NO_COLOR_CONVERSION == 0
public static native boolean setRenderTarget(Object androidSurface);
public static native boolean getRgbFrameInt(int[] rgbFrame, int bufferSize);
public static native boolean getRgbFrame(byte[] rgbFrame, int bufferSize);
public static native boolean redraw();
// Rendering API when NO_COLOR_CONVERSION == 1
public static native boolean getRawFrame(byte[] yuvFrame, int bufferSize);
public static native int getInputPaddingSize();
public static native int decode(byte[] indata, int inoff, int inlen);
} }

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()) {
// Close the activity
AddComputerManually.this.finish();
}
}
});
}
private void startAddThread() {
addThread = new Thread() {
@Override
public void run() {
while (!isInterrupted()) {
String computer;
try {
computer = computersToAdd.take();
} catch (InterruptedException e) {
return;
}
doAddPc(computer);
}
}
};
addThread.setName("UI - AddComputerManually");
addThread.start();
}
private void joinAddThread() {
if (addThread != null) {
addThread.interrupt();
try {
addThread.join();
} catch (InterruptedException ignored) {}
addThread = null;
}
}
@Override if (toastFinish && !isFinishing()) {
protected void onStop() { // Close the activity
super.onStop(); AddComputerManually.this.finish();
}
Dialog.closeDialogs(); }
});
}
private void startAddThread() {
addThread = new Thread() {
@Override
public void run() {
while (!isInterrupted()) {
String computer;
try {
computer = computersToAdd.take();
} catch (InterruptedException e) {
return;
}
doAddPc(computer);
}
}
};
addThread.setName("UI - AddComputerManually");
addThread.start();
}
private void joinAddThread() {
if (addThread != null) {
addThread.interrupt();
try {
addThread.join();
} catch (InterruptedException ignored) {}
addThread = null;
}
}
@Override
protected void onStop() {
super.onStop();
Dialog.closeDialogs();
SpinnerDialog.closeDialogs(this); 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
@ -165,9 +165,9 @@ public class AddComputerManually extends Activity {
return false; return false;
} }
}); });
// 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 static final ArrayList<Dialog> rundownDialogs = new ArrayList<Dialog>();
private Dialog(Activity activity, String title, String message, boolean endAfterDismiss)
{
this.activity = activity;
this.title = title;
this.message = message;
this.endAfterDismiss = endAfterDismiss;
}
public static void closeDialogs()
{
synchronized (rundownDialogs) {
for (Dialog d : rundownDialogs) {
if (d.alert.isShowing()) {
d.alert.dismiss();
}
}
rundownDialogs.clear();
}
}
public static void displayDialog(Activity activity, String title, String message, boolean endAfterDismiss)
{
activity.runOnUiThread(new Dialog(activity, title, message, endAfterDismiss));
}
@Override
public void run() {
// If we're dying, don't bother creating a dialog
if (activity.isFinishing())
return;
alert = new AlertDialog.Builder(activity).create();
alert.setTitle(title); private AlertDialog alert;
alert.setMessage(message);
alert.setCancelable(false); private static final ArrayList<Dialog> rundownDialogs = new ArrayList<Dialog>();
alert.setCanceledOnTouchOutside(false);
private Dialog(Activity activity, String title, String message, boolean endAfterDismiss)
{
this.activity = activity;
this.title = title;
this.message = message;
this.endAfterDismiss = endAfterDismiss;
}
public static void closeDialogs()
{
synchronized (rundownDialogs) {
for (Dialog d : rundownDialogs) {
if (d.alert.isShowing()) {
d.alert.dismiss();
}
}
rundownDialogs.clear();
}
}
public static void displayDialog(Activity activity, String title, String message, boolean endAfterDismiss)
{
activity.runOnUiThread(new Dialog(activity, title, message, endAfterDismiss));
}
@Override
public void run() {
// If we're dying, don't bother creating a dialog
if (activity.isFinishing())
return;
alert = new AlertDialog.Builder(activity).create();
alert.setTitle(title);
alert.setMessage(message);
alert.setCancelable(false);
alert.setCanceledOnTouchOutside(false);
alert.setButton(AlertDialog.BUTTON_NEUTRAL, "OK", new DialogInterface.OnClickListener() { 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 SpinnerDialog(Activity activity, String title, String message, boolean finish)
{
this.activity = activity;
this.title = title;
this.message = message;
this.progress = null;
this.finish = finish;
}
public static SpinnerDialog displayDialog(Activity activity, String title, String message, boolean finish)
{
SpinnerDialog spinner = new SpinnerDialog(activity, title, message, finish);
activity.runOnUiThread(spinner);
return spinner;
}
public static void closeDialogs(Activity activity)
{
synchronized (rundownDialogs) {
Iterator<SpinnerDialog> i = rundownDialogs.iterator();
while (i.hasNext()) {
SpinnerDialog dialog = i.next();
if (dialog.activity == activity) {
i.remove();
if (dialog.progress.isShowing()) {
dialog.progress.dismiss();
}
}
}
}
}
public void dismiss()
{
// Running again with progress != null will destroy it
activity.runOnUiThread(this);
}
public void setMessage(final String message)
{
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
progress.setMessage(message);
}
});
}
@Override
public void run() {
// If we're dying, don't bother doing anything private static final ArrayList<SpinnerDialog> rundownDialogs = new ArrayList<SpinnerDialog>();
if (activity.isFinishing()) {
return;
}
if (progress == null)
{
progress = new ProgressDialog(activity);
progress.setTitle(title);
progress.setMessage(message);
progress.setProgressStyle(ProgressDialog.STYLE_SPINNER);
progress.setOnCancelListener(this);
// If we want to finish the activity when this is killed, make it cancellable
if (finish)
{
progress.setCancelable(true);
progress.setCanceledOnTouchOutside(false);
}
else
{
progress.setCancelable(false);
}
synchronized (rundownDialogs) {
rundownDialogs.add(this);
progress.show();
}
}
else
{
synchronized (rundownDialogs) {
if (rundownDialogs.remove(this) && progress.isShowing()) {
progress.dismiss();
}
}
}
}
@Override private SpinnerDialog(Activity activity, String title, String message, boolean finish)
public void onCancel(DialogInterface dialog) { {
synchronized (rundownDialogs) { this.activity = activity;
rundownDialogs.remove(this); this.title = title;
} this.message = message;
this.progress = null;
// This will only be called if finish was true, so we don't need to check again this.finish = finish;
activity.finish(); }
}
public static SpinnerDialog displayDialog(Activity activity, String title, String message, boolean finish)
{
SpinnerDialog spinner = new SpinnerDialog(activity, title, message, finish);
activity.runOnUiThread(spinner);
return spinner;
}
public static void closeDialogs(Activity activity)
{
synchronized (rundownDialogs) {
Iterator<SpinnerDialog> i = rundownDialogs.iterator();
while (i.hasNext()) {
SpinnerDialog dialog = i.next();
if (dialog.activity == activity) {
i.remove();
if (dialog.progress.isShowing()) {
dialog.progress.dismiss();
}
}
}
}
}
public void dismiss()
{
// Running again with progress != null will destroy it
activity.runOnUiThread(this);
}
public void setMessage(final String message)
{
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
progress.setMessage(message);
}
});
}
@Override
public void run() {
// If we're dying, don't bother doing anything
if (activity.isFinishing()) {
return;
}
if (progress == null)
{
progress = new ProgressDialog(activity);
progress.setTitle(title);
progress.setMessage(message);
progress.setProgressStyle(ProgressDialog.STYLE_SPINNER);
progress.setOnCancelListener(this);
// If we want to finish the activity when this is killed, make it cancellable
if (finish)
{
progress.setCancelable(true);
progress.setCanceledOnTouchOutside(false);
}
else
{
progress.setCancelable(false);
}
synchronized (rundownDialogs) {
rundownDialogs.add(this);
progress.show();
}
}
else
{
synchronized (rundownDialogs) {
if (rundownDialogs.remove(this) && progress.isShowing()) {
progress.dismiss();
}
}
}
}
@Override
public void onCancel(DialogInterface dialog) {
synchronized (rundownDialogs) {
rundownDialogs.remove(this);
}
// This will only be called if finish was true, so we don't need to check again
activity.finish();
}
} }