mirror of
https://github.com/moonlight-stream/moonlight-android.git
synced 2025-07-19 19:13:03 +00:00
Tabs -> Spaces
This commit is contained in:
parent
10204afdb4
commit
2fdecc551a
@ -57,13 +57,13 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
||||
private int consecutiveAppListFailures = 0;
|
||||
private final static int CONSECUTIVE_FAILURE_LIMIT = 3;
|
||||
|
||||
private final static int START_OR_RESUME_ID = 1;
|
||||
private final static int QUIT_ID = 2;
|
||||
private final static int CANCEL_ID = 3;
|
||||
private final static int START_OR_RESUME_ID = 1;
|
||||
private final static int QUIT_ID = 2;
|
||||
private final static int CANCEL_ID = 3;
|
||||
private final static int START_WTIH_QUIT = 4;
|
||||
|
||||
public final static String NAME_EXTRA = "Name";
|
||||
public final static String UUID_EXTRA = "UUID";
|
||||
public final static String UUID_EXTRA = "UUID";
|
||||
|
||||
private ComputerManagerService.ComputerManagerBinder managerBinder;
|
||||
private final ServiceConnection serviceConnection = new ServiceConnection() {
|
||||
@ -183,33 +183,33 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
||||
managerBinder.stopPolling();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
String locale = PreferenceConfiguration.readPreferences(this).language;
|
||||
if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
|
||||
Configuration config = new Configuration(getResources().getConfiguration());
|
||||
config.locale = new Locale(locale);
|
||||
getResources().updateConfiguration(config, getResources().getDisplayMetrics());
|
||||
}
|
||||
if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
|
||||
Configuration config = new Configuration(getResources().getConfiguration());
|
||||
config.locale = new Locale(locale);
|
||||
getResources().updateConfiguration(config, getResources().getDisplayMetrics());
|
||||
}
|
||||
|
||||
setContentView(R.layout.activity_app_view);
|
||||
setContentView(R.layout.activity_app_view);
|
||||
|
||||
UiHelper.notifyNewRootView(this);
|
||||
|
||||
uuidString = getIntent().getStringExtra(UUID_EXTRA);
|
||||
|
||||
String labelText = getResources().getString(R.string.title_applist)+" "+getIntent().getStringExtra(NAME_EXTRA);
|
||||
TextView label = (TextView) findViewById(R.id.appListText);
|
||||
setTitle(labelText);
|
||||
label.setText(labelText);
|
||||
|
||||
String labelText = getResources().getString(R.string.title_applist)+" "+getIntent().getStringExtra(NAME_EXTRA);
|
||||
TextView label = (TextView) findViewById(R.id.appListText);
|
||||
setTitle(labelText);
|
||||
label.setText(labelText);
|
||||
|
||||
// Bind to the computer manager service
|
||||
bindService(new Intent(this, ComputerManagerService.class), serviceConnection,
|
||||
Service.BIND_AUTO_CREATE);
|
||||
}
|
||||
}
|
||||
|
||||
private void populateAppGridWithCache() {
|
||||
try {
|
||||
@ -233,25 +233,25 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
||||
blockingLoadSpinner = SpinnerDialog.displayDialog(this, getResources().getString(R.string.applist_refresh_title),
|
||||
getResources().getString(R.string.applist_refresh_msg), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
SpinnerDialog.closeDialogs(this);
|
||||
Dialog.closeDialogs();
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
SpinnerDialog.closeDialogs(this);
|
||||
Dialog.closeDialogs();
|
||||
|
||||
if (managerBinder != null) {
|
||||
unbindService(serviceConnection);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
startComputerUpdates();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
@ -259,49 +259,49 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
||||
|
||||
stopComputerUpdates();
|
||||
}
|
||||
|
||||
private int getRunningAppId() {
|
||||
|
||||
private int getRunningAppId() {
|
||||
int runningAppId = -1;
|
||||
for (int i = 0; i < appGridAdapter.getCount(); i++) {
|
||||
AppObject app = (AppObject) appGridAdapter.getItem(i);
|
||||
if (app.app == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (app.app.getIsRunning()) {
|
||||
runningAppId = app.app.getAppId();
|
||||
break;
|
||||
}
|
||||
AppObject app = (AppObject) appGridAdapter.getItem(i);
|
||||
if (app.app == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (app.app.getIsRunning()) {
|
||||
runningAppId = app.app.getAppId();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return runningAppId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
|
||||
super.onCreateContextMenu(menu, v, menuInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
|
||||
super.onCreateContextMenu(menu, v, menuInfo);
|
||||
|
||||
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
|
||||
AppObject selectedApp = (AppObject) appGridAdapter.getItem(info.position);
|
||||
if (selectedApp == null || selectedApp.app == null) {
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
int runningAppId = getRunningAppId();
|
||||
if (runningAppId != -1) {
|
||||
if (runningAppId == selectedApp.app.getAppId()) {
|
||||
if (runningAppId == selectedApp.app.getAppId()) {
|
||||
menu.add(Menu.NONE, START_OR_RESUME_ID, 1, getResources().getString(R.string.applist_menu_resume));
|
||||
menu.add(Menu.NONE, QUIT_ID, 2, getResources().getString(R.string.applist_menu_quit));
|
||||
}
|
||||
else {
|
||||
}
|
||||
else {
|
||||
menu.add(Menu.NONE, START_WTIH_QUIT, 1, getResources().getString(R.string.applist_menu_quit_and_start));
|
||||
menu.add(Menu.NONE, CANCEL_ID, 2, getResources().getString(R.string.applist_menu_cancel));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onContextMenuClosed(Menu menu) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onContextMenuClosed(Menu menu) {
|
||||
}
|
||||
|
||||
private void displayQuitConfirmationDialog(final Runnable onYes, final Runnable onNo) {
|
||||
DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
|
||||
@ -414,57 +414,57 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
||||
});
|
||||
}
|
||||
|
||||
private void doStart(NvApp app) {
|
||||
Intent intent = new Intent(this, Game.class);
|
||||
intent.putExtra(Game.EXTRA_HOST,
|
||||
private void doStart(NvApp app) {
|
||||
Intent intent = new Intent(this, Game.class);
|
||||
intent.putExtra(Game.EXTRA_HOST,
|
||||
computer.reachability == ComputerDetails.Reachability.LOCAL ?
|
||||
computer.localIp.getHostAddress() : computer.remoteIp.getHostAddress());
|
||||
intent.putExtra(Game.EXTRA_APP, app.getAppName());
|
||||
intent.putExtra(Game.EXTRA_UNIQUEID, managerBinder.getUniqueId());
|
||||
intent.putExtra(Game.EXTRA_STREAMING_REMOTE,
|
||||
intent.putExtra(Game.EXTRA_APP, app.getAppName());
|
||||
intent.putExtra(Game.EXTRA_UNIQUEID, managerBinder.getUniqueId());
|
||||
intent.putExtra(Game.EXTRA_STREAMING_REMOTE,
|
||||
computer.reachability != ComputerDetails.Reachability.LOCAL);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
private void doQuit(final NvApp app) {
|
||||
Toast.makeText(AppView.this, getResources().getString(R.string.applist_quit_app)+" "+app.getAppName()+"...", Toast.LENGTH_SHORT).show();
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
NvHTTP httpConn;
|
||||
String message;
|
||||
try {
|
||||
httpConn = new NvHTTP(getAddress(),
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
private void doQuit(final NvApp app) {
|
||||
Toast.makeText(AppView.this, getResources().getString(R.string.applist_quit_app)+" "+app.getAppName()+"...", Toast.LENGTH_SHORT).show();
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
NvHTTP httpConn;
|
||||
String message;
|
||||
try {
|
||||
httpConn = new NvHTTP(getAddress(),
|
||||
managerBinder.getUniqueId(), null, PlatformBinding.getCryptoProvider(AppView.this));
|
||||
if (httpConn.quitApp()) {
|
||||
message = getResources().getString(R.string.applist_quit_success)+" "+app.getAppName();
|
||||
}
|
||||
else {
|
||||
message = getResources().getString(R.string.applist_quit_fail)+" "+app.getAppName();
|
||||
}
|
||||
} catch (UnknownHostException e) {
|
||||
message = getResources().getString(R.string.error_unknown_host);
|
||||
} catch (FileNotFoundException e) {
|
||||
message = getResources().getString(R.string.error_404);
|
||||
} catch (Exception e) {
|
||||
message = e.getMessage();
|
||||
} finally {
|
||||
if (httpConn.quitApp()) {
|
||||
message = getResources().getString(R.string.applist_quit_success)+" "+app.getAppName();
|
||||
}
|
||||
else {
|
||||
message = getResources().getString(R.string.applist_quit_fail)+" "+app.getAppName();
|
||||
}
|
||||
} catch (UnknownHostException e) {
|
||||
message = getResources().getString(R.string.error_unknown_host);
|
||||
} catch (FileNotFoundException e) {
|
||||
message = getResources().getString(R.string.error_404);
|
||||
} catch (Exception e) {
|
||||
message = e.getMessage();
|
||||
} finally {
|
||||
// Trigger a poll immediately
|
||||
if (poller != null) {
|
||||
poller.pollNow();
|
||||
}
|
||||
}
|
||||
|
||||
final String toastMessage = message;
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Toast.makeText(AppView.this, toastMessage, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
final String toastMessage = message;
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Toast.makeText(AppView.this, toastMessage, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAdapterFragmentLayoutId() {
|
||||
@ -497,15 +497,15 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
||||
}
|
||||
|
||||
public class AppObject {
|
||||
public final NvApp app;
|
||||
|
||||
public AppObject(NvApp app) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return app.getAppName();
|
||||
}
|
||||
}
|
||||
public final NvApp app;
|
||||
|
||||
public AppObject(NvApp app) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return app.getAppName();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -50,69 +50,69 @@ import android.widget.AdapterView.AdapterContextMenuInfo;
|
||||
public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
private RelativeLayout noPcFoundLayout;
|
||||
private PcGridAdapter pcGridAdapter;
|
||||
private ComputerManagerService.ComputerManagerBinder managerBinder;
|
||||
private boolean freezeUpdates, runningPolling;
|
||||
private final ServiceConnection serviceConnection = new ServiceConnection() {
|
||||
public void onServiceConnected(ComponentName className, IBinder binder) {
|
||||
final ComputerManagerService.ComputerManagerBinder localBinder =
|
||||
((ComputerManagerService.ComputerManagerBinder)binder);
|
||||
|
||||
// Wait in a separate thread to avoid stalling the UI
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Wait for the binder to be ready
|
||||
localBinder.waitForReady();
|
||||
|
||||
// Now make the binder visible
|
||||
managerBinder = localBinder;
|
||||
|
||||
// Start updates
|
||||
startComputerUpdates();
|
||||
|
||||
// Force a keypair to be generated early to avoid discovery delays
|
||||
new AndroidCryptoProvider(PcView.this).getClientCertificate();
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
private ComputerManagerService.ComputerManagerBinder managerBinder;
|
||||
private boolean freezeUpdates, runningPolling;
|
||||
private final ServiceConnection serviceConnection = new ServiceConnection() {
|
||||
public void onServiceConnected(ComponentName className, IBinder binder) {
|
||||
final ComputerManagerService.ComputerManagerBinder localBinder =
|
||||
((ComputerManagerService.ComputerManagerBinder)binder);
|
||||
|
||||
public void onServiceDisconnected(ComponentName className) {
|
||||
managerBinder = null;
|
||||
}
|
||||
};
|
||||
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
|
||||
// Reinitialize views just in case orientation changed
|
||||
initializeViews();
|
||||
}
|
||||
|
||||
private final static int APP_LIST_ID = 1;
|
||||
private final static int PAIR_ID = 2;
|
||||
private final static int UNPAIR_ID = 3;
|
||||
private final static int WOL_ID = 4;
|
||||
private final static int DELETE_ID = 5;
|
||||
|
||||
private void initializeViews() {
|
||||
setContentView(R.layout.activity_pc_view);
|
||||
// Wait in a separate thread to avoid stalling the UI
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Wait for the binder to be ready
|
||||
localBinder.waitForReady();
|
||||
|
||||
// Now make the binder visible
|
||||
managerBinder = localBinder;
|
||||
|
||||
// Start updates
|
||||
startComputerUpdates();
|
||||
|
||||
// Force a keypair to be generated early to avoid discovery delays
|
||||
new AndroidCryptoProvider(PcView.this).getClientCertificate();
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
public void onServiceDisconnected(ComponentName className) {
|
||||
managerBinder = null;
|
||||
}
|
||||
};
|
||||
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
|
||||
// Reinitialize views just in case orientation changed
|
||||
initializeViews();
|
||||
}
|
||||
|
||||
private final static int APP_LIST_ID = 1;
|
||||
private final static int PAIR_ID = 2;
|
||||
private final static int UNPAIR_ID = 3;
|
||||
private final static int WOL_ID = 4;
|
||||
private final static int DELETE_ID = 5;
|
||||
|
||||
private void initializeViews() {
|
||||
setContentView(R.layout.activity_pc_view);
|
||||
|
||||
UiHelper.notifyNewRootView(this);
|
||||
|
||||
// Set default preferences if we've never been run
|
||||
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
|
||||
|
||||
// Setup the list view
|
||||
// Setup the list view
|
||||
ImageButton settingsButton = (ImageButton) findViewById(R.id.settingsButton);
|
||||
ImageButton addComputerButton = (ImageButton) findViewById(R.id.manuallyAddPc);
|
||||
|
||||
settingsButton.setOnClickListener(new OnClickListener() {
|
||||
settingsButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
startActivity(new Intent(PcView.this, StreamSettings.class));
|
||||
}
|
||||
});
|
||||
addComputerButton.setOnClickListener(new OnClickListener() {
|
||||
addComputerButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent i = new Intent(PcView.this, AddComputerManually.class);
|
||||
@ -133,114 +133,114 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
}
|
||||
pcGridAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
String locale = PreferenceConfiguration.readPreferences(this).language;
|
||||
if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
|
||||
Configuration config = new Configuration(getResources().getConfiguration());
|
||||
config.locale = new Locale(locale);
|
||||
getResources().updateConfiguration(config, getResources().getDisplayMetrics());
|
||||
}
|
||||
|
||||
// Bind to the computer manager service
|
||||
bindService(new Intent(PcView.this, ComputerManagerService.class), serviceConnection,
|
||||
Service.BIND_AUTO_CREATE);
|
||||
if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
|
||||
Configuration config = new Configuration(getResources().getConfiguration());
|
||||
config.locale = new Locale(locale);
|
||||
getResources().updateConfiguration(config, getResources().getDisplayMetrics());
|
||||
}
|
||||
|
||||
// Bind to the computer manager service
|
||||
bindService(new Intent(PcView.this, ComputerManagerService.class), serviceConnection,
|
||||
Service.BIND_AUTO_CREATE);
|
||||
|
||||
pcGridAdapter = new PcGridAdapter(this,
|
||||
PreferenceConfiguration.readPreferences(this).listMode,
|
||||
PreferenceConfiguration.readPreferences(this).smallIconMode);
|
||||
|
||||
initializeViews();
|
||||
}
|
||||
|
||||
private void startComputerUpdates() {
|
||||
if (managerBinder != null) {
|
||||
if (runningPolling) {
|
||||
return;
|
||||
}
|
||||
|
||||
freezeUpdates = false;
|
||||
managerBinder.startPolling(new ComputerManagerListener() {
|
||||
@Override
|
||||
public void notifyComputerUpdated(final ComputerDetails details) {
|
||||
if (!freezeUpdates) {
|
||||
PcView.this.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
updateComputer(details);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
runningPolling = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void stopComputerUpdates(boolean wait) {
|
||||
if (managerBinder != null) {
|
||||
if (!runningPolling) {
|
||||
return;
|
||||
}
|
||||
|
||||
freezeUpdates = true;
|
||||
|
||||
managerBinder.stopPolling();
|
||||
|
||||
if (wait) {
|
||||
managerBinder.waitForPollingStopped();
|
||||
}
|
||||
|
||||
runningPolling = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
if (managerBinder != null) {
|
||||
unbindService(serviceConnection);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
startComputerUpdates();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
|
||||
stopComputerUpdates(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
|
||||
Dialog.closeDialogs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
|
||||
stopComputerUpdates(false);
|
||||
|
||||
// Call superclass
|
||||
super.onCreateContextMenu(menu, v, menuInfo);
|
||||
|
||||
initializeViews();
|
||||
}
|
||||
|
||||
private void startComputerUpdates() {
|
||||
if (managerBinder != null) {
|
||||
if (runningPolling) {
|
||||
return;
|
||||
}
|
||||
|
||||
freezeUpdates = false;
|
||||
managerBinder.startPolling(new ComputerManagerListener() {
|
||||
@Override
|
||||
public void notifyComputerUpdated(final ComputerDetails details) {
|
||||
if (!freezeUpdates) {
|
||||
PcView.this.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
updateComputer(details);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
runningPolling = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void stopComputerUpdates(boolean wait) {
|
||||
if (managerBinder != null) {
|
||||
if (!runningPolling) {
|
||||
return;
|
||||
}
|
||||
|
||||
freezeUpdates = true;
|
||||
|
||||
managerBinder.stopPolling();
|
||||
|
||||
if (wait) {
|
||||
managerBinder.waitForPollingStopped();
|
||||
}
|
||||
|
||||
runningPolling = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
if (managerBinder != null) {
|
||||
unbindService(serviceConnection);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
startComputerUpdates();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
|
||||
stopComputerUpdates(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
|
||||
Dialog.closeDialogs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
|
||||
stopComputerUpdates(false);
|
||||
|
||||
// Call superclass
|
||||
super.onCreateContextMenu(menu, v, menuInfo);
|
||||
|
||||
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
|
||||
ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(info.position);
|
||||
if (computer == null || computer.details == null ||
|
||||
computer.details.reachability == ComputerDetails.Reachability.UNKNOWN) {
|
||||
startComputerUpdates();
|
||||
return;
|
||||
startComputerUpdates();
|
||||
return;
|
||||
}
|
||||
|
||||
// Inflate the context menu
|
||||
@ -260,94 +260,94 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
menu.add(Menu.NONE, DELETE_ID, 2, getResources().getString(R.string.pcview_menu_delete_pc));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onContextMenuClosed(Menu menu) {
|
||||
startComputerUpdates();
|
||||
}
|
||||
|
||||
private void doPair(final ComputerDetails computer) {
|
||||
if (computer.reachability == ComputerDetails.Reachability.OFFLINE) {
|
||||
Toast.makeText(PcView.this, getResources().getString(R.string.pair_pc_offline), Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
if (computer.runningGameId != 0) {
|
||||
Toast.makeText(PcView.this, getResources().getString(R.string.pair_pc_ingame), Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
if (managerBinder == null) {
|
||||
Toast.makeText(PcView.this, getResources().getString(R.string.error_manager_not_running), Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.makeText(PcView.this, getResources().getString(R.string.pairing), Toast.LENGTH_SHORT).show();
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
NvHTTP httpConn;
|
||||
String message;
|
||||
|
||||
@Override
|
||||
public void onContextMenuClosed(Menu menu) {
|
||||
startComputerUpdates();
|
||||
}
|
||||
|
||||
private void doPair(final ComputerDetails computer) {
|
||||
if (computer.reachability == ComputerDetails.Reachability.OFFLINE) {
|
||||
Toast.makeText(PcView.this, getResources().getString(R.string.pair_pc_offline), Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
if (computer.runningGameId != 0) {
|
||||
Toast.makeText(PcView.this, getResources().getString(R.string.pair_pc_ingame), Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
if (managerBinder == null) {
|
||||
Toast.makeText(PcView.this, getResources().getString(R.string.error_manager_not_running), Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.makeText(PcView.this, getResources().getString(R.string.pairing), Toast.LENGTH_SHORT).show();
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
NvHTTP httpConn;
|
||||
String message;
|
||||
boolean success = false;
|
||||
try {
|
||||
// Stop updates and wait while pairing
|
||||
stopComputerUpdates(true);
|
||||
|
||||
InetAddress addr = null;
|
||||
if (computer.reachability == ComputerDetails.Reachability.LOCAL) {
|
||||
addr = computer.localIp;
|
||||
}
|
||||
else if (computer.reachability == ComputerDetails.Reachability.REMOTE) {
|
||||
addr = computer.remoteIp;
|
||||
}
|
||||
|
||||
httpConn = new NvHTTP(addr,
|
||||
managerBinder.getUniqueId(),
|
||||
PlatformBinding.getDeviceName(),
|
||||
PlatformBinding.getCryptoProvider(PcView.this));
|
||||
if (httpConn.getPairState() == PairingManager.PairState.PAIRED) {
|
||||
try {
|
||||
// Stop updates and wait while pairing
|
||||
stopComputerUpdates(true);
|
||||
|
||||
InetAddress addr = null;
|
||||
if (computer.reachability == ComputerDetails.Reachability.LOCAL) {
|
||||
addr = computer.localIp;
|
||||
}
|
||||
else if (computer.reachability == ComputerDetails.Reachability.REMOTE) {
|
||||
addr = computer.remoteIp;
|
||||
}
|
||||
|
||||
httpConn = new NvHTTP(addr,
|
||||
managerBinder.getUniqueId(),
|
||||
PlatformBinding.getDeviceName(),
|
||||
PlatformBinding.getCryptoProvider(PcView.this));
|
||||
if (httpConn.getPairState() == PairingManager.PairState.PAIRED) {
|
||||
// Don't display any toast, but open the app list
|
||||
message = null;
|
||||
message = null;
|
||||
success = true;
|
||||
}
|
||||
else {
|
||||
final String pinStr = PairingManager.generatePinString();
|
||||
|
||||
// Spin the dialog off in a thread because it blocks
|
||||
Dialog.displayDialog(PcView.this, getResources().getString(R.string.pair_pairing_title),
|
||||
getResources().getString(R.string.pair_pairing_msg)+" "+pinStr, false);
|
||||
|
||||
PairingManager.PairState pairState = httpConn.pair(pinStr);
|
||||
if (pairState == PairingManager.PairState.PIN_WRONG) {
|
||||
message = getResources().getString(R.string.pair_incorrect_pin);
|
||||
}
|
||||
else if (pairState == PairingManager.PairState.FAILED) {
|
||||
message = getResources().getString(R.string.pair_fail);
|
||||
}
|
||||
else if (pairState == PairingManager.PairState.PAIRED) {
|
||||
}
|
||||
else {
|
||||
final String pinStr = PairingManager.generatePinString();
|
||||
|
||||
// Spin the dialog off in a thread because it blocks
|
||||
Dialog.displayDialog(PcView.this, getResources().getString(R.string.pair_pairing_title),
|
||||
getResources().getString(R.string.pair_pairing_msg)+" "+pinStr, false);
|
||||
|
||||
PairingManager.PairState pairState = httpConn.pair(pinStr);
|
||||
if (pairState == PairingManager.PairState.PIN_WRONG) {
|
||||
message = getResources().getString(R.string.pair_incorrect_pin);
|
||||
}
|
||||
else if (pairState == PairingManager.PairState.FAILED) {
|
||||
message = getResources().getString(R.string.pair_fail);
|
||||
}
|
||||
else if (pairState == PairingManager.PairState.PAIRED) {
|
||||
// Just navigate to the app view without displaying a toast
|
||||
message = null;
|
||||
success = true;
|
||||
}
|
||||
else {
|
||||
// Should be no other values
|
||||
message = null;
|
||||
}
|
||||
}
|
||||
} catch (UnknownHostException e) {
|
||||
message = getResources().getString(R.string.error_unknown_host);
|
||||
} catch (FileNotFoundException e) {
|
||||
message = getResources().getString(R.string.error_404);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
else {
|
||||
// Should be no other values
|
||||
message = null;
|
||||
}
|
||||
}
|
||||
} catch (UnknownHostException e) {
|
||||
message = getResources().getString(R.string.error_unknown_host);
|
||||
} catch (FileNotFoundException e) {
|
||||
message = getResources().getString(R.string.error_404);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
message = e.getMessage();
|
||||
}
|
||||
|
||||
Dialog.closeDialogs();
|
||||
|
||||
final String toastMessage = message;
|
||||
message = e.getMessage();
|
||||
}
|
||||
|
||||
Dialog.closeDialogs();
|
||||
|
||||
final String toastMessage = message;
|
||||
final boolean toastSuccess = success;
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (toastMessage != null) {
|
||||
Toast.makeText(PcView.this, toastMessage, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
@ -356,124 +356,124 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
// Open the app list after a successful pairing attemp
|
||||
doAppList(computer);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Start polling again
|
||||
startComputerUpdates();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void doWakeOnLan(final ComputerDetails computer) {
|
||||
if (computer.reachability != ComputerDetails.Reachability.OFFLINE) {
|
||||
Toast.makeText(PcView.this, getResources().getString(R.string.wol_pc_online), Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (computer.macAddress == null) {
|
||||
Toast.makeText(PcView.this, getResources().getString(R.string.wol_no_mac), Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.makeText(PcView.this, getResources().getString(R.string.wol_waking_pc), Toast.LENGTH_SHORT).show();
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
String message;
|
||||
try {
|
||||
WakeOnLanSender.sendWolPacket(computer);
|
||||
message = getResources().getString(R.string.wol_waking_msg);
|
||||
} catch (IOException e) {
|
||||
message = getResources().getString(R.string.wol_fail);
|
||||
}
|
||||
|
||||
final String toastMessage = message;
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Toast.makeText(PcView.this, toastMessage, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void doUnpair(final ComputerDetails computer) {
|
||||
if (computer.reachability == ComputerDetails.Reachability.OFFLINE) {
|
||||
Toast.makeText(PcView.this, getResources().getString(R.string.error_pc_offline), Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
if (managerBinder == null) {
|
||||
Toast.makeText(PcView.this, getResources().getString(R.string.error_manager_not_running), Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.makeText(PcView.this, getResources().getString(R.string.unpairing), Toast.LENGTH_SHORT).show();
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
NvHTTP httpConn;
|
||||
String message;
|
||||
try {
|
||||
InetAddress addr = null;
|
||||
if (computer.reachability == ComputerDetails.Reachability.LOCAL) {
|
||||
addr = computer.localIp;
|
||||
}
|
||||
else if (computer.reachability == ComputerDetails.Reachability.REMOTE) {
|
||||
addr = computer.remoteIp;
|
||||
}
|
||||
|
||||
httpConn = new NvHTTP(addr,
|
||||
managerBinder.getUniqueId(),
|
||||
PlatformBinding.getDeviceName(),
|
||||
PlatformBinding.getCryptoProvider(PcView.this));
|
||||
if (httpConn.getPairState() == PairingManager.PairState.PAIRED) {
|
||||
httpConn.unpair();
|
||||
if (httpConn.getPairState() == PairingManager.PairState.NOT_PAIRED) {
|
||||
message = getResources().getString(R.string.unpair_success);
|
||||
}
|
||||
else {
|
||||
message = getResources().getString(R.string.unpair_fail);
|
||||
}
|
||||
}
|
||||
else {
|
||||
message = getResources().getString(R.string.unpair_error);
|
||||
}
|
||||
} catch (UnknownHostException e) {
|
||||
message = getResources().getString(R.string.error_unknown_host);
|
||||
} catch (FileNotFoundException e) {
|
||||
message = getResources().getString(R.string.error_404);
|
||||
} catch (Exception e) {
|
||||
message = e.getMessage();
|
||||
}
|
||||
|
||||
final String toastMessage = message;
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Toast.makeText(PcView.this, toastMessage, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void doAppList(ComputerDetails computer) {
|
||||
if (computer.reachability == ComputerDetails.Reachability.OFFLINE) {
|
||||
Toast.makeText(PcView.this, getResources().getString(R.string.error_pc_offline), Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
if (managerBinder == null) {
|
||||
Toast.makeText(PcView.this, getResources().getString(R.string.error_manager_not_running), Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
Intent i = new Intent(this, AppView.class);
|
||||
i.putExtra(AppView.NAME_EXTRA, computer.name);
|
||||
i.putExtra(AppView.UUID_EXTRA, computer.uuid.toString());
|
||||
startActivity(i);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Start polling again
|
||||
startComputerUpdates();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void doWakeOnLan(final ComputerDetails computer) {
|
||||
if (computer.reachability != ComputerDetails.Reachability.OFFLINE) {
|
||||
Toast.makeText(PcView.this, getResources().getString(R.string.wol_pc_online), Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (computer.macAddress == null) {
|
||||
Toast.makeText(PcView.this, getResources().getString(R.string.wol_no_mac), Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.makeText(PcView.this, getResources().getString(R.string.wol_waking_pc), Toast.LENGTH_SHORT).show();
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
String message;
|
||||
try {
|
||||
WakeOnLanSender.sendWolPacket(computer);
|
||||
message = getResources().getString(R.string.wol_waking_msg);
|
||||
} catch (IOException e) {
|
||||
message = getResources().getString(R.string.wol_fail);
|
||||
}
|
||||
|
||||
final String toastMessage = message;
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Toast.makeText(PcView.this, toastMessage, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void doUnpair(final ComputerDetails computer) {
|
||||
if (computer.reachability == ComputerDetails.Reachability.OFFLINE) {
|
||||
Toast.makeText(PcView.this, getResources().getString(R.string.error_pc_offline), Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
if (managerBinder == null) {
|
||||
Toast.makeText(PcView.this, getResources().getString(R.string.error_manager_not_running), Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.makeText(PcView.this, getResources().getString(R.string.unpairing), Toast.LENGTH_SHORT).show();
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
NvHTTP httpConn;
|
||||
String message;
|
||||
try {
|
||||
InetAddress addr = null;
|
||||
if (computer.reachability == ComputerDetails.Reachability.LOCAL) {
|
||||
addr = computer.localIp;
|
||||
}
|
||||
else if (computer.reachability == ComputerDetails.Reachability.REMOTE) {
|
||||
addr = computer.remoteIp;
|
||||
}
|
||||
|
||||
httpConn = new NvHTTP(addr,
|
||||
managerBinder.getUniqueId(),
|
||||
PlatformBinding.getDeviceName(),
|
||||
PlatformBinding.getCryptoProvider(PcView.this));
|
||||
if (httpConn.getPairState() == PairingManager.PairState.PAIRED) {
|
||||
httpConn.unpair();
|
||||
if (httpConn.getPairState() == PairingManager.PairState.NOT_PAIRED) {
|
||||
message = getResources().getString(R.string.unpair_success);
|
||||
}
|
||||
else {
|
||||
message = getResources().getString(R.string.unpair_fail);
|
||||
}
|
||||
}
|
||||
else {
|
||||
message = getResources().getString(R.string.unpair_error);
|
||||
}
|
||||
} catch (UnknownHostException e) {
|
||||
message = getResources().getString(R.string.error_unknown_host);
|
||||
} catch (FileNotFoundException e) {
|
||||
message = getResources().getString(R.string.error_404);
|
||||
} catch (Exception e) {
|
||||
message = e.getMessage();
|
||||
}
|
||||
|
||||
final String toastMessage = message;
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Toast.makeText(PcView.this, toastMessage, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void doAppList(ComputerDetails computer) {
|
||||
if (computer.reachability == ComputerDetails.Reachability.OFFLINE) {
|
||||
Toast.makeText(PcView.this, getResources().getString(R.string.error_pc_offline), Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
if (managerBinder == null) {
|
||||
Toast.makeText(PcView.this, getResources().getString(R.string.error_manager_not_running), Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
Intent i = new Intent(this, AppView.class);
|
||||
i.putExtra(AppView.NAME_EXTRA, computer.name);
|
||||
i.putExtra(AppView.UUID_EXTRA, computer.uuid.toString());
|
||||
startActivity(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem item) {
|
||||
@ -482,75 +482,75 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
switch (item.getItemId())
|
||||
{
|
||||
case PAIR_ID:
|
||||
doPair(computer.details);
|
||||
return true;
|
||||
|
||||
doPair(computer.details);
|
||||
return true;
|
||||
|
||||
case UNPAIR_ID:
|
||||
doUnpair(computer.details);
|
||||
return true;
|
||||
|
||||
doUnpair(computer.details);
|
||||
return true;
|
||||
|
||||
case WOL_ID:
|
||||
doWakeOnLan(computer.details);
|
||||
return true;
|
||||
|
||||
doWakeOnLan(computer.details);
|
||||
return true;
|
||||
|
||||
case DELETE_ID:
|
||||
if (managerBinder == null) {
|
||||
Toast.makeText(PcView.this, getResources().getString(R.string.error_manager_not_running), Toast.LENGTH_LONG).show();
|
||||
return true;
|
||||
}
|
||||
managerBinder.removeComputer(computer.details.name);
|
||||
removeComputer(computer.details);
|
||||
return true;
|
||||
|
||||
if (managerBinder == null) {
|
||||
Toast.makeText(PcView.this, getResources().getString(R.string.error_manager_not_running), Toast.LENGTH_LONG).show();
|
||||
return true;
|
||||
}
|
||||
managerBinder.removeComputer(computer.details.name);
|
||||
removeComputer(computer.details);
|
||||
return true;
|
||||
|
||||
case APP_LIST_ID:
|
||||
doAppList(computer.details);
|
||||
return true;
|
||||
|
||||
doAppList(computer.details);
|
||||
return true;
|
||||
|
||||
default:
|
||||
return super.onContextItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeComputer(ComputerDetails details) {
|
||||
for (int i = 0; i < pcGridAdapter.getCount(); i++) {
|
||||
ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(i);
|
||||
|
||||
if (details.equals(computer.details)) {
|
||||
for (int i = 0; i < pcGridAdapter.getCount(); i++) {
|
||||
ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(i);
|
||||
|
||||
if (details.equals(computer.details)) {
|
||||
pcGridAdapter.removeComputer(computer);
|
||||
pcGridAdapter.notifyDataSetChanged();
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateComputer(ComputerDetails details) {
|
||||
ComputerObject existingEntry = null;
|
||||
|
||||
for (int i = 0; i < pcGridAdapter.getCount(); i++) {
|
||||
ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(i);
|
||||
|
||||
// Check if this is the same computer
|
||||
if (details.uuid.equals(computer.details.uuid)) {
|
||||
existingEntry = computer;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (existingEntry != null) {
|
||||
// Replace the information in the existing entry
|
||||
existingEntry.details = details;
|
||||
}
|
||||
else {
|
||||
// Add a new entry
|
||||
private void updateComputer(ComputerDetails details) {
|
||||
ComputerObject existingEntry = null;
|
||||
|
||||
for (int i = 0; i < pcGridAdapter.getCount(); i++) {
|
||||
ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(i);
|
||||
|
||||
// Check if this is the same computer
|
||||
if (details.uuid.equals(computer.details.uuid)) {
|
||||
existingEntry = computer;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (existingEntry != null) {
|
||||
// Replace the information in the existing entry
|
||||
existingEntry.details = details;
|
||||
}
|
||||
else {
|
||||
// Add a new entry
|
||||
pcGridAdapter.addComputer(new ComputerObject(details));
|
||||
|
||||
// Remove the "Discovery in progress" view
|
||||
noPcFoundLayout.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
// Notify the view that the data has changed
|
||||
pcGridAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAdapterFragmentLayoutId() {
|
||||
@ -584,15 +584,15 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
}
|
||||
|
||||
public class ComputerObject {
|
||||
public ComputerDetails details;
|
||||
|
||||
public ComputerObject(ComputerDetails details) {
|
||||
this.details = details;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return details.name;
|
||||
}
|
||||
}
|
||||
public ComputerDetails details;
|
||||
|
||||
public ComputerObject(ComputerDetails details) {
|
||||
this.details = details;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return details.name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,17 +8,17 @@ import com.limelight.nvstream.av.audio.AudioRenderer;
|
||||
import com.limelight.nvstream.http.LimelightCryptoProvider;
|
||||
|
||||
public class PlatformBinding {
|
||||
public static String getDeviceName() {
|
||||
String deviceName = android.os.Build.MODEL;
|
||||
public static String getDeviceName() {
|
||||
String deviceName = android.os.Build.MODEL;
|
||||
deviceName = deviceName.replace(" ", "");
|
||||
return deviceName;
|
||||
}
|
||||
|
||||
public static AudioRenderer getAudioRenderer() {
|
||||
return new AndroidAudioRenderer();
|
||||
}
|
||||
|
||||
public static LimelightCryptoProvider getCryptoProvider(Context c) {
|
||||
return new AndroidCryptoProvider(c);
|
||||
}
|
||||
}
|
||||
|
||||
public static AudioRenderer getAudioRenderer() {
|
||||
return new AndroidAudioRenderer();
|
||||
}
|
||||
|
||||
public static LimelightCryptoProvider getCryptoProvider(Context c) {
|
||||
return new AndroidCryptoProvider(c);
|
||||
}
|
||||
}
|
||||
|
@ -9,27 +9,27 @@ import com.limelight.nvstream.av.audio.AudioRenderer;
|
||||
|
||||
public class AndroidAudioRenderer implements AudioRenderer {
|
||||
|
||||
private static final int FRAME_SIZE = 960;
|
||||
|
||||
private AudioTrack track;
|
||||
private static final int FRAME_SIZE = 960;
|
||||
|
||||
@Override
|
||||
public boolean streamInitialized(int channelCount, int sampleRate) {
|
||||
int channelConfig;
|
||||
int bufferSize;
|
||||
private AudioTrack track;
|
||||
|
||||
switch (channelCount)
|
||||
{
|
||||
case 1:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_MONO;
|
||||
break;
|
||||
case 2:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
|
||||
break;
|
||||
default:
|
||||
LimeLog.severe("Decoder returned unhandled channel count");
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
public boolean streamInitialized(int channelCount, int sampleRate) {
|
||||
int channelConfig;
|
||||
int bufferSize;
|
||||
|
||||
switch (channelCount)
|
||||
{
|
||||
case 1:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_MONO;
|
||||
break;
|
||||
case 2:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
|
||||
break;
|
||||
default:
|
||||
LimeLog.severe("Decoder returned unhandled channel count");
|
||||
return false;
|
||||
}
|
||||
|
||||
// We're not supposed to request less than the minimum
|
||||
// buffer size for our buffer, but it appears that we can
|
||||
@ -72,26 +72,26 @@ public class AndroidAudioRenderer implements AudioRenderer {
|
||||
AudioTrack.MODE_STREAM);
|
||||
track.play();
|
||||
}
|
||||
|
||||
LimeLog.info("Audio track buffer size: "+bufferSize);
|
||||
|
||||
return true;
|
||||
}
|
||||
LimeLog.info("Audio track buffer size: "+bufferSize);
|
||||
|
||||
@Override
|
||||
public void playDecodedAudio(byte[] audioData, int offset, int length) {
|
||||
track.write(audioData, offset, length);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void streamClosing() {
|
||||
if (track != null) {
|
||||
track.release();
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void playDecodedAudio(byte[] audioData, int offset, int length) {
|
||||
track.write(audioData, offset, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCapabilities() {
|
||||
return 0;
|
||||
}
|
||||
@Override
|
||||
public void streamClosing() {
|
||||
if (track != null) {
|
||||
track.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCapabilities() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
@ -45,239 +45,239 @@ import com.limelight.nvstream.http.LimelightCryptoProvider;
|
||||
|
||||
public class AndroidCryptoProvider implements LimelightCryptoProvider {
|
||||
|
||||
private final File certFile;
|
||||
private final File keyFile;
|
||||
|
||||
private X509Certificate cert;
|
||||
private RSAPrivateKey key;
|
||||
private byte[] pemCertBytes;
|
||||
|
||||
private static final Object globalCryptoLock = new Object();
|
||||
|
||||
static {
|
||||
// Install the Bouncy Castle provider
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
|
||||
public AndroidCryptoProvider(Context c) {
|
||||
String dataPath = c.getFilesDir().getAbsolutePath();
|
||||
|
||||
certFile = new File(dataPath + File.separator + "client.crt");
|
||||
keyFile = new File(dataPath + File.separator + "client.key");
|
||||
}
|
||||
|
||||
private byte[] loadFileToBytes(File f) {
|
||||
if (!f.exists()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
FileInputStream fin = new FileInputStream(f);
|
||||
byte[] fileData = new byte[(int) f.length()];
|
||||
if (fin.read(fileData) != f.length()) {
|
||||
private final File certFile;
|
||||
private final File keyFile;
|
||||
|
||||
private X509Certificate cert;
|
||||
private RSAPrivateKey key;
|
||||
private byte[] pemCertBytes;
|
||||
|
||||
private static final Object globalCryptoLock = new Object();
|
||||
|
||||
static {
|
||||
// Install the Bouncy Castle provider
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
|
||||
public AndroidCryptoProvider(Context c) {
|
||||
String dataPath = c.getFilesDir().getAbsolutePath();
|
||||
|
||||
certFile = new File(dataPath + File.separator + "client.crt");
|
||||
keyFile = new File(dataPath + File.separator + "client.key");
|
||||
}
|
||||
|
||||
private byte[] loadFileToBytes(File f) {
|
||||
if (!f.exists()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
FileInputStream fin = new FileInputStream(f);
|
||||
byte[] fileData = new byte[(int) f.length()];
|
||||
if (fin.read(fileData) != f.length()) {
|
||||
// Failed to read
|
||||
fileData = null;
|
||||
}
|
||||
fin.close();
|
||||
return fileData;
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean loadCertKeyPair() {
|
||||
byte[] certBytes = loadFileToBytes(certFile);
|
||||
byte[] keyBytes = loadFileToBytes(keyFile);
|
||||
|
||||
// If either file was missing, we definitely can't succeed
|
||||
if (certBytes == null || keyBytes == null) {
|
||||
LimeLog.info("Missing cert or key; need to generate a new one");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
CertificateFactory certFactory = CertificateFactory.getInstance("X.509", "BC");
|
||||
cert = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(certBytes));
|
||||
pemCertBytes = certBytes;
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC");
|
||||
key = (RSAPrivateKey) keyFactory.generatePrivate(new PKCS8EncodedKeySpec(keyBytes));
|
||||
} catch (CertificateException e) {
|
||||
// May happen if the cert is corrupt
|
||||
LimeLog.warning("Corrupted certificate");
|
||||
return false;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
// Should never happen
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
} catch (InvalidKeySpecException e) {
|
||||
// May happen if the key is corrupt
|
||||
LimeLog.warning("Corrupted key");
|
||||
return false;
|
||||
} catch (NoSuchProviderException e) {
|
||||
// Should never happen
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressLint("TrulyRandom")
|
||||
private boolean generateCertKeyPair() {
|
||||
byte[] snBytes = new byte[8];
|
||||
new SecureRandom().nextBytes(snBytes);
|
||||
|
||||
KeyPair keyPair;
|
||||
try {
|
||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC");
|
||||
keyPairGenerator.initialize(2048);
|
||||
keyPair = keyPairGenerator.generateKeyPair();
|
||||
} catch (NoSuchAlgorithmException e1) {
|
||||
// Should never happen
|
||||
e1.printStackTrace();
|
||||
return false;
|
||||
} catch (NoSuchProviderException e) {
|
||||
// Should never happen
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
|
||||
Date now = new Date();
|
||||
|
||||
// Expires in 20 years
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.setTime(now);
|
||||
calendar.add(Calendar.YEAR, 20);
|
||||
Date expirationDate = calendar.getTime();
|
||||
|
||||
BigInteger serial = new BigInteger(snBytes).abs();
|
||||
|
||||
X500NameBuilder nameBuilder = new X500NameBuilder(BCStyle.INSTANCE);
|
||||
nameBuilder.addRDN(BCStyle.CN, "NVIDIA GameStream Client");
|
||||
X500Name name = nameBuilder.build();
|
||||
|
||||
X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder(name, serial, now, expirationDate, Locale.ENGLISH, name,
|
||||
SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded()));
|
||||
|
||||
try {
|
||||
ContentSigner sigGen = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BouncyCastleProvider.PROVIDER_NAME).build(keyPair.getPrivate());
|
||||
cert = new JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME).getCertificate(certBuilder.build(sigGen));
|
||||
key = (RSAPrivateKey) keyPair.getPrivate();
|
||||
} catch (Exception e) {
|
||||
// Nothing should go wrong here
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
|
||||
LimeLog.info("Generated a new key pair");
|
||||
|
||||
// Save the resulting pair
|
||||
saveCertKeyPair();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void saveCertKeyPair() {
|
||||
try {
|
||||
FileOutputStream certOut = new FileOutputStream(certFile);
|
||||
FileOutputStream keyOut = new FileOutputStream(keyFile);
|
||||
|
||||
// Write the certificate in OpenSSL PEM format (important for the server)
|
||||
StringWriter strWriter = new StringWriter();
|
||||
JcaPEMWriter pemWriter = new JcaPEMWriter(strWriter);
|
||||
pemWriter.writeObject(cert);
|
||||
pemWriter.close();
|
||||
|
||||
// Line endings MUST be UNIX for the PC to accept the cert properly
|
||||
OutputStreamWriter certWriter = new OutputStreamWriter(certOut);
|
||||
String pemStr = strWriter.getBuffer().toString();
|
||||
for (int i = 0; i < pemStr.length(); i++) {
|
||||
char c = pemStr.charAt(i);
|
||||
if (c != '\r')
|
||||
certWriter.append(c);
|
||||
}
|
||||
certWriter.close();
|
||||
|
||||
// Write the private out in PKCS8 format
|
||||
keyOut.write(key.getEncoded());
|
||||
|
||||
certOut.close();
|
||||
keyOut.close();
|
||||
|
||||
LimeLog.info("Saved generated key pair to disk");
|
||||
} catch (IOException e) {
|
||||
// This isn't good because it means we'll have
|
||||
// to re-pair next time
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public X509Certificate getClientCertificate() {
|
||||
// Use a lock here to ensure only one guy will be generating or loading
|
||||
// the certificate and key at a time
|
||||
synchronized (globalCryptoLock) {
|
||||
// Return a loaded cert if we have one
|
||||
if (cert != null) {
|
||||
return cert;
|
||||
}
|
||||
|
||||
// No loaded cert yet, let's see if we have one on disk
|
||||
if (loadCertKeyPair()) {
|
||||
// Got one
|
||||
return cert;
|
||||
}
|
||||
|
||||
// Try to generate a new key pair
|
||||
if (!generateCertKeyPair()) {
|
||||
// Failed
|
||||
return null;
|
||||
}
|
||||
|
||||
// Load the generated pair
|
||||
loadCertKeyPair();
|
||||
return cert;
|
||||
}
|
||||
}
|
||||
fin.close();
|
||||
return fileData;
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public RSAPrivateKey getClientPrivateKey() {
|
||||
// Use a lock here to ensure only one guy will be generating or loading
|
||||
// the certificate and key at a time
|
||||
synchronized (globalCryptoLock) {
|
||||
// Return a loaded key if we have one
|
||||
if (key != null) {
|
||||
return key;
|
||||
}
|
||||
|
||||
// No loaded key yet, let's see if we have one on disk
|
||||
if (loadCertKeyPair()) {
|
||||
// Got one
|
||||
return key;
|
||||
}
|
||||
|
||||
// Try to generate a new key pair
|
||||
if (!generateCertKeyPair()) {
|
||||
// Failed
|
||||
return null;
|
||||
}
|
||||
|
||||
// Load the generated pair
|
||||
loadCertKeyPair();
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] getPemEncodedClientCertificate() {
|
||||
synchronized (globalCryptoLock) {
|
||||
// Call our helper function to do the cert loading/generation for us
|
||||
getClientCertificate();
|
||||
|
||||
// Return a cached value if we have it
|
||||
return pemCertBytes;
|
||||
}
|
||||
}
|
||||
private boolean loadCertKeyPair() {
|
||||
byte[] certBytes = loadFileToBytes(certFile);
|
||||
byte[] keyBytes = loadFileToBytes(keyFile);
|
||||
|
||||
@Override
|
||||
public String encodeBase64String(byte[] data) {
|
||||
return Base64.encodeToString(data, Base64.NO_WRAP);
|
||||
}
|
||||
// If either file was missing, we definitely can't succeed
|
||||
if (certBytes == null || keyBytes == null) {
|
||||
LimeLog.info("Missing cert or key; need to generate a new one");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
CertificateFactory certFactory = CertificateFactory.getInstance("X.509", "BC");
|
||||
cert = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(certBytes));
|
||||
pemCertBytes = certBytes;
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC");
|
||||
key = (RSAPrivateKey) keyFactory.generatePrivate(new PKCS8EncodedKeySpec(keyBytes));
|
||||
} catch (CertificateException e) {
|
||||
// May happen if the cert is corrupt
|
||||
LimeLog.warning("Corrupted certificate");
|
||||
return false;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
// Should never happen
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
} catch (InvalidKeySpecException e) {
|
||||
// May happen if the key is corrupt
|
||||
LimeLog.warning("Corrupted key");
|
||||
return false;
|
||||
} catch (NoSuchProviderException e) {
|
||||
// Should never happen
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressLint("TrulyRandom")
|
||||
private boolean generateCertKeyPair() {
|
||||
byte[] snBytes = new byte[8];
|
||||
new SecureRandom().nextBytes(snBytes);
|
||||
|
||||
KeyPair keyPair;
|
||||
try {
|
||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC");
|
||||
keyPairGenerator.initialize(2048);
|
||||
keyPair = keyPairGenerator.generateKeyPair();
|
||||
} catch (NoSuchAlgorithmException e1) {
|
||||
// Should never happen
|
||||
e1.printStackTrace();
|
||||
return false;
|
||||
} catch (NoSuchProviderException e) {
|
||||
// Should never happen
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
|
||||
Date now = new Date();
|
||||
|
||||
// Expires in 20 years
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.setTime(now);
|
||||
calendar.add(Calendar.YEAR, 20);
|
||||
Date expirationDate = calendar.getTime();
|
||||
|
||||
BigInteger serial = new BigInteger(snBytes).abs();
|
||||
|
||||
X500NameBuilder nameBuilder = new X500NameBuilder(BCStyle.INSTANCE);
|
||||
nameBuilder.addRDN(BCStyle.CN, "NVIDIA GameStream Client");
|
||||
X500Name name = nameBuilder.build();
|
||||
|
||||
X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder(name, serial, now, expirationDate, Locale.ENGLISH, name,
|
||||
SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded()));
|
||||
|
||||
try {
|
||||
ContentSigner sigGen = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BouncyCastleProvider.PROVIDER_NAME).build(keyPair.getPrivate());
|
||||
cert = new JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME).getCertificate(certBuilder.build(sigGen));
|
||||
key = (RSAPrivateKey) keyPair.getPrivate();
|
||||
} catch (Exception e) {
|
||||
// Nothing should go wrong here
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
|
||||
LimeLog.info("Generated a new key pair");
|
||||
|
||||
// Save the resulting pair
|
||||
saveCertKeyPair();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void saveCertKeyPair() {
|
||||
try {
|
||||
FileOutputStream certOut = new FileOutputStream(certFile);
|
||||
FileOutputStream keyOut = new FileOutputStream(keyFile);
|
||||
|
||||
// Write the certificate in OpenSSL PEM format (important for the server)
|
||||
StringWriter strWriter = new StringWriter();
|
||||
JcaPEMWriter pemWriter = new JcaPEMWriter(strWriter);
|
||||
pemWriter.writeObject(cert);
|
||||
pemWriter.close();
|
||||
|
||||
// Line endings MUST be UNIX for the PC to accept the cert properly
|
||||
OutputStreamWriter certWriter = new OutputStreamWriter(certOut);
|
||||
String pemStr = strWriter.getBuffer().toString();
|
||||
for (int i = 0; i < pemStr.length(); i++) {
|
||||
char c = pemStr.charAt(i);
|
||||
if (c != '\r')
|
||||
certWriter.append(c);
|
||||
}
|
||||
certWriter.close();
|
||||
|
||||
// Write the private out in PKCS8 format
|
||||
keyOut.write(key.getEncoded());
|
||||
|
||||
certOut.close();
|
||||
keyOut.close();
|
||||
|
||||
LimeLog.info("Saved generated key pair to disk");
|
||||
} catch (IOException e) {
|
||||
// This isn't good because it means we'll have
|
||||
// to re-pair next time
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public X509Certificate getClientCertificate() {
|
||||
// Use a lock here to ensure only one guy will be generating or loading
|
||||
// the certificate and key at a time
|
||||
synchronized (globalCryptoLock) {
|
||||
// Return a loaded cert if we have one
|
||||
if (cert != null) {
|
||||
return cert;
|
||||
}
|
||||
|
||||
// No loaded cert yet, let's see if we have one on disk
|
||||
if (loadCertKeyPair()) {
|
||||
// Got one
|
||||
return cert;
|
||||
}
|
||||
|
||||
// Try to generate a new key pair
|
||||
if (!generateCertKeyPair()) {
|
||||
// Failed
|
||||
return null;
|
||||
}
|
||||
|
||||
// Load the generated pair
|
||||
loadCertKeyPair();
|
||||
return cert;
|
||||
}
|
||||
}
|
||||
|
||||
public RSAPrivateKey getClientPrivateKey() {
|
||||
// Use a lock here to ensure only one guy will be generating or loading
|
||||
// the certificate and key at a time
|
||||
synchronized (globalCryptoLock) {
|
||||
// Return a loaded key if we have one
|
||||
if (key != null) {
|
||||
return key;
|
||||
}
|
||||
|
||||
// No loaded key yet, let's see if we have one on disk
|
||||
if (loadCertKeyPair()) {
|
||||
// Got one
|
||||
return key;
|
||||
}
|
||||
|
||||
// Try to generate a new key pair
|
||||
if (!generateCertKeyPair()) {
|
||||
// Failed
|
||||
return null;
|
||||
}
|
||||
|
||||
// Load the generated pair
|
||||
loadCertKeyPair();
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] getPemEncodedClientCertificate() {
|
||||
synchronized (globalCryptoLock) {
|
||||
// Call our helper function to do the cert loading/generation for us
|
||||
getClientCertificate();
|
||||
|
||||
// Return a cached value if we have it
|
||||
return pemCertBytes;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String encodeBase64String(byte[] data) {
|
||||
return Base64.encodeToString(data, Base64.NO_WRAP);
|
||||
}
|
||||
}
|
||||
|
@ -17,23 +17,23 @@ import com.limelight.utils.Vector2d;
|
||||
|
||||
public class ControllerHandler implements InputManager.InputDeviceListener {
|
||||
|
||||
private static final int MAXIMUM_BUMPER_UP_DELAY_MS = 100;
|
||||
private static final int MAXIMUM_BUMPER_UP_DELAY_MS = 100;
|
||||
|
||||
private static final int START_DOWN_TIME_KEYB_MS = 750;
|
||||
|
||||
private static final int MINIMUM_BUTTON_DOWN_TIME_MS = 25;
|
||||
|
||||
private static final int EMULATING_SPECIAL = 0x1;
|
||||
private static final int EMULATING_SELECT = 0x2;
|
||||
|
||||
private static final int EMULATED_SPECIAL_UP_DELAY_MS = 100;
|
||||
private static final int EMULATED_SELECT_UP_DELAY_MS = 30;
|
||||
|
||||
private final Vector2d inputVector = new Vector2d();
|
||||
|
||||
private final HashMap<String, ControllerContext> contexts = new HashMap<String, ControllerContext>();
|
||||
|
||||
private final NvConnection conn;
|
||||
|
||||
private static final int MINIMUM_BUTTON_DOWN_TIME_MS = 25;
|
||||
|
||||
private static final int EMULATING_SPECIAL = 0x1;
|
||||
private static final int EMULATING_SELECT = 0x2;
|
||||
|
||||
private static final int EMULATED_SPECIAL_UP_DELAY_MS = 100;
|
||||
private static final int EMULATED_SELECT_UP_DELAY_MS = 30;
|
||||
|
||||
private final Vector2d inputVector = new Vector2d();
|
||||
|
||||
private final HashMap<String, ControllerContext> contexts = new HashMap<String, ControllerContext>();
|
||||
|
||||
private final NvConnection conn;
|
||||
private final double stickDeadzone;
|
||||
private final ControllerContext defaultContext = new ControllerContext();
|
||||
private final GameGestures gestures;
|
||||
@ -41,9 +41,9 @@ public class ControllerHandler implements InputManager.InputDeviceListener {
|
||||
|
||||
private final boolean multiControllerEnabled;
|
||||
private short currentControllers;
|
||||
|
||||
public ControllerHandler(NvConnection conn, GameGestures gestures, boolean multiControllerEnabled, int deadzonePercentage) {
|
||||
this.conn = conn;
|
||||
|
||||
public ControllerHandler(NvConnection conn, GameGestures gestures, boolean multiControllerEnabled, int deadzonePercentage) {
|
||||
this.conn = conn;
|
||||
this.gestures = gestures;
|
||||
this.multiControllerEnabled = multiControllerEnabled;
|
||||
|
||||
@ -82,20 +82,20 @@ public class ControllerHandler implements InputManager.InputDeviceListener {
|
||||
defaultContext.leftTriggerAxis = MotionEvent.AXIS_BRAKE;
|
||||
defaultContext.rightTriggerAxis = MotionEvent.AXIS_GAS;
|
||||
defaultContext.controllerNumber = (short) 0;
|
||||
}
|
||||
|
||||
private static InputDevice.MotionRange getMotionRangeForJoystickAxis(InputDevice dev, int axis) {
|
||||
InputDevice.MotionRange range;
|
||||
|
||||
// First get the axis for SOURCE_JOYSTICK
|
||||
range = dev.getMotionRange(axis, InputDevice.SOURCE_JOYSTICK);
|
||||
if (range == null) {
|
||||
// Now try the axis for SOURCE_GAMEPAD
|
||||
range = dev.getMotionRange(axis, InputDevice.SOURCE_GAMEPAD);
|
||||
}
|
||||
|
||||
return range;
|
||||
}
|
||||
}
|
||||
|
||||
private static InputDevice.MotionRange getMotionRangeForJoystickAxis(InputDevice dev, int axis) {
|
||||
InputDevice.MotionRange range;
|
||||
|
||||
// First get the axis for SOURCE_JOYSTICK
|
||||
range = dev.getMotionRange(axis, InputDevice.SOURCE_JOYSTICK);
|
||||
if (range == null) {
|
||||
// Now try the axis for SOURCE_GAMEPAD
|
||||
range = dev.getMotionRange(axis, InputDevice.SOURCE_GAMEPAD);
|
||||
}
|
||||
|
||||
return range;
|
||||
}
|
||||
|
||||
private short assignNewControllerNumber() {
|
||||
for (short i = 0; i < 4; i++) {
|
||||
@ -137,9 +137,9 @@ public class ControllerHandler implements InputManager.InputDeviceListener {
|
||||
LimeLog.info("Controller number "+controllerNumber+" is now available");
|
||||
currentControllers &= ~(1 << controllerNumber);
|
||||
}
|
||||
|
||||
private ControllerContext createContextForDevice(InputDevice dev) {
|
||||
ControllerContext context = new ControllerContext();
|
||||
|
||||
private ControllerContext createContextForDevice(InputDevice dev) {
|
||||
ControllerContext context = new ControllerContext();
|
||||
String devName = dev.getName();
|
||||
|
||||
LimeLog.info("Creating controller context for device: "+devName);
|
||||
@ -148,91 +148,91 @@ public class ControllerHandler implements InputManager.InputDeviceListener {
|
||||
context.id = dev.getId();
|
||||
|
||||
context.leftStickXAxis = MotionEvent.AXIS_X;
|
||||
context.leftStickYAxis = MotionEvent.AXIS_Y;
|
||||
context.leftStickYAxis = MotionEvent.AXIS_Y;
|
||||
if (getMotionRangeForJoystickAxis(dev, context.leftStickXAxis) != null &&
|
||||
getMotionRangeForJoystickAxis(dev, context.leftStickYAxis) != null) {
|
||||
// This is a gamepad
|
||||
hasGameController = true;
|
||||
context.hasJoystickAxes = true;
|
||||
}
|
||||
|
||||
InputDevice.MotionRange leftTriggerRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_LTRIGGER);
|
||||
InputDevice.MotionRange rightTriggerRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RTRIGGER);
|
||||
InputDevice.MotionRange brakeRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_BRAKE);
|
||||
InputDevice.MotionRange gasRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_GAS);
|
||||
if (leftTriggerRange != null && rightTriggerRange != null)
|
||||
{
|
||||
// Some controllers use LTRIGGER and RTRIGGER (like Ouya)
|
||||
context.leftTriggerAxis = MotionEvent.AXIS_LTRIGGER;
|
||||
context.rightTriggerAxis = MotionEvent.AXIS_RTRIGGER;
|
||||
}
|
||||
else if (brakeRange != null && gasRange != null)
|
||||
{
|
||||
// Others use GAS and BRAKE (like Moga)
|
||||
context.leftTriggerAxis = MotionEvent.AXIS_BRAKE;
|
||||
context.rightTriggerAxis = MotionEvent.AXIS_GAS;
|
||||
}
|
||||
else
|
||||
{
|
||||
InputDevice.MotionRange rxRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RX);
|
||||
InputDevice.MotionRange ryRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RY);
|
||||
if (rxRange != null && ryRange != null && devName != null) {
|
||||
if (devName.contains("Xbox") || devName.contains("XBox") || devName.contains("X-Box")) {
|
||||
// Xbox controllers use RX and RY for right stick
|
||||
context.rightStickXAxis = MotionEvent.AXIS_RX;
|
||||
context.rightStickYAxis = MotionEvent.AXIS_RY;
|
||||
|
||||
// Xbox controllers use Z and RZ for triggers
|
||||
context.leftTriggerAxis = MotionEvent.AXIS_Z;
|
||||
context.rightTriggerAxis = MotionEvent.AXIS_RZ;
|
||||
context.triggersIdleNegative = true;
|
||||
context.isXboxController = true;
|
||||
}
|
||||
else {
|
||||
// DS4 controller uses RX and RY for triggers
|
||||
context.leftTriggerAxis = MotionEvent.AXIS_RX;
|
||||
context.rightTriggerAxis = MotionEvent.AXIS_RY;
|
||||
context.triggersIdleNegative = true;
|
||||
|
||||
context.isDualShock4 = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (context.rightStickXAxis == -1 && context.rightStickYAxis == -1) {
|
||||
InputDevice.MotionRange zRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_Z);
|
||||
InputDevice.MotionRange rzRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RZ);
|
||||
|
||||
// Most other controllers use Z and RZ for the right stick
|
||||
if (zRange != null && rzRange != null) {
|
||||
context.rightStickXAxis = MotionEvent.AXIS_Z;
|
||||
context.rightStickYAxis = MotionEvent.AXIS_RZ;
|
||||
}
|
||||
else {
|
||||
InputDevice.MotionRange rxRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RX);
|
||||
InputDevice.MotionRange ryRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RY);
|
||||
|
||||
// Try RX and RY now
|
||||
if (rxRange != null && ryRange != null) {
|
||||
context.rightStickXAxis = MotionEvent.AXIS_RX;
|
||||
context.rightStickYAxis = MotionEvent.AXIS_RY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Some devices have "hats" for d-pads
|
||||
InputDevice.MotionRange hatXRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_HAT_X);
|
||||
InputDevice.MotionRange hatYRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_HAT_Y);
|
||||
if (hatXRange != null && hatYRange != null) {
|
||||
context.hatXAxis = MotionEvent.AXIS_HAT_X;
|
||||
context.hatYAxis = MotionEvent.AXIS_HAT_Y;
|
||||
}
|
||||
|
||||
if (context.leftStickXAxis != -1 && context.leftStickYAxis != -1) {
|
||||
|
||||
InputDevice.MotionRange leftTriggerRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_LTRIGGER);
|
||||
InputDevice.MotionRange rightTriggerRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RTRIGGER);
|
||||
InputDevice.MotionRange brakeRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_BRAKE);
|
||||
InputDevice.MotionRange gasRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_GAS);
|
||||
if (leftTriggerRange != null && rightTriggerRange != null)
|
||||
{
|
||||
// Some controllers use LTRIGGER and RTRIGGER (like Ouya)
|
||||
context.leftTriggerAxis = MotionEvent.AXIS_LTRIGGER;
|
||||
context.rightTriggerAxis = MotionEvent.AXIS_RTRIGGER;
|
||||
}
|
||||
else if (brakeRange != null && gasRange != null)
|
||||
{
|
||||
// Others use GAS and BRAKE (like Moga)
|
||||
context.leftTriggerAxis = MotionEvent.AXIS_BRAKE;
|
||||
context.rightTriggerAxis = MotionEvent.AXIS_GAS;
|
||||
}
|
||||
else
|
||||
{
|
||||
InputDevice.MotionRange rxRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RX);
|
||||
InputDevice.MotionRange ryRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RY);
|
||||
if (rxRange != null && ryRange != null && devName != null) {
|
||||
if (devName.contains("Xbox") || devName.contains("XBox") || devName.contains("X-Box")) {
|
||||
// Xbox controllers use RX and RY for right stick
|
||||
context.rightStickXAxis = MotionEvent.AXIS_RX;
|
||||
context.rightStickYAxis = MotionEvent.AXIS_RY;
|
||||
|
||||
// Xbox controllers use Z and RZ for triggers
|
||||
context.leftTriggerAxis = MotionEvent.AXIS_Z;
|
||||
context.rightTriggerAxis = MotionEvent.AXIS_RZ;
|
||||
context.triggersIdleNegative = true;
|
||||
context.isXboxController = true;
|
||||
}
|
||||
else {
|
||||
// DS4 controller uses RX and RY for triggers
|
||||
context.leftTriggerAxis = MotionEvent.AXIS_RX;
|
||||
context.rightTriggerAxis = MotionEvent.AXIS_RY;
|
||||
context.triggersIdleNegative = true;
|
||||
|
||||
context.isDualShock4 = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (context.rightStickXAxis == -1 && context.rightStickYAxis == -1) {
|
||||
InputDevice.MotionRange zRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_Z);
|
||||
InputDevice.MotionRange rzRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RZ);
|
||||
|
||||
// Most other controllers use Z and RZ for the right stick
|
||||
if (zRange != null && rzRange != null) {
|
||||
context.rightStickXAxis = MotionEvent.AXIS_Z;
|
||||
context.rightStickYAxis = MotionEvent.AXIS_RZ;
|
||||
}
|
||||
else {
|
||||
InputDevice.MotionRange rxRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RX);
|
||||
InputDevice.MotionRange ryRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RY);
|
||||
|
||||
// Try RX and RY now
|
||||
if (rxRange != null && ryRange != null) {
|
||||
context.rightStickXAxis = MotionEvent.AXIS_RX;
|
||||
context.rightStickYAxis = MotionEvent.AXIS_RY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Some devices have "hats" for d-pads
|
||||
InputDevice.MotionRange hatXRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_HAT_X);
|
||||
InputDevice.MotionRange hatYRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_HAT_Y);
|
||||
if (hatXRange != null && hatYRange != null) {
|
||||
context.hatXAxis = MotionEvent.AXIS_HAT_X;
|
||||
context.hatYAxis = MotionEvent.AXIS_HAT_Y;
|
||||
}
|
||||
|
||||
if (context.leftStickXAxis != -1 && context.leftStickYAxis != -1) {
|
||||
context.leftStickDeadzoneRadius = (float) stickDeadzone;
|
||||
}
|
||||
|
||||
if (context.rightStickXAxis != -1 && context.rightStickYAxis != -1) {
|
||||
}
|
||||
|
||||
if (context.rightStickXAxis != -1 && context.rightStickYAxis != -1) {
|
||||
context.rightStickDeadzoneRadius = (float) stickDeadzone;
|
||||
}
|
||||
|
||||
@ -300,114 +300,114 @@ public class ControllerHandler implements InputManager.InputDeviceListener {
|
||||
LimeLog.info("Assigned as controller "+context.controllerNumber);
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
private ControllerContext getContextForDevice(InputDevice dev) {
|
||||
// Unknown devices use the default context
|
||||
if (dev == null) {
|
||||
return defaultContext;
|
||||
}
|
||||
|
||||
String descriptor = dev.getDescriptor();
|
||||
|
||||
// Return the existing context if it exists
|
||||
ControllerContext context = contexts.get(descriptor);
|
||||
if (context != null) {
|
||||
return context;
|
||||
}
|
||||
|
||||
// Otherwise create a new context
|
||||
}
|
||||
|
||||
private ControllerContext getContextForDevice(InputDevice dev) {
|
||||
// Unknown devices use the default context
|
||||
if (dev == null) {
|
||||
return defaultContext;
|
||||
}
|
||||
|
||||
String descriptor = dev.getDescriptor();
|
||||
|
||||
// Return the existing context if it exists
|
||||
ControllerContext context = contexts.get(descriptor);
|
||||
if (context != null) {
|
||||
return context;
|
||||
}
|
||||
|
||||
// Otherwise create a new context
|
||||
context = createContextForDevice(dev);
|
||||
contexts.put(descriptor, context);
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
private void sendControllerInputPacket(ControllerContext context) {
|
||||
conn.sendControllerInput(context.controllerNumber, context.inputMap,
|
||||
contexts.put(descriptor, context);
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
private void sendControllerInputPacket(ControllerContext context) {
|
||||
conn.sendControllerInput(context.controllerNumber, context.inputMap,
|
||||
context.leftTrigger, context.rightTrigger,
|
||||
context.leftStickX, context.leftStickY,
|
||||
context.rightStickX, context.rightStickY);
|
||||
}
|
||||
}
|
||||
|
||||
// Return a valid keycode, 0 to consume, or -1 to not consume the event
|
||||
// Device MAY BE NULL
|
||||
private int handleRemapping(ControllerContext context, KeyEvent event) {
|
||||
private int handleRemapping(ControllerContext context, KeyEvent event) {
|
||||
// For remotes, don't capture the back button
|
||||
if (context.isRemote) {
|
||||
if (context.isRemote) {
|
||||
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (context.isDualShock4) {
|
||||
switch (event.getKeyCode()) {
|
||||
case KeyEvent.KEYCODE_BUTTON_Y:
|
||||
return KeyEvent.KEYCODE_BUTTON_L1;
|
||||
|
||||
case KeyEvent.KEYCODE_BUTTON_Z:
|
||||
return KeyEvent.KEYCODE_BUTTON_R1;
|
||||
|
||||
case KeyEvent.KEYCODE_BUTTON_C:
|
||||
return KeyEvent.KEYCODE_BUTTON_B;
|
||||
|
||||
case KeyEvent.KEYCODE_BUTTON_X:
|
||||
return KeyEvent.KEYCODE_BUTTON_Y;
|
||||
|
||||
case KeyEvent.KEYCODE_BUTTON_B:
|
||||
return KeyEvent.KEYCODE_BUTTON_A;
|
||||
|
||||
case KeyEvent.KEYCODE_BUTTON_A:
|
||||
return KeyEvent.KEYCODE_BUTTON_X;
|
||||
|
||||
case KeyEvent.KEYCODE_BUTTON_SELECT:
|
||||
return KeyEvent.KEYCODE_BUTTON_THUMBL;
|
||||
|
||||
case KeyEvent.KEYCODE_BUTTON_START:
|
||||
return KeyEvent.KEYCODE_BUTTON_THUMBR;
|
||||
|
||||
case KeyEvent.KEYCODE_BUTTON_L2:
|
||||
return KeyEvent.KEYCODE_BUTTON_SELECT;
|
||||
|
||||
case KeyEvent.KEYCODE_BUTTON_R2:
|
||||
return KeyEvent.KEYCODE_BUTTON_START;
|
||||
|
||||
// These are duplicate trigger events
|
||||
case KeyEvent.KEYCODE_BUTTON_R1:
|
||||
case KeyEvent.KEYCODE_BUTTON_L1:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (context.hatXAxis != -1 && context.hatYAxis != -1) {
|
||||
switch (event.getKeyCode()) {
|
||||
// These are duplicate dpad events for hat input
|
||||
case KeyEvent.KEYCODE_DPAD_LEFT:
|
||||
case KeyEvent.KEYCODE_DPAD_RIGHT:
|
||||
case KeyEvent.KEYCODE_DPAD_CENTER:
|
||||
case KeyEvent.KEYCODE_DPAD_UP:
|
||||
case KeyEvent.KEYCODE_DPAD_DOWN:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else if (context.hatXAxis == -1 &&
|
||||
context.hatYAxis == -1 &&
|
||||
context.isXboxController &&
|
||||
event.getKeyCode() == KeyEvent.KEYCODE_UNKNOWN) {
|
||||
// If there's not a proper Xbox controller mapping, we'll translate the raw d-pad
|
||||
// scan codes into proper key codes
|
||||
switch (event.getScanCode())
|
||||
{
|
||||
case 704:
|
||||
return KeyEvent.KEYCODE_DPAD_LEFT;
|
||||
case 705:
|
||||
return KeyEvent.KEYCODE_DPAD_RIGHT;
|
||||
case 706:
|
||||
return KeyEvent.KEYCODE_DPAD_UP;
|
||||
case 707:
|
||||
return KeyEvent.KEYCODE_DPAD_DOWN;
|
||||
}
|
||||
}
|
||||
switch (event.getKeyCode()) {
|
||||
case KeyEvent.KEYCODE_BUTTON_Y:
|
||||
return KeyEvent.KEYCODE_BUTTON_L1;
|
||||
|
||||
case KeyEvent.KEYCODE_BUTTON_Z:
|
||||
return KeyEvent.KEYCODE_BUTTON_R1;
|
||||
|
||||
case KeyEvent.KEYCODE_BUTTON_C:
|
||||
return KeyEvent.KEYCODE_BUTTON_B;
|
||||
|
||||
case KeyEvent.KEYCODE_BUTTON_X:
|
||||
return KeyEvent.KEYCODE_BUTTON_Y;
|
||||
|
||||
case KeyEvent.KEYCODE_BUTTON_B:
|
||||
return KeyEvent.KEYCODE_BUTTON_A;
|
||||
|
||||
case KeyEvent.KEYCODE_BUTTON_A:
|
||||
return KeyEvent.KEYCODE_BUTTON_X;
|
||||
|
||||
case KeyEvent.KEYCODE_BUTTON_SELECT:
|
||||
return KeyEvent.KEYCODE_BUTTON_THUMBL;
|
||||
|
||||
case KeyEvent.KEYCODE_BUTTON_START:
|
||||
return KeyEvent.KEYCODE_BUTTON_THUMBR;
|
||||
|
||||
case KeyEvent.KEYCODE_BUTTON_L2:
|
||||
return KeyEvent.KEYCODE_BUTTON_SELECT;
|
||||
|
||||
case KeyEvent.KEYCODE_BUTTON_R2:
|
||||
return KeyEvent.KEYCODE_BUTTON_START;
|
||||
|
||||
// These are duplicate trigger events
|
||||
case KeyEvent.KEYCODE_BUTTON_R1:
|
||||
case KeyEvent.KEYCODE_BUTTON_L1:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (context.hatXAxis != -1 && context.hatYAxis != -1) {
|
||||
switch (event.getKeyCode()) {
|
||||
// These are duplicate dpad events for hat input
|
||||
case KeyEvent.KEYCODE_DPAD_LEFT:
|
||||
case KeyEvent.KEYCODE_DPAD_RIGHT:
|
||||
case KeyEvent.KEYCODE_DPAD_CENTER:
|
||||
case KeyEvent.KEYCODE_DPAD_UP:
|
||||
case KeyEvent.KEYCODE_DPAD_DOWN:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else if (context.hatXAxis == -1 &&
|
||||
context.hatYAxis == -1 &&
|
||||
context.isXboxController &&
|
||||
event.getKeyCode() == KeyEvent.KEYCODE_UNKNOWN) {
|
||||
// If there's not a proper Xbox controller mapping, we'll translate the raw d-pad
|
||||
// scan codes into proper key codes
|
||||
switch (event.getScanCode())
|
||||
{
|
||||
case 704:
|
||||
return KeyEvent.KEYCODE_DPAD_LEFT;
|
||||
case 705:
|
||||
return KeyEvent.KEYCODE_DPAD_RIGHT;
|
||||
case 706:
|
||||
return KeyEvent.KEYCODE_DPAD_UP;
|
||||
case 707:
|
||||
return KeyEvent.KEYCODE_DPAD_DOWN;
|
||||
}
|
||||
}
|
||||
|
||||
// Past here we can fixup the keycode and potentially trigger
|
||||
// another special case so we need to remember what keycode we're using
|
||||
@ -439,21 +439,21 @@ public class ControllerHandler implements InputManager.InputDeviceListener {
|
||||
// Emulate the select button with mode
|
||||
return KeyEvent.KEYCODE_BUTTON_SELECT;
|
||||
}
|
||||
|
||||
return keyCode;
|
||||
}
|
||||
|
||||
return keyCode;
|
||||
}
|
||||
|
||||
private Vector2d populateCachedVector(float x, float y) {
|
||||
// Reinitialize our cached Vector2d object
|
||||
inputVector.initialize(x, y);
|
||||
return inputVector;
|
||||
}
|
||||
|
||||
private void handleDeadZone(Vector2d stickVector, float deadzoneRadius) {
|
||||
if (stickVector.getMagnitude() <= deadzoneRadius) {
|
||||
// Deadzone
|
||||
|
||||
private void handleDeadZone(Vector2d stickVector, float deadzoneRadius) {
|
||||
if (stickVector.getMagnitude() <= deadzoneRadius) {
|
||||
// Deadzone
|
||||
stickVector.initialize(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// We're not normalizing here because we let the computer handle the deadzones.
|
||||
// Normalizing can make the deadzones larger than they should be after the computer also
|
||||
@ -518,9 +518,9 @@ public class ControllerHandler implements InputManager.InputDeviceListener {
|
||||
|
||||
sendControllerInputPacket(context);
|
||||
}
|
||||
|
||||
public boolean handleMotionEvent(MotionEvent event) {
|
||||
ControllerContext context = getContextForDevice(event.getDevice());
|
||||
|
||||
public boolean handleMotionEvent(MotionEvent event) {
|
||||
ControllerContext context = getContextForDevice(event.getDevice());
|
||||
float lsX = 0, lsY = 0, rsX = 0, rsY = 0, rt = 0, lt = 0, hatX = 0, hatY = 0;
|
||||
|
||||
// We purposefully ignore the historical values in the motion event as it makes
|
||||
@ -548,255 +548,255 @@ public class ControllerHandler implements InputManager.InputDeviceListener {
|
||||
|
||||
handleAxisSet(context, lsX, lsY, rsX, rsY, lt, rt, hatX, hatY);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean handleButtonUp(KeyEvent event) {
|
||||
ControllerContext context = getContextForDevice(event.getDevice());
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean handleButtonUp(KeyEvent event) {
|
||||
ControllerContext context = getContextForDevice(event.getDevice());
|
||||
|
||||
int keyCode = handleRemapping(context, event);
|
||||
if (keyCode == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the button hasn't been down long enough, sleep for a bit before sending the up event
|
||||
// This allows "instant" button presses (like OUYA's virtual menu button) to work. This
|
||||
// path should not be triggered during normal usage.
|
||||
if (SystemClock.uptimeMillis() - event.getDownTime() < ControllerHandler.MINIMUM_BUTTON_DOWN_TIME_MS)
|
||||
{
|
||||
// Since our sleep time is so short (10 ms), it shouldn't cause a problem doing this in the
|
||||
// UI thread.
|
||||
try {
|
||||
Thread.sleep(ControllerHandler.MINIMUM_BUTTON_DOWN_TIME_MS);
|
||||
} catch (InterruptedException ignored) {}
|
||||
}
|
||||
|
||||
switch (keyCode) {
|
||||
case KeyEvent.KEYCODE_BUTTON_MODE:
|
||||
if (keyCode == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the button hasn't been down long enough, sleep for a bit before sending the up event
|
||||
// This allows "instant" button presses (like OUYA's virtual menu button) to work. This
|
||||
// path should not be triggered during normal usage.
|
||||
if (SystemClock.uptimeMillis() - event.getDownTime() < ControllerHandler.MINIMUM_BUTTON_DOWN_TIME_MS)
|
||||
{
|
||||
// Since our sleep time is so short (10 ms), it shouldn't cause a problem doing this in the
|
||||
// UI thread.
|
||||
try {
|
||||
Thread.sleep(ControllerHandler.MINIMUM_BUTTON_DOWN_TIME_MS);
|
||||
} catch (InterruptedException ignored) {}
|
||||
}
|
||||
|
||||
switch (keyCode) {
|
||||
case KeyEvent.KEYCODE_BUTTON_MODE:
|
||||
context.inputMap &= ~ControllerPacket.SPECIAL_BUTTON_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_START:
|
||||
case KeyEvent.KEYCODE_MENU:
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_START:
|
||||
case KeyEvent.KEYCODE_MENU:
|
||||
if (SystemClock.uptimeMillis() - context.startDownTime > ControllerHandler.START_DOWN_TIME_KEYB_MS) {
|
||||
gestures.showKeyboard();
|
||||
}
|
||||
context.inputMap &= ~ControllerPacket.PLAY_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BACK:
|
||||
case KeyEvent.KEYCODE_BUTTON_SELECT:
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BACK:
|
||||
case KeyEvent.KEYCODE_BUTTON_SELECT:
|
||||
context.inputMap &= ~ControllerPacket.BACK_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_LEFT:
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_LEFT:
|
||||
context.inputMap &= ~ControllerPacket.LEFT_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_RIGHT:
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_RIGHT:
|
||||
context.inputMap &= ~ControllerPacket.RIGHT_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_UP:
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_UP:
|
||||
context.inputMap &= ~ControllerPacket.UP_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_DOWN:
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_DOWN:
|
||||
context.inputMap &= ~ControllerPacket.DOWN_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_B:
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_B:
|
||||
context.inputMap &= ~ControllerPacket.B_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_CENTER:
|
||||
case KeyEvent.KEYCODE_BUTTON_A:
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_CENTER:
|
||||
case KeyEvent.KEYCODE_BUTTON_A:
|
||||
context.inputMap &= ~ControllerPacket.A_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_X:
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_X:
|
||||
context.inputMap &= ~ControllerPacket.X_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_Y:
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_Y:
|
||||
context.inputMap &= ~ControllerPacket.Y_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_L1:
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_L1:
|
||||
context.inputMap &= ~ControllerPacket.LB_FLAG;
|
||||
context.lastLbUpTime = SystemClock.uptimeMillis();
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_R1:
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_R1:
|
||||
context.inputMap &= ~ControllerPacket.RB_FLAG;
|
||||
context.lastRbUpTime = SystemClock.uptimeMillis();
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_THUMBL:
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_THUMBL:
|
||||
context.inputMap &= ~ControllerPacket.LS_CLK_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_THUMBR:
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_THUMBR:
|
||||
context.inputMap &= ~ControllerPacket.RS_CLK_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_L2:
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_L2:
|
||||
context.leftTrigger = 0;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_R2:
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_R2:
|
||||
context.rightTrigger = 0;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if we're emulating the select button
|
||||
if ((context.emulatingButtonFlags & ControllerHandler.EMULATING_SELECT) != 0)
|
||||
{
|
||||
// If either start or LB is up, select comes up too
|
||||
if ((context.inputMap & ControllerPacket.PLAY_FLAG) == 0 ||
|
||||
(context.inputMap & ControllerPacket.LB_FLAG) == 0)
|
||||
{
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if we're emulating the select button
|
||||
if ((context.emulatingButtonFlags & ControllerHandler.EMULATING_SELECT) != 0)
|
||||
{
|
||||
// If either start or LB is up, select comes up too
|
||||
if ((context.inputMap & ControllerPacket.PLAY_FLAG) == 0 ||
|
||||
(context.inputMap & ControllerPacket.LB_FLAG) == 0)
|
||||
{
|
||||
context.inputMap &= ~ControllerPacket.BACK_FLAG;
|
||||
|
||||
context.emulatingButtonFlags &= ~ControllerHandler.EMULATING_SELECT;
|
||||
|
||||
try {
|
||||
Thread.sleep(EMULATED_SELECT_UP_DELAY_MS);
|
||||
} catch (InterruptedException ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we're emulating the special button
|
||||
if ((context.emulatingButtonFlags & ControllerHandler.EMULATING_SPECIAL) != 0)
|
||||
{
|
||||
// If either start or select and RB is up, the special button comes up too
|
||||
if ((context.inputMap & ControllerPacket.PLAY_FLAG) == 0 ||
|
||||
((context.inputMap & ControllerPacket.BACK_FLAG) == 0 &&
|
||||
(context.inputMap & ControllerPacket.RB_FLAG) == 0))
|
||||
{
|
||||
|
||||
try {
|
||||
Thread.sleep(EMULATED_SELECT_UP_DELAY_MS);
|
||||
} catch (InterruptedException ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we're emulating the special button
|
||||
if ((context.emulatingButtonFlags & ControllerHandler.EMULATING_SPECIAL) != 0)
|
||||
{
|
||||
// If either start or select and RB is up, the special button comes up too
|
||||
if ((context.inputMap & ControllerPacket.PLAY_FLAG) == 0 ||
|
||||
((context.inputMap & ControllerPacket.BACK_FLAG) == 0 &&
|
||||
(context.inputMap & ControllerPacket.RB_FLAG) == 0))
|
||||
{
|
||||
context.inputMap &= ~ControllerPacket.SPECIAL_BUTTON_FLAG;
|
||||
|
||||
context.emulatingButtonFlags &= ~ControllerHandler.EMULATING_SPECIAL;
|
||||
|
||||
try {
|
||||
Thread.sleep(EMULATED_SPECIAL_UP_DELAY_MS);
|
||||
} catch (InterruptedException ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
sendControllerInputPacket(context);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean handleButtonDown(KeyEvent event) {
|
||||
ControllerContext context = getContextForDevice(event.getDevice());
|
||||
|
||||
try {
|
||||
Thread.sleep(EMULATED_SPECIAL_UP_DELAY_MS);
|
||||
} catch (InterruptedException ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
sendControllerInputPacket(context);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean handleButtonDown(KeyEvent event) {
|
||||
ControllerContext context = getContextForDevice(event.getDevice());
|
||||
|
||||
int keyCode = handleRemapping(context, event);
|
||||
if (keyCode == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (keyCode) {
|
||||
case KeyEvent.KEYCODE_BUTTON_MODE:
|
||||
if (keyCode == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (keyCode) {
|
||||
case KeyEvent.KEYCODE_BUTTON_MODE:
|
||||
context.inputMap |= ControllerPacket.SPECIAL_BUTTON_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_START:
|
||||
case KeyEvent.KEYCODE_MENU:
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_START:
|
||||
case KeyEvent.KEYCODE_MENU:
|
||||
if (event.getRepeatCount() == 0) {
|
||||
context.startDownTime = SystemClock.uptimeMillis();
|
||||
}
|
||||
context.inputMap |= ControllerPacket.PLAY_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BACK:
|
||||
case KeyEvent.KEYCODE_BUTTON_SELECT:
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BACK:
|
||||
case KeyEvent.KEYCODE_BUTTON_SELECT:
|
||||
context.inputMap |= ControllerPacket.BACK_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_LEFT:
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_LEFT:
|
||||
context.inputMap |= ControllerPacket.LEFT_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_RIGHT:
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_RIGHT:
|
||||
context.inputMap |= ControllerPacket.RIGHT_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_UP:
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_UP:
|
||||
context.inputMap |= ControllerPacket.UP_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_DOWN:
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_DOWN:
|
||||
context.inputMap |= ControllerPacket.DOWN_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_B:
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_B:
|
||||
context.inputMap |= ControllerPacket.B_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_CENTER:
|
||||
case KeyEvent.KEYCODE_BUTTON_A:
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_CENTER:
|
||||
case KeyEvent.KEYCODE_BUTTON_A:
|
||||
context.inputMap |= ControllerPacket.A_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_X:
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_X:
|
||||
context.inputMap |= ControllerPacket.X_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_Y:
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_Y:
|
||||
context.inputMap |= ControllerPacket.Y_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_L1:
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_L1:
|
||||
context.inputMap |= ControllerPacket.LB_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_R1:
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_R1:
|
||||
context.inputMap |= ControllerPacket.RB_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_THUMBL:
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_THUMBL:
|
||||
context.inputMap |= ControllerPacket.LS_CLK_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_THUMBR:
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_THUMBR:
|
||||
context.inputMap |= ControllerPacket.RS_CLK_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_L2:
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_L2:
|
||||
context.leftTrigger = (byte)0xFF;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_R2:
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_R2:
|
||||
context.rightTrigger = (byte)0xFF;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
// Start+LB acts like select for controllers with one button
|
||||
if ((context.inputMap & ControllerPacket.PLAY_FLAG) != 0 &&
|
||||
((context.inputMap & ControllerPacket.LB_FLAG) != 0 ||
|
||||
SystemClock.uptimeMillis() - context.lastLbUpTime <= MAXIMUM_BUMPER_UP_DELAY_MS))
|
||||
{
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
// Start+LB acts like select for controllers with one button
|
||||
if ((context.inputMap & ControllerPacket.PLAY_FLAG) != 0 &&
|
||||
((context.inputMap & ControllerPacket.LB_FLAG) != 0 ||
|
||||
SystemClock.uptimeMillis() - context.lastLbUpTime <= MAXIMUM_BUMPER_UP_DELAY_MS))
|
||||
{
|
||||
context.inputMap &= ~(ControllerPacket.PLAY_FLAG | ControllerPacket.LB_FLAG);
|
||||
context.inputMap |= ControllerPacket.BACK_FLAG;
|
||||
|
||||
context.emulatingButtonFlags |= ControllerHandler.EMULATING_SELECT;
|
||||
}
|
||||
|
||||
// We detect select+start or start+RB as the special button combo
|
||||
if (((context.inputMap & ControllerPacket.RB_FLAG) != 0 ||
|
||||
(SystemClock.uptimeMillis() - context.lastRbUpTime <= MAXIMUM_BUMPER_UP_DELAY_MS) ||
|
||||
(context.inputMap & ControllerPacket.BACK_FLAG) != 0) &&
|
||||
(context.inputMap & ControllerPacket.PLAY_FLAG) != 0)
|
||||
{
|
||||
}
|
||||
|
||||
// We detect select+start or start+RB as the special button combo
|
||||
if (((context.inputMap & ControllerPacket.RB_FLAG) != 0 ||
|
||||
(SystemClock.uptimeMillis() - context.lastRbUpTime <= MAXIMUM_BUMPER_UP_DELAY_MS) ||
|
||||
(context.inputMap & ControllerPacket.BACK_FLAG) != 0) &&
|
||||
(context.inputMap & ControllerPacket.PLAY_FLAG) != 0)
|
||||
{
|
||||
context.inputMap &= ~(ControllerPacket.BACK_FLAG | ControllerPacket.PLAY_FLAG | ControllerPacket.RB_FLAG);
|
||||
context.inputMap |= ControllerPacket.SPECIAL_BUTTON_FLAG;
|
||||
|
||||
context.emulatingButtonFlags |= ControllerHandler.EMULATING_SPECIAL;
|
||||
}
|
||||
}
|
||||
|
||||
// Send a new input packet if this is the first instance of a button down event
|
||||
// or anytime if we're emulating a button
|
||||
if (event.getRepeatCount() == 0 || context.emulatingButtonFlags != 0) {
|
||||
sendControllerInputPacket(context);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
class ControllerContext {
|
||||
public String name;
|
||||
public int id;
|
||||
|
||||
public int leftStickXAxis = -1;
|
||||
public int leftStickYAxis = -1;
|
||||
public float leftStickDeadzoneRadius;
|
||||
public int leftStickXAxis = -1;
|
||||
public int leftStickYAxis = -1;
|
||||
public float leftStickDeadzoneRadius;
|
||||
|
||||
public int rightStickXAxis = -1;
|
||||
public int rightStickYAxis = -1;
|
||||
public float rightStickDeadzoneRadius;
|
||||
|
||||
public int leftTriggerAxis = -1;
|
||||
public int rightTriggerAxis = -1;
|
||||
public boolean triggersIdleNegative;
|
||||
public int rightStickXAxis = -1;
|
||||
public int rightStickYAxis = -1;
|
||||
public float rightStickDeadzoneRadius;
|
||||
|
||||
public int leftTriggerAxis = -1;
|
||||
public int rightTriggerAxis = -1;
|
||||
public boolean triggersIdleNegative;
|
||||
public float triggerDeadzone;
|
||||
|
||||
public int hatXAxis = -1;
|
||||
public int hatYAxis = -1;
|
||||
|
||||
public boolean isDualShock4;
|
||||
public boolean isXboxController;
|
||||
|
||||
public int hatXAxis = -1;
|
||||
public int hatYAxis = -1;
|
||||
|
||||
public boolean isDualShock4;
|
||||
public boolean isXboxController;
|
||||
public boolean backIsStart;
|
||||
public boolean modeIsSelect;
|
||||
public boolean isRemote;
|
||||
@ -822,5 +822,5 @@ public class ControllerHandler implements InputManager.InputDeviceListener {
|
||||
public long lastRbUpTime = 0;
|
||||
|
||||
public long startDownTime = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,8 +23,8 @@ public class KeyboardTranslator extends KeycodeTranslator {
|
||||
public static final int VK_Z = 90;
|
||||
public static final int VK_ALT = 18;
|
||||
public static final int VK_NUMPAD0 = 96;
|
||||
public static final int VK_BACK_SLASH = 92;
|
||||
public static final int VK_CAPS_LOCK = 20;
|
||||
public static final int VK_BACK_SLASH = 92;
|
||||
public static final int VK_CAPS_LOCK = 20;
|
||||
public static final int VK_CLEAR = 12;
|
||||
public static final int VK_COMMA = 44;
|
||||
public static final int VK_CONTROL = 17;
|
||||
|
@ -4,95 +4,95 @@ import com.limelight.nvstream.NvConnection;
|
||||
import com.limelight.nvstream.input.MouseButtonPacket;
|
||||
|
||||
public class TouchContext {
|
||||
private int lastTouchX = 0;
|
||||
private int lastTouchY = 0;
|
||||
private int originalTouchX = 0;
|
||||
private int originalTouchY = 0;
|
||||
private long originalTouchTime = 0;
|
||||
private int lastTouchX = 0;
|
||||
private int lastTouchY = 0;
|
||||
private int originalTouchX = 0;
|
||||
private int originalTouchY = 0;
|
||||
private long originalTouchTime = 0;
|
||||
private boolean cancelled;
|
||||
|
||||
private final NvConnection conn;
|
||||
private final int actionIndex;
|
||||
|
||||
private final NvConnection conn;
|
||||
private final int actionIndex;
|
||||
private final double xFactor;
|
||||
private final double yFactor;
|
||||
|
||||
private static final int TAP_MOVEMENT_THRESHOLD = 10;
|
||||
private static final int TAP_TIME_THRESHOLD = 250;
|
||||
|
||||
public TouchContext(NvConnection conn, int actionIndex, double xFactor, double yFactor)
|
||||
{
|
||||
this.conn = conn;
|
||||
this.actionIndex = actionIndex;
|
||||
|
||||
private static final int TAP_MOVEMENT_THRESHOLD = 10;
|
||||
private static final int TAP_TIME_THRESHOLD = 250;
|
||||
|
||||
public TouchContext(NvConnection conn, int actionIndex, double xFactor, double yFactor)
|
||||
{
|
||||
this.conn = conn;
|
||||
this.actionIndex = actionIndex;
|
||||
this.xFactor = xFactor;
|
||||
this.yFactor = yFactor;
|
||||
}
|
||||
}
|
||||
|
||||
public int getActionIndex()
|
||||
{
|
||||
return actionIndex;
|
||||
}
|
||||
|
||||
private boolean isTap()
|
||||
{
|
||||
int xDelta = Math.abs(lastTouchX - originalTouchX);
|
||||
int yDelta = Math.abs(lastTouchY - originalTouchY);
|
||||
long timeDelta = System.currentTimeMillis() - originalTouchTime;
|
||||
|
||||
return xDelta <= TAP_MOVEMENT_THRESHOLD &&
|
||||
yDelta <= TAP_MOVEMENT_THRESHOLD &&
|
||||
timeDelta <= TAP_TIME_THRESHOLD;
|
||||
}
|
||||
|
||||
private byte getMouseButtonIndex()
|
||||
{
|
||||
if (actionIndex == 1) {
|
||||
return MouseButtonPacket.BUTTON_RIGHT;
|
||||
}
|
||||
else {
|
||||
return MouseButtonPacket.BUTTON_LEFT;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean touchDownEvent(int eventX, int eventY)
|
||||
{
|
||||
originalTouchX = lastTouchX = eventX;
|
||||
originalTouchY = lastTouchY = eventY;
|
||||
originalTouchTime = System.currentTimeMillis();
|
||||
|
||||
private boolean isTap()
|
||||
{
|
||||
int xDelta = Math.abs(lastTouchX - originalTouchX);
|
||||
int yDelta = Math.abs(lastTouchY - originalTouchY);
|
||||
long timeDelta = System.currentTimeMillis() - originalTouchTime;
|
||||
|
||||
return xDelta <= TAP_MOVEMENT_THRESHOLD &&
|
||||
yDelta <= TAP_MOVEMENT_THRESHOLD &&
|
||||
timeDelta <= TAP_TIME_THRESHOLD;
|
||||
}
|
||||
|
||||
private byte getMouseButtonIndex()
|
||||
{
|
||||
if (actionIndex == 1) {
|
||||
return MouseButtonPacket.BUTTON_RIGHT;
|
||||
}
|
||||
else {
|
||||
return MouseButtonPacket.BUTTON_LEFT;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean touchDownEvent(int eventX, int eventY)
|
||||
{
|
||||
originalTouchX = lastTouchX = eventX;
|
||||
originalTouchY = lastTouchY = eventY;
|
||||
originalTouchTime = System.currentTimeMillis();
|
||||
cancelled = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void touchUpEvent(int eventX, int eventY)
|
||||
{
|
||||
}
|
||||
|
||||
public void touchUpEvent(int eventX, int eventY)
|
||||
{
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isTap())
|
||||
{
|
||||
byte buttonIndex = getMouseButtonIndex();
|
||||
|
||||
// Lower the mouse button
|
||||
conn.sendMouseButtonDown(buttonIndex);
|
||||
|
||||
// We need to sleep a bit here because some games
|
||||
// do input detection by polling
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException ignored) {}
|
||||
|
||||
// Raise the mouse button
|
||||
conn.sendMouseButtonUp(buttonIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean touchMoveEvent(int eventX, int eventY)
|
||||
if (isTap())
|
||||
{
|
||||
byte buttonIndex = getMouseButtonIndex();
|
||||
|
||||
// Lower the mouse button
|
||||
conn.sendMouseButtonDown(buttonIndex);
|
||||
|
||||
// We need to sleep a bit here because some games
|
||||
// do input detection by polling
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException ignored) {}
|
||||
|
||||
// Raise the mouse button
|
||||
conn.sendMouseButtonUp(buttonIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean touchMoveEvent(int eventX, int eventY)
|
||||
{
|
||||
if (eventX != lastTouchX || eventY != lastTouchY)
|
||||
{
|
||||
// We only send moves for the primary touch point
|
||||
if (actionIndex == 0) {
|
||||
{
|
||||
// We only send moves for the primary touch point
|
||||
if (actionIndex == 0) {
|
||||
int deltaX = eventX - lastTouchX;
|
||||
int deltaY = eventY - lastTouchY;
|
||||
|
||||
@ -100,15 +100,15 @@ public class TouchContext {
|
||||
deltaX = (int)Math.round((double)deltaX * xFactor);
|
||||
deltaY = (int)Math.round((double)deltaY * yFactor);
|
||||
|
||||
conn.sendMouseMove((short)deltaX, (short)deltaY);
|
||||
}
|
||||
|
||||
lastTouchX = eventX;
|
||||
lastTouchY = eventY;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
conn.sendMouseMove((short)deltaX, (short)deltaY);
|
||||
}
|
||||
|
||||
lastTouchX = eventX;
|
||||
lastTouchY = eventY;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void cancelTouch() {
|
||||
cancelled = true;
|
||||
|
@ -1,41 +1,41 @@
|
||||
package com.limelight.binding.input.evdev;
|
||||
|
||||
public class EvdevEvent {
|
||||
public static final int EVDEV_MIN_EVENT_SIZE = 16;
|
||||
public static final int EVDEV_MAX_EVENT_SIZE = 24;
|
||||
|
||||
/* Event types */
|
||||
public static final short EV_SYN = 0x00;
|
||||
public static final short EV_KEY = 0x01;
|
||||
public static final short EV_REL = 0x02;
|
||||
public static final short EV_MSC = 0x04;
|
||||
|
||||
/* Relative axes */
|
||||
public static final short REL_X = 0x00;
|
||||
public static final short REL_Y = 0x01;
|
||||
public static final short REL_WHEEL = 0x08;
|
||||
|
||||
/* Buttons */
|
||||
public static final short BTN_LEFT = 0x110;
|
||||
public static final short BTN_RIGHT = 0x111;
|
||||
public static final short BTN_MIDDLE = 0x112;
|
||||
public static final short BTN_SIDE = 0x113;
|
||||
public static final short BTN_EXTRA = 0x114;
|
||||
public static final short BTN_FORWARD = 0x115;
|
||||
public static final short BTN_BACK = 0x116;
|
||||
public static final short BTN_TASK = 0x117;
|
||||
public static final short BTN_GAMEPAD = 0x130;
|
||||
|
||||
/* Keys */
|
||||
public static final short KEY_Q = 16;
|
||||
|
||||
public final short type;
|
||||
public final short code;
|
||||
public final int value;
|
||||
|
||||
public EvdevEvent(short type, short code, int value) {
|
||||
this.type = type;
|
||||
this.code = code;
|
||||
this.value = value;
|
||||
}
|
||||
public static final int EVDEV_MIN_EVENT_SIZE = 16;
|
||||
public static final int EVDEV_MAX_EVENT_SIZE = 24;
|
||||
|
||||
/* Event types */
|
||||
public static final short EV_SYN = 0x00;
|
||||
public static final short EV_KEY = 0x01;
|
||||
public static final short EV_REL = 0x02;
|
||||
public static final short EV_MSC = 0x04;
|
||||
|
||||
/* Relative axes */
|
||||
public static final short REL_X = 0x00;
|
||||
public static final short REL_Y = 0x01;
|
||||
public static final short REL_WHEEL = 0x08;
|
||||
|
||||
/* Buttons */
|
||||
public static final short BTN_LEFT = 0x110;
|
||||
public static final short BTN_RIGHT = 0x111;
|
||||
public static final short BTN_MIDDLE = 0x112;
|
||||
public static final short BTN_SIDE = 0x113;
|
||||
public static final short BTN_EXTRA = 0x114;
|
||||
public static final short BTN_FORWARD = 0x115;
|
||||
public static final short BTN_BACK = 0x116;
|
||||
public static final short BTN_TASK = 0x117;
|
||||
public static final short BTN_GAMEPAD = 0x130;
|
||||
|
||||
/* Keys */
|
||||
public static final short KEY_Q = 16;
|
||||
|
||||
public final short type;
|
||||
public final short code;
|
||||
public final int value;
|
||||
|
||||
public EvdevEvent(short type, short code, int value) {
|
||||
this.type = type;
|
||||
this.code = code;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
@ -7,161 +7,161 @@ import com.limelight.LimeLog;
|
||||
|
||||
public class EvdevHandler {
|
||||
|
||||
private final String absolutePath;
|
||||
private final EvdevListener listener;
|
||||
private boolean shutdown = false;
|
||||
private int fd = -1;
|
||||
|
||||
private final Thread handlerThread = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
// All the finally blocks here make this code look like a mess
|
||||
// but it's important that we get this right to avoid causing
|
||||
// system-wide input problems.
|
||||
|
||||
// Open the /dev/input/eventX file
|
||||
fd = EvdevReader.open(absolutePath);
|
||||
if (fd == -1) {
|
||||
LimeLog.warning("Unable to open "+absolutePath);
|
||||
return;
|
||||
}
|
||||
private final String absolutePath;
|
||||
private final EvdevListener listener;
|
||||
private boolean shutdown = false;
|
||||
private int fd = -1;
|
||||
|
||||
try {
|
||||
// Check if it's a mouse or keyboard, but not a gamepad
|
||||
if ((!EvdevReader.isMouse(fd) && !EvdevReader.isAlphaKeyboard(fd)) ||
|
||||
EvdevReader.isGamepad(fd)) {
|
||||
// We only handle keyboards and mice
|
||||
return;
|
||||
}
|
||||
private final Thread handlerThread = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
// All the finally blocks here make this code look like a mess
|
||||
// but it's important that we get this right to avoid causing
|
||||
// system-wide input problems.
|
||||
|
||||
// Grab it for ourselves
|
||||
if (!EvdevReader.grab(fd)) {
|
||||
LimeLog.warning("Unable to grab "+absolutePath);
|
||||
return;
|
||||
}
|
||||
// Open the /dev/input/eventX file
|
||||
fd = EvdevReader.open(absolutePath);
|
||||
if (fd == -1) {
|
||||
LimeLog.warning("Unable to open "+absolutePath);
|
||||
return;
|
||||
}
|
||||
|
||||
LimeLog.info("Grabbed device for raw keyboard/mouse input: "+absolutePath);
|
||||
try {
|
||||
// Check if it's a mouse or keyboard, but not a gamepad
|
||||
if ((!EvdevReader.isMouse(fd) && !EvdevReader.isAlphaKeyboard(fd)) ||
|
||||
EvdevReader.isGamepad(fd)) {
|
||||
// We only handle keyboards and mice
|
||||
return;
|
||||
}
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.allocate(EvdevEvent.EVDEV_MAX_EVENT_SIZE).order(ByteOrder.nativeOrder());
|
||||
// Grab it for ourselves
|
||||
if (!EvdevReader.grab(fd)) {
|
||||
LimeLog.warning("Unable to grab "+absolutePath);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
int deltaX = 0;
|
||||
int deltaY = 0;
|
||||
byte deltaScroll = 0;
|
||||
LimeLog.info("Grabbed device for raw keyboard/mouse input: "+absolutePath);
|
||||
|
||||
while (!isInterrupted() && !shutdown) {
|
||||
EvdevEvent event = EvdevReader.read(fd, buffer);
|
||||
if (event == null) {
|
||||
return;
|
||||
}
|
||||
ByteBuffer buffer = ByteBuffer.allocate(EvdevEvent.EVDEV_MAX_EVENT_SIZE).order(ByteOrder.nativeOrder());
|
||||
|
||||
switch (event.type)
|
||||
{
|
||||
case EvdevEvent.EV_SYN:
|
||||
if (deltaX != 0 || deltaY != 0) {
|
||||
listener.mouseMove(deltaX, deltaY);
|
||||
deltaX = deltaY = 0;
|
||||
}
|
||||
if (deltaScroll != 0) {
|
||||
listener.mouseScroll(deltaScroll);
|
||||
deltaScroll = 0;
|
||||
}
|
||||
break;
|
||||
try {
|
||||
int deltaX = 0;
|
||||
int deltaY = 0;
|
||||
byte deltaScroll = 0;
|
||||
|
||||
case EvdevEvent.EV_REL:
|
||||
switch (event.code)
|
||||
{
|
||||
case EvdevEvent.REL_X:
|
||||
deltaX = event.value;
|
||||
break;
|
||||
case EvdevEvent.REL_Y:
|
||||
deltaY = event.value;
|
||||
break;
|
||||
case EvdevEvent.REL_WHEEL:
|
||||
deltaScroll = (byte) event.value;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
while (!isInterrupted() && !shutdown) {
|
||||
EvdevEvent event = EvdevReader.read(fd, buffer);
|
||||
if (event == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
case EvdevEvent.EV_KEY:
|
||||
switch (event.code)
|
||||
{
|
||||
case EvdevEvent.BTN_LEFT:
|
||||
listener.mouseButtonEvent(EvdevListener.BUTTON_LEFT,
|
||||
event.value != 0);
|
||||
break;
|
||||
case EvdevEvent.BTN_MIDDLE:
|
||||
listener.mouseButtonEvent(EvdevListener.BUTTON_MIDDLE,
|
||||
event.value != 0);
|
||||
break;
|
||||
case EvdevEvent.BTN_RIGHT:
|
||||
listener.mouseButtonEvent(EvdevListener.BUTTON_RIGHT,
|
||||
event.value != 0);
|
||||
break;
|
||||
|
||||
case EvdevEvent.BTN_SIDE:
|
||||
case EvdevEvent.BTN_EXTRA:
|
||||
case EvdevEvent.BTN_FORWARD:
|
||||
case EvdevEvent.BTN_BACK:
|
||||
case EvdevEvent.BTN_TASK:
|
||||
// Other unhandled mouse buttons
|
||||
break;
|
||||
|
||||
default:
|
||||
// We got some unrecognized button. This means
|
||||
// someone is trying to use the other device in this
|
||||
// "combination" input device. We'll try to handle
|
||||
// it via keyboard, but we're not going to disconnect
|
||||
// if we can't
|
||||
short keyCode = EvdevTranslator.translateEvdevKeyCode(event.code);
|
||||
if (keyCode != 0) {
|
||||
listener.keyboardEvent(event.value != 0, keyCode);
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case EvdevEvent.EV_MSC:
|
||||
break;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
// Release our grab
|
||||
EvdevReader.ungrab(fd);
|
||||
}
|
||||
} finally {
|
||||
// Close the file
|
||||
EvdevReader.close(fd);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public EvdevHandler(String absolutePath, EvdevListener listener) {
|
||||
this.absolutePath = absolutePath;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
handlerThread.start();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
// Close the fd. It doesn't matter if this races
|
||||
// with the handler thread. We'll close this out from
|
||||
// under the thread to wake it up
|
||||
if (fd != -1) {
|
||||
EvdevReader.close(fd);
|
||||
}
|
||||
|
||||
shutdown = true;
|
||||
handlerThread.interrupt();
|
||||
|
||||
try {
|
||||
handlerThread.join();
|
||||
} catch (InterruptedException ignored) {}
|
||||
}
|
||||
|
||||
public void notifyDeleted() {
|
||||
stop();
|
||||
}
|
||||
switch (event.type)
|
||||
{
|
||||
case EvdevEvent.EV_SYN:
|
||||
if (deltaX != 0 || deltaY != 0) {
|
||||
listener.mouseMove(deltaX, deltaY);
|
||||
deltaX = deltaY = 0;
|
||||
}
|
||||
if (deltaScroll != 0) {
|
||||
listener.mouseScroll(deltaScroll);
|
||||
deltaScroll = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case EvdevEvent.EV_REL:
|
||||
switch (event.code)
|
||||
{
|
||||
case EvdevEvent.REL_X:
|
||||
deltaX = event.value;
|
||||
break;
|
||||
case EvdevEvent.REL_Y:
|
||||
deltaY = event.value;
|
||||
break;
|
||||
case EvdevEvent.REL_WHEEL:
|
||||
deltaScroll = (byte) event.value;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case EvdevEvent.EV_KEY:
|
||||
switch (event.code)
|
||||
{
|
||||
case EvdevEvent.BTN_LEFT:
|
||||
listener.mouseButtonEvent(EvdevListener.BUTTON_LEFT,
|
||||
event.value != 0);
|
||||
break;
|
||||
case EvdevEvent.BTN_MIDDLE:
|
||||
listener.mouseButtonEvent(EvdevListener.BUTTON_MIDDLE,
|
||||
event.value != 0);
|
||||
break;
|
||||
case EvdevEvent.BTN_RIGHT:
|
||||
listener.mouseButtonEvent(EvdevListener.BUTTON_RIGHT,
|
||||
event.value != 0);
|
||||
break;
|
||||
|
||||
case EvdevEvent.BTN_SIDE:
|
||||
case EvdevEvent.BTN_EXTRA:
|
||||
case EvdevEvent.BTN_FORWARD:
|
||||
case EvdevEvent.BTN_BACK:
|
||||
case EvdevEvent.BTN_TASK:
|
||||
// Other unhandled mouse buttons
|
||||
break;
|
||||
|
||||
default:
|
||||
// We got some unrecognized button. This means
|
||||
// someone is trying to use the other device in this
|
||||
// "combination" input device. We'll try to handle
|
||||
// it via keyboard, but we're not going to disconnect
|
||||
// if we can't
|
||||
short keyCode = EvdevTranslator.translateEvdevKeyCode(event.code);
|
||||
if (keyCode != 0) {
|
||||
listener.keyboardEvent(event.value != 0, keyCode);
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case EvdevEvent.EV_MSC:
|
||||
break;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
// Release our grab
|
||||
EvdevReader.ungrab(fd);
|
||||
}
|
||||
} finally {
|
||||
// Close the file
|
||||
EvdevReader.close(fd);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public EvdevHandler(String absolutePath, EvdevListener listener) {
|
||||
this.absolutePath = absolutePath;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
handlerThread.start();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
// Close the fd. It doesn't matter if this races
|
||||
// with the handler thread. We'll close this out from
|
||||
// under the thread to wake it up
|
||||
if (fd != -1) {
|
||||
EvdevReader.close(fd);
|
||||
}
|
||||
|
||||
shutdown = true;
|
||||
handlerThread.interrupt();
|
||||
|
||||
try {
|
||||
handlerThread.join();
|
||||
} catch (InterruptedException ignored) {}
|
||||
}
|
||||
|
||||
public void notifyDeleted() {
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
package com.limelight.binding.input.evdev;
|
||||
|
||||
public interface EvdevListener {
|
||||
public static final int BUTTON_LEFT = 1;
|
||||
public static final int BUTTON_MIDDLE = 2;
|
||||
public static final int BUTTON_RIGHT = 3;
|
||||
|
||||
public void mouseMove(int deltaX, int deltaY);
|
||||
public void mouseButtonEvent(int buttonId, boolean down);
|
||||
public void mouseScroll(byte amount);
|
||||
public void keyboardEvent(boolean buttonDown, short keyCode);
|
||||
public static final int BUTTON_LEFT = 1;
|
||||
public static final int BUTTON_MIDDLE = 2;
|
||||
public static final int BUTTON_RIGHT = 3;
|
||||
|
||||
public void mouseMove(int deltaX, int deltaY);
|
||||
public void mouseButtonEvent(int buttonId, boolean down);
|
||||
public void mouseScroll(byte amount);
|
||||
public void keyboardEvent(boolean buttonDown, short keyCode);
|
||||
}
|
||||
|
@ -8,9 +8,9 @@ import java.util.Locale;
|
||||
import com.limelight.LimeLog;
|
||||
|
||||
public class EvdevReader {
|
||||
static {
|
||||
System.loadLibrary("evdev_reader");
|
||||
}
|
||||
static {
|
||||
System.loadLibrary("evdev_reader");
|
||||
}
|
||||
|
||||
public static void patchSeLinuxPolicies() {
|
||||
//
|
||||
@ -30,76 +30,76 @@ public class EvdevReader {
|
||||
"\"allow untrusted_app input_device chr_file { open read write ioctl }\"");
|
||||
}
|
||||
}
|
||||
|
||||
// Requires root to chmod /dev/input/eventX
|
||||
public static void setPermissions(String[] files, int octalPermissions) {
|
||||
|
||||
// Requires root to chmod /dev/input/eventX
|
||||
public static void setPermissions(String[] files, int octalPermissions) {
|
||||
EvdevShell shell = EvdevShell.getInstance();
|
||||
|
||||
for (String file : files) {
|
||||
shell.runCommand(String.format((Locale)null, "chmod %o %s", octalPermissions, file));
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the fd to be passed to other function or -1 on error
|
||||
public static native int open(String fileName);
|
||||
|
||||
// Prevent other apps (including Android itself) from using the device while "grabbed"
|
||||
public static native boolean grab(int fd);
|
||||
public static native boolean ungrab(int fd);
|
||||
|
||||
// Used for checking device capabilities
|
||||
public static native boolean hasRelAxis(int fd, short axis);
|
||||
public static native boolean hasAbsAxis(int fd, short axis);
|
||||
public static native boolean hasKey(int fd, short key);
|
||||
|
||||
public static boolean isMouse(int fd) {
|
||||
// This is the same check that Android does in EventHub.cpp
|
||||
return hasRelAxis(fd, EvdevEvent.REL_X) &&
|
||||
hasRelAxis(fd, EvdevEvent.REL_Y) &&
|
||||
hasKey(fd, EvdevEvent.BTN_LEFT);
|
||||
}
|
||||
|
||||
public static boolean isAlphaKeyboard(int fd) {
|
||||
// This is the same check that Android does in EventHub.cpp
|
||||
return hasKey(fd, EvdevEvent.KEY_Q);
|
||||
}
|
||||
|
||||
public static boolean isGamepad(int fd) {
|
||||
return hasKey(fd, EvdevEvent.BTN_GAMEPAD);
|
||||
}
|
||||
|
||||
// Returns the bytes read or -1 on error
|
||||
private static native int read(int fd, byte[] buffer);
|
||||
|
||||
// Takes a byte buffer to use to read the output into.
|
||||
// This buffer MUST be in native byte order and at least
|
||||
// EVDEV_MAX_EVENT_SIZE bytes long.
|
||||
public static EvdevEvent read(int fd, ByteBuffer buffer) {
|
||||
int bytesRead = read(fd, buffer.array());
|
||||
if (bytesRead < 0) {
|
||||
LimeLog.warning("Failed to read: "+bytesRead);
|
||||
return null;
|
||||
}
|
||||
else if (bytesRead < EvdevEvent.EVDEV_MIN_EVENT_SIZE) {
|
||||
LimeLog.warning("Short read: "+bytesRead);
|
||||
return null;
|
||||
}
|
||||
|
||||
buffer.limit(bytesRead);
|
||||
buffer.rewind();
|
||||
|
||||
// Throw away the time stamp
|
||||
if (bytesRead == EvdevEvent.EVDEV_MAX_EVENT_SIZE) {
|
||||
buffer.getLong();
|
||||
buffer.getLong();
|
||||
} else {
|
||||
buffer.getInt();
|
||||
buffer.getInt();
|
||||
}
|
||||
|
||||
return new EvdevEvent(buffer.getShort(), buffer.getShort(), buffer.getInt());
|
||||
}
|
||||
|
||||
// Closes the fd from open()
|
||||
public static native int close(int fd);
|
||||
}
|
||||
|
||||
// Returns the fd to be passed to other function or -1 on error
|
||||
public static native int open(String fileName);
|
||||
|
||||
// Prevent other apps (including Android itself) from using the device while "grabbed"
|
||||
public static native boolean grab(int fd);
|
||||
public static native boolean ungrab(int fd);
|
||||
|
||||
// Used for checking device capabilities
|
||||
public static native boolean hasRelAxis(int fd, short axis);
|
||||
public static native boolean hasAbsAxis(int fd, short axis);
|
||||
public static native boolean hasKey(int fd, short key);
|
||||
|
||||
public static boolean isMouse(int fd) {
|
||||
// This is the same check that Android does in EventHub.cpp
|
||||
return hasRelAxis(fd, EvdevEvent.REL_X) &&
|
||||
hasRelAxis(fd, EvdevEvent.REL_Y) &&
|
||||
hasKey(fd, EvdevEvent.BTN_LEFT);
|
||||
}
|
||||
|
||||
public static boolean isAlphaKeyboard(int fd) {
|
||||
// This is the same check that Android does in EventHub.cpp
|
||||
return hasKey(fd, EvdevEvent.KEY_Q);
|
||||
}
|
||||
|
||||
public static boolean isGamepad(int fd) {
|
||||
return hasKey(fd, EvdevEvent.BTN_GAMEPAD);
|
||||
}
|
||||
|
||||
// Returns the bytes read or -1 on error
|
||||
private static native int read(int fd, byte[] buffer);
|
||||
|
||||
// Takes a byte buffer to use to read the output into.
|
||||
// This buffer MUST be in native byte order and at least
|
||||
// EVDEV_MAX_EVENT_SIZE bytes long.
|
||||
public static EvdevEvent read(int fd, ByteBuffer buffer) {
|
||||
int bytesRead = read(fd, buffer.array());
|
||||
if (bytesRead < 0) {
|
||||
LimeLog.warning("Failed to read: "+bytesRead);
|
||||
return null;
|
||||
}
|
||||
else if (bytesRead < EvdevEvent.EVDEV_MIN_EVENT_SIZE) {
|
||||
LimeLog.warning("Short read: "+bytesRead);
|
||||
return null;
|
||||
}
|
||||
|
||||
buffer.limit(bytesRead);
|
||||
buffer.rewind();
|
||||
|
||||
// Throw away the time stamp
|
||||
if (bytesRead == EvdevEvent.EVDEV_MAX_EVENT_SIZE) {
|
||||
buffer.getLong();
|
||||
buffer.getLong();
|
||||
} else {
|
||||
buffer.getInt();
|
||||
buffer.getInt();
|
||||
}
|
||||
|
||||
return new EvdevEvent(buffer.getShort(), buffer.getShort(), buffer.getInt());
|
||||
}
|
||||
|
||||
// Closes the fd from open()
|
||||
public static native int close(int fd);
|
||||
}
|
||||
|
@ -4,136 +4,136 @@ import android.view.KeyEvent;
|
||||
|
||||
public class EvdevTranslator {
|
||||
|
||||
private static final short[] EVDEV_KEY_CODES = {
|
||||
0, //KeyEvent.VK_RESERVED
|
||||
KeyEvent.KEYCODE_ESCAPE,
|
||||
KeyEvent.KEYCODE_1,
|
||||
KeyEvent.KEYCODE_2,
|
||||
KeyEvent.KEYCODE_3,
|
||||
KeyEvent.KEYCODE_4,
|
||||
KeyEvent.KEYCODE_5,
|
||||
KeyEvent.KEYCODE_6,
|
||||
KeyEvent.KEYCODE_7,
|
||||
KeyEvent.KEYCODE_8,
|
||||
KeyEvent.KEYCODE_9,
|
||||
KeyEvent.KEYCODE_0,
|
||||
KeyEvent.KEYCODE_MINUS,
|
||||
KeyEvent.KEYCODE_EQUALS,
|
||||
KeyEvent.KEYCODE_DEL,
|
||||
KeyEvent.KEYCODE_TAB,
|
||||
KeyEvent.KEYCODE_Q,
|
||||
KeyEvent.KEYCODE_W,
|
||||
KeyEvent.KEYCODE_E,
|
||||
KeyEvent.KEYCODE_R,
|
||||
KeyEvent.KEYCODE_T,
|
||||
KeyEvent.KEYCODE_Y,
|
||||
KeyEvent.KEYCODE_U,
|
||||
KeyEvent.KEYCODE_I,
|
||||
KeyEvent.KEYCODE_O,
|
||||
KeyEvent.KEYCODE_P,
|
||||
KeyEvent.KEYCODE_LEFT_BRACKET,
|
||||
KeyEvent.KEYCODE_RIGHT_BRACKET,
|
||||
KeyEvent.KEYCODE_ENTER,
|
||||
KeyEvent.KEYCODE_CTRL_LEFT,
|
||||
KeyEvent.KEYCODE_A,
|
||||
KeyEvent.KEYCODE_S,
|
||||
KeyEvent.KEYCODE_D,
|
||||
KeyEvent.KEYCODE_F,
|
||||
KeyEvent.KEYCODE_G,
|
||||
KeyEvent.KEYCODE_H,
|
||||
KeyEvent.KEYCODE_J,
|
||||
KeyEvent.KEYCODE_K,
|
||||
KeyEvent.KEYCODE_L,
|
||||
KeyEvent.KEYCODE_SEMICOLON,
|
||||
KeyEvent.KEYCODE_APOSTROPHE,
|
||||
KeyEvent.KEYCODE_GRAVE,
|
||||
KeyEvent.KEYCODE_SHIFT_LEFT,
|
||||
KeyEvent.KEYCODE_BACKSLASH,
|
||||
KeyEvent.KEYCODE_Z,
|
||||
KeyEvent.KEYCODE_X,
|
||||
KeyEvent.KEYCODE_C,
|
||||
KeyEvent.KEYCODE_V,
|
||||
KeyEvent.KEYCODE_B,
|
||||
KeyEvent.KEYCODE_N,
|
||||
KeyEvent.KEYCODE_M,
|
||||
KeyEvent.KEYCODE_COMMA,
|
||||
KeyEvent.KEYCODE_PERIOD,
|
||||
KeyEvent.KEYCODE_SLASH,
|
||||
KeyEvent.KEYCODE_SHIFT_RIGHT,
|
||||
KeyEvent.KEYCODE_NUMPAD_MULTIPLY,
|
||||
KeyEvent.KEYCODE_ALT_LEFT,
|
||||
KeyEvent.KEYCODE_SPACE,
|
||||
KeyEvent.KEYCODE_CAPS_LOCK,
|
||||
KeyEvent.KEYCODE_F1,
|
||||
KeyEvent.KEYCODE_F2,
|
||||
KeyEvent.KEYCODE_F3,
|
||||
KeyEvent.KEYCODE_F4,
|
||||
KeyEvent.KEYCODE_F5,
|
||||
KeyEvent.KEYCODE_F6,
|
||||
KeyEvent.KEYCODE_F7,
|
||||
KeyEvent.KEYCODE_F8,
|
||||
KeyEvent.KEYCODE_F9,
|
||||
KeyEvent.KEYCODE_F10,
|
||||
KeyEvent.KEYCODE_NUM_LOCK,
|
||||
KeyEvent.KEYCODE_SCROLL_LOCK,
|
||||
KeyEvent.KEYCODE_NUMPAD_7,
|
||||
KeyEvent.KEYCODE_NUMPAD_8,
|
||||
KeyEvent.KEYCODE_NUMPAD_9,
|
||||
KeyEvent.KEYCODE_NUMPAD_SUBTRACT,
|
||||
KeyEvent.KEYCODE_NUMPAD_4,
|
||||
KeyEvent.KEYCODE_NUMPAD_5,
|
||||
KeyEvent.KEYCODE_NUMPAD_6,
|
||||
KeyEvent.KEYCODE_NUMPAD_ADD,
|
||||
KeyEvent.KEYCODE_NUMPAD_1,
|
||||
KeyEvent.KEYCODE_NUMPAD_2,
|
||||
KeyEvent.KEYCODE_NUMPAD_3,
|
||||
KeyEvent.KEYCODE_NUMPAD_0,
|
||||
KeyEvent.KEYCODE_NUMPAD_DOT,
|
||||
0,
|
||||
0, //KeyEvent.VK_ZENKAKUHANKAKU,
|
||||
0, //KeyEvent.VK_102ND,
|
||||
KeyEvent.KEYCODE_F11,
|
||||
KeyEvent.KEYCODE_F12,
|
||||
0, //KeyEvent.VK_RO,
|
||||
0, //KeyEvent.VK_KATAKANA,
|
||||
0, //KeyEvent.VK_HIRAGANA,
|
||||
0, //KeyEvent.VK_HENKAN,
|
||||
0, //KeyEvent.VK_KATAKANAHIRAGANA,
|
||||
0, //KeyEvent.VK_MUHENKAN,
|
||||
0, //KeyEvent.VK_KPJPCOMMA,
|
||||
KeyEvent.KEYCODE_NUMPAD_ENTER,
|
||||
KeyEvent.KEYCODE_CTRL_RIGHT,
|
||||
KeyEvent.KEYCODE_NUMPAD_DIVIDE,
|
||||
KeyEvent.KEYCODE_SYSRQ,
|
||||
KeyEvent.KEYCODE_ALT_RIGHT,
|
||||
0, //KeyEvent.VK_LINEFEED,
|
||||
KeyEvent.KEYCODE_HOME,
|
||||
KeyEvent.KEYCODE_DPAD_UP,
|
||||
KeyEvent.KEYCODE_PAGE_UP,
|
||||
KeyEvent.KEYCODE_DPAD_LEFT,
|
||||
KeyEvent.KEYCODE_DPAD_RIGHT,
|
||||
KeyEvent.KEYCODE_MOVE_END,
|
||||
KeyEvent.KEYCODE_DPAD_DOWN,
|
||||
KeyEvent.KEYCODE_PAGE_DOWN,
|
||||
KeyEvent.KEYCODE_INSERT,
|
||||
KeyEvent.KEYCODE_FORWARD_DEL,
|
||||
0, //KeyEvent.VK_MACRO,
|
||||
0, //KeyEvent.VK_MUTE,
|
||||
0, //KeyEvent.VK_VOLUMEDOWN,
|
||||
0, //KeyEvent.VK_VOLUMEUP,
|
||||
0, //KeyEvent.VK_POWER, /* SC System Power Down */
|
||||
KeyEvent.KEYCODE_NUMPAD_EQUALS,
|
||||
0, //KeyEvent.VK_KPPLUSMINUS,
|
||||
KeyEvent.KEYCODE_BREAK,
|
||||
0, //KeyEvent.VK_SCALE, /* AL Compiz Scale (Expose) */
|
||||
};
|
||||
|
||||
public static short translateEvdevKeyCode(short evdevKeyCode) {
|
||||
if (evdevKeyCode < EVDEV_KEY_CODES.length) {
|
||||
return EVDEV_KEY_CODES[evdevKeyCode];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
private static final short[] EVDEV_KEY_CODES = {
|
||||
0, //KeyEvent.VK_RESERVED
|
||||
KeyEvent.KEYCODE_ESCAPE,
|
||||
KeyEvent.KEYCODE_1,
|
||||
KeyEvent.KEYCODE_2,
|
||||
KeyEvent.KEYCODE_3,
|
||||
KeyEvent.KEYCODE_4,
|
||||
KeyEvent.KEYCODE_5,
|
||||
KeyEvent.KEYCODE_6,
|
||||
KeyEvent.KEYCODE_7,
|
||||
KeyEvent.KEYCODE_8,
|
||||
KeyEvent.KEYCODE_9,
|
||||
KeyEvent.KEYCODE_0,
|
||||
KeyEvent.KEYCODE_MINUS,
|
||||
KeyEvent.KEYCODE_EQUALS,
|
||||
KeyEvent.KEYCODE_DEL,
|
||||
KeyEvent.KEYCODE_TAB,
|
||||
KeyEvent.KEYCODE_Q,
|
||||
KeyEvent.KEYCODE_W,
|
||||
KeyEvent.KEYCODE_E,
|
||||
KeyEvent.KEYCODE_R,
|
||||
KeyEvent.KEYCODE_T,
|
||||
KeyEvent.KEYCODE_Y,
|
||||
KeyEvent.KEYCODE_U,
|
||||
KeyEvent.KEYCODE_I,
|
||||
KeyEvent.KEYCODE_O,
|
||||
KeyEvent.KEYCODE_P,
|
||||
KeyEvent.KEYCODE_LEFT_BRACKET,
|
||||
KeyEvent.KEYCODE_RIGHT_BRACKET,
|
||||
KeyEvent.KEYCODE_ENTER,
|
||||
KeyEvent.KEYCODE_CTRL_LEFT,
|
||||
KeyEvent.KEYCODE_A,
|
||||
KeyEvent.KEYCODE_S,
|
||||
KeyEvent.KEYCODE_D,
|
||||
KeyEvent.KEYCODE_F,
|
||||
KeyEvent.KEYCODE_G,
|
||||
KeyEvent.KEYCODE_H,
|
||||
KeyEvent.KEYCODE_J,
|
||||
KeyEvent.KEYCODE_K,
|
||||
KeyEvent.KEYCODE_L,
|
||||
KeyEvent.KEYCODE_SEMICOLON,
|
||||
KeyEvent.KEYCODE_APOSTROPHE,
|
||||
KeyEvent.KEYCODE_GRAVE,
|
||||
KeyEvent.KEYCODE_SHIFT_LEFT,
|
||||
KeyEvent.KEYCODE_BACKSLASH,
|
||||
KeyEvent.KEYCODE_Z,
|
||||
KeyEvent.KEYCODE_X,
|
||||
KeyEvent.KEYCODE_C,
|
||||
KeyEvent.KEYCODE_V,
|
||||
KeyEvent.KEYCODE_B,
|
||||
KeyEvent.KEYCODE_N,
|
||||
KeyEvent.KEYCODE_M,
|
||||
KeyEvent.KEYCODE_COMMA,
|
||||
KeyEvent.KEYCODE_PERIOD,
|
||||
KeyEvent.KEYCODE_SLASH,
|
||||
KeyEvent.KEYCODE_SHIFT_RIGHT,
|
||||
KeyEvent.KEYCODE_NUMPAD_MULTIPLY,
|
||||
KeyEvent.KEYCODE_ALT_LEFT,
|
||||
KeyEvent.KEYCODE_SPACE,
|
||||
KeyEvent.KEYCODE_CAPS_LOCK,
|
||||
KeyEvent.KEYCODE_F1,
|
||||
KeyEvent.KEYCODE_F2,
|
||||
KeyEvent.KEYCODE_F3,
|
||||
KeyEvent.KEYCODE_F4,
|
||||
KeyEvent.KEYCODE_F5,
|
||||
KeyEvent.KEYCODE_F6,
|
||||
KeyEvent.KEYCODE_F7,
|
||||
KeyEvent.KEYCODE_F8,
|
||||
KeyEvent.KEYCODE_F9,
|
||||
KeyEvent.KEYCODE_F10,
|
||||
KeyEvent.KEYCODE_NUM_LOCK,
|
||||
KeyEvent.KEYCODE_SCROLL_LOCK,
|
||||
KeyEvent.KEYCODE_NUMPAD_7,
|
||||
KeyEvent.KEYCODE_NUMPAD_8,
|
||||
KeyEvent.KEYCODE_NUMPAD_9,
|
||||
KeyEvent.KEYCODE_NUMPAD_SUBTRACT,
|
||||
KeyEvent.KEYCODE_NUMPAD_4,
|
||||
KeyEvent.KEYCODE_NUMPAD_5,
|
||||
KeyEvent.KEYCODE_NUMPAD_6,
|
||||
KeyEvent.KEYCODE_NUMPAD_ADD,
|
||||
KeyEvent.KEYCODE_NUMPAD_1,
|
||||
KeyEvent.KEYCODE_NUMPAD_2,
|
||||
KeyEvent.KEYCODE_NUMPAD_3,
|
||||
KeyEvent.KEYCODE_NUMPAD_0,
|
||||
KeyEvent.KEYCODE_NUMPAD_DOT,
|
||||
0,
|
||||
0, //KeyEvent.VK_ZENKAKUHANKAKU,
|
||||
0, //KeyEvent.VK_102ND,
|
||||
KeyEvent.KEYCODE_F11,
|
||||
KeyEvent.KEYCODE_F12,
|
||||
0, //KeyEvent.VK_RO,
|
||||
0, //KeyEvent.VK_KATAKANA,
|
||||
0, //KeyEvent.VK_HIRAGANA,
|
||||
0, //KeyEvent.VK_HENKAN,
|
||||
0, //KeyEvent.VK_KATAKANAHIRAGANA,
|
||||
0, //KeyEvent.VK_MUHENKAN,
|
||||
0, //KeyEvent.VK_KPJPCOMMA,
|
||||
KeyEvent.KEYCODE_NUMPAD_ENTER,
|
||||
KeyEvent.KEYCODE_CTRL_RIGHT,
|
||||
KeyEvent.KEYCODE_NUMPAD_DIVIDE,
|
||||
KeyEvent.KEYCODE_SYSRQ,
|
||||
KeyEvent.KEYCODE_ALT_RIGHT,
|
||||
0, //KeyEvent.VK_LINEFEED,
|
||||
KeyEvent.KEYCODE_HOME,
|
||||
KeyEvent.KEYCODE_DPAD_UP,
|
||||
KeyEvent.KEYCODE_PAGE_UP,
|
||||
KeyEvent.KEYCODE_DPAD_LEFT,
|
||||
KeyEvent.KEYCODE_DPAD_RIGHT,
|
||||
KeyEvent.KEYCODE_MOVE_END,
|
||||
KeyEvent.KEYCODE_DPAD_DOWN,
|
||||
KeyEvent.KEYCODE_PAGE_DOWN,
|
||||
KeyEvent.KEYCODE_INSERT,
|
||||
KeyEvent.KEYCODE_FORWARD_DEL,
|
||||
0, //KeyEvent.VK_MACRO,
|
||||
0, //KeyEvent.VK_MUTE,
|
||||
0, //KeyEvent.VK_VOLUMEDOWN,
|
||||
0, //KeyEvent.VK_VOLUMEUP,
|
||||
0, //KeyEvent.VK_POWER, /* SC System Power Down */
|
||||
KeyEvent.KEYCODE_NUMPAD_EQUALS,
|
||||
0, //KeyEvent.VK_KPPLUSMINUS,
|
||||
KeyEvent.KEYCODE_BREAK,
|
||||
0, //KeyEvent.VK_SCALE, /* AL Compiz Scale (Expose) */
|
||||
};
|
||||
|
||||
public static short translateEvdevKeyCode(short evdevKeyCode) {
|
||||
if (evdevKeyCode < EVDEV_KEY_CODES.length) {
|
||||
return EVDEV_KEY_CODES[evdevKeyCode];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -10,115 +10,115 @@ import android.os.FileObserver;
|
||||
|
||||
@SuppressWarnings("ALL")
|
||||
public class EvdevWatcher {
|
||||
private static final String PATH = "/dev/input";
|
||||
private static final String REQUIRED_FILE_PREFIX = "event";
|
||||
|
||||
private final HashMap<String, EvdevHandler> handlers = new HashMap<String, EvdevHandler>();
|
||||
private boolean shutdown = false;
|
||||
private boolean init = false;
|
||||
private boolean ungrabbed = false;
|
||||
private EvdevListener listener;
|
||||
private Thread startThread;
|
||||
private static final String PATH = "/dev/input";
|
||||
private static final String REQUIRED_FILE_PREFIX = "event";
|
||||
|
||||
private final HashMap<String, EvdevHandler> handlers = new HashMap<String, EvdevHandler>();
|
||||
private boolean shutdown = false;
|
||||
private boolean init = false;
|
||||
private boolean ungrabbed = false;
|
||||
private EvdevListener listener;
|
||||
private Thread startThread;
|
||||
|
||||
private static boolean patchedSeLinuxPolicies = false;
|
||||
|
||||
private FileObserver observer = new FileObserver(PATH, FileObserver.CREATE | FileObserver.DELETE) {
|
||||
@Override
|
||||
public void onEvent(int event, String fileName) {
|
||||
if (fileName == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fileName.startsWith(REQUIRED_FILE_PREFIX)) {
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (handlers) {
|
||||
if (shutdown) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((event & FileObserver.CREATE) != 0) {
|
||||
LimeLog.info("Starting evdev handler for "+fileName);
|
||||
|
||||
if (!init) {
|
||||
// If this a real new device, update permissions again so we can read it
|
||||
EvdevReader.setPermissions(new String[]{PATH + "/" + fileName}, 0666);
|
||||
}
|
||||
|
||||
EvdevHandler handler = new EvdevHandler(PATH + "/" + fileName, listener);
|
||||
|
||||
// If we're ungrabbed now, don't start the handler
|
||||
if (!ungrabbed) {
|
||||
handler.start();
|
||||
}
|
||||
|
||||
handlers.put(fileName, handler);
|
||||
}
|
||||
|
||||
if ((event & FileObserver.DELETE) != 0) {
|
||||
LimeLog.info("Halting evdev handler for "+fileName);
|
||||
|
||||
EvdevHandler handler = handlers.remove(fileName);
|
||||
if (handler != null) {
|
||||
handler.notifyDeleted();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public EvdevWatcher(EvdevListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
private File[] rundownWithPermissionsChange(int newPermissions) {
|
||||
// Rundown existing files
|
||||
File devInputDir = new File(PATH);
|
||||
File[] files = devInputDir.listFiles();
|
||||
if (files == null) {
|
||||
return new File[0];
|
||||
}
|
||||
|
||||
// Set desired permissions
|
||||
String[] filePaths = new String[files.length];
|
||||
for (int i = 0; i < files.length; i++) {
|
||||
filePaths[i] = files[i].getAbsolutePath();
|
||||
}
|
||||
EvdevReader.setPermissions(filePaths, newPermissions);
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
public void ungrabAll() {
|
||||
synchronized (handlers) {
|
||||
// Note that we're ungrabbed for now
|
||||
ungrabbed = true;
|
||||
|
||||
// Stop all handlers
|
||||
for (EvdevHandler handler : handlers.values()) {
|
||||
handler.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void regrabAll() {
|
||||
synchronized (handlers) {
|
||||
// We're regrabbing everything now
|
||||
ungrabbed = false;
|
||||
|
||||
for (Map.Entry<String, EvdevHandler> entry : handlers.entrySet()) {
|
||||
// We need to recreate each entry since we can't reuse a stopped one
|
||||
entry.setValue(new EvdevHandler(PATH + "/" + entry.getKey(), listener));
|
||||
entry.getValue().start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void start() {
|
||||
startThread = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
private FileObserver observer = new FileObserver(PATH, FileObserver.CREATE | FileObserver.DELETE) {
|
||||
@Override
|
||||
public void onEvent(int event, String fileName) {
|
||||
if (fileName == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fileName.startsWith(REQUIRED_FILE_PREFIX)) {
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (handlers) {
|
||||
if (shutdown) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((event & FileObserver.CREATE) != 0) {
|
||||
LimeLog.info("Starting evdev handler for "+fileName);
|
||||
|
||||
if (!init) {
|
||||
// If this a real new device, update permissions again so we can read it
|
||||
EvdevReader.setPermissions(new String[]{PATH + "/" + fileName}, 0666);
|
||||
}
|
||||
|
||||
EvdevHandler handler = new EvdevHandler(PATH + "/" + fileName, listener);
|
||||
|
||||
// If we're ungrabbed now, don't start the handler
|
||||
if (!ungrabbed) {
|
||||
handler.start();
|
||||
}
|
||||
|
||||
handlers.put(fileName, handler);
|
||||
}
|
||||
|
||||
if ((event & FileObserver.DELETE) != 0) {
|
||||
LimeLog.info("Halting evdev handler for "+fileName);
|
||||
|
||||
EvdevHandler handler = handlers.remove(fileName);
|
||||
if (handler != null) {
|
||||
handler.notifyDeleted();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public EvdevWatcher(EvdevListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
private File[] rundownWithPermissionsChange(int newPermissions) {
|
||||
// Rundown existing files
|
||||
File devInputDir = new File(PATH);
|
||||
File[] files = devInputDir.listFiles();
|
||||
if (files == null) {
|
||||
return new File[0];
|
||||
}
|
||||
|
||||
// Set desired permissions
|
||||
String[] filePaths = new String[files.length];
|
||||
for (int i = 0; i < files.length; i++) {
|
||||
filePaths[i] = files[i].getAbsolutePath();
|
||||
}
|
||||
EvdevReader.setPermissions(filePaths, newPermissions);
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
public void ungrabAll() {
|
||||
synchronized (handlers) {
|
||||
// Note that we're ungrabbed for now
|
||||
ungrabbed = true;
|
||||
|
||||
// Stop all handlers
|
||||
for (EvdevHandler handler : handlers.values()) {
|
||||
handler.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void regrabAll() {
|
||||
synchronized (handlers) {
|
||||
// We're regrabbing everything now
|
||||
ungrabbed = false;
|
||||
|
||||
for (Map.Entry<String, EvdevHandler> entry : handlers.entrySet()) {
|
||||
// We need to recreate each entry since we can't reuse a stopped one
|
||||
entry.setValue(new EvdevHandler(PATH + "/" + entry.getKey(), listener));
|
||||
entry.getValue().start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void start() {
|
||||
startThread = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Initialize the root shell
|
||||
EvdevShell.getInstance().startShell();
|
||||
|
||||
@ -128,61 +128,61 @@ public class EvdevWatcher {
|
||||
patchedSeLinuxPolicies = true;
|
||||
}
|
||||
|
||||
// List all files and allow us access
|
||||
File[] files = rundownWithPermissionsChange(0666);
|
||||
|
||||
init = true;
|
||||
for (File f : files) {
|
||||
observer.onEvent(FileObserver.CREATE, f.getName());
|
||||
}
|
||||
|
||||
// Done with initial onEvent calls
|
||||
init = false;
|
||||
|
||||
// Start watching for new files
|
||||
observer.startWatching();
|
||||
|
||||
synchronized (startThread) {
|
||||
// Wait to be awoken again by shutdown()
|
||||
try {
|
||||
startThread.wait();
|
||||
} catch (InterruptedException e) {}
|
||||
}
|
||||
|
||||
// Giveup eventX permissions
|
||||
rundownWithPermissionsChange(0660);
|
||||
// List all files and allow us access
|
||||
File[] files = rundownWithPermissionsChange(0666);
|
||||
|
||||
init = true;
|
||||
for (File f : files) {
|
||||
observer.onEvent(FileObserver.CREATE, f.getName());
|
||||
}
|
||||
|
||||
// Done with initial onEvent calls
|
||||
init = false;
|
||||
|
||||
// Start watching for new files
|
||||
observer.startWatching();
|
||||
|
||||
synchronized (startThread) {
|
||||
// Wait to be awoken again by shutdown()
|
||||
try {
|
||||
startThread.wait();
|
||||
} catch (InterruptedException e) {}
|
||||
}
|
||||
|
||||
// Giveup eventX permissions
|
||||
rundownWithPermissionsChange(0660);
|
||||
|
||||
// Kill the root shell
|
||||
try {
|
||||
EvdevShell.getInstance().stopShell();
|
||||
} catch (InterruptedException e) {}
|
||||
}
|
||||
};
|
||||
startThread.start();
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
// Let start thread cleanup on it's own sweet time
|
||||
synchronized (startThread) {
|
||||
startThread.notify();
|
||||
}
|
||||
|
||||
// Stop the observer
|
||||
observer.stopWatching();
|
||||
|
||||
synchronized (handlers) {
|
||||
// Stop creating new handlers
|
||||
shutdown = true;
|
||||
|
||||
// If we've already ungrabbed, there's nothing else to do
|
||||
if (ungrabbed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop all handlers
|
||||
for (EvdevHandler handler : handlers.values()) {
|
||||
handler.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
startThread.start();
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
// Let start thread cleanup on it's own sweet time
|
||||
synchronized (startThread) {
|
||||
startThread.notify();
|
||||
}
|
||||
|
||||
// Stop the observer
|
||||
observer.stopWatching();
|
||||
|
||||
synchronized (handlers) {
|
||||
// Stop creating new handlers
|
||||
shutdown = true;
|
||||
|
||||
// If we've already ungrabbed, there's nothing else to do
|
||||
if (ungrabbed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop all handlers
|
||||
for (EvdevHandler handler : handlers.values()) {
|
||||
handler.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,140 +20,140 @@ import com.limelight.nvstream.av.video.cpu.AvcDecoder;
|
||||
@SuppressWarnings("EmptyCatchBlock")
|
||||
public class AndroidCpuDecoderRenderer extends EnhancedDecoderRenderer {
|
||||
|
||||
private Thread rendererThread, decoderThread;
|
||||
private int targetFps;
|
||||
|
||||
private static final int DECODER_BUFFER_SIZE = 92*1024;
|
||||
private ByteBuffer decoderBuffer;
|
||||
|
||||
// Only sleep if the difference is above this value
|
||||
private static final int WAIT_CEILING_MS = 5;
|
||||
|
||||
private static final int LOW_PERF = 1;
|
||||
private static final int MED_PERF = 2;
|
||||
private static final int HIGH_PERF = 3;
|
||||
|
||||
private int totalFrames;
|
||||
private long totalTimeMs;
|
||||
|
||||
private final int cpuCount = Runtime.getRuntime().availableProcessors();
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private int findOptimalPerformanceLevel() {
|
||||
StringBuilder cpuInfo = new StringBuilder();
|
||||
BufferedReader br = null;
|
||||
try {
|
||||
br = new BufferedReader(new FileReader(new File("/proc/cpuinfo")));
|
||||
for (;;) {
|
||||
int ch = br.read();
|
||||
if (ch == -1)
|
||||
break;
|
||||
cpuInfo.append((char)ch);
|
||||
}
|
||||
|
||||
// Here we're doing very simple heuristics based on CPU model
|
||||
String cpuInfoStr = cpuInfo.toString();
|
||||
private Thread rendererThread, decoderThread;
|
||||
private int targetFps;
|
||||
|
||||
// We order them from greatest to least for proper detection
|
||||
// of devices with multiple sets of cores (like Exynos 5 Octa)
|
||||
// TODO Make this better (only even kind of works on ARM)
|
||||
if (Build.FINGERPRINT.contains("generic")) {
|
||||
// Emulator
|
||||
return LOW_PERF;
|
||||
}
|
||||
else if (cpuInfoStr.contains("0xc0f")) {
|
||||
// Cortex-A15
|
||||
return MED_PERF;
|
||||
}
|
||||
else if (cpuInfoStr.contains("0xc09")) {
|
||||
// Cortex-A9
|
||||
return LOW_PERF;
|
||||
}
|
||||
else if (cpuInfoStr.contains("0xc07")) {
|
||||
// Cortex-A7
|
||||
return LOW_PERF;
|
||||
}
|
||||
else {
|
||||
// Didn't have anything we're looking for
|
||||
return MED_PERF;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
} finally {
|
||||
if (br != null) {
|
||||
try {
|
||||
br.close();
|
||||
} catch (IOException e) {}
|
||||
}
|
||||
}
|
||||
|
||||
// Couldn't read cpuinfo, so assume medium
|
||||
return MED_PERF;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setup(int width, int height, int redrawRate, Object renderTarget, int drFlags) {
|
||||
this.targetFps = redrawRate;
|
||||
|
||||
int perfLevel = LOW_PERF; //findOptimalPerformanceLevel();
|
||||
int threadCount;
|
||||
|
||||
int avcFlags = 0;
|
||||
switch (perfLevel) {
|
||||
case HIGH_PERF:
|
||||
// Single threaded low latency decode is ideal but hard to acheive
|
||||
avcFlags = AvcDecoder.LOW_LATENCY_DECODE;
|
||||
threadCount = 1;
|
||||
break;
|
||||
private static final int DECODER_BUFFER_SIZE = 92*1024;
|
||||
private ByteBuffer decoderBuffer;
|
||||
|
||||
case LOW_PERF:
|
||||
// Disable the loop filter for performance reasons
|
||||
avcFlags = AvcDecoder.FAST_BILINEAR_FILTERING;
|
||||
|
||||
// Use plenty of threads to try to utilize the CPU as best we can
|
||||
threadCount = cpuCount - 1;
|
||||
break;
|
||||
// Only sleep if the difference is above this value
|
||||
private static final int WAIT_CEILING_MS = 5;
|
||||
|
||||
default:
|
||||
case MED_PERF:
|
||||
avcFlags = AvcDecoder.BILINEAR_FILTERING;
|
||||
|
||||
// Only use 2 threads to minimize frame processing latency
|
||||
threadCount = 2;
|
||||
break;
|
||||
}
|
||||
|
||||
// If the user wants quality, we'll remove the low IQ flags
|
||||
if ((drFlags & VideoDecoderRenderer.FLAG_PREFER_QUALITY) != 0) {
|
||||
// Make sure the loop filter is enabled
|
||||
avcFlags &= ~AvcDecoder.DISABLE_LOOP_FILTER;
|
||||
|
||||
// Disable the non-compliant speed optimizations
|
||||
avcFlags &= ~AvcDecoder.FAST_DECODE;
|
||||
|
||||
LimeLog.info("Using high quality decoding");
|
||||
}
|
||||
|
||||
SurfaceHolder sh = (SurfaceHolder)renderTarget;
|
||||
sh.setFormat(PixelFormat.RGBX_8888);
|
||||
|
||||
int err = AvcDecoder.init(width, height, avcFlags, threadCount);
|
||||
if (err != 0) {
|
||||
throw new IllegalStateException("AVC decoder initialization failure: "+err);
|
||||
}
|
||||
|
||||
if (!AvcDecoder.setRenderTarget(sh.getSurface())) {
|
||||
private static final int LOW_PERF = 1;
|
||||
private static final int MED_PERF = 2;
|
||||
private static final int HIGH_PERF = 3;
|
||||
|
||||
private int totalFrames;
|
||||
private long totalTimeMs;
|
||||
|
||||
private final int cpuCount = Runtime.getRuntime().availableProcessors();
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private int findOptimalPerformanceLevel() {
|
||||
StringBuilder cpuInfo = new StringBuilder();
|
||||
BufferedReader br = null;
|
||||
try {
|
||||
br = new BufferedReader(new FileReader(new File("/proc/cpuinfo")));
|
||||
for (;;) {
|
||||
int ch = br.read();
|
||||
if (ch == -1)
|
||||
break;
|
||||
cpuInfo.append((char)ch);
|
||||
}
|
||||
|
||||
// Here we're doing very simple heuristics based on CPU model
|
||||
String cpuInfoStr = cpuInfo.toString();
|
||||
|
||||
// We order them from greatest to least for proper detection
|
||||
// of devices with multiple sets of cores (like Exynos 5 Octa)
|
||||
// TODO Make this better (only even kind of works on ARM)
|
||||
if (Build.FINGERPRINT.contains("generic")) {
|
||||
// Emulator
|
||||
return LOW_PERF;
|
||||
}
|
||||
else if (cpuInfoStr.contains("0xc0f")) {
|
||||
// Cortex-A15
|
||||
return MED_PERF;
|
||||
}
|
||||
else if (cpuInfoStr.contains("0xc09")) {
|
||||
// Cortex-A9
|
||||
return LOW_PERF;
|
||||
}
|
||||
else if (cpuInfoStr.contains("0xc07")) {
|
||||
// Cortex-A7
|
||||
return LOW_PERF;
|
||||
}
|
||||
else {
|
||||
// Didn't have anything we're looking for
|
||||
return MED_PERF;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
} finally {
|
||||
if (br != null) {
|
||||
try {
|
||||
br.close();
|
||||
} catch (IOException e) {}
|
||||
}
|
||||
}
|
||||
|
||||
// Couldn't read cpuinfo, so assume medium
|
||||
return MED_PERF;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setup(int width, int height, int redrawRate, Object renderTarget, int drFlags) {
|
||||
this.targetFps = redrawRate;
|
||||
|
||||
int perfLevel = LOW_PERF; //findOptimalPerformanceLevel();
|
||||
int threadCount;
|
||||
|
||||
int avcFlags = 0;
|
||||
switch (perfLevel) {
|
||||
case HIGH_PERF:
|
||||
// Single threaded low latency decode is ideal but hard to acheive
|
||||
avcFlags = AvcDecoder.LOW_LATENCY_DECODE;
|
||||
threadCount = 1;
|
||||
break;
|
||||
|
||||
case LOW_PERF:
|
||||
// Disable the loop filter for performance reasons
|
||||
avcFlags = AvcDecoder.FAST_BILINEAR_FILTERING;
|
||||
|
||||
// Use plenty of threads to try to utilize the CPU as best we can
|
||||
threadCount = cpuCount - 1;
|
||||
break;
|
||||
|
||||
default:
|
||||
case MED_PERF:
|
||||
avcFlags = AvcDecoder.BILINEAR_FILTERING;
|
||||
|
||||
// Only use 2 threads to minimize frame processing latency
|
||||
threadCount = 2;
|
||||
break;
|
||||
}
|
||||
|
||||
// If the user wants quality, we'll remove the low IQ flags
|
||||
if ((drFlags & VideoDecoderRenderer.FLAG_PREFER_QUALITY) != 0) {
|
||||
// Make sure the loop filter is enabled
|
||||
avcFlags &= ~AvcDecoder.DISABLE_LOOP_FILTER;
|
||||
|
||||
// Disable the non-compliant speed optimizations
|
||||
avcFlags &= ~AvcDecoder.FAST_DECODE;
|
||||
|
||||
LimeLog.info("Using high quality decoding");
|
||||
}
|
||||
|
||||
SurfaceHolder sh = (SurfaceHolder)renderTarget;
|
||||
sh.setFormat(PixelFormat.RGBX_8888);
|
||||
|
||||
int err = AvcDecoder.init(width, height, avcFlags, threadCount);
|
||||
if (err != 0) {
|
||||
throw new IllegalStateException("AVC decoder initialization failure: "+err);
|
||||
}
|
||||
|
||||
if (!AvcDecoder.setRenderTarget(sh.getSurface())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
decoderBuffer = ByteBuffer.allocate(DECODER_BUFFER_SIZE + AvcDecoder.getInputPaddingSize());
|
||||
|
||||
LimeLog.info("Using software decoding (performance level: "+perfLevel+")");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean start(final VideoDepacketizer depacketizer) {
|
||||
decoderBuffer = ByteBuffer.allocate(DECODER_BUFFER_SIZE + AvcDecoder.getInputPaddingSize());
|
||||
|
||||
LimeLog.info("Using software decoding (performance level: "+perfLevel+")");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean start(final VideoDepacketizer depacketizer) {
|
||||
decoderThread = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
@ -174,112 +174,112 @@ public class AndroidCpuDecoderRenderer extends EnhancedDecoderRenderer {
|
||||
decoderThread.setPriority(Thread.MAX_PRIORITY - 1);
|
||||
decoderThread.start();
|
||||
|
||||
rendererThread = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
long nextFrameTime = System.currentTimeMillis();
|
||||
DecodeUnit du;
|
||||
while (!isInterrupted())
|
||||
{
|
||||
long diff = nextFrameTime - System.currentTimeMillis();
|
||||
rendererThread = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
long nextFrameTime = System.currentTimeMillis();
|
||||
DecodeUnit du;
|
||||
while (!isInterrupted())
|
||||
{
|
||||
long diff = nextFrameTime - System.currentTimeMillis();
|
||||
|
||||
if (diff > WAIT_CEILING_MS) {
|
||||
if (diff > WAIT_CEILING_MS) {
|
||||
try {
|
||||
Thread.sleep(diff - WAIT_CEILING_MS);
|
||||
} catch (InterruptedException e) {
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
nextFrameTime = computePresentationTimeMs(targetFps);
|
||||
AvcDecoder.redraw();
|
||||
}
|
||||
}
|
||||
};
|
||||
rendererThread.setName("Video - Renderer (CPU)");
|
||||
rendererThread.setPriority(Thread.MAX_PRIORITY);
|
||||
rendererThread.start();
|
||||
return true;
|
||||
}
|
||||
|
||||
private long computePresentationTimeMs(int frameRate) {
|
||||
return System.currentTimeMillis() + (1000 / frameRate);
|
||||
}
|
||||
nextFrameTime = computePresentationTimeMs(targetFps);
|
||||
AvcDecoder.redraw();
|
||||
}
|
||||
}
|
||||
};
|
||||
rendererThread.setName("Video - Renderer (CPU)");
|
||||
rendererThread.setPriority(Thread.MAX_PRIORITY);
|
||||
rendererThread.start();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
rendererThread.interrupt();
|
||||
private long computePresentationTimeMs(int frameRate) {
|
||||
return System.currentTimeMillis() + (1000 / frameRate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
rendererThread.interrupt();
|
||||
decoderThread.interrupt();
|
||||
|
||||
try {
|
||||
|
||||
try {
|
||||
rendererThread.join();
|
||||
} catch (InterruptedException e) { }
|
||||
try {
|
||||
decoderThread.join();
|
||||
} catch (InterruptedException e) { }
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
AvcDecoder.destroy();
|
||||
}
|
||||
@Override
|
||||
public void release() {
|
||||
AvcDecoder.destroy();
|
||||
}
|
||||
|
||||
private boolean submitDecodeUnit(DecodeUnit decodeUnit) {
|
||||
byte[] data;
|
||||
|
||||
// Use the reserved decoder buffer if this decode unit will fit
|
||||
if (decodeUnit.getDataLength() <= DECODER_BUFFER_SIZE) {
|
||||
decoderBuffer.clear();
|
||||
|
||||
for (ByteBufferDescriptor bbd : decodeUnit.getBufferList()) {
|
||||
decoderBuffer.put(bbd.data, bbd.offset, bbd.length);
|
||||
}
|
||||
|
||||
data = decoderBuffer.array();
|
||||
}
|
||||
else {
|
||||
data = new byte[decodeUnit.getDataLength()+AvcDecoder.getInputPaddingSize()];
|
||||
|
||||
int offset = 0;
|
||||
for (ByteBufferDescriptor bbd : decodeUnit.getBufferList()) {
|
||||
System.arraycopy(bbd.data, bbd.offset, data, offset, bbd.length);
|
||||
offset += bbd.length;
|
||||
}
|
||||
}
|
||||
|
||||
boolean success = (AvcDecoder.decode(data, 0, decodeUnit.getDataLength()) == 0);
|
||||
if (success) {
|
||||
long timeAfterDecode = System.currentTimeMillis();
|
||||
|
||||
// Add delta time to the totals (excluding probable outliers)
|
||||
long delta = timeAfterDecode - decodeUnit.getReceiveTimestamp();
|
||||
if (delta >= 0 && delta < 1000) {
|
||||
totalTimeMs += delta;
|
||||
totalFrames++;
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
private boolean submitDecodeUnit(DecodeUnit decodeUnit) {
|
||||
byte[] data;
|
||||
|
||||
@Override
|
||||
public int getCapabilities() {
|
||||
return 0;
|
||||
}
|
||||
// Use the reserved decoder buffer if this decode unit will fit
|
||||
if (decodeUnit.getDataLength() <= DECODER_BUFFER_SIZE) {
|
||||
decoderBuffer.clear();
|
||||
|
||||
@Override
|
||||
public int getAverageDecoderLatency() {
|
||||
return 0;
|
||||
}
|
||||
for (ByteBufferDescriptor bbd : decodeUnit.getBufferList()) {
|
||||
decoderBuffer.put(bbd.data, bbd.offset, bbd.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAverageEndToEndLatency() {
|
||||
if (totalFrames == 0) {
|
||||
return 0;
|
||||
}
|
||||
return (int)(totalTimeMs / totalFrames);
|
||||
}
|
||||
data = decoderBuffer.array();
|
||||
}
|
||||
else {
|
||||
data = new byte[decodeUnit.getDataLength()+AvcDecoder.getInputPaddingSize()];
|
||||
|
||||
int offset = 0;
|
||||
for (ByteBufferDescriptor bbd : decodeUnit.getBufferList()) {
|
||||
System.arraycopy(bbd.data, bbd.offset, data, offset, bbd.length);
|
||||
offset += bbd.length;
|
||||
}
|
||||
}
|
||||
|
||||
boolean success = (AvcDecoder.decode(data, 0, decodeUnit.getDataLength()) == 0);
|
||||
if (success) {
|
||||
long timeAfterDecode = System.currentTimeMillis();
|
||||
|
||||
// Add delta time to the totals (excluding probable outliers)
|
||||
long delta = timeAfterDecode - decodeUnit.getReceiveTimestamp();
|
||||
if (delta >= 0 && delta < 1000) {
|
||||
totalTimeMs += delta;
|
||||
totalFrames++;
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCapabilities() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAverageDecoderLatency() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAverageEndToEndLatency() {
|
||||
if (totalFrames == 0) {
|
||||
return 0;
|
||||
}
|
||||
return (int)(totalTimeMs / totalFrames);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDecoderName() {
|
||||
|
@ -5,75 +5,75 @@ import com.limelight.nvstream.av.video.VideoDepacketizer;
|
||||
|
||||
public class ConfigurableDecoderRenderer extends EnhancedDecoderRenderer {
|
||||
|
||||
private EnhancedDecoderRenderer decoderRenderer;
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
if (decoderRenderer != null) {
|
||||
decoderRenderer.release();
|
||||
}
|
||||
}
|
||||
private EnhancedDecoderRenderer decoderRenderer;
|
||||
|
||||
@Override
|
||||
public boolean setup(int width, int height, int redrawRate, Object renderTarget, int drFlags) {
|
||||
if (decoderRenderer == null) {
|
||||
throw new IllegalStateException("ConfigurableDecoderRenderer not initialized");
|
||||
}
|
||||
return decoderRenderer.setup(width, height, redrawRate, renderTarget, drFlags);
|
||||
}
|
||||
|
||||
public void initializeWithFlags(int drFlags) {
|
||||
if ((drFlags & VideoDecoderRenderer.FLAG_FORCE_HARDWARE_DECODING) != 0 ||
|
||||
((drFlags & VideoDecoderRenderer.FLAG_FORCE_SOFTWARE_DECODING) == 0 &&
|
||||
MediaCodecHelper.findProbableSafeDecoder() != null)) {
|
||||
decoderRenderer = new MediaCodecDecoderRenderer();
|
||||
}
|
||||
else {
|
||||
decoderRenderer = new AndroidCpuDecoderRenderer();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isHardwareAccelerated() {
|
||||
if (decoderRenderer == null) {
|
||||
throw new IllegalStateException("ConfigurableDecoderRenderer not initialized");
|
||||
}
|
||||
return (decoderRenderer instanceof MediaCodecDecoderRenderer);
|
||||
}
|
||||
@Override
|
||||
public void release() {
|
||||
if (decoderRenderer != null) {
|
||||
decoderRenderer.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean start(VideoDepacketizer depacketizer) {
|
||||
return decoderRenderer.start(depacketizer);
|
||||
}
|
||||
@Override
|
||||
public boolean setup(int width, int height, int redrawRate, Object renderTarget, int drFlags) {
|
||||
if (decoderRenderer == null) {
|
||||
throw new IllegalStateException("ConfigurableDecoderRenderer not initialized");
|
||||
}
|
||||
return decoderRenderer.setup(width, height, redrawRate, renderTarget, drFlags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
decoderRenderer.stop();
|
||||
}
|
||||
public void initializeWithFlags(int drFlags) {
|
||||
if ((drFlags & VideoDecoderRenderer.FLAG_FORCE_HARDWARE_DECODING) != 0 ||
|
||||
((drFlags & VideoDecoderRenderer.FLAG_FORCE_SOFTWARE_DECODING) == 0 &&
|
||||
MediaCodecHelper.findProbableSafeDecoder() != null)) {
|
||||
decoderRenderer = new MediaCodecDecoderRenderer();
|
||||
}
|
||||
else {
|
||||
decoderRenderer = new AndroidCpuDecoderRenderer();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCapabilities() {
|
||||
return decoderRenderer.getCapabilities();
|
||||
}
|
||||
public boolean isHardwareAccelerated() {
|
||||
if (decoderRenderer == null) {
|
||||
throw new IllegalStateException("ConfigurableDecoderRenderer not initialized");
|
||||
}
|
||||
return (decoderRenderer instanceof MediaCodecDecoderRenderer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAverageDecoderLatency() {
|
||||
if (decoderRenderer != null) {
|
||||
return decoderRenderer.getAverageDecoderLatency();
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public boolean start(VideoDepacketizer depacketizer) {
|
||||
return decoderRenderer.start(depacketizer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAverageEndToEndLatency() {
|
||||
if (decoderRenderer != null) {
|
||||
return decoderRenderer.getAverageEndToEndLatency();
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void stop() {
|
||||
decoderRenderer.stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCapabilities() {
|
||||
return decoderRenderer.getCapabilities();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAverageDecoderLatency() {
|
||||
if (decoderRenderer != null) {
|
||||
return decoderRenderer.getAverageDecoderLatency();
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAverageEndToEndLatency() {
|
||||
if (decoderRenderer != null) {
|
||||
return decoderRenderer.getAverageEndToEndLatency();
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDecoderName() {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -72,8 +72,8 @@ public class MediaCodecHelper {
|
||||
@TargetApi(Build.VERSION_CODES.KITKAT)
|
||||
public static boolean decoderSupportsAdaptivePlayback(String decoderName, MediaCodecInfo decoderInfo) {
|
||||
/*
|
||||
FIXME: Intel's decoder on Nexus Player forces the high latency path if adaptive playback is enabled
|
||||
so we'll keep it off for now, since we don't know whether other devices also do the same
|
||||
FIXME: Intel's decoder on Nexus Player forces the high latency path if adaptive playback is enabled
|
||||
so we'll keep it off for now, since we don't know whether other devices also do the same
|
||||
|
||||
if (isDecoderInList(whitelistedAdaptiveResolutionPrefixes, decoderName)) {
|
||||
LimeLog.info("Adaptive playback supported (whitelist)");
|
||||
|
@ -17,153 +17,153 @@ import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteException;
|
||||
|
||||
public class ComputerDatabaseManager {
|
||||
private static final String COMPUTER_DB_NAME = "computers.db";
|
||||
private static final String COMPUTER_TABLE_NAME = "Computers";
|
||||
private static final String COMPUTER_NAME_COLUMN_NAME = "ComputerName";
|
||||
private static final String COMPUTER_UUID_COLUMN_NAME = "UUID";
|
||||
private static final String LOCAL_IP_COLUMN_NAME = "LocalIp";
|
||||
private static final String REMOTE_IP_COLUMN_NAME = "RemoteIp";
|
||||
private static final String MAC_COLUMN_NAME = "Mac";
|
||||
|
||||
private SQLiteDatabase computerDb;
|
||||
|
||||
public ComputerDatabaseManager(Context c) {
|
||||
try {
|
||||
// Create or open an existing DB
|
||||
computerDb = c.openOrCreateDatabase(COMPUTER_DB_NAME, 0, null);
|
||||
} catch (SQLiteException e) {
|
||||
// Delete the DB and try again
|
||||
c.deleteDatabase(COMPUTER_DB_NAME);
|
||||
computerDb = c.openOrCreateDatabase(COMPUTER_DB_NAME, 0, null);
|
||||
}
|
||||
initializeDb();
|
||||
}
|
||||
|
||||
public void close() {
|
||||
computerDb.close();
|
||||
}
|
||||
|
||||
private void initializeDb() {
|
||||
// Create tables if they aren't already there
|
||||
computerDb.execSQL(String.format((Locale)null, "CREATE TABLE IF NOT EXISTS %s(%s TEXT PRIMARY KEY," +
|
||||
" %s TEXT NOT NULL, %s TEXT NOT NULL, %s TEXT NOT NULL, %s TEXT NOT NULL)",
|
||||
COMPUTER_TABLE_NAME,
|
||||
COMPUTER_NAME_COLUMN_NAME, COMPUTER_UUID_COLUMN_NAME, LOCAL_IP_COLUMN_NAME,
|
||||
REMOTE_IP_COLUMN_NAME, MAC_COLUMN_NAME));
|
||||
}
|
||||
|
||||
public void deleteComputer(String name) {
|
||||
computerDb.delete(COMPUTER_TABLE_NAME, COMPUTER_NAME_COLUMN_NAME+"='"+name+"'", null);
|
||||
}
|
||||
|
||||
public boolean updateComputer(ComputerDetails details) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(COMPUTER_NAME_COLUMN_NAME, details.name);
|
||||
values.put(COMPUTER_UUID_COLUMN_NAME, details.uuid.toString());
|
||||
values.put(LOCAL_IP_COLUMN_NAME, details.localIp.getAddress());
|
||||
values.put(REMOTE_IP_COLUMN_NAME, details.remoteIp.getAddress());
|
||||
values.put(MAC_COLUMN_NAME, details.macAddress);
|
||||
return -1 != computerDb.insertWithOnConflict(COMPUTER_TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_REPLACE);
|
||||
}
|
||||
|
||||
public List<ComputerDetails> getAllComputers() {
|
||||
Cursor c = computerDb.rawQuery("SELECT * FROM "+COMPUTER_TABLE_NAME, null);
|
||||
LinkedList<ComputerDetails> computerList = new LinkedList<ComputerDetails>();
|
||||
while (c.moveToNext()) {
|
||||
ComputerDetails details = new ComputerDetails();
|
||||
|
||||
details.name = c.getString(0);
|
||||
|
||||
String uuidStr = c.getString(1);
|
||||
try {
|
||||
details.uuid = UUID.fromString(uuidStr);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// We'll delete this entry
|
||||
LimeLog.severe("DB: Corrupted UUID for "+details.name);
|
||||
}
|
||||
|
||||
try {
|
||||
details.localIp = InetAddress.getByAddress(c.getBlob(2));
|
||||
} catch (UnknownHostException e) {
|
||||
// We'll delete this entry
|
||||
LimeLog.severe("DB: Corrupted local IP for "+details.name);
|
||||
}
|
||||
|
||||
try {
|
||||
details.remoteIp = InetAddress.getByAddress(c.getBlob(3));
|
||||
} catch (UnknownHostException e) {
|
||||
// We'll delete this entry
|
||||
LimeLog.severe("DB: Corrupted remote IP for "+details.name);
|
||||
}
|
||||
|
||||
details.macAddress = c.getString(4);
|
||||
|
||||
// This signifies we don't have dynamic state (like pair state)
|
||||
private static final String COMPUTER_DB_NAME = "computers.db";
|
||||
private static final String COMPUTER_TABLE_NAME = "Computers";
|
||||
private static final String COMPUTER_NAME_COLUMN_NAME = "ComputerName";
|
||||
private static final String COMPUTER_UUID_COLUMN_NAME = "UUID";
|
||||
private static final String LOCAL_IP_COLUMN_NAME = "LocalIp";
|
||||
private static final String REMOTE_IP_COLUMN_NAME = "RemoteIp";
|
||||
private static final String MAC_COLUMN_NAME = "Mac";
|
||||
|
||||
private SQLiteDatabase computerDb;
|
||||
|
||||
public ComputerDatabaseManager(Context c) {
|
||||
try {
|
||||
// Create or open an existing DB
|
||||
computerDb = c.openOrCreateDatabase(COMPUTER_DB_NAME, 0, null);
|
||||
} catch (SQLiteException e) {
|
||||
// Delete the DB and try again
|
||||
c.deleteDatabase(COMPUTER_DB_NAME);
|
||||
computerDb = c.openOrCreateDatabase(COMPUTER_DB_NAME, 0, null);
|
||||
}
|
||||
initializeDb();
|
||||
}
|
||||
|
||||
public void close() {
|
||||
computerDb.close();
|
||||
}
|
||||
|
||||
private void initializeDb() {
|
||||
// Create tables if they aren't already there
|
||||
computerDb.execSQL(String.format((Locale)null, "CREATE TABLE IF NOT EXISTS %s(%s TEXT PRIMARY KEY," +
|
||||
" %s TEXT NOT NULL, %s TEXT NOT NULL, %s TEXT NOT NULL, %s TEXT NOT NULL)",
|
||||
COMPUTER_TABLE_NAME,
|
||||
COMPUTER_NAME_COLUMN_NAME, COMPUTER_UUID_COLUMN_NAME, LOCAL_IP_COLUMN_NAME,
|
||||
REMOTE_IP_COLUMN_NAME, MAC_COLUMN_NAME));
|
||||
}
|
||||
|
||||
public void deleteComputer(String name) {
|
||||
computerDb.delete(COMPUTER_TABLE_NAME, COMPUTER_NAME_COLUMN_NAME+"='"+name+"'", null);
|
||||
}
|
||||
|
||||
public boolean updateComputer(ComputerDetails details) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(COMPUTER_NAME_COLUMN_NAME, details.name);
|
||||
values.put(COMPUTER_UUID_COLUMN_NAME, details.uuid.toString());
|
||||
values.put(LOCAL_IP_COLUMN_NAME, details.localIp.getAddress());
|
||||
values.put(REMOTE_IP_COLUMN_NAME, details.remoteIp.getAddress());
|
||||
values.put(MAC_COLUMN_NAME, details.macAddress);
|
||||
return -1 != computerDb.insertWithOnConflict(COMPUTER_TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_REPLACE);
|
||||
}
|
||||
|
||||
public List<ComputerDetails> getAllComputers() {
|
||||
Cursor c = computerDb.rawQuery("SELECT * FROM "+COMPUTER_TABLE_NAME, null);
|
||||
LinkedList<ComputerDetails> computerList = new LinkedList<ComputerDetails>();
|
||||
while (c.moveToNext()) {
|
||||
ComputerDetails details = new ComputerDetails();
|
||||
|
||||
details.name = c.getString(0);
|
||||
|
||||
String uuidStr = c.getString(1);
|
||||
try {
|
||||
details.uuid = UUID.fromString(uuidStr);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// We'll delete this entry
|
||||
LimeLog.severe("DB: Corrupted UUID for "+details.name);
|
||||
}
|
||||
|
||||
try {
|
||||
details.localIp = InetAddress.getByAddress(c.getBlob(2));
|
||||
} catch (UnknownHostException e) {
|
||||
// We'll delete this entry
|
||||
LimeLog.severe("DB: Corrupted local IP for "+details.name);
|
||||
}
|
||||
|
||||
try {
|
||||
details.remoteIp = InetAddress.getByAddress(c.getBlob(3));
|
||||
} catch (UnknownHostException e) {
|
||||
// We'll delete this entry
|
||||
LimeLog.severe("DB: Corrupted remote IP for "+details.name);
|
||||
}
|
||||
|
||||
details.macAddress = c.getString(4);
|
||||
|
||||
// This signifies we don't have dynamic state (like pair state)
|
||||
details.state = ComputerDetails.State.UNKNOWN;
|
||||
details.reachability = ComputerDetails.Reachability.UNKNOWN;
|
||||
|
||||
// If a field is corrupt or missing, skip the database entry
|
||||
if (details.uuid == null || details.localIp == null || details.remoteIp == null ||
|
||||
details.macAddress == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If a field is corrupt or missing, skip the database entry
|
||||
if (details.uuid == null || details.localIp == null || details.remoteIp == null ||
|
||||
details.macAddress == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
computerList.add(details);
|
||||
}
|
||||
|
||||
c.close();
|
||||
|
||||
return computerList;
|
||||
}
|
||||
|
||||
public ComputerDetails getComputerByName(String name) {
|
||||
Cursor c = computerDb.rawQuery("SELECT * FROM "+COMPUTER_TABLE_NAME+" WHERE "+COMPUTER_NAME_COLUMN_NAME+"='"+name+"'", null);
|
||||
ComputerDetails details = new ComputerDetails();
|
||||
if (!c.moveToFirst()) {
|
||||
// No matching computer
|
||||
c.close();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
details.name = c.getString(0);
|
||||
|
||||
String uuidStr = c.getString(1);
|
||||
try {
|
||||
details.uuid = UUID.fromString(uuidStr);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// We'll delete this entry
|
||||
LimeLog.severe("DB: Corrupted UUID for "+details.name);
|
||||
}
|
||||
|
||||
try {
|
||||
details.localIp = InetAddress.getByAddress(c.getBlob(2));
|
||||
} catch (UnknownHostException e) {
|
||||
// We'll delete this entry
|
||||
LimeLog.severe("DB: Corrupted local IP for "+details.name);
|
||||
}
|
||||
|
||||
try {
|
||||
details.remoteIp = InetAddress.getByAddress(c.getBlob(3));
|
||||
} catch (UnknownHostException e) {
|
||||
// We'll delete this entry
|
||||
LimeLog.severe("DB: Corrupted remote IP for "+details.name);
|
||||
}
|
||||
|
||||
details.macAddress = c.getString(4);
|
||||
|
||||
c.close();
|
||||
c.close();
|
||||
|
||||
return computerList;
|
||||
}
|
||||
|
||||
public ComputerDetails getComputerByName(String name) {
|
||||
Cursor c = computerDb.rawQuery("SELECT * FROM "+COMPUTER_TABLE_NAME+" WHERE "+COMPUTER_NAME_COLUMN_NAME+"='"+name+"'", null);
|
||||
ComputerDetails details = new ComputerDetails();
|
||||
if (!c.moveToFirst()) {
|
||||
// No matching computer
|
||||
c.close();
|
||||
return null;
|
||||
}
|
||||
|
||||
details.name = c.getString(0);
|
||||
|
||||
String uuidStr = c.getString(1);
|
||||
try {
|
||||
details.uuid = UUID.fromString(uuidStr);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// We'll delete this entry
|
||||
LimeLog.severe("DB: Corrupted UUID for "+details.name);
|
||||
}
|
||||
|
||||
try {
|
||||
details.localIp = InetAddress.getByAddress(c.getBlob(2));
|
||||
} catch (UnknownHostException e) {
|
||||
// We'll delete this entry
|
||||
LimeLog.severe("DB: Corrupted local IP for "+details.name);
|
||||
}
|
||||
|
||||
try {
|
||||
details.remoteIp = InetAddress.getByAddress(c.getBlob(3));
|
||||
} catch (UnknownHostException e) {
|
||||
// We'll delete this entry
|
||||
LimeLog.severe("DB: Corrupted remote IP for "+details.name);
|
||||
}
|
||||
|
||||
details.macAddress = c.getString(4);
|
||||
|
||||
c.close();
|
||||
|
||||
details.state = ComputerDetails.State.UNKNOWN;
|
||||
details.reachability = ComputerDetails.Reachability.UNKNOWN;
|
||||
|
||||
// If a field is corrupt or missing, delete the database entry
|
||||
if (details.uuid == null || details.localIp == null || details.remoteIp == null ||
|
||||
details.macAddress == null) {
|
||||
deleteComputer(details.name);
|
||||
return null;
|
||||
}
|
||||
|
||||
return details;
|
||||
}
|
||||
|
||||
// If a field is corrupt or missing, delete the database entry
|
||||
if (details.uuid == null || details.localIp == null || details.remoteIp == null ||
|
||||
details.macAddress == null) {
|
||||
deleteComputer(details.name);
|
||||
return null;
|
||||
}
|
||||
|
||||
return details;
|
||||
}
|
||||
}
|
||||
|
@ -3,5 +3,5 @@ package com.limelight.computers;
|
||||
import com.limelight.nvstream.http.ComputerDetails;
|
||||
|
||||
public interface ComputerManagerListener {
|
||||
public void notifyComputerUpdated(ComputerDetails details);
|
||||
public void notifyComputerUpdated(ComputerDetails details);
|
||||
}
|
||||
|
@ -29,39 +29,39 @@ import android.os.IBinder;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
public class ComputerManagerService extends Service {
|
||||
private static final int POLLING_PERIOD_MS = 3000;
|
||||
private static final int MDNS_QUERY_PERIOD_MS = 1000;
|
||||
|
||||
private final ComputerManagerBinder binder = new ComputerManagerBinder();
|
||||
|
||||
private ComputerDatabaseManager dbManager;
|
||||
private final AtomicInteger dbRefCount = new AtomicInteger(0);
|
||||
|
||||
private IdentityManager idManager;
|
||||
private final LinkedList<PollingTuple> pollingTuples = new LinkedList<PollingTuple>();
|
||||
private ComputerManagerListener listener = null;
|
||||
private final AtomicInteger activePolls = new AtomicInteger(0);
|
||||
private static final int POLLING_PERIOD_MS = 3000;
|
||||
private static final int MDNS_QUERY_PERIOD_MS = 1000;
|
||||
|
||||
private final ComputerManagerBinder binder = new ComputerManagerBinder();
|
||||
|
||||
private ComputerDatabaseManager dbManager;
|
||||
private final AtomicInteger dbRefCount = new AtomicInteger(0);
|
||||
|
||||
private IdentityManager idManager;
|
||||
private final LinkedList<PollingTuple> pollingTuples = new LinkedList<PollingTuple>();
|
||||
private ComputerManagerListener listener = null;
|
||||
private final AtomicInteger activePolls = new AtomicInteger(0);
|
||||
private boolean pollingActive = false;
|
||||
|
||||
private DiscoveryService.DiscoveryBinder discoveryBinder;
|
||||
private final ServiceConnection discoveryServiceConnection = new ServiceConnection() {
|
||||
public void onServiceConnected(ComponentName className, IBinder binder) {
|
||||
synchronized (discoveryServiceConnection) {
|
||||
DiscoveryService.DiscoveryBinder privateBinder = ((DiscoveryService.DiscoveryBinder)binder);
|
||||
|
||||
// Set us as the event listener
|
||||
privateBinder.setListener(createDiscoveryListener());
|
||||
|
||||
// Signal a possible waiter that we're all setup
|
||||
discoveryBinder = privateBinder;
|
||||
discoveryServiceConnection.notifyAll();
|
||||
}
|
||||
}
|
||||
private DiscoveryService.DiscoveryBinder discoveryBinder;
|
||||
private final ServiceConnection discoveryServiceConnection = new ServiceConnection() {
|
||||
public void onServiceConnected(ComponentName className, IBinder binder) {
|
||||
synchronized (discoveryServiceConnection) {
|
||||
DiscoveryService.DiscoveryBinder privateBinder = ((DiscoveryService.DiscoveryBinder)binder);
|
||||
|
||||
public void onServiceDisconnected(ComponentName className) {
|
||||
discoveryBinder = null;
|
||||
}
|
||||
};
|
||||
// Set us as the event listener
|
||||
privateBinder.setListener(createDiscoveryListener());
|
||||
|
||||
// Signal a possible waiter that we're all setup
|
||||
discoveryBinder = privateBinder;
|
||||
discoveryServiceConnection.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
public void onServiceDisconnected(ComponentName className) {
|
||||
discoveryBinder = null;
|
||||
}
|
||||
};
|
||||
|
||||
// Returns true if the details object was modified
|
||||
private boolean runPoll(ComputerDetails details, boolean newPc)
|
||||
@ -124,17 +124,17 @@ public class ComputerManagerService extends Service {
|
||||
t.setName("Polling thread for "+details.localIp.getHostAddress());
|
||||
return t;
|
||||
}
|
||||
|
||||
public class ComputerManagerBinder extends Binder {
|
||||
public void startPolling(ComputerManagerListener listener) {
|
||||
|
||||
public class ComputerManagerBinder extends Binder {
|
||||
public void startPolling(ComputerManagerListener listener) {
|
||||
// Polling is active
|
||||
pollingActive = true;
|
||||
|
||||
// Set the listener
|
||||
ComputerManagerService.this.listener = listener;
|
||||
|
||||
// Start mDNS autodiscovery too
|
||||
discoveryBinder.startDiscovery(MDNS_QUERY_PERIOD_MS);
|
||||
// Set the listener
|
||||
ComputerManagerService.this.listener = listener;
|
||||
|
||||
// Start mDNS autodiscovery too
|
||||
discoveryBinder.startDiscovery(MDNS_QUERY_PERIOD_MS);
|
||||
|
||||
synchronized (pollingTuples) {
|
||||
for (PollingTuple tuple : pollingTuples) {
|
||||
@ -148,48 +148,48 @@ public class ComputerManagerService extends Service {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void waitForReady() {
|
||||
synchronized (discoveryServiceConnection) {
|
||||
try {
|
||||
while (discoveryBinder == null) {
|
||||
// Wait for the bind notification
|
||||
discoveryServiceConnection.wait(1000);
|
||||
}
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void waitForPollingStopped() {
|
||||
while (activePolls.get() != 0) {
|
||||
try {
|
||||
Thread.sleep(250);
|
||||
} catch (InterruptedException ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean addComputerBlocking(InetAddress addr) {
|
||||
return ComputerManagerService.this.addComputerBlocking(addr);
|
||||
}
|
||||
|
||||
public void removeComputer(String name) {
|
||||
ComputerManagerService.this.removeComputer(name);
|
||||
}
|
||||
|
||||
public void stopPolling() {
|
||||
// Just call the unbind handler to cleanup
|
||||
ComputerManagerService.this.onUnbind(null);
|
||||
}
|
||||
}
|
||||
|
||||
public void waitForReady() {
|
||||
synchronized (discoveryServiceConnection) {
|
||||
try {
|
||||
while (discoveryBinder == null) {
|
||||
// Wait for the bind notification
|
||||
discoveryServiceConnection.wait(1000);
|
||||
}
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void waitForPollingStopped() {
|
||||
while (activePolls.get() != 0) {
|
||||
try {
|
||||
Thread.sleep(250);
|
||||
} catch (InterruptedException ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean addComputerBlocking(InetAddress addr) {
|
||||
return ComputerManagerService.this.addComputerBlocking(addr);
|
||||
}
|
||||
|
||||
public void removeComputer(String name) {
|
||||
ComputerManagerService.this.removeComputer(name);
|
||||
}
|
||||
|
||||
public void stopPolling() {
|
||||
// Just call the unbind handler to cleanup
|
||||
ComputerManagerService.this.onUnbind(null);
|
||||
}
|
||||
|
||||
public ApplistPoller createAppListPoller(ComputerDetails computer) {
|
||||
return new ApplistPoller(computer);
|
||||
}
|
||||
|
||||
public String getUniqueId() {
|
||||
return idManager.getUniqueId();
|
||||
}
|
||||
|
||||
public String getUniqueId() {
|
||||
return idManager.getUniqueId();
|
||||
}
|
||||
|
||||
public ComputerDetails getComputer(UUID uuid) {
|
||||
synchronized (pollingTuples) {
|
||||
@ -202,14 +202,14 @@ public class ComputerManagerService extends Service {
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onUnbind(Intent intent) {
|
||||
// Stop mDNS autodiscovery
|
||||
discoveryBinder.stopDiscovery();
|
||||
|
||||
// Stop polling
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onUnbind(Intent intent) {
|
||||
// Stop mDNS autodiscovery
|
||||
discoveryBinder.stopDiscovery();
|
||||
|
||||
// Stop polling
|
||||
pollingActive = false;
|
||||
synchronized (pollingTuples) {
|
||||
for (PollingTuple tuple : pollingTuples) {
|
||||
@ -220,33 +220,33 @@ public class ComputerManagerService extends Service {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the listener
|
||||
listener = null;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private MdnsDiscoveryListener createDiscoveryListener() {
|
||||
return new MdnsDiscoveryListener() {
|
||||
@Override
|
||||
public void notifyComputerAdded(MdnsComputer computer) {
|
||||
// Kick off a serverinfo poll on this machine
|
||||
addComputerBlocking(computer.getAddress());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyComputerRemoved(MdnsComputer computer) {
|
||||
// Nothing to do here
|
||||
}
|
||||
// Remove the listener
|
||||
listener = null;
|
||||
|
||||
@Override
|
||||
public void notifyDiscoveryFailure(Exception e) {
|
||||
LimeLog.severe("mDNS discovery failed");
|
||||
e.printStackTrace();
|
||||
}
|
||||
};
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private MdnsDiscoveryListener createDiscoveryListener() {
|
||||
return new MdnsDiscoveryListener() {
|
||||
@Override
|
||||
public void notifyComputerAdded(MdnsComputer computer) {
|
||||
// Kick off a serverinfo poll on this machine
|
||||
addComputerBlocking(computer.getAddress());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyComputerRemoved(MdnsComputer computer) {
|
||||
// Nothing to do here
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyDiscoveryFailure(Exception e) {
|
||||
LimeLog.severe("mDNS discovery failed");
|
||||
e.printStackTrace();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void addTuple(ComputerDetails details) {
|
||||
synchronized (pollingTuples) {
|
||||
@ -278,17 +278,17 @@ public class ComputerManagerService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean addComputerBlocking(InetAddress addr) {
|
||||
// Setup a placeholder
|
||||
ComputerDetails fakeDetails = new ComputerDetails();
|
||||
fakeDetails.localIp = addr;
|
||||
fakeDetails.remoteIp = addr;
|
||||
public boolean addComputerBlocking(InetAddress addr) {
|
||||
// Setup a placeholder
|
||||
ComputerDetails fakeDetails = new ComputerDetails();
|
||||
fakeDetails.localIp = addr;
|
||||
fakeDetails.remoteIp = addr;
|
||||
|
||||
// Block while we try to fill the details
|
||||
// Block while we try to fill the details
|
||||
runPoll(fakeDetails, true);
|
||||
|
||||
// If the machine is reachable, it was successful
|
||||
if (fakeDetails.state == ComputerDetails.State.ONLINE) {
|
||||
|
||||
// If the machine is reachable, it was successful
|
||||
if (fakeDetails.state == ComputerDetails.State.ONLINE) {
|
||||
LimeLog.info("New PC ("+fakeDetails.name+") is UUID "+fakeDetails.uuid);
|
||||
|
||||
// Start a polling thread for this machine
|
||||
@ -298,15 +298,15 @@ public class ComputerManagerService extends Service {
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void removeComputer(String name) {
|
||||
if (!getLocalDatabaseReference()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove it from the database
|
||||
dbManager.deleteComputer(name);
|
||||
}
|
||||
|
||||
public void removeComputer(String name) {
|
||||
if (!getLocalDatabaseReference()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove it from the database
|
||||
dbManager.deleteComputer(name);
|
||||
|
||||
synchronized (pollingTuples) {
|
||||
// Remove the computer from the computer list
|
||||
@ -321,31 +321,31 @@ public class ComputerManagerService extends Service {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
releaseLocalDatabaseReference();
|
||||
}
|
||||
|
||||
private boolean getLocalDatabaseReference() {
|
||||
if (dbRefCount.get() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
dbRefCount.incrementAndGet();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void releaseLocalDatabaseReference() {
|
||||
if (dbRefCount.decrementAndGet() == 0) {
|
||||
dbManager.close();
|
||||
}
|
||||
}
|
||||
|
||||
private ComputerDetails tryPollIp(ComputerDetails details, InetAddress ipAddr) {
|
||||
try {
|
||||
NvHTTP http = new NvHTTP(ipAddr, idManager.getUniqueId(),
|
||||
null, PlatformBinding.getCryptoProvider(ComputerManagerService.this));
|
||||
|
||||
ComputerDetails newDetails = http.getComputerDetails();
|
||||
releaseLocalDatabaseReference();
|
||||
}
|
||||
|
||||
private boolean getLocalDatabaseReference() {
|
||||
if (dbRefCount.get() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
dbRefCount.incrementAndGet();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void releaseLocalDatabaseReference() {
|
||||
if (dbRefCount.decrementAndGet() == 0) {
|
||||
dbManager.close();
|
||||
}
|
||||
}
|
||||
|
||||
private ComputerDetails tryPollIp(ComputerDetails details, InetAddress ipAddr) {
|
||||
try {
|
||||
NvHTTP http = new NvHTTP(ipAddr, idManager.getUniqueId(),
|
||||
null, PlatformBinding.getCryptoProvider(ComputerManagerService.this));
|
||||
|
||||
ComputerDetails newDetails = http.getComputerDetails();
|
||||
|
||||
// Check if this is the PC we expected
|
||||
if (details.uuid != null && newDetails.uuid != null &&
|
||||
@ -356,58 +356,58 @@ public class ComputerManagerService extends Service {
|
||||
}
|
||||
|
||||
return newDetails;
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean pollComputer(ComputerDetails details, boolean localFirst) {
|
||||
ComputerDetails polledDetails;
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean pollComputer(ComputerDetails details, boolean localFirst) {
|
||||
ComputerDetails polledDetails;
|
||||
|
||||
// If the local address is routable across the Internet,
|
||||
// always consider this PC remote to be conservative
|
||||
if (details.localIp.equals(details.remoteIp)) {
|
||||
localFirst = false;
|
||||
}
|
||||
|
||||
if (localFirst) {
|
||||
polledDetails = tryPollIp(details, details.localIp);
|
||||
}
|
||||
else {
|
||||
polledDetails = tryPollIp(details, details.remoteIp);
|
||||
}
|
||||
|
||||
if (polledDetails == null && !details.localIp.equals(details.remoteIp)) {
|
||||
// Failed, so let's try the fallback
|
||||
if (!localFirst) {
|
||||
polledDetails = tryPollIp(details, details.localIp);
|
||||
}
|
||||
else {
|
||||
polledDetails = tryPollIp(details, details.remoteIp);
|
||||
}
|
||||
|
||||
// The fallback poll worked
|
||||
if (polledDetails != null) {
|
||||
polledDetails.reachability = !localFirst ? ComputerDetails.Reachability.LOCAL :
|
||||
ComputerDetails.Reachability.REMOTE;
|
||||
}
|
||||
}
|
||||
else if (polledDetails != null) {
|
||||
polledDetails.reachability = localFirst ? ComputerDetails.Reachability.LOCAL :
|
||||
ComputerDetails.Reachability.REMOTE;
|
||||
}
|
||||
|
||||
// Machine was unreachable both tries
|
||||
if (polledDetails == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we got here, it's reachable
|
||||
details.update(polledDetails);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean doPollMachine(ComputerDetails details) {
|
||||
|
||||
if (localFirst) {
|
||||
polledDetails = tryPollIp(details, details.localIp);
|
||||
}
|
||||
else {
|
||||
polledDetails = tryPollIp(details, details.remoteIp);
|
||||
}
|
||||
|
||||
if (polledDetails == null && !details.localIp.equals(details.remoteIp)) {
|
||||
// Failed, so let's try the fallback
|
||||
if (!localFirst) {
|
||||
polledDetails = tryPollIp(details, details.localIp);
|
||||
}
|
||||
else {
|
||||
polledDetails = tryPollIp(details, details.remoteIp);
|
||||
}
|
||||
|
||||
// The fallback poll worked
|
||||
if (polledDetails != null) {
|
||||
polledDetails.reachability = !localFirst ? ComputerDetails.Reachability.LOCAL :
|
||||
ComputerDetails.Reachability.REMOTE;
|
||||
}
|
||||
}
|
||||
else if (polledDetails != null) {
|
||||
polledDetails.reachability = localFirst ? ComputerDetails.Reachability.LOCAL :
|
||||
ComputerDetails.Reachability.REMOTE;
|
||||
}
|
||||
|
||||
// Machine was unreachable both tries
|
||||
if (polledDetails == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we got here, it's reachable
|
||||
details.update(polledDetails);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean doPollMachine(ComputerDetails details) {
|
||||
if (details.reachability == ComputerDetails.Reachability.UNKNOWN ||
|
||||
details.reachability == ComputerDetails.Reachability.OFFLINE) {
|
||||
// Always try local first to avoid potential UDP issues when
|
||||
@ -420,20 +420,20 @@ public class ComputerManagerService extends Service {
|
||||
// always try that one first
|
||||
return pollComputer(details, details.reachability == ComputerDetails.Reachability.LOCAL);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
// Bind to the discovery service
|
||||
bindService(new Intent(this, DiscoveryService.class),
|
||||
discoveryServiceConnection, Service.BIND_AUTO_CREATE);
|
||||
|
||||
// Lookup or generate this device's UID
|
||||
idManager = new IdentityManager(this);
|
||||
|
||||
// Initialize the DB
|
||||
dbManager = new ComputerDatabaseManager(this);
|
||||
dbRefCount.set(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
// Bind to the discovery service
|
||||
bindService(new Intent(this, DiscoveryService.class),
|
||||
discoveryServiceConnection, Service.BIND_AUTO_CREATE);
|
||||
|
||||
// Lookup or generate this device's UID
|
||||
idManager = new IdentityManager(this);
|
||||
|
||||
// Initialize the DB
|
||||
dbManager = new ComputerDatabaseManager(this);
|
||||
dbRefCount.set(1);
|
||||
|
||||
// Grab known machines into our computer list
|
||||
if (!getLocalDatabaseReference()) {
|
||||
@ -446,25 +446,25 @@ public class ComputerManagerService extends Service {
|
||||
}
|
||||
|
||||
releaseLocalDatabaseReference();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
if (discoveryBinder != null) {
|
||||
// Unbind from the discovery service
|
||||
unbindService(discoveryServiceConnection);
|
||||
}
|
||||
|
||||
// FIXME: Should await termination here but we have timeout issues in HttpURLConnection
|
||||
|
||||
// Remove the initial DB reference
|
||||
releaseLocalDatabaseReference();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return binder;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
if (discoveryBinder != null) {
|
||||
// Unbind from the discovery service
|
||||
unbindService(discoveryServiceConnection);
|
||||
}
|
||||
|
||||
// FIXME: Should await termination here but we have timeout issues in HttpURLConnection
|
||||
|
||||
// Remove the initial DB reference
|
||||
releaseLocalDatabaseReference();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return binder;
|
||||
}
|
||||
|
||||
public class ApplistPoller {
|
||||
private Thread thread;
|
||||
|
@ -12,75 +12,75 @@ import com.limelight.LimeLog;
|
||||
import android.content.Context;
|
||||
|
||||
public class IdentityManager {
|
||||
private static final String UNIQUE_ID_FILE_NAME = "uniqueid";
|
||||
private static final int UID_SIZE_IN_BYTES = 8;
|
||||
|
||||
private String uniqueId;
|
||||
|
||||
public IdentityManager(Context c) {
|
||||
uniqueId = loadUniqueId(c);
|
||||
if (uniqueId == null) {
|
||||
uniqueId = generateNewUniqueId(c);
|
||||
}
|
||||
|
||||
LimeLog.info("UID is now: "+uniqueId);
|
||||
}
|
||||
|
||||
public String getUniqueId() {
|
||||
return uniqueId;
|
||||
}
|
||||
|
||||
private static String loadUniqueId(Context c) {
|
||||
// 2 Hex digits per byte
|
||||
char[] uid = new char[UID_SIZE_IN_BYTES * 2];
|
||||
InputStreamReader reader = null;
|
||||
LimeLog.info("Reading UID from disk");
|
||||
try {
|
||||
reader = new InputStreamReader(c.openFileInput(UNIQUE_ID_FILE_NAME));
|
||||
if (reader.read(uid) != UID_SIZE_IN_BYTES * 2)
|
||||
{
|
||||
LimeLog.severe("UID file data is truncated");
|
||||
return null;
|
||||
}
|
||||
return new String(uid);
|
||||
} catch (FileNotFoundException e) {
|
||||
LimeLog.info("No UID file found");
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
LimeLog.severe("Error while reading UID file");
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
} finally {
|
||||
if (reader != null) {
|
||||
try {
|
||||
reader.close();
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String generateNewUniqueId(Context c) {
|
||||
// Generate a new UID hex string
|
||||
LimeLog.info("Generating new UID");
|
||||
String uidStr = String.format((Locale)null, "%016x", new Random().nextLong());
|
||||
|
||||
OutputStreamWriter writer = null;
|
||||
try {
|
||||
writer = new OutputStreamWriter(c.openFileOutput(UNIQUE_ID_FILE_NAME, 0));
|
||||
writer.write(uidStr);
|
||||
LimeLog.info("UID written to disk");
|
||||
} catch (IOException e) {
|
||||
LimeLog.severe("Error while writing UID file");
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (writer != null) {
|
||||
try {
|
||||
writer.close();
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
// We can return a UID even if I/O fails
|
||||
return uidStr;
|
||||
}
|
||||
private static final String UNIQUE_ID_FILE_NAME = "uniqueid";
|
||||
private static final int UID_SIZE_IN_BYTES = 8;
|
||||
|
||||
private String uniqueId;
|
||||
|
||||
public IdentityManager(Context c) {
|
||||
uniqueId = loadUniqueId(c);
|
||||
if (uniqueId == null) {
|
||||
uniqueId = generateNewUniqueId(c);
|
||||
}
|
||||
|
||||
LimeLog.info("UID is now: "+uniqueId);
|
||||
}
|
||||
|
||||
public String getUniqueId() {
|
||||
return uniqueId;
|
||||
}
|
||||
|
||||
private static String loadUniqueId(Context c) {
|
||||
// 2 Hex digits per byte
|
||||
char[] uid = new char[UID_SIZE_IN_BYTES * 2];
|
||||
InputStreamReader reader = null;
|
||||
LimeLog.info("Reading UID from disk");
|
||||
try {
|
||||
reader = new InputStreamReader(c.openFileInput(UNIQUE_ID_FILE_NAME));
|
||||
if (reader.read(uid) != UID_SIZE_IN_BYTES * 2)
|
||||
{
|
||||
LimeLog.severe("UID file data is truncated");
|
||||
return null;
|
||||
}
|
||||
return new String(uid);
|
||||
} catch (FileNotFoundException e) {
|
||||
LimeLog.info("No UID file found");
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
LimeLog.severe("Error while reading UID file");
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
} finally {
|
||||
if (reader != null) {
|
||||
try {
|
||||
reader.close();
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String generateNewUniqueId(Context c) {
|
||||
// Generate a new UID hex string
|
||||
LimeLog.info("Generating new UID");
|
||||
String uidStr = String.format((Locale)null, "%016x", new Random().nextLong());
|
||||
|
||||
OutputStreamWriter writer = null;
|
||||
try {
|
||||
writer = new OutputStreamWriter(c.openFileOutput(UNIQUE_ID_FILE_NAME, 0));
|
||||
writer.write(uidStr);
|
||||
LimeLog.info("UID written to disk");
|
||||
} catch (IOException e) {
|
||||
LimeLog.severe("Error while writing UID file");
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (writer != null) {
|
||||
try {
|
||||
writer.close();
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
// We can return a UID even if I/O fails
|
||||
return uidStr;
|
||||
}
|
||||
}
|
||||
|
@ -15,76 +15,76 @@ import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
|
||||
public class DiscoveryService extends Service {
|
||||
|
||||
private MdnsDiscoveryAgent discoveryAgent;
|
||||
private MdnsDiscoveryListener boundListener;
|
||||
private MulticastLock multicastLock;
|
||||
|
||||
public class DiscoveryBinder extends Binder {
|
||||
public void setListener(MdnsDiscoveryListener listener) {
|
||||
boundListener = listener;
|
||||
}
|
||||
|
||||
public void startDiscovery(int queryIntervalMs) {
|
||||
multicastLock.acquire();
|
||||
discoveryAgent.startDiscovery(queryIntervalMs);
|
||||
}
|
||||
|
||||
public void stopDiscovery() {
|
||||
discoveryAgent.stopDiscovery();
|
||||
multicastLock.release();
|
||||
}
|
||||
|
||||
public List<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
|
||||
public void notifyComputerRemoved(MdnsComputer computer) {
|
||||
if (boundListener != null) {
|
||||
boundListener.notifyComputerRemoved(computer);
|
||||
}
|
||||
}
|
||||
private MdnsDiscoveryAgent discoveryAgent;
|
||||
private MdnsDiscoveryListener boundListener;
|
||||
private MulticastLock multicastLock;
|
||||
|
||||
@Override
|
||||
public void notifyDiscoveryFailure(Exception e) {
|
||||
if (boundListener != null) {
|
||||
boundListener.notifyDiscoveryFailure(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private final DiscoveryBinder binder = new DiscoveryBinder();
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return binder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onUnbind(Intent intent) {
|
||||
// Stop any discovery session
|
||||
discoveryAgent.stopDiscovery();
|
||||
multicastLock.release();
|
||||
|
||||
// Unbind the listener
|
||||
boundListener = null;
|
||||
return false;
|
||||
}
|
||||
public class DiscoveryBinder extends Binder {
|
||||
public void setListener(MdnsDiscoveryListener listener) {
|
||||
boundListener = listener;
|
||||
}
|
||||
|
||||
public void startDiscovery(int queryIntervalMs) {
|
||||
multicastLock.acquire();
|
||||
discoveryAgent.startDiscovery(queryIntervalMs);
|
||||
}
|
||||
|
||||
public void stopDiscovery() {
|
||||
discoveryAgent.stopDiscovery();
|
||||
multicastLock.release();
|
||||
}
|
||||
|
||||
public List<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
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -1,44 +1,44 @@
|
||||
package com.limelight.nvstream.av.video.cpu;
|
||||
|
||||
public class AvcDecoder {
|
||||
static {
|
||||
// FFMPEG dependencies
|
||||
System.loadLibrary("avutil-52");
|
||||
System.loadLibrary("swresample-0");
|
||||
System.loadLibrary("swscale-2");
|
||||
System.loadLibrary("avcodec-55");
|
||||
System.loadLibrary("avformat-55");
|
||||
|
||||
System.loadLibrary("nv_avc_dec");
|
||||
}
|
||||
|
||||
/** Disables the deblocking filter at the cost of image quality */
|
||||
public static final int DISABLE_LOOP_FILTER = 0x1;
|
||||
/** Uses the low latency decode flag (disables multithreading) */
|
||||
public static final int LOW_LATENCY_DECODE = 0x2;
|
||||
/** Threads process each slice, rather than each frame */
|
||||
public static final int SLICE_THREADING = 0x4;
|
||||
/** Uses nonstandard speedup tricks */
|
||||
public static final int FAST_DECODE = 0x8;
|
||||
/** Uses bilinear filtering instead of bicubic */
|
||||
public static final int BILINEAR_FILTERING = 0x10;
|
||||
/** Uses a faster bilinear filtering with lower image quality */
|
||||
public static final int FAST_BILINEAR_FILTERING = 0x20;
|
||||
/** Disables color conversion (output is NV21) */
|
||||
public static final int NO_COLOR_CONVERSION = 0x40;
|
||||
|
||||
public static native int init(int width, int height, int perflvl, int threadcount);
|
||||
public static native void destroy();
|
||||
|
||||
// Rendering API when NO_COLOR_CONVERSION == 0
|
||||
public static native boolean setRenderTarget(Object androidSurface);
|
||||
public static native boolean getRgbFrameInt(int[] rgbFrame, int bufferSize);
|
||||
public static native boolean getRgbFrame(byte[] rgbFrame, int bufferSize);
|
||||
public static native boolean redraw();
|
||||
static {
|
||||
// FFMPEG dependencies
|
||||
System.loadLibrary("avutil-52");
|
||||
System.loadLibrary("swresample-0");
|
||||
System.loadLibrary("swscale-2");
|
||||
System.loadLibrary("avcodec-55");
|
||||
System.loadLibrary("avformat-55");
|
||||
|
||||
// Rendering API when NO_COLOR_CONVERSION == 1
|
||||
public static native boolean getRawFrame(byte[] yuvFrame, int bufferSize);
|
||||
|
||||
public static native int getInputPaddingSize();
|
||||
public static native int decode(byte[] indata, int inoff, int inlen);
|
||||
System.loadLibrary("nv_avc_dec");
|
||||
}
|
||||
|
||||
/** Disables the deblocking filter at the cost of image quality */
|
||||
public static final int DISABLE_LOOP_FILTER = 0x1;
|
||||
/** Uses the low latency decode flag (disables multithreading) */
|
||||
public static final int LOW_LATENCY_DECODE = 0x2;
|
||||
/** Threads process each slice, rather than each frame */
|
||||
public static final int SLICE_THREADING = 0x4;
|
||||
/** Uses nonstandard speedup tricks */
|
||||
public static final int FAST_DECODE = 0x8;
|
||||
/** Uses bilinear filtering instead of bicubic */
|
||||
public static final int BILINEAR_FILTERING = 0x10;
|
||||
/** Uses a faster bilinear filtering with lower image quality */
|
||||
public static final int FAST_BILINEAR_FILTERING = 0x20;
|
||||
/** Disables color conversion (output is NV21) */
|
||||
public static final int NO_COLOR_CONVERSION = 0x40;
|
||||
|
||||
public static native int init(int width, int height, int perflvl, int threadcount);
|
||||
public static native void destroy();
|
||||
|
||||
// Rendering API when NO_COLOR_CONVERSION == 0
|
||||
public static native boolean setRenderTarget(Object androidSurface);
|
||||
public static native boolean getRgbFrameInt(int[] rgbFrame, int bufferSize);
|
||||
public static native boolean getRgbFrame(byte[] rgbFrame, int bufferSize);
|
||||
public static native boolean redraw();
|
||||
|
||||
// Rendering API when NO_COLOR_CONVERSION == 1
|
||||
public static native boolean getRawFrame(byte[] yuvFrame, int bufferSize);
|
||||
|
||||
public static native int getInputPaddingSize();
|
||||
public static native int decode(byte[] indata, int inoff, int inlen);
|
||||
}
|
||||
|
@ -25,127 +25,127 @@ import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class AddComputerManually extends Activity {
|
||||
private TextView hostText;
|
||||
private ComputerManagerService.ComputerManagerBinder managerBinder;
|
||||
private final LinkedBlockingQueue<String> computersToAdd = new LinkedBlockingQueue<String>();
|
||||
private Thread addThread;
|
||||
private final ServiceConnection serviceConnection = new ServiceConnection() {
|
||||
public void onServiceConnected(ComponentName className, final IBinder binder) {
|
||||
managerBinder = ((ComputerManagerService.ComputerManagerBinder)binder);
|
||||
startAddThread();
|
||||
}
|
||||
private TextView hostText;
|
||||
private ComputerManagerService.ComputerManagerBinder managerBinder;
|
||||
private final LinkedBlockingQueue<String> computersToAdd = new LinkedBlockingQueue<String>();
|
||||
private Thread addThread;
|
||||
private final ServiceConnection serviceConnection = new ServiceConnection() {
|
||||
public void onServiceConnected(ComponentName className, final IBinder binder) {
|
||||
managerBinder = ((ComputerManagerService.ComputerManagerBinder)binder);
|
||||
startAddThread();
|
||||
}
|
||||
|
||||
public void onServiceDisconnected(ComponentName className) {
|
||||
joinAddThread();
|
||||
managerBinder = null;
|
||||
}
|
||||
};
|
||||
|
||||
private void doAddPc(String host) {
|
||||
String msg;
|
||||
boolean finish = false;
|
||||
public void onServiceDisconnected(ComponentName className) {
|
||||
joinAddThread();
|
||||
managerBinder = null;
|
||||
}
|
||||
};
|
||||
|
||||
private void doAddPc(String host) {
|
||||
String msg;
|
||||
boolean finish = false;
|
||||
|
||||
SpinnerDialog dialog = SpinnerDialog.displayDialog(this, getResources().getString(R.string.title_add_pc),
|
||||
getResources().getString(R.string.msg_add_pc), false);
|
||||
getResources().getString(R.string.msg_add_pc), false);
|
||||
|
||||
try {
|
||||
InetAddress addr = InetAddress.getByName(host);
|
||||
|
||||
if (!managerBinder.addComputerBlocking(addr)){
|
||||
msg = getResources().getString(R.string.addpc_fail);
|
||||
}
|
||||
else {
|
||||
msg = getResources().getString(R.string.addpc_success);
|
||||
finish = true;
|
||||
}
|
||||
} catch (UnknownHostException e) {
|
||||
msg = getResources().getString(R.string.addpc_unknown_host);
|
||||
}
|
||||
try {
|
||||
InetAddress addr = InetAddress.getByName(host);
|
||||
|
||||
if (!managerBinder.addComputerBlocking(addr)){
|
||||
msg = getResources().getString(R.string.addpc_fail);
|
||||
}
|
||||
else {
|
||||
msg = getResources().getString(R.string.addpc_success);
|
||||
finish = true;
|
||||
}
|
||||
} catch (UnknownHostException e) {
|
||||
msg = getResources().getString(R.string.addpc_unknown_host);
|
||||
}
|
||||
|
||||
dialog.dismiss();
|
||||
|
||||
final boolean toastFinish = finish;
|
||||
final String toastMsg = msg;
|
||||
AddComputerManually.this.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Toast.makeText(AddComputerManually.this, toastMsg, Toast.LENGTH_LONG).show();
|
||||
|
||||
if (toastFinish && !isFinishing()) {
|
||||
// Close the activity
|
||||
AddComputerManually.this.finish();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void startAddThread() {
|
||||
addThread = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
while (!isInterrupted()) {
|
||||
String computer;
|
||||
|
||||
try {
|
||||
computer = computersToAdd.take();
|
||||
} catch (InterruptedException e) {
|
||||
return;
|
||||
}
|
||||
|
||||
doAddPc(computer);
|
||||
}
|
||||
}
|
||||
};
|
||||
addThread.setName("UI - AddComputerManually");
|
||||
addThread.start();
|
||||
}
|
||||
|
||||
private void joinAddThread() {
|
||||
if (addThread != null) {
|
||||
addThread.interrupt();
|
||||
|
||||
try {
|
||||
addThread.join();
|
||||
} catch (InterruptedException ignored) {}
|
||||
|
||||
addThread = null;
|
||||
}
|
||||
}
|
||||
final String toastMsg = msg;
|
||||
AddComputerManually.this.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Toast.makeText(AddComputerManually.this, toastMsg, Toast.LENGTH_LONG).show();
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
|
||||
Dialog.closeDialogs();
|
||||
if (toastFinish && !isFinishing()) {
|
||||
// Close the activity
|
||||
AddComputerManually.this.finish();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void startAddThread() {
|
||||
addThread = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
while (!isInterrupted()) {
|
||||
String computer;
|
||||
|
||||
try {
|
||||
computer = computersToAdd.take();
|
||||
} catch (InterruptedException e) {
|
||||
return;
|
||||
}
|
||||
|
||||
doAddPc(computer);
|
||||
}
|
||||
}
|
||||
};
|
||||
addThread.setName("UI - AddComputerManually");
|
||||
addThread.start();
|
||||
}
|
||||
|
||||
private void joinAddThread() {
|
||||
if (addThread != null) {
|
||||
addThread.interrupt();
|
||||
|
||||
try {
|
||||
addThread.join();
|
||||
} catch (InterruptedException ignored) {}
|
||||
|
||||
addThread = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
|
||||
Dialog.closeDialogs();
|
||||
SpinnerDialog.closeDialogs(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
if (managerBinder != null) {
|
||||
joinAddThread();
|
||||
unbindService(serviceConnection);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
String locale = PreferenceConfiguration.readPreferences(this).language;
|
||||
if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
|
||||
Configuration config = new Configuration(getResources().getConfiguration());
|
||||
config.locale = new Locale(locale);
|
||||
getResources().updateConfiguration(config, getResources().getDisplayMetrics());
|
||||
}
|
||||
|
||||
setContentView(R.layout.activity_add_computer_manually);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
if (managerBinder != null) {
|
||||
joinAddThread();
|
||||
unbindService(serviceConnection);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
String locale = PreferenceConfiguration.readPreferences(this).language;
|
||||
if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
|
||||
Configuration config = new Configuration(getResources().getConfiguration());
|
||||
config.locale = new Locale(locale);
|
||||
getResources().updateConfiguration(config, getResources().getDisplayMetrics());
|
||||
}
|
||||
|
||||
setContentView(R.layout.activity_add_computer_manually);
|
||||
|
||||
UiHelper.notifyNewRootView(this);
|
||||
|
||||
this.hostText = (TextView) findViewById(R.id.hostTextView);
|
||||
this.hostText = (TextView) findViewById(R.id.hostTextView);
|
||||
hostText.setImeOptions(EditorInfo.IME_ACTION_DONE);
|
||||
hostText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||
@Override
|
||||
@ -165,9 +165,9 @@ public class AddComputerManually extends Activity {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// Bind to the ComputerManager service
|
||||
bindService(new Intent(AddComputerManually.this,
|
||||
ComputerManagerService.class), serviceConnection, Service.BIND_AUTO_CREATE);
|
||||
}
|
||||
|
||||
// Bind to the ComputerManager service
|
||||
bindService(new Intent(AddComputerManually.this,
|
||||
ComputerManagerService.class), serviceConnection, Service.BIND_AUTO_CREATE);
|
||||
}
|
||||
}
|
||||
|
@ -7,71 +7,71 @@ import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
|
||||
public class Dialog implements Runnable {
|
||||
private final String title;
|
||||
private final String title;
|
||||
private final String message;
|
||||
private final Activity activity;
|
||||
private final boolean endAfterDismiss;
|
||||
|
||||
private AlertDialog alert;
|
||||
|
||||
private static final ArrayList<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();
|
||||
private final Activity activity;
|
||||
private final boolean endAfterDismiss;
|
||||
|
||||
alert.setTitle(title);
|
||||
alert.setMessage(message);
|
||||
alert.setCancelable(false);
|
||||
alert.setCanceledOnTouchOutside(false);
|
||||
private AlertDialog alert;
|
||||
|
||||
private static final ArrayList<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);
|
||||
alert.setMessage(message);
|
||||
alert.setCancelable(false);
|
||||
alert.setCanceledOnTouchOutside(false);
|
||||
|
||||
alert.setButton(AlertDialog.BUTTON_NEUTRAL, "OK", new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
synchronized (rundownDialogs) {
|
||||
rundownDialogs.remove(Dialog.this);
|
||||
alert.dismiss();
|
||||
}
|
||||
|
||||
if (endAfterDismiss) {
|
||||
activity.finish();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
synchronized (rundownDialogs) {
|
||||
rundownDialogs.add(this);
|
||||
alert.show();
|
||||
}
|
||||
}
|
||||
alert.setButton(AlertDialog.BUTTON_NEUTRAL, "OK", new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
synchronized (rundownDialogs) {
|
||||
rundownDialogs.remove(Dialog.this);
|
||||
alert.dismiss();
|
||||
}
|
||||
|
||||
if (endAfterDismiss) {
|
||||
activity.finish();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
synchronized (rundownDialogs) {
|
||||
rundownDialogs.add(this);
|
||||
alert.show();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -9,112 +9,112 @@ import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnCancelListener;
|
||||
|
||||
public class SpinnerDialog implements Runnable,OnCancelListener {
|
||||
private final String title;
|
||||
private final String title;
|
||||
private final String message;
|
||||
private final Activity activity;
|
||||
private ProgressDialog progress;
|
||||
private final boolean finish;
|
||||
|
||||
private static final ArrayList<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() {
|
||||
private final Activity activity;
|
||||
private ProgressDialog progress;
|
||||
private final boolean finish;
|
||||
|
||||
// If we're dying, don't bother doing anything
|
||||
if (activity.isFinishing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (progress == null)
|
||||
{
|
||||
progress = new ProgressDialog(activity);
|
||||
|
||||
progress.setTitle(title);
|
||||
progress.setMessage(message);
|
||||
progress.setProgressStyle(ProgressDialog.STYLE_SPINNER);
|
||||
progress.setOnCancelListener(this);
|
||||
|
||||
// If we want to finish the activity when this is killed, make it cancellable
|
||||
if (finish)
|
||||
{
|
||||
progress.setCancelable(true);
|
||||
progress.setCanceledOnTouchOutside(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
progress.setCancelable(false);
|
||||
}
|
||||
|
||||
synchronized (rundownDialogs) {
|
||||
rundownDialogs.add(this);
|
||||
progress.show();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
synchronized (rundownDialogs) {
|
||||
if (rundownDialogs.remove(this) && progress.isShowing()) {
|
||||
progress.dismiss();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private static final ArrayList<SpinnerDialog> rundownDialogs = new ArrayList<SpinnerDialog>();
|
||||
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
synchronized (rundownDialogs) {
|
||||
rundownDialogs.remove(this);
|
||||
}
|
||||
|
||||
// This will only be called if finish was true, so we don't need to check again
|
||||
activity.finish();
|
||||
}
|
||||
private SpinnerDialog(Activity activity, String title, String message, boolean finish)
|
||||
{
|
||||
this.activity = activity;
|
||||
this.title = title;
|
||||
this.message = message;
|
||||
this.progress = null;
|
||||
this.finish = finish;
|
||||
}
|
||||
|
||||
public static SpinnerDialog displayDialog(Activity activity, String title, String message, boolean finish)
|
||||
{
|
||||
SpinnerDialog spinner = new SpinnerDialog(activity, title, message, finish);
|
||||
activity.runOnUiThread(spinner);
|
||||
return spinner;
|
||||
}
|
||||
|
||||
public static void closeDialogs(Activity activity)
|
||||
{
|
||||
synchronized (rundownDialogs) {
|
||||
Iterator<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();
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user