mirror of
https://github.com/moonlight-stream/moonlight-android.git
synced 2026-06-17 22:31:35 +00:00
fix analogstick, add minimum range and press deadzone, add movement touch to digital buttons depending on layers
This commit is contained in:
+33
-25
@@ -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.
@@ -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,27 +48,149 @@ 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) {
|
||||||
@@ -72,39 +207,39 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
|
|
||||||
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);
|
||||||
|
|
||||||
try {
|
// Bind to the computer manager service
|
||||||
ipAddress = InetAddress.getByAddress(address);
|
bindService(new Intent(this, ComputerManagerService.class), serviceConnection,
|
||||||
} catch (UnknownHostException e) {
|
Service.BIND_AUTO_CREATE);
|
||||||
e.printStackTrace();
|
|
||||||
finish();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void populateAppGridWithCache() {
|
||||||
try {
|
try {
|
||||||
appGridAdapter = new AppGridAdapter(this,
|
// Try to load from cache
|
||||||
PreferenceConfiguration.readPreferences(this).listMode,
|
lastRawApplist = CacheHelper.readInputStreamToString(CacheHelper.openCacheFileForInput(getCacheDir(), "applist", uuidString));
|
||||||
ipAddress, uniqueId);
|
List<NvApp> applist = NvHTTP.getAppListByReader(new StringReader(lastRawApplist));
|
||||||
|
updateUiWithAppList(applist);
|
||||||
|
LimeLog.info("Loaded applist from cache");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
if (lastRawApplist != null) {
|
||||||
|
LimeLog.warning("Saved applist corrupted: "+lastRawApplist);
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
finish();
|
}
|
||||||
return;
|
LimeLog.info("Loading applist from the network");
|
||||||
|
// We'll need to load from the network
|
||||||
|
loadAppsBlocking();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getFragmentManager().beginTransaction()
|
private void loadAppsBlocking() {
|
||||||
.add(R.id.appFragmentContainer, new AdapterFragment()).commitAllowingStateLoss();
|
blockingLoadSpinner = SpinnerDialog.displayDialog(this, getResources().getString(R.string.applist_refresh_title),
|
||||||
|
getResources().getString(R.string.applist_refresh_msg), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -113,17 +248,24 @@ 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() {
|
||||||
@@ -155,11 +297,11 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -169,18 +311,61 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
public void onContextMenuClosed(Menu menu) {
|
public void onContextMenuClosed(Menu menu) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void displayQuitConfirmationDialog(final Runnable onYes, final Runnable onNo) {
|
||||||
|
DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
switch (which){
|
||||||
|
case DialogInterface.BUTTON_POSITIVE:
|
||||||
|
if (onYes != null) {
|
||||||
|
onYes.run();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DialogInterface.BUTTON_NEGATIVE:
|
||||||
|
if (onNo != null) {
|
||||||
|
onNo.run();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||||
|
builder.setMessage(getResources().getString(R.string.applist_quit_confirmation))
|
||||||
|
.setPositiveButton(getResources().getString(R.string.yes), dialogClickListener)
|
||||||
|
.setNegativeButton(getResources().getString(R.string.no), dialogClickListener)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onContextItemSelected(MenuItem item) {
|
public boolean onContextItemSelected(MenuItem item) {
|
||||||
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
|
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
|
||||||
AppObject app = (AppObject) appGridAdapter.getItem(info.position);
|
final AppObject app = (AppObject) appGridAdapter.getItem(info.position);
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case RESUME_ID:
|
case START_WTIH_QUIT:
|
||||||
|
// Display a confirmation dialog first
|
||||||
|
displayQuitConfirmationDialog(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
doStart(app.app);
|
||||||
|
}
|
||||||
|
}, null);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case START_OR_RESUME_ID:
|
||||||
// Resume is the same as start for us
|
// Resume is the same as start for us
|
||||||
doStart(app.app);
|
doStart(app.app);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case QUIT_ID:
|
case QUIT_ID:
|
||||||
|
// Display a confirmation dialog first
|
||||||
|
displayQuitConfirmationDialog(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
doQuit(app.app);
|
doQuit(app.app);
|
||||||
|
}
|
||||||
|
}, null);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case CANCEL_ID:
|
case CANCEL_ID:
|
||||||
@@ -191,61 +376,61 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateAppList(final boolean displayError) {
|
private void updateUiWithAppList(final List<NvApp> appList) {
|
||||||
final SpinnerDialog spinner = SpinnerDialog.displayDialog(this, getResources().getString(R.string.applist_refresh_title),
|
|
||||||
getResources().getString(R.string.applist_refresh_msg), true);
|
|
||||||
new Thread() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
NvHTTP httpConn = new NvHTTP(ipAddress, uniqueId, null, PlatformBinding.getCryptoProvider(AppView.this));
|
|
||||||
|
|
||||||
try {
|
|
||||||
final List<NvApp> appList = httpConn.getAppList();
|
|
||||||
|
|
||||||
AppView.this.runOnUiThread(new Runnable() {
|
AppView.this.runOnUiThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
appGridAdapter.clear();
|
boolean updated = false;
|
||||||
|
|
||||||
for (NvApp app : appList) {
|
for (NvApp app : appList) {
|
||||||
appGridAdapter.addApp(new AppObject(app));
|
boolean foundExistingApp = false;
|
||||||
|
|
||||||
|
// Try to update an existing app in the list first
|
||||||
|
for (int i = 0; i < appGridAdapter.getCount(); i++) {
|
||||||
|
AppObject existingApp = (AppObject) appGridAdapter.getItem(i);
|
||||||
|
if (existingApp.app == null) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (existingApp.app.getAppId() == app.getAppId()) {
|
||||||
|
// Found the app; update its properties
|
||||||
|
if (existingApp.app.getIsRunning() != app.getIsRunning()) {
|
||||||
|
existingApp.app.setIsRunningBoolean(app.getIsRunning());
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
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 (updated) {
|
||||||
appGridAdapter.notifyDataSetChanged();
|
appGridAdapter.notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
// Success case
|
|
||||||
return;
|
|
||||||
} catch (GfeHttpResponseException ignored) {
|
|
||||||
} catch (IOException ignored) {
|
|
||||||
} catch (XmlPullParserException ignored) {
|
|
||||||
} finally {
|
|
||||||
spinner.dismiss();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (displayError) {
|
|
||||||
Dialog.displayDialog(AppView.this, getResources().getString(R.string.applist_refresh_error_title),
|
|
||||||
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;
|
||||||
@@ -287,7 +477,8 @@ public class AppView 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.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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
+64
-115
@@ -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_complete = 0;
|
||||||
|
float radius_minimum = 0;
|
||||||
float radius_dead_zone = 0;
|
float radius_dead_zone = 0;
|
||||||
float radius_analog_stick = 0;
|
float radius_analog_stick = 0;
|
||||||
|
|
||||||
float position_pressed_x = 0;
|
float position_pressed_x = 0;
|
||||||
float position_pressed_y = 0;
|
float position_pressed_y = 0;
|
||||||
|
|
||||||
|
float position_moved_x = 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;
|
||||||
|
|
||||||
boolean viewPressed = false;
|
Paint paint = new Paint();
|
||||||
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,8 +81,9 @@ 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_dead_zone = getPercent(getCorrectWidth() / 2, 10);
|
||||||
radius_analog_stick = getPercent(getCorrectWidth() / 2, 20);
|
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,35 +108,30 @@ 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);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MOVED_IN_DEAD_ZONE:
|
||||||
{
|
{
|
||||||
paint.setColor(normalColor);
|
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:
|
case MOVED_ACTIVE:
|
||||||
{
|
{
|
||||||
paint.setColor(pressedColor);
|
paint.setColor(pressedColor);
|
||||||
canvas.drawCircle(position_stick_x, position_stick_y, radius_analog_stick, paint);
|
canvas.drawCircle(position_stick_x, position_stick_y, radius_analog_stick, paint);
|
||||||
@@ -143,12 +139,6 @@ public class AnalogStick extends VirtualControllerElement
|
|||||||
break;
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+97
-7
@@ -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,7 +144,7 @@ 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,
|
||||||
@@ -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;
|
||||||
|
|||||||
+7
-4
@@ -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()
|
||||||
{
|
{
|
||||||
|
|||||||
+1
-1
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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">Sì</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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user