mirror of
https://github.com/moonlight-stream/moonlight-android.git
synced 2026-10-13 03:54:09 +00:00
Compare commits
47
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fdbecfa6dd | ||
|
|
5e45aeb70b | ||
|
|
3a0c1db168 | ||
|
|
bd21692323 | ||
|
|
5ae245bdca | ||
|
|
d3052cd97d | ||
|
|
336f85a31c | ||
|
|
b01f7c796e | ||
|
|
56f438fe47 | ||
|
|
baa5199b83 | ||
|
|
23ca62b304 | ||
|
|
2c3511195c | ||
|
|
d31ef481f3 | ||
|
|
a490da5e5c | ||
|
|
72d3576257 | ||
|
|
ebd93a55a0 | ||
|
|
4d01e1afe6 | ||
|
|
9ff1386751 | ||
|
|
5fca35f0b1 | ||
|
|
d23c763441 | ||
|
|
fa058c4783 | ||
|
|
e0ddd5f045 | ||
|
|
b7443451a4 | ||
|
|
e90e4a22c4 | ||
|
|
3a53172145 | ||
|
|
1dfcb7bc29 | ||
|
|
897bb76858 | ||
|
|
bcc67269ab | ||
|
|
4d24c654b9 | ||
|
|
cba44b091b | ||
|
|
f2d8f8a41b | ||
|
|
4b1c7e7e3c | ||
|
|
1cba278876 | ||
|
|
766898fdf9 | ||
|
|
13e91d594b | ||
|
|
ca0a0da19f | ||
|
|
82cabce86e | ||
|
|
51a630995a | ||
|
|
3a74f0726c | ||
|
|
efa6c7bba0 | ||
|
|
b8141542f8 | ||
|
|
8fc9a90207 | ||
|
|
13d707d98d | ||
|
|
aae0ff6e7a | ||
|
|
22958cfbb1 | ||
|
|
9340dff45d | ||
|
|
99aa616188 |
@@ -8,6 +8,8 @@ whether in your own home or over the internet.
|
||||
|
||||
[Limelight-pc](https://github.com/limelight-stream/limelight-pc) is also currently in development for Windows, OS X and Linux. Versions for [iOS](https://github.com/limelight-stream/limelight-ios) and [Windows and Windows Phone](https://github.com/limelight-stream/limelight-windows) are also in development.
|
||||
|
||||
Check our [wiki](https://github.com/limelight-stream/limelight-android/wiki) for more detailed information or a troubleshooting guide.
|
||||
|
||||
##Features
|
||||
|
||||
* Streams any of your games from your PC to your Android device
|
||||
|
||||
+6
-4
@@ -31,6 +31,7 @@
|
||||
<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/debug" isTestSource="false" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/nonRoot/debug" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/generated/nonRoot/debug" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/nonRootDebug/res" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/nonRootDebug/resources" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/nonRootDebug/assets" type="java-resource" />
|
||||
@@ -43,6 +44,7 @@
|
||||
<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/resources" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/nonRoot/assets" type="java-resource" />
|
||||
@@ -102,17 +104,17 @@
|
||||
<orderEntry type="jdk" jdkName="Android API 21 Platform" jdkType="Android SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" exported="" name="bcprov-jdk15on-1.51" level="project" />
|
||||
<orderEntry type="library" exported="" name="androidasync-2.0.5" level="project" />
|
||||
<orderEntry type="library" exported="" name="gson-2.3.1" level="project" />
|
||||
<orderEntry type="library" exported="" name="support-v4-r7" level="project" />
|
||||
<orderEntry type="library" exported="" name="jmdns-fixed" level="project" />
|
||||
<orderEntry type="library" exported="" name="bcpkix-jdk15on-1.51" level="project" />
|
||||
<orderEntry type="library" exported="" name="tinyrtsp" level="project" />
|
||||
<orderEntry type="library" exported="" name="okhttp-2.1.0" level="project" />
|
||||
<orderEntry type="library" exported="" name="limelight-common" level="project" />
|
||||
<orderEntry type="library" exported="" name="okio-1.0.1" level="project" />
|
||||
<orderEntry type="library" exported="" name="androidasync-e1dfb4" level="project" />
|
||||
<orderEntry type="library" exported="" name="ion-2.0.5" level="project" />
|
||||
<orderEntry type="library" exported="" name="okhttp-2.2.0" level="project" />
|
||||
<orderEntry type="library" exported="" name="jcodec-0.1.9" level="project" />
|
||||
<orderEntry type="library" exported="" name="ion-2f46fa" level="project" />
|
||||
<orderEntry type="library" exported="" name="okio-1.2.0" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
|
||||
|
||||
+8
-9
@@ -5,14 +5,14 @@ apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 21
|
||||
buildToolsVersion "21.1.1"
|
||||
buildToolsVersion "21.1.2"
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 21
|
||||
|
||||
versionName "3.0.2"
|
||||
versionCode = 48
|
||||
versionName "3.1-beta1"
|
||||
versionCode = 50
|
||||
}
|
||||
|
||||
productFlavors {
|
||||
@@ -27,7 +27,7 @@ android {
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
runProguard false
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
||||
}
|
||||
}
|
||||
@@ -63,18 +63,17 @@ android {
|
||||
|
||||
dependencies {
|
||||
compile group: 'org.jcodec', name: 'jcodec', version: '+'
|
||||
|
||||
compile group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '+'
|
||||
compile group: 'org.bouncycastle', name: 'bcpkix-jdk15on', version: '+'
|
||||
compile group: 'com.google.android', name: 'support-v4', version:'+'
|
||||
|
||||
// FIXME: Pending resolution of issue #346 using custom build
|
||||
//compile group: 'com.koushikdutta.ion', name: 'ion', version:'+'
|
||||
compile group: 'com.google.android', name: 'support-v4', version:'+'
|
||||
compile group: 'com.koushikdutta.ion', name: 'ion', version:'+'
|
||||
compile group: 'com.google.code.gson', name: 'gson', version:'+'
|
||||
compile files('libs/androidasync-e1dfb4.jar')
|
||||
compile files('libs/ion-2f46fa.jar')
|
||||
|
||||
compile group: 'com.squareup.okhttp', name: 'okhttp', version:'+'
|
||||
compile group: 'com.squareup.okio', name:'okio', version:'+'
|
||||
|
||||
compile files('libs/jmdns-fixed.jar')
|
||||
compile files('libs/limelight-common.jar')
|
||||
compile files('libs/tinyrtsp.jar')
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -68,7 +68,7 @@
|
||||
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection" >
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="com.limelight.Connection" />
|
||||
android:value="com.limelight.AppView" />
|
||||
</activity>
|
||||
<service
|
||||
android:name=".discovery.DiscoveryService"
|
||||
|
||||
@@ -1,110 +1,246 @@
|
||||
package com.limelight;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.StringReader;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
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.nvstream.http.ComputerDetails;
|
||||
import com.limelight.nvstream.http.GfeHttpResponseException;
|
||||
import com.limelight.nvstream.http.NvApp;
|
||||
import com.limelight.nvstream.http.NvHTTP;
|
||||
import com.limelight.preferences.PreferenceConfiguration;
|
||||
import com.limelight.ui.AdapterFragment;
|
||||
import com.limelight.ui.AdapterFragmentCallbacks;
|
||||
import com.limelight.utils.CacheHelper;
|
||||
import com.limelight.utils.Dialog;
|
||||
import com.limelight.utils.SpinnerDialog;
|
||||
import com.limelight.utils.UiHelper;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Service;
|
||||
import android.content.ComponentName;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.widget.AbsListView;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.GridView;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import android.widget.AdapterView.AdapterContextMenuInfo;
|
||||
|
||||
public class AppView extends Activity {
|
||||
public class AppView extends Activity implements AdapterFragmentCallbacks {
|
||||
private AppGridAdapter appGridAdapter;
|
||||
private InetAddress ipAddress;
|
||||
private String uniqueId;
|
||||
private boolean remote;
|
||||
private boolean firstLoad = true;
|
||||
|
||||
private final static int RESUME_ID = 1;
|
||||
private String uuidString;
|
||||
|
||||
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 CANCEL_ID = 3;
|
||||
|
||||
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 REMOTE_EXTRA = "Remote";
|
||||
private final static int START_WTIH_QUIT = 4;
|
||||
|
||||
public final static String NAME_EXTRA = "Name";
|
||||
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
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
String locale = PreferenceConfiguration.readPreferences(this).language;
|
||||
if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
|
||||
Configuration config = new Configuration(getResources().getConfiguration());
|
||||
config.locale = new Locale(locale);
|
||||
getResources().updateConfiguration(config, getResources().getDisplayMetrics());
|
||||
}
|
||||
|
||||
setContentView(R.layout.activity_app_view);
|
||||
|
||||
UiHelper.notifyNewRootView(this);
|
||||
|
||||
byte[] address = getIntent().getByteArrayExtra(ADDRESS_EXTRA);
|
||||
uniqueId = getIntent().getStringExtra(UNIQUEID_EXTRA);
|
||||
remote = getIntent().getBooleanExtra(REMOTE_EXTRA, false);
|
||||
if (address == null || uniqueId == null) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
uuidString = getIntent().getStringExtra(UUID_EXTRA);
|
||||
|
||||
String labelText = getResources().getString(R.string.title_applist)+" "+getIntent().getStringExtra(NAME_EXTRA);
|
||||
TextView label = (TextView) findViewById(R.id.appListText);
|
||||
setTitle(labelText);
|
||||
label.setText(labelText);
|
||||
|
||||
try {
|
||||
ipAddress = InetAddress.getByAddress(address);
|
||||
} catch (UnknownHostException e) {
|
||||
e.printStackTrace();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
// Setup the list view
|
||||
GridView appGrid = (GridView) findViewById(R.id.appGridView);
|
||||
try {
|
||||
appGridAdapter = new AppGridAdapter(this, ipAddress, uniqueId);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
appGrid.setAdapter(appGridAdapter);
|
||||
appGrid.setOnItemClickListener(new OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> arg0, View arg1, int pos,
|
||||
long id) {
|
||||
AppObject app = (AppObject) appGridAdapter.getItem(pos);
|
||||
if (app == null || app.app == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only open the context menu if something is running, otherwise start it
|
||||
if (getRunningAppId() != -1) {
|
||||
openContextMenu(arg1);
|
||||
} else {
|
||||
doStart(app.app);
|
||||
}
|
||||
}
|
||||
});
|
||||
registerForContextMenu(appGrid);
|
||||
// Bind to the computer manager service
|
||||
bindService(new Intent(this, ComputerManagerService.class), serviceConnection,
|
||||
Service.BIND_AUTO_CREATE);
|
||||
}
|
||||
|
||||
private void populateAppGridWithCache() {
|
||||
try {
|
||||
// Try to load from cache
|
||||
lastRawApplist = CacheHelper.readInputStreamToString(CacheHelper.openCacheFileForInput(getCacheDir(), "applist", uuidString));
|
||||
List<NvApp> applist = NvHTTP.getAppListByReader(new StringReader(lastRawApplist));
|
||||
updateUiWithAppList(applist);
|
||||
LimeLog.info("Loaded applist from cache");
|
||||
} catch (Exception e) {
|
||||
if (lastRawApplist != null) {
|
||||
LimeLog.warning("Saved applist corrupted: "+lastRawApplist);
|
||||
e.printStackTrace();
|
||||
}
|
||||
LimeLog.info("Loading applist from the network");
|
||||
// We'll need to load from the network
|
||||
loadAppsBlocking();
|
||||
}
|
||||
}
|
||||
|
||||
private void loadAppsBlocking() {
|
||||
blockingLoadSpinner = SpinnerDialog.displayDialog(this, getResources().getString(R.string.applist_refresh_title),
|
||||
getResources().getString(R.string.applist_refresh_msg), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
@@ -112,18 +248,25 @@ public class AppView extends Activity {
|
||||
|
||||
SpinnerDialog.closeDialogs(this);
|
||||
Dialog.closeDialogs();
|
||||
|
||||
if (managerBinder != null) {
|
||||
unbindService(serviceConnection);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
// Display the error message if it was the
|
||||
// first load, but just kill the activity
|
||||
// on subsequent errors
|
||||
updateAppList(firstLoad);
|
||||
firstLoad = false;
|
||||
startComputerUpdates();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
|
||||
stopComputerUpdates();
|
||||
}
|
||||
|
||||
private int getRunningAppId() {
|
||||
int runningAppId = -1;
|
||||
@@ -154,11 +297,11 @@ public class AppView extends Activity {
|
||||
int runningAppId = getRunningAppId();
|
||||
if (runningAppId != -1) {
|
||||
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));
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -168,18 +311,61 @@ public class AppView extends Activity {
|
||||
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
|
||||
public boolean onContextItemSelected(MenuItem item) {
|
||||
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
|
||||
AppObject app = (AppObject) appGridAdapter.getItem(info.position);
|
||||
final AppObject app = (AppObject) appGridAdapter.getItem(info.position);
|
||||
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
|
||||
doStart(app.app);
|
||||
return true;
|
||||
|
||||
case QUIT_ID:
|
||||
doQuit(app.app);
|
||||
// Display a confirmation dialog first
|
||||
displayQuitConfirmationDialog(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
doQuit(app.app);
|
||||
}
|
||||
}, null);
|
||||
return true;
|
||||
|
||||
case CANCEL_ID:
|
||||
@@ -189,62 +375,62 @@ public class AppView extends Activity {
|
||||
return super.onContextItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateAppList(final boolean displayError) {
|
||||
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() {
|
||||
@Override
|
||||
public void run() {
|
||||
appGridAdapter.clear();
|
||||
for (NvApp app : appList) {
|
||||
appGridAdapter.addApp(new AppObject(app));
|
||||
|
||||
private void updateUiWithAppList(final List<NvApp> appList) {
|
||||
AppView.this.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
boolean updated = false;
|
||||
|
||||
for (NvApp app : appList) {
|
||||
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;
|
||||
}
|
||||
|
||||
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();
|
||||
foundExistingApp = true;
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!foundExistingApp) {
|
||||
// This app must be new
|
||||
appGridAdapter.addApp(new AppObject(app));
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
|
||||
if (updated) {
|
||||
appGridAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private void doStart(NvApp app) {
|
||||
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_UNIQUEID, uniqueId);
|
||||
intent.putExtra(Game.EXTRA_STREAMING_REMOTE, remote);
|
||||
intent.putExtra(Game.EXTRA_UNIQUEID, managerBinder.getUniqueId());
|
||||
intent.putExtra(Game.EXTRA_STREAMING_REMOTE,
|
||||
computer.reachability != ComputerDetails.Reachability.LOCAL);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
@@ -256,21 +442,26 @@ public class AppView extends Activity {
|
||||
NvHTTP httpConn;
|
||||
String message;
|
||||
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()) {
|
||||
message = getResources().getString(R.string.applist_quit_success)+" "+app.getAppName();
|
||||
}
|
||||
else {
|
||||
message = getResources().getString(R.string.applist_quit_fail)+" "+app.getAppName();
|
||||
}
|
||||
updateAppList(true);
|
||||
} catch (UnknownHostException e) {
|
||||
message = getResources().getString(R.string.error_unknown_host);
|
||||
} catch (FileNotFoundException e) {
|
||||
message = getResources().getString(R.string.error_404);
|
||||
} catch (Exception e) {
|
||||
message = e.getMessage();
|
||||
}
|
||||
} finally {
|
||||
// Trigger a poll immediately
|
||||
if (poller != null) {
|
||||
poller.pollNow();
|
||||
}
|
||||
}
|
||||
|
||||
final String toastMessage = message;
|
||||
runOnUiThread(new Runnable() {
|
||||
@@ -282,8 +473,38 @@ public class AppView extends Activity {
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
public class AppObject {
|
||||
|
||||
@Override
|
||||
public int getAdapterFragmentLayoutId() {
|
||||
return PreferenceConfiguration.readPreferences(this).listMode ?
|
||||
R.layout.list_view : (PreferenceConfiguration.readPreferences(AppView.this).smallIconMode ?
|
||||
R.layout.app_grid_view_small : R.layout.app_grid_view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receiveAbsListView(AbsListView listView) {
|
||||
listView.setAdapter(appGridAdapter);
|
||||
listView.setOnItemClickListener(new OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> arg0, View arg1, int pos,
|
||||
long id) {
|
||||
AppObject app = (AppObject) appGridAdapter.getItem(pos);
|
||||
if (app == null || app.app == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only open the context menu if something is running, otherwise start it
|
||||
if (getRunningAppId() != -1) {
|
||||
openContextMenu(arg1);
|
||||
} else {
|
||||
doStart(app.app);
|
||||
}
|
||||
}
|
||||
});
|
||||
registerForContextMenu(listView);
|
||||
}
|
||||
|
||||
public class AppObject {
|
||||
public NvApp app;
|
||||
|
||||
public AppObject(NvApp app) {
|
||||
|
||||
@@ -16,18 +16,21 @@ import com.limelight.nvstream.av.video.VideoDecoderRenderer;
|
||||
import com.limelight.nvstream.input.KeyboardPacket;
|
||||
import com.limelight.nvstream.input.MouseButtonPacket;
|
||||
import com.limelight.preferences.PreferenceConfiguration;
|
||||
import com.limelight.ui.GameGestures;
|
||||
import com.limelight.utils.Dialog;
|
||||
import com.limelight.utils.SpinnerDialog;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Point;
|
||||
import android.media.AudioManager;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.SystemClock;
|
||||
import android.view.Display;
|
||||
import android.view.InputDevice;
|
||||
import android.view.KeyEvent;
|
||||
@@ -41,12 +44,15 @@ import android.view.View.OnTouchListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
|
||||
public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
OnGenericMotionListener, OnTouchListener, NvConnectionListener, EvdevListener,
|
||||
OnSystemUiVisibilityChangeListener
|
||||
OnSystemUiVisibilityChangeListener, GameGestures
|
||||
{
|
||||
private int lastMouseX = Integer.MIN_VALUE;
|
||||
private int lastMouseY = Integer.MIN_VALUE;
|
||||
@@ -54,6 +60,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
|
||||
// Only 2 touches are supported
|
||||
private TouchContext[] touchContextMap = new TouchContext[2];
|
||||
private long threeFingerDownTime = 0;
|
||||
|
||||
private static final int THREE_FINGER_TAP_THRESHOLD = 300;
|
||||
|
||||
private ControllerHandler controllerHandler;
|
||||
private KeyboardTranslator keybTranslator;
|
||||
@@ -86,6 +95,13 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
String locale = PreferenceConfiguration.readPreferences(this).language;
|
||||
if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
|
||||
Configuration config = new Configuration(getResources().getConfiguration());
|
||||
config.locale = new Locale(locale);
|
||||
getResources().updateConfiguration(config, getResources().getDisplayMetrics());
|
||||
}
|
||||
|
||||
// We don't want a title bar
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
@@ -177,7 +193,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
// Initialize the connection
|
||||
conn = new NvConnection(host, uniqueId, Game.this, config, PlatformBinding.getCryptoProvider(this));
|
||||
keybTranslator = new KeyboardTranslator(conn);
|
||||
controllerHandler = new ControllerHandler(conn, prefConfig.deadzonePercentage);
|
||||
controllerHandler = new ControllerHandler(conn, this, prefConfig.deadzonePercentage);
|
||||
|
||||
SurfaceHolder sh = sv.getHolder();
|
||||
if (prefConfig.stretchVideo || !decoderRenderer.isHardwareAccelerated()) {
|
||||
@@ -204,14 +220,24 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
|
||||
private void resizeSurfaceWithAspectRatio(SurfaceView sv, double vidWidth, double vidHeight)
|
||||
{
|
||||
// Get the visible width of the activity
|
||||
double visibleWidth = getWindow().getDecorView().getWidth();
|
||||
|
||||
ViewGroup.LayoutParams lp = sv.getLayoutParams();
|
||||
|
||||
// Calculate the new size of the SurfaceView
|
||||
lp.width = (int) visibleWidth;
|
||||
lp.height = (int) ((vidHeight / vidWidth) * visibleWidth);
|
||||
// Get the visible width of the activity
|
||||
double visibleWidth = getWindow().getDecorView().getWidth();
|
||||
double visibleHeight = getWindow().getDecorView().getHeight();
|
||||
|
||||
ViewGroup.LayoutParams lp = sv.getLayoutParams();
|
||||
|
||||
double vidRatio = vidHeight / vidWidth;
|
||||
double visibleRatio = visibleHeight / visibleWidth;
|
||||
|
||||
// Calculate the new size of the SurfaceView
|
||||
if (vidRatio < visibleRatio) {
|
||||
lp.width = (int) visibleWidth;
|
||||
lp.height = (int) ((vidHeight / vidWidth) * visibleWidth);
|
||||
}
|
||||
else {
|
||||
lp.width = (int) ((vidWidth / vidHeight) * visibleHeight);
|
||||
lp.height = (int) visibleHeight;
|
||||
}
|
||||
|
||||
// Apply the size change
|
||||
sv.setLayoutParams(lp);
|
||||
@@ -468,6 +494,13 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showKeyboard() {
|
||||
LimeLog.info("Showing keyboard overlay");
|
||||
InputMethodManager inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
inputManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY);
|
||||
}
|
||||
|
||||
// Returns true if the event was consumed
|
||||
private boolean handleMotionEvent(MotionEvent event) {
|
||||
// Pass through keyboard input if we're not grabbing
|
||||
@@ -489,7 +522,22 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
int actionIndex = event.getActionIndex();
|
||||
|
||||
int eventX = (int)event.getX(actionIndex);
|
||||
int eventY = (int)event.getY(actionIndex);
|
||||
int eventY = (int)event.getY(actionIndex);
|
||||
|
||||
// Special handling for 3 finger gesture
|
||||
if (event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN &&
|
||||
event.getPointerCount() == 3) {
|
||||
// Three fingers down
|
||||
threeFingerDownTime = SystemClock.uptimeMillis();
|
||||
|
||||
// Cancel the first and second touches to avoid
|
||||
// erroneous events
|
||||
for (TouchContext aTouchContext : touchContextMap) {
|
||||
aTouchContext.cancelTouch();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
TouchContext context = getTouchContext(actionIndex);
|
||||
if (context == null) {
|
||||
@@ -504,8 +552,16 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
break;
|
||||
case MotionEvent.ACTION_POINTER_UP:
|
||||
case MotionEvent.ACTION_UP:
|
||||
if (event.getPointerCount() == 1) {
|
||||
// All fingers up
|
||||
if (SystemClock.uptimeMillis() - threeFingerDownTime < THREE_FINGER_TAP_THRESHOLD) {
|
||||
// This is a 3 finger tap to bring up the keyboard
|
||||
showKeyboard();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
context.touchUpEvent(eventX, eventY);
|
||||
if (actionIndex == 0 && event.getPointerCount() > 1) {
|
||||
if (actionIndex == 0 && event.getPointerCount() > 1 && !context.isCancelled()) {
|
||||
// The original secondary touch now becomes primary
|
||||
context.touchDownEvent((int)event.getX(1), (int)event.getY(1));
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Locale;
|
||||
|
||||
import com.limelight.binding.PlatformBinding;
|
||||
import com.limelight.binding.crypto.AndroidCryptoProvider;
|
||||
@@ -16,11 +17,15 @@ import com.limelight.nvstream.http.PairingManager;
|
||||
import com.limelight.nvstream.http.PairingManager.PairState;
|
||||
import com.limelight.nvstream.wol.WakeOnLanSender;
|
||||
import com.limelight.preferences.AddComputerManually;
|
||||
import com.limelight.preferences.PreferenceConfiguration;
|
||||
import com.limelight.preferences.StreamSettings;
|
||||
import com.limelight.ui.AdapterFragment;
|
||||
import com.limelight.ui.AdapterFragmentCallbacks;
|
||||
import com.limelight.utils.Dialog;
|
||||
import com.limelight.utils.UiHelper;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.FragmentTransaction;
|
||||
import android.app.Service;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
@@ -35,15 +40,15 @@ import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.AbsListView;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.GridView;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.Toast;
|
||||
import android.widget.AdapterView.AdapterContextMenuInfo;
|
||||
|
||||
public class PcView extends Activity {
|
||||
public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
private RelativeLayout noPcFoundLayout;
|
||||
private PcGridAdapter pcGridAdapter;
|
||||
private ComputerManagerService.ComputerManagerBinder managerBinder;
|
||||
@@ -102,27 +107,6 @@ public class PcView extends Activity {
|
||||
ImageButton settingsButton = (ImageButton) findViewById(R.id.settingsButton);
|
||||
ImageButton addComputerButton = (ImageButton) findViewById(R.id.manuallyAddPc);
|
||||
|
||||
GridView pcGrid = (GridView) findViewById(R.id.pcGridView);
|
||||
pcGrid.setAdapter(pcGridAdapter);
|
||||
pcGrid.setOnItemClickListener(new OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> arg0, View arg1, int pos,
|
||||
long id) {
|
||||
ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(pos);
|
||||
if (computer.details.reachability == ComputerDetails.Reachability.UNKNOWN) {
|
||||
// Do nothing
|
||||
} else if (computer.details.reachability == ComputerDetails.Reachability.OFFLINE) {
|
||||
// Open the context menu if a PC is offline
|
||||
openContextMenu(arg1);
|
||||
} else if (computer.details.pairState != PairState.PAIRED) {
|
||||
// Pair an unpaired machine by default
|
||||
doPair(computer.details);
|
||||
} else {
|
||||
doAppList(computer.details);
|
||||
}
|
||||
}
|
||||
});
|
||||
registerForContextMenu(pcGrid);
|
||||
settingsButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
@@ -137,6 +121,10 @@ public class PcView extends Activity {
|
||||
}
|
||||
});
|
||||
|
||||
getFragmentManager().beginTransaction()
|
||||
.replace(R.id.pcFragmentContainer, new AdapterFragment())
|
||||
.commitAllowingStateLoss();
|
||||
|
||||
noPcFoundLayout = (RelativeLayout) findViewById(R.id.no_pc_found_layout);
|
||||
if (pcGridAdapter.getCount() == 0) {
|
||||
noPcFoundLayout.setVisibility(View.VISIBLE);
|
||||
@@ -150,12 +138,21 @@ public class PcView extends Activity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
String locale = PreferenceConfiguration.readPreferences(this).language;
|
||||
if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
|
||||
Configuration config = new Configuration(getResources().getConfiguration());
|
||||
config.locale = new Locale(locale);
|
||||
getResources().updateConfiguration(config, getResources().getDisplayMetrics());
|
||||
}
|
||||
|
||||
// Bind to the computer manager service
|
||||
bindService(new Intent(PcView.this, ComputerManagerService.class), serviceConnection,
|
||||
Service.BIND_AUTO_CREATE);
|
||||
|
||||
pcGridAdapter = new PcGridAdapter(this);
|
||||
pcGridAdapter = new PcGridAdapter(this,
|
||||
PreferenceConfiguration.readPreferences(this).listMode,
|
||||
PreferenceConfiguration.readPreferences(this).smallIconMode);
|
||||
|
||||
initializeViews();
|
||||
}
|
||||
@@ -472,16 +469,7 @@ public class PcView extends Activity {
|
||||
|
||||
Intent i = new Intent(this, AppView.class);
|
||||
i.putExtra(AppView.NAME_EXTRA, computer.name);
|
||||
i.putExtra(AppView.UNIQUEID_EXTRA, managerBinder.getUniqueId());
|
||||
|
||||
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);
|
||||
}
|
||||
i.putExtra(AppView.UUID_EXTRA, computer.uuid.toString());
|
||||
startActivity(i);
|
||||
}
|
||||
|
||||
@@ -561,8 +549,39 @@ public class PcView extends Activity {
|
||||
// Notify the view that the data has changed
|
||||
pcGridAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public class ComputerObject {
|
||||
|
||||
@Override
|
||||
public int getAdapterFragmentLayoutId() {
|
||||
return PreferenceConfiguration.readPreferences(this).listMode ?
|
||||
R.layout.list_view : (PreferenceConfiguration.readPreferences(this).smallIconMode ?
|
||||
R.layout.pc_grid_view_small : R.layout.pc_grid_view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receiveAbsListView(AbsListView listView) {
|
||||
listView.setAdapter(pcGridAdapter);
|
||||
listView.setOnItemClickListener(new OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> arg0, View arg1, int pos,
|
||||
long id) {
|
||||
ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(pos);
|
||||
if (computer.details.reachability == ComputerDetails.Reachability.UNKNOWN) {
|
||||
// Do nothing
|
||||
} else if (computer.details.reachability == ComputerDetails.Reachability.OFFLINE) {
|
||||
// Open the context menu if a PC is offline
|
||||
openContextMenu(arg1);
|
||||
} else if (computer.details.pairState != PairState.PAIRED) {
|
||||
// Pair an unpaired machine by default
|
||||
doPair(computer.details);
|
||||
} else {
|
||||
doAppList(computer.details);
|
||||
}
|
||||
}
|
||||
});
|
||||
registerForContextMenu(listView);
|
||||
}
|
||||
|
||||
public class ComputerObject {
|
||||
public ComputerDetails details;
|
||||
|
||||
public ComputerObject(ComputerDetails details) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import android.view.MotionEvent;
|
||||
import com.limelight.LimeLog;
|
||||
import com.limelight.nvstream.NvConnection;
|
||||
import com.limelight.nvstream.input.ControllerPacket;
|
||||
import com.limelight.ui.GameGestures;
|
||||
import com.limelight.utils.Vector2d;
|
||||
|
||||
public class ControllerHandler {
|
||||
@@ -30,6 +31,9 @@ public class ControllerHandler {
|
||||
private long lastLbUpTime = 0;
|
||||
private long lastRbUpTime = 0;
|
||||
private static final int MAXIMUM_BUMPER_UP_DELAY_MS = 100;
|
||||
|
||||
private long startDownTime = 0;
|
||||
private static final int START_DOWN_TIME_KEYB_MS = 750;
|
||||
|
||||
private static final int MINIMUM_BUTTON_DOWN_TIME_MS = 25;
|
||||
|
||||
@@ -46,10 +50,12 @@ public class ControllerHandler {
|
||||
private NvConnection conn;
|
||||
private double stickDeadzone;
|
||||
private final ControllerMapping defaultMapping = new ControllerMapping();
|
||||
private GameGestures gestures;
|
||||
private boolean hasGameController;
|
||||
|
||||
public ControllerHandler(NvConnection conn, int deadzonePercentage) {
|
||||
public ControllerHandler(NvConnection conn, GameGestures gestures, int deadzonePercentage) {
|
||||
this.conn = conn;
|
||||
this.gestures = gestures;
|
||||
|
||||
// HACK: For now we're hardcoding a 10% deadzone. Some deadzone
|
||||
// is required for controller batching support to work.
|
||||
@@ -513,6 +519,9 @@ public class ControllerHandler {
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_START:
|
||||
case KeyEvent.KEYCODE_MENU:
|
||||
if (SystemClock.uptimeMillis() - startDownTime > ControllerHandler.START_DOWN_TIME_KEYB_MS) {
|
||||
gestures.showKeyboard();
|
||||
}
|
||||
inputMap &= ~ControllerPacket.PLAY_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BACK:
|
||||
@@ -621,6 +630,9 @@ public class ControllerHandler {
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BUTTON_START:
|
||||
case KeyEvent.KEYCODE_MENU:
|
||||
if (event.getRepeatCount() == 0) {
|
||||
startDownTime = SystemClock.uptimeMillis();
|
||||
}
|
||||
inputMap |= ControllerPacket.PLAY_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BACK:
|
||||
|
||||
@@ -9,6 +9,7 @@ public class TouchContext {
|
||||
private int originalTouchX = 0;
|
||||
private int originalTouchY = 0;
|
||||
private long originalTouchTime = 0;
|
||||
private boolean cancelled;
|
||||
|
||||
private NvConnection conn;
|
||||
private int actionIndex;
|
||||
@@ -56,12 +57,17 @@ public class TouchContext {
|
||||
originalTouchX = lastTouchX = eventX;
|
||||
originalTouchY = lastTouchY = eventY;
|
||||
originalTouchTime = System.currentTimeMillis();
|
||||
|
||||
return true;
|
||||
cancelled = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void touchUpEvent(int eventX, int eventY)
|
||||
{
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isTap())
|
||||
{
|
||||
byte buttonIndex = getMouseButtonIndex();
|
||||
@@ -81,8 +87,8 @@ public class TouchContext {
|
||||
}
|
||||
|
||||
public boolean touchMoveEvent(int eventX, int eventY)
|
||||
{
|
||||
if (eventX != lastTouchX || eventY != lastTouchY)
|
||||
{
|
||||
if (eventX != lastTouchX || eventY != lastTouchY)
|
||||
{
|
||||
// We only send moves for the primary touch point
|
||||
if (actionIndex == 0) {
|
||||
@@ -102,4 +108,12 @@ public class TouchContext {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void cancelTouch() {
|
||||
cancelled = true;
|
||||
}
|
||||
|
||||
public boolean isCancelled() {
|
||||
return cancelled;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ public class EvdevReader {
|
||||
// 4.4 and later to do live SELinux policy changes.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
EvdevShell shell = EvdevShell.getInstance();
|
||||
shell.runCommand("supolicy --live \"allow untrusted_app input_device dir getattr\" " +
|
||||
shell.runCommand("supolicy --live \"allow untrusted_app input_device dir { getattr read search }\" " +
|
||||
"\"allow untrusted_app input_device chr_file { open read write ioctl }\"");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
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.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import com.limelight.LimeLog;
|
||||
import com.limelight.binding.PlatformBinding;
|
||||
import com.limelight.discovery.DiscoveryService;
|
||||
import com.limelight.nvstream.http.ComputerDetails;
|
||||
import com.limelight.nvstream.http.NvApp;
|
||||
import com.limelight.nvstream.http.NvHTTP;
|
||||
import com.limelight.nvstream.mdns.MdnsComputer;
|
||||
import com.limelight.nvstream.mdns.MdnsDiscoveryListener;
|
||||
import com.limelight.utils.CacheHelper;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.ComponentName;
|
||||
@@ -19,6 +27,8 @@ import android.content.ServiceConnection;
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
public class ComputerManagerService extends Service {
|
||||
private static final int POLLING_PERIOD_MS = 3000;
|
||||
private static final int MDNS_QUERY_PERIOD_MS = 1000;
|
||||
@@ -179,10 +189,26 @@ public class ComputerManagerService extends Service {
|
||||
// Just call the unbind handler to cleanup
|
||||
ComputerManagerService.this.onUnbind(null);
|
||||
}
|
||||
|
||||
public ApplistPoller createAppListPoller(ComputerDetails computer) {
|
||||
return new ApplistPoller(computer);
|
||||
}
|
||||
|
||||
public String getUniqueId() {
|
||||
return idManager.getUniqueId();
|
||||
}
|
||||
|
||||
public ComputerDetails getComputer(UUID uuid) {
|
||||
synchronized (pollingTuples) {
|
||||
for (PollingTuple tuple : pollingTuples) {
|
||||
if (uuid.equals(tuple.computer.uuid)) {
|
||||
return tuple.computer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -251,6 +277,11 @@ public class ComputerManagerService extends Service {
|
||||
((!details.name.isEmpty() && !tuple.computer.name.isEmpty()) &&
|
||||
tuple.computer.name.equals(details.name))) {
|
||||
|
||||
// Update details anyway in case this machine has been re-added by IP
|
||||
// after not being reachable by our existing information
|
||||
tuple.computer.localIp = details.localIp;
|
||||
tuple.computer.remoteIp = details.remoteIp;
|
||||
|
||||
// Start a polling thread if polling is active
|
||||
if (pollingActive && tuple.thread == null) {
|
||||
tuple.thread = createPollingThread(details);
|
||||
@@ -457,6 +488,107 @@ public class ComputerManagerService extends Service {
|
||||
public IBinder onBind(Intent intent) {
|
||||
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 {
|
||||
|
||||
@@ -1,16 +1,31 @@
|
||||
package com.limelight.grid;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.os.AsyncTask;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.koushikdutta.async.future.FutureCallback;
|
||||
import com.koushikdutta.ion.ImageViewBitmapInfo;
|
||||
import com.koushikdutta.ion.Ion;
|
||||
import com.limelight.AppView;
|
||||
import com.limelight.LimeLog;
|
||||
import com.limelight.R;
|
||||
import com.limelight.binding.PlatformBinding;
|
||||
import com.limelight.nvstream.http.ComputerDetails;
|
||||
import com.limelight.nvstream.http.LimelightCryptoProvider;
|
||||
import com.limelight.utils.CacheHelper;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
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.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.security.KeyManagementException;
|
||||
@@ -18,7 +33,11 @@ import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Principal;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
@@ -32,16 +51,16 @@ import java.security.cert.X509Certificate;
|
||||
|
||||
public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
|
||||
|
||||
private InetAddress address;
|
||||
private ComputerDetails computer;
|
||||
private String uniqueId;
|
||||
private LimelightCryptoProvider cryptoProvider;
|
||||
private SSLContext sslContext;
|
||||
private final HashMap<ImageView, Future> pendingRequests = new HashMap<ImageView, Future>();
|
||||
|
||||
public AppGridAdapter(Context context, InetAddress address, String uniqueId) throws NoSuchAlgorithmException, KeyManagementException {
|
||||
super(context, R.layout.app_grid_item, R.drawable.image_loading);
|
||||
public AppGridAdapter(Context context, boolean listMode, boolean small, ComputerDetails computer, String uniqueId) throws NoSuchAlgorithmException, KeyManagementException {
|
||||
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;
|
||||
|
||||
cryptoProvider = PlatformBinding.getCryptoProvider(context);
|
||||
@@ -94,8 +113,27 @@ public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
|
||||
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) {
|
||||
itemList.add(app);
|
||||
sortList();
|
||||
}
|
||||
|
||||
public void abortPendingRequests() {
|
||||
@@ -107,7 +145,9 @@ public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
|
||||
}
|
||||
|
||||
for (Future f : tempMap.values()) {
|
||||
f.cancel(true);
|
||||
if (!f.isCancelled() && !f.isDone()) {
|
||||
f.cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
synchronized (pendingRequests) {
|
||||
@@ -118,30 +158,25 @@ public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean populateImageView(final ImageView imgView, AppView.AppObject obj) {
|
||||
|
||||
// Set SSL contexts correctly to allow us to authenticate
|
||||
Ion.getDefault(imgView.getContext()).getHttpClient().getSSLSocketMiddleware().setTrustManagers(trustAllCerts);
|
||||
Ion.getDefault(imgView.getContext()).getHttpClient().getSSLSocketMiddleware().setSSLContext(sslContext);
|
||||
|
||||
// Set off the deferred image load
|
||||
synchronized (pendingRequests) {
|
||||
Future f = Ion.with(imgView)
|
||||
.placeholder(defaultImageRes)
|
||||
.error(defaultImageRes)
|
||||
.load("https://" + address.getHostAddress() + ":47984/appasset?uniqueid=" + uniqueId + "&appid=" +
|
||||
obj.app.getAppId() + "&AssetType=2&AssetIdx=0")
|
||||
.setCallback(new FutureCallback<ImageView>() {
|
||||
@Override
|
||||
public void onCompleted(Exception e, ImageView result) {
|
||||
synchronized (pendingRequests) {
|
||||
pendingRequests.remove(imgView);
|
||||
}
|
||||
}
|
||||
});
|
||||
pendingRequests.put(imgView, f);
|
||||
// TODO: Handle pruning of bitmap cache
|
||||
private void populateBitmapCache(UUID uuid, int appId, Bitmap bitmap) {
|
||||
try {
|
||||
// PNG ignores quality setting
|
||||
FileOutputStream out = CacheHelper.openCacheFileForOutput(context.getCacheDir(), "boxart", uuid.toString(), appId+".png");
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 0, out);
|
||||
out.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean populateImageView(final ImageView imgView, final AppView.AppObject obj) {
|
||||
// Hide the image view while we're loading the image from disk cache
|
||||
imgView.setVisibility(View.INVISIBLE);
|
||||
|
||||
// Check the on-disk cache
|
||||
new ImageCacheRequest(imgView, obj.app.getAppId()).execute();
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -166,4 +201,84 @@ public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
|
||||
// No overlay
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -60,17 +60,21 @@ public abstract class GenericGridAdapter<T> extends BaseAdapter {
|
||||
ImageView overlayView = (ImageView) convertView.findViewById(R.id.grid_overlay);
|
||||
TextView txtView = (TextView) convertView.findViewById(R.id.grid_text);
|
||||
|
||||
if (!populateImageView(imgView, itemList.get(i))) {
|
||||
imgView.setImageResource(defaultImageRes);
|
||||
if (imgView != null) {
|
||||
if (!populateImageView(imgView, itemList.get(i))) {
|
||||
imgView.setImageResource(defaultImageRes);
|
||||
}
|
||||
}
|
||||
if (!populateTextView(txtView, itemList.get(i))) {
|
||||
txtView.setText(itemList.get(i).toString());
|
||||
}
|
||||
if (!populateOverlayView(overlayView, itemList.get(i))) {
|
||||
overlayView.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
else {
|
||||
overlayView.setVisibility(View.VISIBLE);
|
||||
if (overlayView != null) {
|
||||
if (!populateOverlayView(overlayView, itemList.get(i))) {
|
||||
overlayView.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
else {
|
||||
overlayView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
return convertView;
|
||||
|
||||
@@ -8,14 +8,27 @@ import com.limelight.PcView;
|
||||
import com.limelight.R;
|
||||
import com.limelight.nvstream.http.ComputerDetails;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
|
||||
public class PcGridAdapter extends GenericGridAdapter<PcView.ComputerObject> {
|
||||
|
||||
public PcGridAdapter(Context context) {
|
||||
super(context, R.layout.pc_grid_item, R.drawable.computer);
|
||||
public PcGridAdapter(Context context, boolean listMode, boolean small) {
|
||||
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) {
|
||||
itemList.add(computer);
|
||||
sortList();
|
||||
}
|
||||
|
||||
private void sortList() {
|
||||
Collections.sort(itemList, new Comparator<PcView.ComputerObject>() {
|
||||
@Override
|
||||
public int compare(PcView.ComputerObject lhs, PcView.ComputerObject rhs) {
|
||||
return lhs.details.name.compareTo(rhs.details.name);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public boolean removeComputer(PcView.ComputerObject computer) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.limelight.preferences;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
import com.limelight.computers.ComputerManagerService;
|
||||
@@ -15,6 +16,7 @@ import android.app.Service;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.view.KeyEvent;
|
||||
@@ -132,6 +134,13 @@ public class AddComputerManually extends Activity {
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
String locale = PreferenceConfiguration.readPreferences(this).language;
|
||||
if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
|
||||
Configuration config = new Configuration(getResources().getConfiguration());
|
||||
config.locale = new Locale(locale);
|
||||
getResources().updateConfiguration(config, getResources().getDisplayMetrics());
|
||||
}
|
||||
|
||||
setContentView(R.layout.activity_add_computer_manually);
|
||||
|
||||
UiHelper.notifyNewRootView(this);
|
||||
@@ -150,7 +159,7 @@ public class AddComputerManually extends Activity {
|
||||
return true;
|
||||
}
|
||||
|
||||
computersToAdd.add(hostText.getText().toString());
|
||||
computersToAdd.add(hostText.getText().toString().trim());
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -5,7 +5,9 @@ import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
public class PreferenceConfiguration {
|
||||
static final String RES_FPS_PREF_STRING = "list_resolution_fps";
|
||||
static final String RESX_PREF_STRING = "resolution_x";
|
||||
static final String RESY_PREF_STRING = "resolution_y";
|
||||
private static final String FPS_PREF_STRING = "fps";
|
||||
private static final String DECODER_PREF_STRING = "list_decoders";
|
||||
static final String BITRATE_PREF_STRING = "seekbar_bitrate";
|
||||
private static final String STRETCH_PREF_STRING = "checkbox_stretch_video";
|
||||
@@ -13,20 +15,27 @@ public class PreferenceConfiguration {
|
||||
private static final String DISABLE_TOASTS_PREF_STRING = "checkbox_disable_warnings";
|
||||
private static final String HOST_AUDIO_PREF_STRING = "checkbox_host_audio";
|
||||
private static final String DEADZONE_PREF_STRING = "seekbar_deadzone";
|
||||
private static final String LANGUAGE_PREF_STRING = "list_languages";
|
||||
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 int BITRATE_DEFAULT_720_30 = 5;
|
||||
private static final int BITRATE_DEFAULT_720_60 = 10;
|
||||
private static final int BITRATE_DEFAULT_1080_30 = 10;
|
||||
private static final int BITRATE_DEFAULT_1080_60 = 20;
|
||||
private static final int BITRATE_DEFAULT_MAX = 35;
|
||||
|
||||
private static final String DEFAULT_RES_FPS = "720p60";
|
||||
private static final int DEFAULT_RESX = 1280;
|
||||
private static final int DEFAULT_RESY = 720;
|
||||
private static final String DEFAULT_FPS = "60";
|
||||
private static final String DEFAULT_DECODER = "auto";
|
||||
private static final int DEFAULT_BITRATE = BITRATE_DEFAULT_720_60;
|
||||
private static final boolean DEFAULT_STRETCH = false;
|
||||
private static final boolean DEFAULT_SOPS = true;
|
||||
private static final boolean DEFAULT_DISABLE_TOASTS = false;
|
||||
private static final boolean DEFAULT_HOST_AUDIO = false;
|
||||
private static final int DEFAULT_DEADZONE = 15;
|
||||
public static final String DEFAULT_LANGUAGE = "default";
|
||||
private static final boolean DEFAULT_LIST_MODE = false;
|
||||
|
||||
public static final int FORCE_HARDWARE_DECODER = -1;
|
||||
public static final int AUTOSELECT_DECODER = 0;
|
||||
@@ -37,45 +46,43 @@ public class PreferenceConfiguration {
|
||||
public int decoder;
|
||||
public int deadzonePercentage;
|
||||
public boolean stretchVideo, enableSops, playHostAudio, disableWarnings;
|
||||
public String language;
|
||||
public boolean listMode, smallIconMode;
|
||||
|
||||
public static int getDefaultBitrate(String resFpsString) {
|
||||
if (resFpsString.equals("720p30")) {
|
||||
return BITRATE_DEFAULT_720_30;
|
||||
}
|
||||
else if (resFpsString.equals("720p60")) {
|
||||
return BITRATE_DEFAULT_720_60;
|
||||
}
|
||||
else if (resFpsString.equals("1080p30")) {
|
||||
return BITRATE_DEFAULT_1080_30;
|
||||
}
|
||||
else if (resFpsString.equals("1080p60")) {
|
||||
return BITRATE_DEFAULT_1080_60;
|
||||
}
|
||||
else {
|
||||
// Should never get here
|
||||
return DEFAULT_BITRATE;
|
||||
public static boolean getDefaultSmallMode(Context context) {
|
||||
// Use small mode on anything smaller than a 7" tablet
|
||||
return context.getResources().getConfiguration().smallestScreenWidthDp < 600;
|
||||
}
|
||||
|
||||
private static int getFpsInt(SharedPreferences prefs) {
|
||||
try {
|
||||
return Integer.parseInt(prefs.getString(FPS_PREF_STRING, DEFAULT_FPS));
|
||||
} catch (NumberFormatException e) {
|
||||
return Integer.parseInt(DEFAULT_FPS);
|
||||
}
|
||||
}
|
||||
|
||||
public static int getDefaultBitrate(Context context) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
|
||||
String str = prefs.getString(RES_FPS_PREF_STRING, DEFAULT_RES_FPS);
|
||||
if (str.equals("720p30")) {
|
||||
long width = prefs.getInt(RESX_PREF_STRING, DEFAULT_RESX);
|
||||
long height = prefs.getInt(RESY_PREF_STRING, DEFAULT_RESY);
|
||||
|
||||
long product = width * height * getFpsInt(prefs);
|
||||
if (product <= 1280 * 720 * 30) {
|
||||
return BITRATE_DEFAULT_720_30;
|
||||
}
|
||||
else if (str.equals("720p60")) {
|
||||
else if (product <= 1280 * 720 * 60) {
|
||||
return BITRATE_DEFAULT_720_60;
|
||||
}
|
||||
else if (str.equals("1080p30")) {
|
||||
else if (product <= 1920 * 1080 * 30) {
|
||||
return BITRATE_DEFAULT_1080_30;
|
||||
}
|
||||
else if (str.equals("1080p60")) {
|
||||
else if (product <= 1920 * 1080 * 60) {
|
||||
return BITRATE_DEFAULT_1080_60;
|
||||
}
|
||||
else {
|
||||
// Should never get here
|
||||
return DEFAULT_BITRATE;
|
||||
return BITRATE_DEFAULT_MAX;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,43 +110,23 @@ public class PreferenceConfiguration {
|
||||
PreferenceConfiguration config = new PreferenceConfiguration();
|
||||
|
||||
config.bitrate = prefs.getInt(BITRATE_PREF_STRING, getDefaultBitrate(context));
|
||||
String str = prefs.getString(RES_FPS_PREF_STRING, DEFAULT_RES_FPS);
|
||||
if (str.equals("720p30")) {
|
||||
config.width = 1280;
|
||||
config.height = 720;
|
||||
config.fps = 30;
|
||||
}
|
||||
else if (str.equals("720p60")) {
|
||||
config.width = 1280;
|
||||
config.height = 720;
|
||||
config.fps = 60;
|
||||
}
|
||||
else if (str.equals("1080p30")) {
|
||||
config.width = 1920;
|
||||
config.height = 1080;
|
||||
config.fps = 30;
|
||||
}
|
||||
else if (str.equals("1080p60")) {
|
||||
config.width = 1920;
|
||||
config.height = 1080;
|
||||
config.fps = 60;
|
||||
}
|
||||
else {
|
||||
// Should never get here
|
||||
config.width = 1280;
|
||||
config.height = 720;
|
||||
config.fps = 60;
|
||||
}
|
||||
config.width = prefs.getInt(RESX_PREF_STRING, DEFAULT_RESX);
|
||||
config.height = prefs.getInt(RESY_PREF_STRING, DEFAULT_RESY);
|
||||
config.fps = getFpsInt(prefs);
|
||||
|
||||
config.decoder = getDecoderValue(context);
|
||||
|
||||
config.deadzonePercentage = prefs.getInt(DEADZONE_PREF_STRING, DEFAULT_DEADZONE);
|
||||
|
||||
config.language = prefs.getString(LANGUAGE_PREF_STRING, DEFAULT_LANGUAGE);
|
||||
|
||||
// Checkbox preferences
|
||||
config.disableWarnings = prefs.getBoolean(DISABLE_TOASTS_PREF_STRING, DEFAULT_DISABLE_TOASTS);
|
||||
config.enableSops = prefs.getBoolean(SOPS_PREF_STRING, DEFAULT_SOPS);
|
||||
config.stretchVideo = prefs.getBoolean(STRETCH_PREF_STRING, DEFAULT_STRETCH);
|
||||
config.playHostAudio = prefs.getBoolean(HOST_AUDIO_PREF_STRING, DEFAULT_HOST_AUDIO);
|
||||
config.listMode = prefs.getBoolean(LIST_MODE_PREF_STRING, DEFAULT_LIST_MODE);
|
||||
config.smallIconMode = prefs.getBoolean(SMALL_ICONS_PREF_STRING, getDefaultSmallMode(context));
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
package com.limelight.preferences;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.DialogPreference;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.RadioButton;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.limelight.R;
|
||||
|
||||
public class ResolutionPreference extends DialogPreference {
|
||||
private RadioButton a4_3;
|
||||
private RadioButton a16_9;
|
||||
private RadioButton a16_10;
|
||||
private SeekBar resolutionSeekBar;
|
||||
private TextView resolutionText;
|
||||
|
||||
private int resolutionX;
|
||||
private int resolutionY;
|
||||
|
||||
public ResolutionPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
setDialogLayoutResource(R.layout.dialog_resolution);
|
||||
setPositiveButtonText(android.R.string.ok);
|
||||
setNegativeButtonText(android.R.string.cancel);
|
||||
|
||||
setDialogIcon(null);
|
||||
}
|
||||
|
||||
private int getPreferenceState() {
|
||||
int checkboxState;
|
||||
int sliderState;
|
||||
|
||||
if (a4_3.isChecked()) {
|
||||
checkboxState = 1;
|
||||
}
|
||||
else if (a16_10.isChecked()) {
|
||||
checkboxState = 2;
|
||||
}
|
||||
else {
|
||||
checkboxState = 0;
|
||||
}
|
||||
|
||||
sliderState = resolutionSeekBar.getProgress();
|
||||
|
||||
return checkboxState << 24 | sliderState;
|
||||
}
|
||||
|
||||
private void setPreferenceState(int state) {
|
||||
int checkboxState;
|
||||
int sliderState;
|
||||
|
||||
checkboxState = state >> 24;
|
||||
sliderState = state & 0x00FFFFFF;
|
||||
|
||||
a4_3.setChecked(false);
|
||||
a16_10.setChecked(false);
|
||||
a16_9.setChecked(false);
|
||||
|
||||
if (checkboxState == 1) {
|
||||
a4_3.setChecked(true);
|
||||
}
|
||||
else if (checkboxState == 2) {
|
||||
a16_10.setChecked(true);
|
||||
}
|
||||
else {
|
||||
a16_9.setChecked(true);
|
||||
}
|
||||
|
||||
resolutionSeekBar.setProgress(sliderState);
|
||||
}
|
||||
|
||||
private void updateResolution() {
|
||||
double aspectFactor;
|
||||
int step = resolutionSeekBar.getProgress() + 1;
|
||||
|
||||
if (a4_3.isChecked()) {
|
||||
aspectFactor = 4.0/3.0;
|
||||
|
||||
switch (step) {
|
||||
case 1:
|
||||
// 640x480
|
||||
resolutionY = 480;
|
||||
break;
|
||||
case 2:
|
||||
// 800x600
|
||||
resolutionY = 600;
|
||||
break;
|
||||
case 3:
|
||||
// 1024x768
|
||||
resolutionY = 768;
|
||||
break;
|
||||
case 4:
|
||||
// 1600x1200
|
||||
resolutionY = 1200;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (a16_9.isChecked()) {
|
||||
aspectFactor = 16.0/9.0;
|
||||
|
||||
switch (step) {
|
||||
case 1:
|
||||
// 1280x720
|
||||
resolutionY = 720;
|
||||
break;
|
||||
case 2:
|
||||
// 1920x1080
|
||||
resolutionY = 1080;
|
||||
break;
|
||||
case 3:
|
||||
// 2560x1440
|
||||
resolutionY = 1440;
|
||||
break;
|
||||
case 4:
|
||||
// 3840x2160
|
||||
resolutionY = 2160;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else /* if (a16_10.isChecked() */ {
|
||||
aspectFactor = 16.0/10.0;
|
||||
|
||||
switch (step) {
|
||||
case 1:
|
||||
// 1280x800
|
||||
resolutionY = 800;
|
||||
break;
|
||||
case 2:
|
||||
// 1920x1200
|
||||
resolutionY = 1200;
|
||||
break;
|
||||
case 3:
|
||||
// 2560x1600
|
||||
resolutionY = 1600;
|
||||
break;
|
||||
case 4:
|
||||
// 3840x2400
|
||||
resolutionY = 2400;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
resolutionX = (int)(resolutionY * aspectFactor);
|
||||
|
||||
resolutionText.setText(resolutionX+"x"+resolutionY);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDialogClosed(boolean positiveResult) {
|
||||
if (positiveResult) {
|
||||
SharedPreferences.Editor e = getPreferenceManager().getSharedPreferences().edit();
|
||||
e.putInt(PreferenceConfiguration.RESX_PREF_STRING, resolutionX);
|
||||
e.putInt(PreferenceConfiguration.RESY_PREF_STRING, resolutionY);
|
||||
persistInt(getPreferenceState());
|
||||
e.commit();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View onCreateDialogView() {
|
||||
View v = super.onCreateDialogView();
|
||||
|
||||
a4_3 = (RadioButton) v.findViewById(R.id.aspect_4_3);
|
||||
a16_9 = (RadioButton) v.findViewById(R.id.aspect_16_9);
|
||||
a16_10 = (RadioButton) v.findViewById(R.id.aspect_16_10);
|
||||
resolutionText = (TextView) v.findViewById(R.id.resolutionText);
|
||||
resolutionSeekBar = (SeekBar) v.findViewById(R.id.resolutionSeekbar);
|
||||
|
||||
CompoundButton.OnCheckedChangeListener ccListener = new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
updateResolution();
|
||||
}
|
||||
};
|
||||
a4_3.setOnCheckedChangeListener(ccListener);
|
||||
a16_9.setOnCheckedChangeListener(ccListener);
|
||||
a16_10.setOnCheckedChangeListener(ccListener);
|
||||
|
||||
resolutionSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
updateResolution();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
}
|
||||
});
|
||||
|
||||
// Check the correct checkbox
|
||||
setPreferenceState(getPersistedInt(0));
|
||||
|
||||
// Set initial resolution value
|
||||
updateResolution();
|
||||
|
||||
return v;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,32 @@
|
||||
package com.limelight.preferences;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.app.Activity;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceFragment;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import com.limelight.PcView;
|
||||
import com.limelight.R;
|
||||
import com.limelight.utils.UiHelper;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class StreamSettings extends Activity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
String locale = PreferenceConfiguration.readPreferences(this).language;
|
||||
if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
|
||||
Configuration config = new Configuration(getResources().getConfiguration());
|
||||
config.locale = new Locale(locale);
|
||||
getResources().updateConfiguration(config, getResources().getDisplayMetrics());
|
||||
}
|
||||
|
||||
setContentView(R.layout.activity_stream_settings);
|
||||
getFragmentManager().beginTransaction().replace(
|
||||
R.id.stream_settings, new SettingsFragment()
|
||||
@@ -23,32 +35,22 @@ public class StreamSettings extends Activity {
|
||||
UiHelper.notifyNewRootView(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
finish();
|
||||
|
||||
// Restart the PC view to apply UI changes
|
||||
Intent intent = new Intent(this, PcView.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent, null);
|
||||
}
|
||||
|
||||
public static class SettingsFragment extends PreferenceFragment {
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
addPreferencesFromResource(R.xml.preferences);
|
||||
|
||||
// Add a listener to the FPS and resolution preference
|
||||
// so the bitrate can be auto-adjusted
|
||||
Preference pref = findPreference(PreferenceConfiguration.RES_FPS_PREF_STRING);
|
||||
pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SettingsFragment.this.getActivity());
|
||||
String valueStr = (String) newValue;
|
||||
|
||||
// Write the new bitrate value
|
||||
prefs.edit()
|
||||
.putInt(PreferenceConfiguration.BITRATE_PREF_STRING,
|
||||
PreferenceConfiguration.getDefaultBitrate(valueStr))
|
||||
.apply();
|
||||
|
||||
// Allow the original preference change to take place
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.limelight.ui;
|
||||
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Fragment;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AbsListView;
|
||||
|
||||
import com.limelight.R;
|
||||
|
||||
public class AdapterFragment extends Fragment {
|
||||
private AdapterFragmentCallbacks callbacks;
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
|
||||
callbacks = (AdapterFragmentCallbacks) activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
return inflater.inflate(callbacks.getAdapterFragmentLayoutId(), container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
callbacks.receiveAbsListView((AbsListView) getView().findViewById(R.id.fragmentView));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.limelight.ui;
|
||||
|
||||
import android.widget.AbsListView;
|
||||
|
||||
public interface AdapterFragmentCallbacks {
|
||||
public int getAdapterFragmentLayoutId();
|
||||
public void receiveAbsListView(AbsListView gridView);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.limelight.ui;
|
||||
|
||||
public interface GameGestures {
|
||||
public void showKeyboard();
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle" >
|
||||
|
||||
<stroke android:width="1dip" android:color="#ffffff"/>
|
||||
|
||||
</shape>
|
||||
@@ -33,12 +33,10 @@
|
||||
android:text="@string/searching_pc"/>
|
||||
</RelativeLayout>
|
||||
|
||||
<GridView
|
||||
android:id="@+id/pcGridView"
|
||||
<FrameLayout
|
||||
android:id="@+id/pcFragmentContainer"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:numColumns="auto_fit"
|
||||
android:columnWidth="160dp"
|
||||
android:gravity="center"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_alignParentTop="true"
|
||||
|
||||
@@ -33,12 +33,10 @@
|
||||
android:text="@string/searching_pc"/>
|
||||
</RelativeLayout>
|
||||
|
||||
<GridView
|
||||
android:id="@+id/pcGridView"
|
||||
<FrameLayout
|
||||
android:id="@+id/pcFragmentContainer"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:numColumns="auto_fit"
|
||||
android:columnWidth="160dp"
|
||||
android:gravity="center"
|
||||
android:layout_toLeftOf="@+id/manuallyAddPc"
|
||||
android:layout_toStartOf="@+id/manuallyAddPc"
|
||||
|
||||
@@ -8,21 +8,17 @@
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
tools:context=".AppView" >
|
||||
|
||||
<GridView
|
||||
android:id="@+id/appGridView"
|
||||
<FrameLayout
|
||||
android:id="@+id/appFragmentContainer"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:numColumns="auto_fit"
|
||||
android:columnWidth="160dp"
|
||||
android:stretchMode="spacingWidth"
|
||||
android:gravity="center"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_below="@+id/appListText">
|
||||
</GridView>
|
||||
android:layout_below="@+id/appListText"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/appListText"
|
||||
@@ -30,6 +26,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:gravity="center"
|
||||
android:paddingTop="0dp"
|
||||
android:paddingBottom="10dp"
|
||||
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>
|
||||
@@ -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="160dp"
|
||||
android:stretchMode="spacingWidth"
|
||||
android:gravity="center"/>
|
||||
</LinearLayout>
|
||||
@@ -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,47 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/dialog_resolution"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<RadioGroup
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:id="@+id/aspectGroup">
|
||||
<RadioButton
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="5dp"
|
||||
android:id="@+id/aspect_4_3"
|
||||
android:text="@string/aspect_4_3"/>
|
||||
<RadioButton
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="5dp"
|
||||
android:id="@+id/aspect_16_9"
|
||||
android:text="@string/aspect_16_9"/>
|
||||
<RadioButton
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="5dp"
|
||||
android:id="@+id/aspect_16_10"
|
||||
android:text="@string/aspect_16_10"/>
|
||||
</RadioGroup>
|
||||
|
||||
<SeekBar
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/aspectGroup"
|
||||
android:max="3"
|
||||
android:id="@+id/resolutionSeekbar"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:textSize="20sp"
|
||||
android:id="@+id/resolutionText"
|
||||
android:layout_below="@+id/resolutionSeekbar"/>
|
||||
|
||||
</RelativeLayout>
|
||||
@@ -0,0 +1,17 @@
|
||||
<?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">
|
||||
|
||||
<ListView
|
||||
android:id="@+id/fragmentView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/list_view_unselected"
|
||||
android:fastScrollEnabled="true"
|
||||
android:longClickable="false"
|
||||
android:stackFromBottom="false" >
|
||||
|
||||
</ListView>
|
||||
|
||||
</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>
|
||||
@@ -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="160dp"
|
||||
android:gravity="center"/>
|
||||
</LinearLayout>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:padding="10dp"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/grid_text"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textIsSelectable="false"
|
||||
android:textSize="16sp" >
|
||||
</TextView>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -56,6 +56,9 @@
|
||||
<!-- General strings -->
|
||||
<string name="ip_hint">Indirizzo IP del PC</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 -->
|
||||
<string name="title_applist">Applicazioni su</string>
|
||||
@@ -70,6 +73,7 @@
|
||||
<string name="applist_quit_app">Chiusura in corso…</string>
|
||||
<string name="applist_quit_success">Sessione chiusa con successo</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 -->
|
||||
<string name="title_add_pc">Aggiungi PC Manualmente</string>
|
||||
@@ -94,11 +98,19 @@
|
||||
<string name="title_seekbar_deadzone">Aggiusta deadzone degli stick analogici</string>
|
||||
<string name="suffix_seekbar_deadzone">%</string>
|
||||
|
||||
<string name="category_ui_settings">Impostazioni Interfaccia</string>
|
||||
<string name="title_language_list">Lingua</string>
|
||||
<string name="summary_language_list">Lingua da usare in Limelight</string>
|
||||
<string name="title_checkbox_list_mode">Usa lista invece della griglia</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="title_checkbox_enable_sops">Ottimizza le impostazioni dei giochi</string>
|
||||
<string name="summary_checkbox_enable_sops">Permetti a GFE di modificare le impostazioni dei giochi per uno streaming ottimale</string>
|
||||
<string name="title_checkbox_host_audio">Riproduci audio sul PC</string>
|
||||
<string name="summary_checkbox_host_audio">Riproduci l\'audio sul computer e su questo dispositivo. Richiede GFE 2.1.2+</string>
|
||||
<string name="summary_checkbox_host_audio">Riproduci l\'audio sul computer e su questo dispositivo</string>
|
||||
|
||||
<string name="category_advanced_settings">Impostazioni Avanzate</string>
|
||||
<string name="title_decoder_list">Cambia decoder</string>
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string-array name="resolution_names">
|
||||
<item>720p 30 FPS</item>
|
||||
<item>720p 60 FPS</item>
|
||||
<item>1080p 30 FPS</item>
|
||||
<item>1080p 60 FPS</item>
|
||||
<string-array name="fps_names">
|
||||
<item>30 FPS</item>
|
||||
<item>60 FPS</item>
|
||||
</string-array>
|
||||
<string-array name="resolution_values" translatable="false">
|
||||
<item>720p30</item>
|
||||
<item>720p60</item>
|
||||
<item>1080p30</item>
|
||||
<item>1080p60</item>
|
||||
<string-array name="fps_values" translatable="false">
|
||||
<item>30</item>
|
||||
<item>60</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="language_names" translatable="false">
|
||||
<item>Default</item>
|
||||
<item>English</item>
|
||||
<item>Italiano</item>
|
||||
</string-array>
|
||||
<string-array name="language_values" translatable="false">
|
||||
<item>default</item>
|
||||
<item>en</item>
|
||||
<item>it</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="decoder_names">
|
||||
|
||||
@@ -56,7 +56,10 @@
|
||||
<!-- General strings -->
|
||||
<string name="ip_hint">IP address of GeForce PC</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 -->
|
||||
<string name="title_applist">Apps on</string>
|
||||
<string name="applist_menu_resume">Resume Session</string>
|
||||
@@ -70,7 +73,8 @@
|
||||
<string name="applist_quit_app">Quitting</string>
|
||||
<string name="applist_quit_success">Successfully 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 -->
|
||||
<string name="title_add_pc">Add PC Manually</string>
|
||||
<string name="msg_add_pc">Connecting to the PC…</string>
|
||||
@@ -81,8 +85,10 @@
|
||||
|
||||
<!-- Preferences -->
|
||||
<string name="category_basic_settings">Basic Settings</string>
|
||||
<string name="title_resolution_list">Select resolution and FPS target</string>
|
||||
<string name="summary_resolution_list">Setting values too high for your device may cause lag or crashing</string>
|
||||
<string name="title_resolution">Select stream resolution</string>
|
||||
<string name="summary_resolution">Some resolutions may not be supported on your device</string>
|
||||
<string name="title_fps_list">Select target FPS</string>
|
||||
<string name="summary_fps_list">Some FPS values may not be supported with high resolutions on your device</string>
|
||||
<string name="title_seekbar_bitrate">Select target video bitrate</string>
|
||||
<string name="summary_seekbar_bitrate">Lower bitrate to reduce stuttering. Raise bitrate to increase image quality.</string>
|
||||
<string name="suffix_seekbar_bitrate">Mbps</string>
|
||||
@@ -94,13 +100,26 @@
|
||||
<string name="title_seekbar_deadzone">Adjust analog stick deadzone</string>
|
||||
<string name="suffix_seekbar_deadzone">%</string>
|
||||
|
||||
<string name="category_ui_settings">UI Settings</string>
|
||||
<string name="title_language_list">Language</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="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="title_checkbox_enable_sops">Optimize game settings</string>
|
||||
<string name="summary_checkbox_enable_sops">Allow GFE to modify game settings for optimal streaming</string>
|
||||
<string name="title_checkbox_host_audio">Play audio on PC</string>
|
||||
<string name="summary_checkbox_host_audio">Play audio from the computer and this device. Requires GFE 2.1.2+</string>
|
||||
<string name="summary_checkbox_host_audio">Play audio from the computer and this device</string>
|
||||
|
||||
<string name="category_advanced_settings">Advanced Settings</string>
|
||||
<string name="title_decoder_list">Change decoder</string>
|
||||
<string name="summary_decoder_list">Software decoding may improve video latency at lower streaming settings</string>
|
||||
|
||||
<!-- Resolution Preference -->
|
||||
<string name="aspect_4_3">4:3</string>
|
||||
<string name="aspect_16_9">16:9</string>
|
||||
<string name="aspect_16_10">16:10</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,13 +2,17 @@
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<PreferenceCategory android:title="@string/category_basic_settings">
|
||||
<com.limelight.preferences.ResolutionPreference
|
||||
android:key="resolution"
|
||||
android:title="@string/title_resolution"
|
||||
android:summary="@string/summary_resolution" />
|
||||
<ListPreference
|
||||
android:key="list_resolution_fps"
|
||||
android:title="@string/title_resolution_list"
|
||||
android:summary="@string/summary_resolution_list"
|
||||
android:entries="@array/resolution_names"
|
||||
android:entryValues="@array/resolution_values"
|
||||
android:defaultValue="720p60" />
|
||||
android:title="@string/title_fps_list"
|
||||
android:summary="@string/summary_fps_list"
|
||||
android:entries="@array/fps_names"
|
||||
android:entryValues="@array/fps_values"
|
||||
android:defaultValue="60" />
|
||||
<com.limelight.preferences.SeekBarPreference
|
||||
android:key="seekbar_bitrate"
|
||||
android:dialogMessage="@string/summary_seekbar_bitrate"
|
||||
@@ -46,6 +50,24 @@
|
||||
android:summary="@string/summary_checkbox_host_audio"
|
||||
android:defaultValue="false" />
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory android:title="@string/category_ui_settings">
|
||||
<ListPreference
|
||||
android:key="list_languages"
|
||||
android:title="@string/title_language_list"
|
||||
android:entries="@array/language_names"
|
||||
android:entryValues="@array/language_values"
|
||||
android:summary="@string/summary_language_list"
|
||||
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
|
||||
android:key="checkbox_list_mode"
|
||||
android:title="@string/title_checkbox_list_mode"
|
||||
android:summary="@string/summary_checkbox_list_mode"
|
||||
android:defaultValue="false" />
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory android:title="@string/category_advanced_settings">
|
||||
<ListPreference
|
||||
android:key="list_decoders"
|
||||
@@ -56,4 +78,4 @@
|
||||
android:defaultValue="auto" />
|
||||
</PreferenceCategory>
|
||||
|
||||
</PreferenceScreen>
|
||||
</PreferenceScreen>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.limelight" >
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<!-- Non-root application name -->
|
||||
<application android:label="Limelight" />
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.limelight.root" >
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<!-- Root permissions -->
|
||||
<uses-permission android:name="android.permission.ACCESS_SUPERUSER" />
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ buildscript {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:0.13.2'
|
||||
classpath 'com.android.tools.build:gradle:1.0.0'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
#Wed Apr 10 15:27:10 PDT 2013
|
||||
#Sun Dec 07 22:52:07 PST 2014
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
</configuration>
|
||||
</facet>
|
||||
</component>
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="false">
|
||||
<output url="file://$MODULE_DIR$/build/classes/main" />
|
||||
<output-test url="file://$MODULE_DIR$/build/classes/test" />
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
|
||||
|
||||
Reference in New Issue
Block a user