fix analogstick, add minimum range and press deadzone, add movement touch to digital buttons depending on layers

This commit is contained in:
Karim Mreisi
2015-02-03 21:51:27 +01:00
26 changed files with 1069 additions and 481 deletions
+33 -25
View File
@@ -8,10 +8,12 @@
</facet> </facet>
<facet type="android" name="Android"> <facet type="android" name="Android">
<configuration> <configuration>
<option name="SELECTED_BUILD_VARIANT" value="nonRootRelease" /> <option name="SELECTED_BUILD_VARIANT" value="nonRootDebug" />
<option name="ASSEMBLE_TASK_NAME" value="assembleNonRootRelease" /> <option name="ASSEMBLE_TASK_NAME" value="assembleNonRootDebug" />
<option name="COMPILE_JAVA_TASK_NAME" value="compileNonRootReleaseSources" /> <option name="COMPILE_JAVA_TASK_NAME" value="compileNonRootDebugSources" />
<option name="SOURCE_GEN_TASK_NAME" value="generateNonRootReleaseSources" /> <option name="ASSEMBLE_TEST_TASK_NAME" value="assembleNonRootDebugTest" />
<option name="SOURCE_GEN_TASK_NAME" value="generateNonRootDebugSources" />
<option name="TEST_SOURCE_GEN_TASK_NAME" value="generateNonRootDebugTestSources" />
<option name="ALLOW_USER_CONFIGURATION" value="false" /> <option name="ALLOW_USER_CONFIGURATION" value="false" />
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" /> <option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" /> <option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
@@ -21,22 +23,28 @@
</facet> </facet>
</component> </component>
<component name="NewModuleRootManager" inherit-compiler-output="false"> <component name="NewModuleRootManager" inherit-compiler-output="false">
<output url="file://$MODULE_DIR$/build/intermediates/classes/nonRoot/release" /> <output url="file://$MODULE_DIR$/build/intermediates/classes/nonRoot/debug" />
<exclude-output /> <exclude-output />
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/nonRoot/release" isTestSource="false" generated="true" /> <sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/nonRoot/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/nonRoot/release" isTestSource="false" generated="true" /> <sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/nonRoot/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/nonRoot/release" isTestSource="false" generated="true" /> <sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/nonRoot/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/nonRoot/release" isTestSource="false" generated="true" /> <sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/nonRoot/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/nonRoot/release" type="java-resource" /> <sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/nonRoot/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/generated/nonRoot/release" type="java-resource" /> <sourceFolder url="file://$MODULE_DIR$/build/generated/res/generated/nonRoot/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/nonRootRelease/res" type="java-resource" /> <sourceFolder url="file://$MODULE_DIR$/src/nonRootDebug/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/nonRootRelease/resources" type="java-resource" /> <sourceFolder url="file://$MODULE_DIR$/src/nonRootDebug/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/nonRootRelease/assets" type="java-resource" /> <sourceFolder url="file://$MODULE_DIR$/src/nonRootDebug/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/nonRootRelease/aidl" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/src/nonRootDebug/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/nonRootRelease/java" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/src/nonRootDebug/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/nonRootRelease/jni" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/src/nonRootDebug/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/nonRootRelease/rs" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/src/nonRootDebug/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/test/nonRoot/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/test/nonRoot/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/test/nonRoot/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/test/nonRoot/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/test/nonRoot/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/generated/test/nonRoot/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/nonRoot/res" type="java-resource" /> <sourceFolder url="file://$MODULE_DIR$/src/nonRoot/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/nonRoot/resources" type="java-resource" /> <sourceFolder url="file://$MODULE_DIR$/src/nonRoot/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/nonRoot/assets" type="java-resource" /> <sourceFolder url="file://$MODULE_DIR$/src/nonRoot/assets" type="java-resource" />
@@ -51,13 +59,13 @@
<sourceFolder url="file://$MODULE_DIR$/src/androidTestNonRoot/java" isTestSource="true" /> <sourceFolder url="file://$MODULE_DIR$/src/androidTestNonRoot/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestNonRoot/jni" isTestSource="true" /> <sourceFolder url="file://$MODULE_DIR$/src/androidTestNonRoot/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestNonRoot/rs" isTestSource="true" /> <sourceFolder url="file://$MODULE_DIR$/src/androidTestNonRoot/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/release/res" type="java-resource" /> <sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/release/resources" type="java-resource" /> <sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/release/assets" type="java-resource" /> <sourceFolder url="file://$MODULE_DIR$/src/debug/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/release/aidl" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/release/java" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/release/jni" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/release/rs" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" /> <sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" /> <sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" /> <sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" />
Binary file not shown.
+353 -162
View File
@@ -1,32 +1,45 @@
package com.limelight; package com.limelight;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.UUID;
import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserException;
import com.limelight.binding.PlatformBinding; import com.limelight.binding.PlatformBinding;
import com.limelight.binding.crypto.AndroidCryptoProvider;
import com.limelight.computers.ComputerManagerListener;
import com.limelight.computers.ComputerManagerService;
import com.limelight.grid.AppGridAdapter; import com.limelight.grid.AppGridAdapter;
import com.limelight.nvstream.http.ComputerDetails;
import com.limelight.nvstream.http.GfeHttpResponseException; import com.limelight.nvstream.http.GfeHttpResponseException;
import com.limelight.nvstream.http.NvApp; import com.limelight.nvstream.http.NvApp;
import com.limelight.nvstream.http.NvHTTP; import com.limelight.nvstream.http.NvHTTP;
import com.limelight.preferences.PreferenceConfiguration; import com.limelight.preferences.PreferenceConfiguration;
import com.limelight.ui.AdapterFragment; import com.limelight.ui.AdapterFragment;
import com.limelight.ui.AdapterFragmentCallbacks; import com.limelight.ui.AdapterFragmentCallbacks;
import com.limelight.utils.CacheHelper;
import com.limelight.utils.Dialog; import com.limelight.utils.Dialog;
import com.limelight.utils.SpinnerDialog; import com.limelight.utils.SpinnerDialog;
import com.limelight.utils.UiHelper; import com.limelight.utils.UiHelper;
import android.app.Activity; import android.app.Activity;
import android.app.FragmentManager; import android.app.AlertDialog;
import android.app.Service;
import android.content.ComponentName;
import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.ServiceConnection;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder;
import android.view.ContextMenu; import android.view.ContextMenu;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
@@ -35,33 +48,155 @@ import android.view.ContextMenu.ContextMenuInfo;
import android.widget.AbsListView; import android.widget.AbsListView;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemClickListener;
import android.widget.GridView; import android.widget.Spinner;
import android.widget.ListView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.AdapterView.AdapterContextMenuInfo;
public class AppView extends Activity implements AdapterFragmentCallbacks { public class AppView extends Activity implements AdapterFragmentCallbacks {
private AppGridAdapter appGridAdapter; private AppGridAdapter appGridAdapter;
private InetAddress ipAddress; private String uuidString;
private String uniqueId;
private boolean remote;
private boolean firstLoad = true;
private final static int RESUME_ID = 1; private ComputerDetails computer;
private ComputerManagerService.ApplistPoller poller;
private SpinnerDialog blockingLoadSpinner;
private String lastRawApplist;
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 QUIT_ID = 2;
private final static int CANCEL_ID = 3; private final static int CANCEL_ID = 3;
private final static int START_WTIH_QUIT = 4;
public final static String ADDRESS_EXTRA = "Address";
public final static String UNIQUEID_EXTRA = "UniqueId";
public final static String NAME_EXTRA = "Name"; public final static String NAME_EXTRA = "Name";
public final static String REMOTE_EXTRA = "Remote"; public final static String UUID_EXTRA = "UUID";
private ComputerManagerService.ComputerManagerBinder managerBinder;
private 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;
// Get the computer object
computer = managerBinder.getComputer(UUID.fromString(uuidString));
try {
appGridAdapter = new AppGridAdapter(AppView.this,
PreferenceConfiguration.readPreferences(AppView.this).listMode,
PreferenceConfiguration.readPreferences(AppView.this).smallIconMode,
computer, managerBinder.getUniqueId());
} catch (Exception e) {
e.printStackTrace();
finish();
return;
}
// Start updates
startComputerUpdates();
// Load the app grid with cached data (if possible)
populateAppGridWithCache();
getFragmentManager().beginTransaction()
.replace(R.id.appFragmentContainer, new AdapterFragment())
.commitAllowingStateLoss();
}
}.start();
}
public void onServiceDisconnected(ComponentName className) {
managerBinder = null;
}
};
private InetAddress getAddress() {
return computer.reachability == ComputerDetails.Reachability.LOCAL ?
computer.localIp : computer.remoteIp;
}
private void startComputerUpdates() {
if (managerBinder == null) {
return;
}
managerBinder.startPolling(new ComputerManagerListener() {
@Override
public void notifyComputerUpdated(ComputerDetails details) {
// Don't care about other computers
if (details != computer) {
return;
}
if (details.state != ComputerDetails.State.ONLINE) {
consecutiveAppListFailures++;
if (consecutiveAppListFailures >= CONSECUTIVE_FAILURE_LIMIT) {
// The PC is unreachable now
AppView.this.runOnUiThread(new Runnable() {
@Override
public void run() {
// Display a toast to the user and quit the activity
Toast.makeText(AppView.this, getResources().getText(R.string.lost_connection), Toast.LENGTH_SHORT).show();
finish();
}
});
}
return;
}
consecutiveAppListFailures = 0;
// App list is the same or empty; nothing to do
if (details.rawAppList == null || details.rawAppList.equals(lastRawApplist)) {
return;
}
try {
lastRawApplist = details.rawAppList;
updateUiWithAppList(NvHTTP.getAppListByReader(new StringReader(details.rawAppList)));
if (blockingLoadSpinner != null) {
blockingLoadSpinner.dismiss();
blockingLoadSpinner = null;
}
} catch (Exception e) {}
}
});
if (poller == null) {
poller = managerBinder.createAppListPoller(computer);
}
poller.start();
}
private void stopComputerUpdates() {
if (poller != null) {
poller.stop();
}
if (managerBinder != null) {
managerBinder.stopPolling();
}
}
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
String locale = PreferenceConfiguration.readPreferences(this).language; String locale = PreferenceConfiguration.readPreferences(this).language;
if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) { if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
Configuration config = new Configuration(getResources().getConfiguration()); Configuration config = new Configuration(getResources().getConfiguration());
config.locale = new Locale(locale); config.locale = new Locale(locale);
@@ -70,41 +205,41 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
setContentView(R.layout.activity_app_view); setContentView(R.layout.activity_app_view);
UiHelper.notifyNewRootView(this); UiHelper.notifyNewRootView(this);
byte[] address = getIntent().getByteArrayExtra(ADDRESS_EXTRA); uuidString = getIntent().getStringExtra(UUID_EXTRA);
uniqueId = getIntent().getStringExtra(UNIQUEID_EXTRA);
remote = getIntent().getBooleanExtra(REMOTE_EXTRA, false);
if (address == null || uniqueId == null) {
finish();
return;
}
String labelText = getResources().getString(R.string.title_applist)+" "+getIntent().getStringExtra(NAME_EXTRA); String labelText = getResources().getString(R.string.title_applist)+" "+getIntent().getStringExtra(NAME_EXTRA);
TextView label = (TextView) findViewById(R.id.appListText); TextView label = (TextView) findViewById(R.id.appListText);
setTitle(labelText); setTitle(labelText);
label.setText(labelText); label.setText(labelText);
// Bind to the computer manager service
bindService(new Intent(this, ComputerManagerService.class), serviceConnection,
Service.BIND_AUTO_CREATE);
}
private void populateAppGridWithCache() {
try { try {
ipAddress = InetAddress.getByAddress(address); // Try to load from cache
} catch (UnknownHostException e) { lastRawApplist = CacheHelper.readInputStreamToString(CacheHelper.openCacheFileForInput(getCacheDir(), "applist", uuidString));
e.printStackTrace(); List<NvApp> applist = NvHTTP.getAppListByReader(new StringReader(lastRawApplist));
finish(); updateUiWithAppList(applist);
return; LimeLog.info("Loaded applist from cache");
} catch (Exception e) {
if (lastRawApplist != null) {
LimeLog.warning("Saved applist corrupted: "+lastRawApplist);
e.printStackTrace();
}
LimeLog.info("Loading applist from the network");
// We'll need to load from the network
loadAppsBlocking();
} }
}
try { private void loadAppsBlocking() {
appGridAdapter = new AppGridAdapter(this, blockingLoadSpinner = SpinnerDialog.displayDialog(this, getResources().getString(R.string.applist_refresh_title),
PreferenceConfiguration.readPreferences(this).listMode, getResources().getString(R.string.applist_refresh_msg), true);
ipAddress, uniqueId);
} catch (Exception e) {
e.printStackTrace();
finish();
return;
}
getFragmentManager().beginTransaction()
.add(R.id.appFragmentContainer, new AdapterFragment()).commitAllowingStateLoss();
} }
@Override @Override
@@ -113,139 +248,189 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
SpinnerDialog.closeDialogs(this); SpinnerDialog.closeDialogs(this);
Dialog.closeDialogs(); Dialog.closeDialogs();
if (managerBinder != null) {
unbindService(serviceConnection);
}
} }
@Override @Override
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
// Display the error message if it was the startComputerUpdates();
// first load, but just kill the activity }
// on subsequent errors
updateAppList(firstLoad); @Override
firstLoad = false; protected void onPause() {
super.onPause();
stopComputerUpdates();
} }
private int getRunningAppId() { private int getRunningAppId() {
int runningAppId = -1; int runningAppId = -1;
for (int i = 0; i < appGridAdapter.getCount(); i++) { for (int i = 0; i < appGridAdapter.getCount(); i++) {
AppObject app = (AppObject) appGridAdapter.getItem(i); AppObject app = (AppObject) appGridAdapter.getItem(i);
if (app.app == null) { if (app.app == null) {
continue; continue;
} }
if (app.app.getIsRunning()) { if (app.app.getIsRunning()) {
runningAppId = app.app.getAppId(); runningAppId = app.app.getAppId();
break; break;
} }
} }
return runningAppId; return runningAppId;
} }
@Override @Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo); super.onCreateContextMenu(menu, v, menuInfo);
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo; AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
AppObject selectedApp = (AppObject) appGridAdapter.getItem(info.position); AppObject selectedApp = (AppObject) appGridAdapter.getItem(info.position);
if (selectedApp == null || selectedApp.app == null) { if (selectedApp == null || selectedApp.app == null) {
return; return;
} }
int runningAppId = getRunningAppId(); int runningAppId = getRunningAppId();
if (runningAppId != -1) { if (runningAppId != -1) {
if (runningAppId == selectedApp.app.getAppId()) { if (runningAppId == selectedApp.app.getAppId()) {
menu.add(Menu.NONE, RESUME_ID, 1, getResources().getString(R.string.applist_menu_resume)); menu.add(Menu.NONE, START_OR_RESUME_ID, 1, getResources().getString(R.string.applist_menu_resume));
menu.add(Menu.NONE, QUIT_ID, 2, getResources().getString(R.string.applist_menu_quit)); menu.add(Menu.NONE, QUIT_ID, 2, getResources().getString(R.string.applist_menu_quit));
} }
else { else {
menu.add(Menu.NONE, RESUME_ID, 1, getResources().getString(R.string.applist_menu_quit_and_start)); menu.add(Menu.NONE, START_WTIH_QUIT, 1, getResources().getString(R.string.applist_menu_quit_and_start));
menu.add(Menu.NONE, CANCEL_ID, 2, getResources().getString(R.string.applist_menu_cancel)); menu.add(Menu.NONE, CANCEL_ID, 2, getResources().getString(R.string.applist_menu_cancel));
} }
} }
} }
@Override @Override
public void onContextMenuClosed(Menu menu) { public void onContextMenuClosed(Menu menu) {
} }
@Override private void displayQuitConfirmationDialog(final Runnable onYes, final Runnable onNo) {
public boolean onContextItemSelected(MenuItem item) { DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo(); @Override
AppObject app = (AppObject) appGridAdapter.getItem(info.position); public void onClick(DialogInterface dialog, int which) {
switch (item.getItemId()) { switch (which){
case RESUME_ID: case DialogInterface.BUTTON_POSITIVE:
// Resume is the same as start for us if (onYes != null) {
doStart(app.app); onYes.run();
return true; }
break;
case QUIT_ID: case DialogInterface.BUTTON_NEGATIVE:
doQuit(app.app); if (onNo != null) {
return true; onNo.run();
}
break;
}
}
};
case CANCEL_ID: AlertDialog.Builder builder = new AlertDialog.Builder(this);
return true; builder.setMessage(getResources().getString(R.string.applist_quit_confirmation))
.setPositiveButton(getResources().getString(R.string.yes), dialogClickListener)
.setNegativeButton(getResources().getString(R.string.no), dialogClickListener)
.show();
}
default: @Override
return super.onContextItemSelected(item); public boolean onContextItemSelected(MenuItem item) {
} AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
} final AppObject app = (AppObject) appGridAdapter.getItem(info.position);
switch (item.getItemId()) {
case START_WTIH_QUIT:
// Display a confirmation dialog first
displayQuitConfirmationDialog(new Runnable() {
@Override
public void run() {
doStart(app.app);
}
}, null);
return true;
private void updateAppList(final boolean displayError) { case START_OR_RESUME_ID:
final SpinnerDialog spinner = SpinnerDialog.displayDialog(this, getResources().getString(R.string.applist_refresh_title), // Resume is the same as start for us
getResources().getString(R.string.applist_refresh_msg), true); doStart(app.app);
new Thread() { return true;
case QUIT_ID:
// Display a confirmation dialog first
displayQuitConfirmationDialog(new Runnable() {
@Override
public void run() {
doQuit(app.app);
}
}, null);
return true;
case CANCEL_ID:
return true;
default:
return super.onContextItemSelected(item);
}
}
private void updateUiWithAppList(final List<NvApp> appList) {
AppView.this.runOnUiThread(new Runnable() {
@Override @Override
public void run() { public void run() {
NvHTTP httpConn = new NvHTTP(ipAddress, uniqueId, null, PlatformBinding.getCryptoProvider(AppView.this)); boolean updated = false;
try { for (NvApp app : appList) {
final List<NvApp> appList = httpConn.getAppList(); boolean foundExistingApp = false;
AppView.this.runOnUiThread(new Runnable() { // Try to update an existing app in the list first
@Override for (int i = 0; i < appGridAdapter.getCount(); i++) {
public void run() { AppObject existingApp = (AppObject) appGridAdapter.getItem(i);
appGridAdapter.clear(); if (existingApp.app == null) {
for (NvApp app : appList) { continue;
appGridAdapter.addApp(new AppObject(app));
}
appGridAdapter.notifyDataSetChanged();
} }
});
// Success case if (existingApp.app.getAppId() == app.getAppId()) {
return; // Found the app; update its properties
} catch (GfeHttpResponseException ignored) { if (existingApp.app.getIsRunning() != app.getIsRunning()) {
} catch (IOException ignored) { existingApp.app.setIsRunningBoolean(app.getIsRunning());
} catch (XmlPullParserException ignored) { updated = true;
} finally { }
spinner.dismiss(); if (!existingApp.app.getAppName().equals(app.getAppName())) {
existingApp.app.setAppName(app.getAppName());
updated = true;
}
foundExistingApp = true;
break;
}
}
if (!foundExistingApp) {
// This app must be new
appGridAdapter.addApp(new AppObject(app));
updated = true;
}
} }
if (displayError) { if (updated) {
Dialog.displayDialog(AppView.this, getResources().getString(R.string.applist_refresh_error_title), appGridAdapter.notifyDataSetChanged();
getResources().getString(R.string.applist_refresh_error_msg), true); }
}
else {
// Just finish the activity immediately
AppView.this.runOnUiThread(new Runnable() {
@Override
public void run() {
finish();
}
});
}
} }
}.start(); });
} }
private void doStart(NvApp app) { private void doStart(NvApp app) {
Intent intent = new Intent(this, Game.class); Intent intent = new Intent(this, Game.class);
intent.putExtra(Game.EXTRA_HOST, ipAddress.getHostAddress()); 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_APP, app.getAppName());
intent.putExtra(Game.EXTRA_UNIQUEID, uniqueId); intent.putExtra(Game.EXTRA_UNIQUEID, managerBinder.getUniqueId());
intent.putExtra(Game.EXTRA_STREAMING_REMOTE, remote); intent.putExtra(Game.EXTRA_STREAMING_REMOTE,
computer.reachability != ComputerDetails.Reachability.LOCAL);
startActivity(intent); startActivity(intent);
} }
@@ -257,20 +442,25 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
NvHTTP httpConn; NvHTTP httpConn;
String message; String message;
try { try {
httpConn = new NvHTTP(ipAddress, uniqueId, null, PlatformBinding.getCryptoProvider(AppView.this)); httpConn = new NvHTTP(getAddress(),
managerBinder.getUniqueId(), null, PlatformBinding.getCryptoProvider(AppView.this));
if (httpConn.quitApp()) { if (httpConn.quitApp()) {
message = getResources().getString(R.string.applist_quit_success)+" "+app.getAppName(); message = getResources().getString(R.string.applist_quit_success)+" "+app.getAppName();
} }
else { else {
message = getResources().getString(R.string.applist_quit_fail)+" "+app.getAppName(); message = getResources().getString(R.string.applist_quit_fail)+" "+app.getAppName();
} }
updateAppList(true);
} catch (UnknownHostException e) { } catch (UnknownHostException e) {
message = getResources().getString(R.string.error_unknown_host); message = getResources().getString(R.string.error_unknown_host);
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
message = getResources().getString(R.string.error_404); message = getResources().getString(R.string.error_404);
} catch (Exception e) { } catch (Exception e) {
message = e.getMessage(); message = e.getMessage();
} finally {
// Trigger a poll immediately
if (poller != null) {
poller.pollNow();
}
} }
final String toastMessage = message; final String toastMessage = message;
@@ -284,36 +474,37 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
}).start(); }).start();
} }
@Override @Override
public int getAdapterFragmentLayoutId() { public int getAdapterFragmentLayoutId() {
return PreferenceConfiguration.readPreferences(this).listMode ? return PreferenceConfiguration.readPreferences(this).listMode ?
R.layout.list_view : R.layout.app_grid_view; R.layout.list_view : (PreferenceConfiguration.readPreferences(AppView.this).smallIconMode ?
} R.layout.app_grid_view_small : R.layout.app_grid_view);
}
@Override @Override
public void receiveAbsListView(AbsListView listView) { public void receiveAbsListView(AbsListView listView) {
listView.setAdapter(appGridAdapter); listView.setAdapter(appGridAdapter);
listView.setOnItemClickListener(new OnItemClickListener() { listView.setOnItemClickListener(new OnItemClickListener() {
@Override @Override
public void onItemClick(AdapterView<?> arg0, View arg1, int pos, public void onItemClick(AdapterView<?> arg0, View arg1, int pos,
long id) { long id) {
AppObject app = (AppObject) appGridAdapter.getItem(pos); AppObject app = (AppObject) appGridAdapter.getItem(pos);
if (app == null || app.app == null) { if (app == null || app.app == null) {
return; return;
} }
// Only open the context menu if something is running, otherwise start it // Only open the context menu if something is running, otherwise start it
if (getRunningAppId() != -1) { if (getRunningAppId() != -1) {
openContextMenu(arg1); openContextMenu(arg1);
} else { } else {
doStart(app.app); doStart(app.app);
} }
} }
}); });
registerForContextMenu(listView); registerForContextMenu(listView);
} }
public class AppObject { public class AppObject {
public NvApp app; public NvApp app;
public AppObject(NvApp app) { public AppObject(NvApp app) {
@@ -24,7 +24,6 @@ import com.limelight.utils.SpinnerDialog;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.graphics.Point; import android.graphics.Point;
import android.media.AudioManager; import android.media.AudioManager;
@@ -33,7 +32,6 @@ import android.net.wifi.WifiManager;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.SystemClock; import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.view.Display; import android.view.Display;
import android.view.InputDevice; import android.view.InputDevice;
import android.view.KeyEvent; import android.view.KeyEvent;
+8 -24
View File
@@ -30,7 +30,6 @@ import android.app.Service;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Intent; import android.content.Intent;
import android.content.ServiceConnection; import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
@@ -44,15 +43,12 @@ import android.view.View.OnClickListener;
import android.widget.AbsListView; import android.widget.AbsListView;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemClickListener;
import android.widget.GridView;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.ListView;
import android.widget.RelativeLayout; import android.widget.RelativeLayout;
import android.widget.Toast; import android.widget.Toast;
import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.AdapterView.AdapterContextMenuInfo;
public class PcView extends Activity implements AdapterFragmentCallbacks { public class PcView extends Activity implements AdapterFragmentCallbacks {
private AdapterFragment adapterFragment;
private RelativeLayout noPcFoundLayout; private RelativeLayout noPcFoundLayout;
private PcGridAdapter pcGridAdapter; private PcGridAdapter pcGridAdapter;
private ComputerManagerService.ComputerManagerBinder managerBinder; private ComputerManagerService.ComputerManagerBinder managerBinder;
@@ -125,14 +121,9 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
} }
}); });
FragmentTransaction transaction = getFragmentManager().beginTransaction(); getFragmentManager().beginTransaction()
if (adapterFragment != null) { .replace(R.id.pcFragmentContainer, new AdapterFragment())
// Remove the old fragment .commitAllowingStateLoss();
transaction.remove(adapterFragment);
}
adapterFragment = new AdapterFragment();
transaction.add(R.id.pcFragmentContainer, adapterFragment);
transaction.commitAllowingStateLoss();
noPcFoundLayout = (RelativeLayout) findViewById(R.id.no_pc_found_layout); noPcFoundLayout = (RelativeLayout) findViewById(R.id.no_pc_found_layout);
if (pcGridAdapter.getCount() == 0) { if (pcGridAdapter.getCount() == 0) {
@@ -160,7 +151,8 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
Service.BIND_AUTO_CREATE); Service.BIND_AUTO_CREATE);
pcGridAdapter = new PcGridAdapter(this, pcGridAdapter = new PcGridAdapter(this,
PreferenceConfiguration.readPreferences(this).listMode); PreferenceConfiguration.readPreferences(this).listMode,
PreferenceConfiguration.readPreferences(this).smallIconMode);
initializeViews(); initializeViews();
} }
@@ -477,16 +469,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
Intent i = new Intent(this, AppView.class); Intent i = new Intent(this, AppView.class);
i.putExtra(AppView.NAME_EXTRA, computer.name); i.putExtra(AppView.NAME_EXTRA, computer.name);
i.putExtra(AppView.UNIQUEID_EXTRA, managerBinder.getUniqueId()); i.putExtra(AppView.UUID_EXTRA, computer.uuid.toString());
if (computer.reachability == ComputerDetails.Reachability.LOCAL) {
i.putExtra(AppView.ADDRESS_EXTRA, computer.localIp.getAddress());
i.putExtra(AppView.REMOTE_EXTRA, false);
}
else {
i.putExtra(AppView.ADDRESS_EXTRA, computer.remoteIp.getAddress());
i.putExtra(AppView.REMOTE_EXTRA, true);
}
startActivity(i); startActivity(i);
} }
@@ -570,7 +553,8 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
@Override @Override
public int getAdapterFragmentLayoutId() { public int getAdapterFragmentLayoutId() {
return PreferenceConfiguration.readPreferences(this).listMode ? return PreferenceConfiguration.readPreferences(this).listMode ?
R.layout.list_view : R.layout.pc_grid_view; R.layout.list_view : (PreferenceConfiguration.readPreferences(this).smallIconMode ?
R.layout.pc_grid_view_small : R.layout.pc_grid_view);
} }
@Override @Override
@@ -6,7 +6,6 @@ import android.graphics.Color;
import android.graphics.Paint; import android.graphics.Paint;
import android.view.MotionEvent; import android.view.MotionEvent;
import java.lang.reflect.Array;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -15,25 +14,27 @@ import java.util.List;
*/ */
public class AnalogStick extends VirtualControllerElement public class AnalogStick extends VirtualControllerElement
{ {
protected static boolean _PRINT_DEBUG_INFORMATION = true; float radius_complete = 0;
float radius_minimum = 0;
float radius_dead_zone = 0;
float radius_analog_stick = 0;
float radius_complete = 0; float position_pressed_x = 0;
float radius_dead_zone = 0; float position_pressed_y = 0;
float radius_analog_stick = 0;
float position_pressed_x = 0; float position_moved_x = 0;
float position_pressed_y = 0; float position_moved_y = 0;
float position_stick_x = 0; float position_stick_x = 0;
float position_stick_y = 0; float position_stick_y = 0;
Paint paint = new Paint();
boolean viewPressed = false;
boolean analogStickActive = false;
_STICK_STATE stick_state = _STICK_STATE.NO_MOVEMENT; _STICK_STATE stick_state = _STICK_STATE.NO_MOVEMENT;
_CLICK_STATE click_state = _CLICK_STATE.SINGLE; _CLICK_STATE click_state = _CLICK_STATE.SINGLE;
List<AnalogStickListener> listeners = new ArrayList<AnalogStickListener>(); List<AnalogStickListener> listeners = new ArrayList<>();
OnTouchListener onTouchListener = null; OnTouchListener onTouchListener = null;
private long timeoutDoubleClick = 250; private long timeoutDoubleClick = 250;
private long timeLastClick = 0; private long timeLastClick = 0;
@@ -80,9 +81,10 @@ public class AnalogStick extends VirtualControllerElement
@Override @Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) protected void onSizeChanged(int w, int h, int oldw, int oldh)
{ {
radius_complete = getPercent(getCorrectWidth() / 2, 40); radius_complete = getPercent(getCorrectWidth() / 2, 90);
radius_dead_zone = getPercent(getCorrectWidth() / 2, 20); radius_minimum = getPercent(getCorrectWidth() / 2, 30);
radius_analog_stick = getPercent(getCorrectWidth() / 2, 20); radius_dead_zone = getPercent(getCorrectWidth() / 2, 10);
radius_analog_stick = getPercent(getCorrectWidth() / 2, 20);
super.onSizeChanged(w, h, oldw, oldh); super.onSizeChanged(w, h, oldw, oldh);
} }
@@ -93,12 +95,11 @@ public class AnalogStick extends VirtualControllerElement
// set transparent background // set transparent background
canvas.drawColor(Color.TRANSPARENT); canvas.drawColor(Color.TRANSPARENT);
Paint paint = new Paint();
paint.setStyle(Paint.Style.STROKE); paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(getPercent(getCorrectWidth() / 2, 2)); paint.setStrokeWidth(getPercent(getCorrectWidth() / 2, 2));
// draw outer circle // draw outer circle
if (!viewPressed || click_state == _CLICK_STATE.SINGLE) if (!isPressed() || click_state == _CLICK_STATE.SINGLE)
{ {
paint.setColor(normalColor); paint.setColor(normalColor);
} }
@@ -107,47 +108,36 @@ public class AnalogStick extends VirtualControllerElement
paint.setColor(pressedColor); paint.setColor(pressedColor);
} }
canvas.drawRect(0, 0, canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius_complete, paint);
getWidth(), getHeight(),
paint);
paint.setColor(normalColor); paint.setColor(normalColor);
// draw minimum
// draw dead zone canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius_minimum, paint);
if (analogStickActive)
{
canvas.drawCircle(position_pressed_x, position_pressed_y, radius_dead_zone, paint);
}
else
{
canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius_dead_zone, paint);
}
// draw stick depending on state (no movement, moved, active(out of dead zone)) // draw stick depending on state (no movement, moved, active(out of dead zone))
if (analogStickActive) switch (stick_state)
{ {
switch (stick_state) case NO_MOVEMENT:
{ {
case NO_MOVEMENT: paint.setColor(normalColor);
{ canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius_analog_stick, paint);
paint.setColor(normalColor);
canvas.drawCircle(position_stick_x, position_stick_y, radius_analog_stick, paint);
break; break;
} }
case MOVED: case MOVED_IN_DEAD_ZONE:
{ {
paint.setColor(pressedColor); paint.setColor(normalColor);
canvas.drawCircle(position_stick_x, position_stick_y, radius_analog_stick, paint); canvas.drawCircle(position_stick_x, position_stick_y, radius_analog_stick, paint);
break; break;
} }
case MOVED_ACTIVE:
{
paint.setColor(pressedColor);
canvas.drawCircle(position_stick_x, position_stick_y, radius_analog_stick, paint);
break;
} }
}
else
{
paint.setColor(normalColor);
canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius_analog_stick, paint);
} }
super.onDraw(canvas); super.onDraw(canvas);
@@ -256,116 +246,79 @@ public class AnalogStick extends VirtualControllerElement
} }
} }
private void updatePosition(float x, float y) private void updatePosition()
{ {
float way_x; // get real way for each axis
float way_y; float way_center_x = -(getWidth() / 2 - position_moved_x);
float way_center_y = -(getHeight() / 2 - position_moved_y);
if (x > position_pressed_x) // get radius and angel of movement from center
{ double movement_radius = getMovementRadius(way_center_x, way_center_y);
way_x = x - position_pressed_x; double movement_angle = getAngle(way_center_x, way_center_y);
if (way_x > radius_complete) // get dead zone way for each axis
{ float way_pressed_x = position_pressed_x - position_moved_x;
way_x = radius_complete; float way_pressed_y = position_pressed_y - position_moved_y;
}
}
else
{
way_x = -(position_pressed_x - x);
if (way_x < -radius_complete) // get radius and angel from pressed position
{ double movement_dead_zone_radius = getMovementRadius(way_pressed_x, way_pressed_y);
way_x = -radius_complete;
}
}
if (y > position_pressed_y)
{
way_y = y - position_pressed_y;
if (way_y > radius_complete)
{
way_y = radius_complete;
}
}
else
{
way_y = -(position_pressed_y - y);
if (way_y < -radius_complete)
{
way_y = -radius_complete;
}
}
float movement_x = 0;
float movement_y = 0;
double movement_radius = getMovementRadius(way_x, way_y);
//double movement_angle = getAngle(way_x, way_y);
/*
// chop radius if out of outer circle // chop radius if out of outer circle
if (movement_radius > (radius_complete - radius_analog_stick)) if (movement_radius > (radius_complete - radius_analog_stick))
{ {
movement_radius = radius_complete - radius_analog_stick; movement_radius = radius_complete - radius_analog_stick;
} }
// calculate new positions
float correlated_y = float correlated_y =
(float) (Math.sin(Math.PI / 2 - movement_angle) * (movement_radius)); (float) (Math.sin(Math.PI / 2 - movement_angle) * (movement_radius));
float correlated_x = float correlated_x =
(float) (Math.cos(Math.PI / 2 - movement_angle) * (movement_radius)); (float) (Math.cos(Math.PI / 2 - movement_angle) * (movement_radius));
float complete = (radius_complete - radius_analog_stick); float complete = (radius_complete - radius_analog_stick - radius_minimum);
movement_x = -(1 / complete) * correlated_x; float movement_x;
movement_y = (1 / complete) * correlated_y; float movement_y;
*/ movement_x = -(1 / complete) * (correlated_x - (correlated_x > 0 ? radius_minimum : -radius_minimum));
movement_y = (1 / complete) * (correlated_y - (correlated_y > 0 ? radius_minimum : -radius_minimum));
movement_x = (1 / radius_complete) * way_x; position_stick_x = getWidth() / 2 - correlated_x;
movement_y = -(1 / radius_complete) * way_y; position_stick_y = getHeight() / 2 - correlated_y;
position_stick_x = position_pressed_x + way_x; // check if analog stick is outside of dead zone and minimum
position_stick_y = position_pressed_y + way_y; if (movement_radius > radius_minimum && movement_dead_zone_radius > radius_dead_zone)
{
// set active
stick_state = _STICK_STATE.MOVED_ACTIVE;
}
// check if analog stick is outside of dead zone if (stick_state == _STICK_STATE.MOVED_ACTIVE)
if (movement_radius > radius_dead_zone)
{ {
moveActionCallback(movement_x, movement_y); moveActionCallback(movement_x, movement_y);
stick_state = _STICK_STATE.MOVED;
}
else
{
stick_state = _STICK_STATE.NO_MOVEMENT;
} }
} }
@Override @Override
public boolean onTouchEvent(MotionEvent event) public boolean onTouchEvent(MotionEvent event)
{ {
if (onTouchListener != null)
{
return onTouchListener.onTouch(this, event);
}
// get masked (not specific to a pointer) action // get masked (not specific to a pointer) action
int action = event.getActionMasked(); int action = event.getActionMasked();
_CLICK_STATE lastClickState = click_state; _CLICK_STATE lastClickState = click_state;
boolean wasActive = analogStickActive;
position_moved_x = event.getX();
position_moved_y = event.getY();
switch (action) switch (action)
{ {
case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN: case MotionEvent.ACTION_POINTER_DOWN:
{ {
position_pressed_x = event.getX(); setPressed(true);
position_pressed_y = event.getY(); position_pressed_x = position_moved_x;
position_pressed_y = position_moved_y;
stick_state = _STICK_STATE.MOVED_IN_DEAD_ZONE;
analogStickActive = true;
viewPressed = true;
// check for double click // check for double click
if (lastClickState == _CLICK_STATE.SINGLE && timeLastClick + timeoutDoubleClick > System.currentTimeMillis()) if (lastClickState == _CLICK_STATE.SINGLE && timeLastClick + timeoutDoubleClick > System.currentTimeMillis())
{ {
@@ -384,15 +337,11 @@ public class AnalogStick extends VirtualControllerElement
break; break;
} }
case MotionEvent.ACTION_MOVE:
{
break;
}
case MotionEvent.ACTION_UP: case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP: case MotionEvent.ACTION_POINTER_UP:
{ {
analogStickActive = false; setPressed(false);
viewPressed = false; stick_state = _STICK_STATE.NO_MOVEMENT;
revokeActionCallback(); revokeActionCallback();
@@ -400,13 +349,12 @@ public class AnalogStick extends VirtualControllerElement
} }
} }
// no longer pressed reset movement if (isPressed())
if (analogStickActive)
{ // when is pressed calculate new positions (will trigger movement if necessary) { // when is pressed calculate new positions (will trigger movement if necessary)
updatePosition(event.getX(), event.getY()); updatePosition();
} }
else if (wasActive) else
{ { // not longer pressed reset analog stick
moveActionCallback(0, 0); moveActionCallback(0, 0);
} }
@@ -419,7 +367,8 @@ public class AnalogStick extends VirtualControllerElement
private enum _STICK_STATE private enum _STICK_STATE
{ {
NO_MOVEMENT, NO_MOVEMENT,
MOVED MOVED_IN_DEAD_ZONE,
MOVED_ACTIVE
} }
@@ -17,19 +17,97 @@ import java.util.TimerTask;
*/ */
public class DigitalButton extends VirtualControllerElement public class DigitalButton extends VirtualControllerElement
{ {
List<DigitalButtonListener> listeners = new ArrayList<DigitalButtonListener>(); static List<DigitalButton> allButtonsList = new ArrayList<>();
List<DigitalButtonListener> listeners = new ArrayList<>();
OnTouchListener onTouchListener = null; OnTouchListener onTouchListener = null;
boolean clicked;
private String text = ""; private String text = "";
private int icon = -1; private int icon = -1;
private long timerLongClickTimeout = 3000; private long timerLongClickTimeout = 3000;
private Timer timerLongClick = null; private Timer timerLongClick = null;
private TimerLongClickTimerTask longClickTimerTask = null; private TimerLongClickTimerTask longClickTimerTask = null;
public DigitalButton(Context context) private int layer;
private DigitalButton movingButton = null;
boolean inRange(float x, float y)
{
return (this.getX() < x && this.getX() + this.getWidth() > x) &&
(this.getY() < y && this.getY() + this.getHeight() > y);
}
public boolean checkMovement(float x, float y, DigitalButton movingButton)
{
// check if the movement happened in the same layer
if (movingButton.layer != this.layer)
{
return false;
}
// save current pressed state
boolean wasPressed = isPressed();
// check if the movement directly happened on the button
if ((this.movingButton == null || movingButton == this.movingButton)
&& this.inRange(x, y))
{
// set button pressed state depending on moving button pressed state
if (this.isPressed() != movingButton.isPressed())
{
this.setPressed(movingButton.isPressed());
}
}
// check if the movement is outside of the range and the movement button
// is saved moving button
else if (movingButton == this.movingButton)
{
this.setPressed(false);
}
// check if a change occurred
if (wasPressed != isPressed())
{
if (isPressed())
{ // is pressed set moving button and emit click event
this.movingButton = movingButton;
onClickCallback();
}
else
{ // no longer pressed reset moving button and emit release event
this.movingButton = null;
onReleaseCallback();
}
invalidate();
return true;
}
return false;
}
private void checkMovementForAllButtons(float x, float y)
{
for (DigitalButton button : allButtonsList)
{
if (button != this)
{
button.checkMovement(x, y, this);
}
}
}
public DigitalButton(int layer, Context context)
{ {
super(context); super(context);
clicked = false;
this.layer = layer;
allButtonsList.add(this);
} }
public void addDigitalButtonListener(DigitalButtonListener listener) public void addDigitalButtonListener(DigitalButtonListener listener)
@@ -66,10 +144,10 @@ public class DigitalButton extends VirtualControllerElement
paint.setTextAlign(Paint.Align.CENTER); paint.setTextAlign(Paint.Align.CENTER);
paint.setStrokeWidth(3); paint.setStrokeWidth(3);
paint.setColor(clicked ? pressedColor : normalColor); paint.setColor(isPressed() ? pressedColor : normalColor);
paint.setStyle(Paint.Style.STROKE); paint.setStyle(Paint.Style.STROKE);
canvas.drawRect( canvas.drawRect(
1, 1, 1, 1,
getWidth() - 1, getHeight() - 1, getWidth() - 1, getHeight() - 1,
paint paint
); );
@@ -142,6 +220,9 @@ public class DigitalButton extends VirtualControllerElement
} }
*/ */
// get masked (not specific to a pointer) action // get masked (not specific to a pointer) action
float x = getX() + event.getX();
float y = getY() + event.getY();
int action = event.getActionMasked(); int action = event.getActionMasked();
switch (action) switch (action)
@@ -149,20 +230,29 @@ public class DigitalButton extends VirtualControllerElement
case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN: case MotionEvent.ACTION_POINTER_DOWN:
{ {
clicked = true; movingButton = null;
setPressed(true);
onClickCallback(); onClickCallback();
invalidate(); invalidate();
return true; return true;
} }
case MotionEvent.ACTION_MOVE:
{
checkMovementForAllButtons(x, y);
return true;
}
case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP: case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP: case MotionEvent.ACTION_POINTER_UP:
{ {
clicked = false; setPressed(false);
onReleaseCallback(); onReleaseCallback();
checkMovementForAllButtons(x, y);
invalidate(); invalidate();
return true; return true;
@@ -10,6 +10,9 @@ import com.limelight.R;
import com.limelight.nvstream.NvConnection; import com.limelight.nvstream.NvConnection;
import com.limelight.nvstream.input.ControllerPacket; import com.limelight.nvstream.input.ControllerPacket;
import java.util.ArrayList;
import java.util.List;
/** /**
* Created by Karim Mreisi on 30.11.2014. * Created by Karim Mreisi on 30.11.2014.
*/ */
@@ -135,7 +138,7 @@ public class VirtualController
buttonA = createDigitalButton("A", ControllerPacket.A_FLAG, context); buttonA = createDigitalButton("A", ControllerPacket.A_FLAG, context);
buttonB = createDigitalButton("B", ControllerPacket.B_FLAG, context); buttonB = createDigitalButton("B", ControllerPacket.B_FLAG, context);
buttonLT = new DigitalButton(context); buttonLT = new DigitalButton(2, context);
buttonLT.setText("LT"); buttonLT.setText("LT");
buttonLT.addDigitalButtonListener(new DigitalButton.DigitalButtonListener() buttonLT.addDigitalButtonListener(new DigitalButton.DigitalButtonListener()
{ {
@@ -162,7 +165,7 @@ public class VirtualController
} }
}); });
buttonRT = new DigitalButton(context); buttonRT = new DigitalButton(2, context);
buttonRT.setText("RT"); buttonRT.setText("RT");
buttonRT.addDigitalButtonListener(new DigitalButton.DigitalButtonListener() buttonRT.addDigitalButtonListener(new DigitalButton.DigitalButtonListener()
{ {
@@ -267,7 +270,7 @@ public class VirtualController
buttonSelect = buttonSelect =
createDigitalButton("SELECT", ControllerPacket.SPECIAL_BUTTON_FLAG, context); createDigitalButton("SELECT", ControllerPacket.SPECIAL_BUTTON_FLAG, context);
buttonConfigure = new DigitalButton(context); buttonConfigure = new DigitalButton(1, context);
buttonConfigure.setIcon(R.drawable.settings); buttonConfigure.setIcon(R.drawable.settings);
buttonConfigure.addDigitalButtonListener(new DigitalButton.DigitalButtonListener() buttonConfigure.addDigitalButtonListener(new DigitalButton.DigitalButtonListener()
{ {
@@ -412,7 +415,7 @@ public class VirtualController
private DigitalButton createDigitalButton(String text, final int key, Context context) private DigitalButton createDigitalButton(String text, final int key, Context context)
{ {
DigitalButton button = new DigitalButton(context); DigitalButton button = new DigitalButton(1, context);
button.setText(text); button.setText(text);
button.addDigitalButtonListener(new DigitalButton.DigitalButtonListener() button.addDigitalButtonListener(new DigitalButton.DigitalButtonListener()
{ {
@@ -21,7 +21,7 @@ public abstract class VirtualControllerElement extends View
{ {
if (_PRINT_DEBUG_INFORMATION) if (_PRINT_DEBUG_INFORMATION)
{ {
System.out.println("DigitalButton: " + text); System.out.println(text);
} }
} }
@@ -1,16 +1,24 @@
package com.limelight.computers; package com.limelight.computers;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.StringReader;
import java.net.InetAddress; import java.net.InetAddress;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import com.limelight.LimeLog; import com.limelight.LimeLog;
import com.limelight.binding.PlatformBinding; import com.limelight.binding.PlatformBinding;
import com.limelight.discovery.DiscoveryService; import com.limelight.discovery.DiscoveryService;
import com.limelight.nvstream.http.ComputerDetails; import com.limelight.nvstream.http.ComputerDetails;
import com.limelight.nvstream.http.NvApp;
import com.limelight.nvstream.http.NvHTTP; import com.limelight.nvstream.http.NvHTTP;
import com.limelight.nvstream.mdns.MdnsComputer; import com.limelight.nvstream.mdns.MdnsComputer;
import com.limelight.nvstream.mdns.MdnsDiscoveryListener; import com.limelight.nvstream.mdns.MdnsDiscoveryListener;
import com.limelight.utils.CacheHelper;
import android.app.Service; import android.app.Service;
import android.content.ComponentName; import android.content.ComponentName;
@@ -19,6 +27,8 @@ import android.content.ServiceConnection;
import android.os.Binder; import android.os.Binder;
import android.os.IBinder; import android.os.IBinder;
import org.xmlpull.v1.XmlPullParserException;
public class ComputerManagerService extends Service { public class ComputerManagerService extends Service {
private static final int POLLING_PERIOD_MS = 3000; private static final int POLLING_PERIOD_MS = 3000;
private static final int MDNS_QUERY_PERIOD_MS = 1000; private static final int MDNS_QUERY_PERIOD_MS = 1000;
@@ -180,9 +190,25 @@ public class ComputerManagerService extends Service {
ComputerManagerService.this.onUnbind(null); ComputerManagerService.this.onUnbind(null);
} }
public ApplistPoller createAppListPoller(ComputerDetails computer) {
return new ApplistPoller(computer);
}
public String getUniqueId() { public String getUniqueId() {
return idManager.getUniqueId(); return idManager.getUniqueId();
} }
public ComputerDetails getComputer(UUID uuid) {
synchronized (pollingTuples) {
for (PollingTuple tuple : pollingTuples) {
if (uuid.equals(tuple.computer.uuid)) {
return tuple.computer;
}
}
}
return null;
}
} }
@Override @Override
@@ -462,6 +488,107 @@ public class ComputerManagerService extends Service {
public IBinder onBind(Intent intent) { public IBinder onBind(Intent intent) {
return binder; return binder;
} }
public class ApplistPoller {
private Thread thread;
private ComputerDetails computer;
private Object pollEvent = new Object();
public ApplistPoller(ComputerDetails computer) {
this.computer = computer;
}
public void pollNow() {
synchronized (pollEvent) {
pollEvent.notify();
}
}
private boolean waitPollingDelay() {
try {
synchronized (pollEvent) {
pollEvent.wait(POLLING_PERIOD_MS);
}
} catch (InterruptedException e) {
return false;
}
return thread != null && !thread.isInterrupted();
}
public void start() {
thread = new Thread() {
@Override
public void run() {
do {
InetAddress selectedAddr;
// Can't poll if it's not online
if (computer.state != ComputerDetails.State.ONLINE) {
if (listener != null) {
listener.notifyComputerUpdated(computer);
}
continue;
}
// Can't poll if there's no UUID yet
if (computer.uuid == null) {
continue;
}
if (computer.reachability == ComputerDetails.Reachability.LOCAL) {
selectedAddr = computer.localIp;
}
else {
selectedAddr = computer.remoteIp;
}
NvHTTP http = new NvHTTP(selectedAddr, idManager.getUniqueId(),
null, PlatformBinding.getCryptoProvider(ComputerManagerService.this));
try {
// Query the app list from the server
String appList = http.getAppListRaw();
List<NvApp> list = NvHTTP.getAppListByReader(new StringReader(appList));
if (appList != null && !appList.isEmpty() && !list.isEmpty()) {
// Open the cache file
FileOutputStream cacheOut = CacheHelper.openCacheFileForOutput(getCacheDir(), "applist", computer.uuid.toString());
CacheHelper.writeStringToOutputStream(cacheOut, appList);
cacheOut.close();
// Update the computer
computer.rawAppList = appList;
// Notify that the app list has been updated
// and ensure that the thread is still active
if (listener != null && thread != null) {
listener.notifyComputerUpdated(computer);
}
}
else {
LimeLog.warning("Empty app list received from "+computer.uuid);
}
} catch (IOException e) {
e.printStackTrace();
} catch (XmlPullParserException e) {
e.printStackTrace();
}
} while (waitPollingDelay());
}
};
thread.start();
}
public void stop() {
if (thread != null) {
thread.interrupt();
// Don't join here because we might be blocked on network I/O
thread = null;
}
}
}
} }
class PollingTuple { class PollingTuple {
@@ -3,20 +3,21 @@ package com.limelight.grid;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable; import android.os.AsyncTask;
import android.graphics.drawable.Drawable; import android.view.View;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import com.koushikdutta.async.future.FutureCallback; import com.koushikdutta.async.future.FutureCallback;
import com.koushikdutta.ion.ImageViewBitmapInfo; import com.koushikdutta.ion.ImageViewBitmapInfo;
import com.koushikdutta.ion.Ion; import com.koushikdutta.ion.Ion;
import com.koushikdutta.ion.bitmap.BitmapInfo;
import com.limelight.AppView; import com.limelight.AppView;
import com.limelight.LimeLog; import com.limelight.LimeLog;
import com.limelight.R; import com.limelight.R;
import com.limelight.binding.PlatformBinding; import com.limelight.binding.PlatformBinding;
import com.limelight.nvstream.http.ComputerDetails;
import com.limelight.nvstream.http.LimelightCryptoProvider; import com.limelight.nvstream.http.LimelightCryptoProvider;
import com.limelight.utils.CacheHelper;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.File; import java.io.File;
@@ -32,7 +33,11 @@ import java.security.NoSuchAlgorithmException;
import java.security.Principal; import java.security.Principal;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HostnameVerifier;
@@ -46,17 +51,16 @@ import java.security.cert.X509Certificate;
public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> { public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
private boolean listMode; private ComputerDetails computer;
private InetAddress address;
private String uniqueId; private String uniqueId;
private LimelightCryptoProvider cryptoProvider; private LimelightCryptoProvider cryptoProvider;
private SSLContext sslContext; private SSLContext sslContext;
private final HashMap<ImageView, Future> pendingRequests = new HashMap<ImageView, Future>(); private final HashMap<ImageView, Future> pendingRequests = new HashMap<ImageView, Future>();
public AppGridAdapter(Context context, boolean listMode, InetAddress address, String uniqueId) throws NoSuchAlgorithmException, KeyManagementException { public AppGridAdapter(Context context, boolean listMode, boolean small, ComputerDetails computer, String uniqueId) throws NoSuchAlgorithmException, KeyManagementException {
super(context, listMode ? R.layout.simple_row : R.layout.app_grid_item, R.drawable.image_loading); super(context, listMode ? R.layout.simple_row : (small ? R.layout.app_grid_item_small : R.layout.app_grid_item), R.drawable.image_loading);
this.address = address; this.computer = computer;
this.uniqueId = uniqueId; this.uniqueId = uniqueId;
cryptoProvider = PlatformBinding.getCryptoProvider(context); cryptoProvider = PlatformBinding.getCryptoProvider(context);
@@ -109,8 +113,27 @@ public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
public boolean verify(String hostname, SSLSession session) { return true; } public boolean verify(String hostname, SSLSession session) { return true; }
}; };
private void sortList() {
Collections.sort(itemList, new Comparator<AppView.AppObject>() {
@Override
public int compare(AppView.AppObject lhs, AppView.AppObject rhs) {
return lhs.app.getAppName().compareTo(rhs.app.getAppName());
}
});
}
private InetAddress getCurrentAddress() {
if (computer.reachability == ComputerDetails.Reachability.LOCAL) {
return computer.localIp;
}
else {
return computer.remoteIp;
}
}
public void addApp(AppView.AppObject app) { public void addApp(AppView.AppObject app) {
itemList.add(app); itemList.add(app);
sortList();
} }
public void abortPendingRequests() { public void abortPendingRequests() {
@@ -135,94 +158,25 @@ public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
} }
} }
private Bitmap checkBitmapCache(String addrStr, int appId) {
File addrFolder = new File(context.getCacheDir(), addrStr);
if (addrFolder.isDirectory()) {
File bitmapFile = new File(addrFolder, appId+".png");
if (bitmapFile.exists()) {
InputStream fileIn = null;
try {
fileIn = new BufferedInputStream(new FileInputStream(bitmapFile));
Bitmap bm = BitmapFactory.decodeStream(fileIn);
if (bm == null) {
// The image seems corrupt
bitmapFile.delete();
}
return bm;
} catch (IOException e) {
e.printStackTrace();
bitmapFile.delete();
} finally {
if (fileIn != null) {
try {
fileIn.close();
} catch (IOException ignored) {}
}
}
}
}
return null;
}
// TODO: Handle pruning of bitmap cache // TODO: Handle pruning of bitmap cache
private void populateBitmapCache(String addrStr, int appId, Bitmap bitmap) { private void populateBitmapCache(UUID uuid, int appId, Bitmap bitmap) {
File addrFolder = new File(context.getCacheDir(), addrStr);
addrFolder.mkdirs();
File bitmapFile = new File(addrFolder, appId+".png");
try { try {
// PNG ignores quality setting // PNG ignores quality setting
bitmap.compress(Bitmap.CompressFormat.PNG, 0, new FileOutputStream(bitmapFile)); FileOutputStream out = CacheHelper.openCacheFileForOutput(context.getCacheDir(), "boxart", uuid.toString(), appId+".png");
} catch (FileNotFoundException e) { bitmap.compress(Bitmap.CompressFormat.PNG, 0, out);
out.close();
} catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
@Override @Override
public boolean populateImageView(final ImageView imgView, final AppView.AppObject obj) { public boolean populateImageView(final ImageView imgView, final AppView.AppObject obj) {
// Hide the image view while we're loading the image from disk cache
// Set SSL contexts correctly to allow us to authenticate imgView.setVisibility(View.INVISIBLE);
Ion.getDefault(imgView.getContext()).getHttpClient().getSSLSocketMiddleware().setTrustManagers(trustAllCerts);
Ion.getDefault(imgView.getContext()).getHttpClient().getSSLSocketMiddleware().setSSLContext(sslContext);
// Check the on-disk cache // Check the on-disk cache
Bitmap cachedBitmap = checkBitmapCache(address.getHostAddress(), obj.app.getAppId()); new ImageCacheRequest(imgView, obj.app.getAppId()).execute();
if (cachedBitmap != null) {
// Cache hit; we're done
LimeLog.info("Image cache hit for ("+address.getHostAddress()+", "+obj.app.getAppId()+")");
imgView.setImageBitmap(cachedBitmap);
return true;
}
// Kick off the deferred image load
synchronized (pendingRequests) {
Future<ImageViewBitmapInfo> f = Ion.with(imgView)
.placeholder(defaultImageRes)
.error(defaultImageRes)
.load("https://" + address.getHostAddress() + ":47984/appasset?uniqueid=" + uniqueId + "&appid=" +
obj.app.getAppId() + "&AssetType=2&AssetIdx=0")
.withBitmapInfo()
.setCallback(
new FutureCallback<ImageViewBitmapInfo>() {
@Override
public void onCompleted(Exception e, ImageViewBitmapInfo result) {
synchronized (pendingRequests) {
pendingRequests.remove(imgView);
}
// Populate the cache if we got an image back
if (result != null &&
result.getBitmapInfo() != null &&
result.getBitmapInfo().bitmap != null) {
populateBitmapCache(address.getHostAddress(), obj.app.getAppId(),
result.getBitmapInfo().bitmap);
}
}
});
pendingRequests.put(imgView, f);
}
return true; return true;
} }
@@ -247,4 +201,84 @@ public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
// No overlay // No overlay
return false; return false;
} }
private class ImageCacheRequest extends AsyncTask<Void, Void, Bitmap> {
private ImageView view;
private int appId;
public ImageCacheRequest(ImageView view, int appId) {
this.view = view;
this.appId = appId;
}
@Override
protected Bitmap doInBackground(Void... v) {
InputStream in = null;
try {
in = CacheHelper.openCacheFileForInput(context.getCacheDir(), "boxart", computer.uuid.toString(), appId + ".png");
return BitmapFactory.decodeStream(in);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {}
}
}
return null;
}
@Override
protected void onPostExecute(Bitmap result) {
if (result != null) {
// Disk cache was read successfully
LimeLog.info("Image disk cache hit for ("+computer.uuid+", "+appId+")");
view.setImageBitmap(result);
view.setVisibility(View.VISIBLE);
}
else {
LimeLog.info("Image disk cache miss for ("+computer.uuid+", "+appId+")");
LimeLog.info("Requesting: "+"https://" + getCurrentAddress().getHostAddress() + ":47984/appasset?uniqueid=" + uniqueId + "&appid=" +
appId + "&AssetType=2&AssetIdx=0");
// Load the placeholder image
view.setImageResource(defaultImageRes);
view.setVisibility(View.VISIBLE);
// Set SSL contexts correctly to allow us to authenticate
Ion.getDefault(context).getHttpClient().getSSLSocketMiddleware().setTrustManagers(trustAllCerts);
Ion.getDefault(context).getHttpClient().getSSLSocketMiddleware().setSSLContext(sslContext);
// Kick off the deferred image load
synchronized (pendingRequests) {
Future<Bitmap> f = Ion.with(context)
.load("https://" + getCurrentAddress().getHostAddress() + ":47984/appasset?uniqueid=" + uniqueId + "&appid=" +
appId + "&AssetType=2&AssetIdx=0")
.asBitmap()
.setCallback(new FutureCallback<Bitmap>() {
@Override
public void onCompleted(Exception e, Bitmap result) {
synchronized (pendingRequests) {
pendingRequests.remove(view);
}
if (result != null) {
// Make the view visible now
view.setImageBitmap(result);
view.setVisibility(View.VISIBLE);
// Populate the disk cache if we got an image back
populateBitmapCache(computer.uuid, appId, result);
}
else {
// Leave the loading icon as is (probably should change this eventually...)
}
}
});
pendingRequests.put(view, f);
}
}
}
};
} }
@@ -13,8 +13,8 @@ import java.util.Comparator;
public class PcGridAdapter extends GenericGridAdapter<PcView.ComputerObject> { public class PcGridAdapter extends GenericGridAdapter<PcView.ComputerObject> {
public PcGridAdapter(Context context, boolean listMode) { public PcGridAdapter(Context context, boolean listMode, boolean small) {
super(context, listMode ? R.layout.simple_row : R.layout.pc_grid_item, R.drawable.computer); super(context, listMode ? R.layout.simple_row : (small ? R.layout.pc_grid_item_small : R.layout.pc_grid_item), R.drawable.computer);
} }
public void addComputer(PcView.ComputerObject computer) { public void addComputer(PcView.ComputerObject computer) {
@@ -19,7 +19,6 @@ import android.content.ServiceConnection;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
import android.preference.Preference;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.inputmethod.EditorInfo; import android.view.inputmethod.EditorInfo;
import android.widget.TextView; import android.widget.TextView;
@@ -160,7 +159,7 @@ public class AddComputerManually extends Activity {
return true; return true;
} }
computersToAdd.add(hostText.getText().toString()); computersToAdd.add(hostText.getText().toString().trim());
} }
return false; return false;
@@ -15,6 +15,7 @@ public class PreferenceConfiguration {
private static final String DEADZONE_PREF_STRING = "seekbar_deadzone"; private static final String DEADZONE_PREF_STRING = "seekbar_deadzone";
private static final String LANGUAGE_PREF_STRING = "list_languages"; private static final String LANGUAGE_PREF_STRING = "list_languages";
private static final String LIST_MODE_PREF_STRING = "checkbox_list_mode"; private static final String LIST_MODE_PREF_STRING = "checkbox_list_mode";
private static final String SMALL_ICONS_PREF_STRING = "checkbox_small_icon_mode";
private static final String VIRTUAL_CONTROLLER_ENABLE = "virtual_controller_checkbox_enable"; private static final String VIRTUAL_CONTROLLER_ENABLE = "virtual_controller_checkbox_enable";
private static final Boolean VIRTUAL_CONTROLLER_ENABLE_DEFAULT = true; private static final Boolean VIRTUAL_CONTROLLER_ENABLE_DEFAULT = true;
@@ -45,7 +46,7 @@ public class PreferenceConfiguration {
public int deadzonePercentage; public int deadzonePercentage;
public boolean stretchVideo, enableSops, playHostAudio, disableWarnings; public boolean stretchVideo, enableSops, playHostAudio, disableWarnings;
public String language; public String language;
public boolean listMode; public boolean listMode, smallIconMode;
public boolean virtualController_enable; public boolean virtualController_enable;
@@ -68,6 +69,11 @@ public class PreferenceConfiguration {
} }
} }
public static boolean getDefaultSmallMode(Context context) {
// Use small mode on anything smaller than a 7" tablet
return context.getResources().getConfiguration().smallestScreenWidthDp < 600;
}
public static int getDefaultBitrate(Context context) { public static int getDefaultBitrate(Context context) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
@@ -154,6 +160,7 @@ public class PreferenceConfiguration {
config.stretchVideo = prefs.getBoolean(STRETCH_PREF_STRING, DEFAULT_STRETCH); config.stretchVideo = prefs.getBoolean(STRETCH_PREF_STRING, DEFAULT_STRETCH);
config.playHostAudio = prefs.getBoolean(HOST_AUDIO_PREF_STRING, DEFAULT_HOST_AUDIO); config.playHostAudio = prefs.getBoolean(HOST_AUDIO_PREF_STRING, DEFAULT_HOST_AUDIO);
config.listMode = prefs.getBoolean(LIST_MODE_PREF_STRING, DEFAULT_LIST_MODE); config.listMode = prefs.getBoolean(LIST_MODE_PREF_STRING, DEFAULT_LIST_MODE);
config.smallIconMode = prefs.getBoolean(SMALL_ICONS_PREF_STRING, getDefaultSmallMode(context));
config.virtualController_enable = prefs.getBoolean(VIRTUAL_CONTROLLER_ENABLE, VIRTUAL_CONTROLLER_ENABLE_DEFAULT); config.virtualController_enable = prefs.getBoolean(VIRTUAL_CONTROLLER_ENABLE, VIRTUAL_CONTROLLER_ENABLE_DEFAULT);
@@ -0,0 +1,21 @@
package com.limelight.preferences;
import android.content.Context;
import android.content.res.TypedArray;
import android.preference.CheckBoxPreference;
import android.util.AttributeSet;
public class SmallIconCheckboxPreference extends CheckBoxPreference {
public SmallIconCheckboxPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public SmallIconCheckboxPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected Object onGetDefaultValue(TypedArray a, int index) {
return PreferenceConfiguration.getDefaultSmallMode(getContext());
}
}
@@ -0,0 +1,57 @@
package com.limelight.utils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.util.Scanner;
public class CacheHelper {
private static File openPath(boolean createPath, File root, String... path) {
File f = root;
for (int i = 0; i < path.length; i++) {
String component = path[i];
if (i == path.length - 1) {
// This is the file component so now we create parent directories
if (createPath) {
f.mkdirs();
}
}
f = new File(f, component);
}
return f;
}
public static FileInputStream openCacheFileForInput(File root, String... path) throws FileNotFoundException {
return new FileInputStream(openPath(false, root, path));
}
public static FileOutputStream openCacheFileForOutput(File root, String... path) throws FileNotFoundException {
return new FileOutputStream(openPath(true, root, path));
}
public static String readInputStreamToString(InputStream in) throws IOException {
Reader r = new InputStreamReader(in);
StringBuilder sb = new StringBuilder();
char[] buf = new char[256];
int bytesRead;
while ((bytesRead = r.read(buf)) != -1) {
sb.append(buf, 0, bytesRead);
}
return sb.toString();
}
public static void writeStringToOutputStream(OutputStream out, String str) throws IOException {
out.write(str.getBytes("UTF-8"));
}
}
@@ -26,6 +26,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_centerHorizontal="true" android:layout_centerHorizontal="true"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
android:gravity="center"
android:paddingTop="0dp" android:paddingTop="0dp"
android:paddingBottom="10dp" android:paddingBottom="10dp"
android:textSize="28sp"/> android:textSize="28sp"/>
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp">
<RelativeLayout
android:id="@+id/grid_image_layout"
android:layout_centerHorizontal="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/grid_image"
android:cropToPadding="false"
android:scaleType="fitXY"
android:layout_centerHorizontal="true"
android:layout_width="100dp"
android:layout_height="117dp">
</ImageView>
<ImageView
android:id="@+id/grid_overlay"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:layout_width="33dp"
android:layout_height="33dp">
</ImageView>
</RelativeLayout>
<TextView
android:id="@+id/grid_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/grid_image_layout"
android:layout_marginTop="10dp"
android:layout_centerHorizontal="true"
android:gravity="center"
android:singleLine="true"
android:ellipsize="marquee"
android:marqueeRepeatLimit="marquee_forever"
android:scrollHorizontally="true"
android:textSize="14sp" >
</TextView>
</RelativeLayout>
+1 -1
View File
@@ -5,7 +5,7 @@
<GridView <GridView
android:id="@+id/fragmentView" android:id="@+id/fragmentView"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="fill_parent"
android:numColumns="auto_fit" android:numColumns="auto_fit"
android:columnWidth="160dp" android:columnWidth="160dp"
android:stretchMode="spacingWidth" android:stretchMode="spacingWidth"
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<GridView
android:id="@+id/fragmentView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:numColumns="auto_fit"
android:columnWidth="105dp"
android:stretchMode="spacingWidth"
android:gravity="center"/>
</LinearLayout>
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="15dp">
<RelativeLayout
android:id="@+id/grid_image_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/grid_image"
android:layout_centerHorizontal="true"
android:layout_width="100dp"
android:layout_height="67dp">
</ImageView>
<ImageView
android:id="@+id/grid_overlay"
android:layout_marginTop="10dp"
android:layout_marginLeft="42dp"
android:layout_marginStart="42dp"
android:layout_marginRight="13dp"
android:layout_marginEnd="13dp"
android:layout_width="33dp"
android:layout_height="33dp">
</ImageView>
</RelativeLayout>
<TextView
android:id="@+id/grid_text"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_below="@id/grid_image_layout"
android:layout_marginTop="10dp"
android:layout_centerHorizontal="true"
android:gravity="center"
android:textSize="14sp" >
</TextView>
</RelativeLayout>
+1 -1
View File
@@ -5,7 +5,7 @@
<GridView <GridView
android:id="@+id/fragmentView" android:id="@+id/fragmentView"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="fill_parent"
android:numColumns="auto_fit" android:numColumns="auto_fit"
android:columnWidth="160dp" android:columnWidth="160dp"
android:gravity="center"/> android:gravity="center"/>
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<GridView
android:id="@+id/fragmentView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:numColumns="auto_fit"
android:columnWidth="105dp"
android:gravity="center"/>
</LinearLayout>
+9 -3
View File
@@ -56,6 +56,9 @@
<!-- General strings --> <!-- General strings -->
<string name="ip_hint">Indirizzo IP del PC</string> <string name="ip_hint">Indirizzo IP del PC</string>
<string name="searching_pc">Ricerca PC in corso…</string> <string name="searching_pc">Ricerca PC in corso…</string>
<string name="yes"></string>
<string name="no">No</string>
<string name="lost_connection">Connessione con il PC persa</string>
<!-- AppList activity --> <!-- AppList activity -->
<string name="title_applist">Applicazioni su</string> <string name="title_applist">Applicazioni su</string>
@@ -70,6 +73,7 @@
<string name="applist_quit_app">Chiusura in corso…</string> <string name="applist_quit_app">Chiusura in corso…</string>
<string name="applist_quit_success">Sessione chiusa con successo</string> <string name="applist_quit_success">Sessione chiusa con successo</string>
<string name="applist_quit_fail">Chiusura sessione fallita</string> <string name="applist_quit_fail">Chiusura sessione fallita</string>
<string name="applist_quit_confirmation">Sei sicuro di voler chiudere l\'applicazione avviata? Tutti i dati non salvati saranno persi.</string>
<!-- Add computer manually activity --> <!-- Add computer manually activity -->
<string name="title_add_pc">Aggiungi PC Manualmente</string> <string name="title_add_pc">Aggiungi PC Manualmente</string>
@@ -94,11 +98,13 @@
<string name="title_seekbar_deadzone">Aggiusta deadzone degli stick analogici</string> <string name="title_seekbar_deadzone">Aggiusta deadzone degli stick analogici</string>
<string name="suffix_seekbar_deadzone">%</string> <string name="suffix_seekbar_deadzone">%</string>
<string name="category_ui_settings">UI Settings</string> <string name="category_ui_settings">Impostazioni Interfaccia</string>
<string name="title_language_list">Lingua</string> <string name="title_language_list">Lingua</string>
<string name="summary_language_list">Lingua da usare in Limelight</string> <string name="summary_language_list">Lingua da usare in Limelight</string>
<string name="title_checkbox_list_mode">Use lists instead of grids</string> <string name="title_checkbox_list_mode">Usa lista invece della griglia</string>
<string name="summary_checkbox_list_mode">Display apps and PCs in lists instead of grids</string> <string name="summary_checkbox_list_mode">Visualizza applicazioni e computers in una lista invece di una griglia</string>
<string name="title_checkbox_small_icon_mode">Usa icone piccole</string>
<string name="summary_checkbox_small_icon_mode">Usa icone piccole nella vista a griglia per avere più oggetti sullo schermo</string>
<string name="category_host_settings">Impostazioni Host</string> <string name="category_host_settings">Impostazioni Host</string>
<string name="title_checkbox_enable_sops">Ottimizza le impostazioni dei giochi</string> <string name="title_checkbox_enable_sops">Ottimizza le impostazioni dei giochi</string>
+6
View File
@@ -56,6 +56,9 @@
<!-- General strings --> <!-- General strings -->
<string name="ip_hint">IP address of GeForce PC</string> <string name="ip_hint">IP address of GeForce PC</string>
<string name="searching_pc">Searching for PCs…</string> <string name="searching_pc">Searching for PCs…</string>
<string name="yes">Yes</string>
<string name="no">No</string>
<string name="lost_connection">Lost connection to PC</string>
<!-- AppList activity --> <!-- AppList activity -->
<string name="title_applist">Apps on</string> <string name="title_applist">Apps on</string>
@@ -70,6 +73,7 @@
<string name="applist_quit_app">Quitting</string> <string name="applist_quit_app">Quitting</string>
<string name="applist_quit_success">Successfully quit</string> <string name="applist_quit_success">Successfully quit</string>
<string name="applist_quit_fail">Failed to quit</string> <string name="applist_quit_fail">Failed to quit</string>
<string name="applist_quit_confirmation">Are you sure you want to quit the running app? All unsaved data will be lost.</string>
<!-- Add computer manually activity --> <!-- Add computer manually activity -->
<string name="title_add_pc">Add PC Manually</string> <string name="title_add_pc">Add PC Manually</string>
@@ -99,6 +103,8 @@
<string name="summary_language_list">Language to use for Limelight</string> <string name="summary_language_list">Language to use for Limelight</string>
<string name="title_checkbox_list_mode">Use lists instead of grids</string> <string name="title_checkbox_list_mode">Use lists instead of grids</string>
<string name="summary_checkbox_list_mode">Display apps and PCs in lists instead of grids</string> <string name="summary_checkbox_list_mode">Display apps and PCs in lists instead of grids</string>
<string name="title_checkbox_small_icon_mode">Use small icons</string>
<string name="summary_checkbox_small_icon_mode">Use small icons in grid items to allow more items on screen</string>
<string name="category_host_settings">Host Settings</string> <string name="category_host_settings">Host Settings</string>
<string name="title_checkbox_enable_sops">Optimize game settings</string> <string name="title_checkbox_enable_sops">Optimize game settings</string>
+4
View File
@@ -54,6 +54,10 @@
android:entryValues="@array/language_values" android:entryValues="@array/language_values"
android:summary="@string/summary_language_list" android:summary="@string/summary_language_list"
android:defaultValue="default" /> android:defaultValue="default" />
<com.limelight.preferences.SmallIconCheckboxPreference
android:key="checkbox_small_icon_mode"
android:title="@string/title_checkbox_small_icon_mode"
android:summary="@string/summary_checkbox_small_icon_mode" />
<CheckBoxPreference <CheckBoxPreference
android:key="checkbox_list_mode" android:key="checkbox_list_mode"
android:title="@string/title_checkbox_list_mode" android:title="@string/title_checkbox_list_mode"