mirror of
https://github.com/moonlight-stream/moonlight-android.git
synced 2026-02-16 10:31:07 +00:00
Add new app view UI
This commit is contained in:
@@ -102,11 +102,15 @@
|
||||
<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-1.3.7" level="project" />
|
||||
<orderEntry type="library" exported="" name="jmdns-fixed" level="project" />
|
||||
<orderEntry type="library" exported="" name="jcodec-0.1.6-3" 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="limelight-common" level="project" />
|
||||
<orderEntry type="library" exported="" name="support-v4-r6" level="project" />
|
||||
<orderEntry type="library" exported="" name="gson-2.3" level="project" />
|
||||
<orderEntry type="library" exported="" name="ion-1.3.7" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
|
||||
|
||||
@@ -65,6 +65,8 @@ dependencies {
|
||||
compile group: 'org.jcodec', name: 'jcodec', version: '0.1.6-3'
|
||||
compile group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '1.51'
|
||||
compile group: 'org.bouncycastle', name: 'bcpkix-jdk15on', version: '1.51'
|
||||
compile group: 'com.google.android', name: 'support-v4', version:'r6'
|
||||
compile group: 'com.koushikdutta.ion', name: 'ion', version:'1.3.7'
|
||||
compile files('libs/jmdns-fixed.jar')
|
||||
compile files('libs/limelight-common.jar')
|
||||
compile files('libs/tinyrtsp.jar')
|
||||
|
||||
@@ -4,15 +4,17 @@ import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.List;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import com.limelight.binding.PlatformBinding;
|
||||
import com.limelight.grid.AppGridAdapter;
|
||||
import com.limelight.nvstream.http.GfeHttpResponseException;
|
||||
import com.limelight.nvstream.http.NvApp;
|
||||
import com.limelight.nvstream.http.NvHTTP;
|
||||
import com.limelight.R;
|
||||
import com.limelight.utils.Dialog;
|
||||
import com.limelight.utils.SpinnerDialog;
|
||||
import com.limelight.utils.UiHelper;
|
||||
@@ -27,15 +29,14 @@ import android.view.View;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.GridView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import android.widget.AdapterView.AdapterContextMenuInfo;
|
||||
|
||||
public class AppView extends Activity {
|
||||
private ListView appList;
|
||||
private ArrayAdapter<AppObject> appListAdapter;
|
||||
private GridView appGrid;
|
||||
private AppGridAdapter appGridAdapter;
|
||||
private InetAddress ipAddress;
|
||||
private String uniqueId;
|
||||
private boolean remote;
|
||||
@@ -60,10 +61,11 @@ public class AppView extends Activity {
|
||||
uniqueId = getIntent().getStringExtra(UNIQUEID_EXTRA);
|
||||
remote = getIntent().getBooleanExtra(REMOTE_EXTRA, false);
|
||||
if (address == null || uniqueId == null) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
String labelText = "App List for "+getIntent().getStringExtra(NAME_EXTRA);
|
||||
String labelText = "Apps on "+getIntent().getStringExtra(NAME_EXTRA);
|
||||
TextView label = (TextView) findViewById(R.id.appListText);
|
||||
setTitle(labelText);
|
||||
label.setText(labelText);
|
||||
@@ -71,20 +73,26 @@ public class AppView extends Activity {
|
||||
try {
|
||||
ipAddress = InetAddress.getByAddress(address);
|
||||
} catch (UnknownHostException e) {
|
||||
return;
|
||||
e.printStackTrace();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
// Setup the list view
|
||||
appList = (ListView)findViewById(R.id.pcListView);
|
||||
appListAdapter = new ArrayAdapter<AppObject>(this, R.layout.simplerow, R.id.rowTextView);
|
||||
appListAdapter.setNotifyOnChange(false);
|
||||
appList.setAdapter(appListAdapter);
|
||||
appList.setItemsCanFocus(true);
|
||||
appList.setOnItemClickListener(new OnItemClickListener() {
|
||||
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 = appListAdapter.getItem(pos);
|
||||
AppObject app = (AppObject) appGridAdapter.getItem(pos);
|
||||
if (app == null || app.app == null) {
|
||||
return;
|
||||
}
|
||||
@@ -98,7 +106,7 @@ public class AppView extends Activity {
|
||||
}
|
||||
}
|
||||
});
|
||||
registerForContextMenu(appList);
|
||||
registerForContextMenu(appGrid);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -118,8 +126,8 @@ public class AppView extends Activity {
|
||||
|
||||
private int getRunningAppId() {
|
||||
int runningAppId = -1;
|
||||
for (int i = 0; i < appListAdapter.getCount(); i++) {
|
||||
AppObject app = appListAdapter.getItem(i);
|
||||
for (int i = 0; i < appGridAdapter.getCount(); i++) {
|
||||
AppObject app = (AppObject) appGridAdapter.getItem(i);
|
||||
if (app.app == null) {
|
||||
continue;
|
||||
}
|
||||
@@ -137,7 +145,7 @@ public class AppView extends Activity {
|
||||
super.onCreateContextMenu(menu, v, menuInfo);
|
||||
|
||||
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
|
||||
AppObject selectedApp = appListAdapter.getItem(info.position);
|
||||
AppObject selectedApp = (AppObject) appGridAdapter.getItem(info.position);
|
||||
if (selectedApp == null || selectedApp.app == null) {
|
||||
return;
|
||||
}
|
||||
@@ -162,7 +170,7 @@ public class AppView extends Activity {
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem item) {
|
||||
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
|
||||
AppObject app = appListAdapter.getItem(info.position);
|
||||
AppObject app = (AppObject) appGridAdapter.getItem(info.position);
|
||||
switch (item.getItemId())
|
||||
{
|
||||
case RESUME_ID:
|
||||
@@ -191,12 +199,8 @@ public class AppView extends Activity {
|
||||
return str.toString();
|
||||
}
|
||||
|
||||
private void addListPlaceholder() {
|
||||
appListAdapter.add(new AppObject("No apps found. Try rescanning for games in GeForce Experience.", null));
|
||||
}
|
||||
|
||||
private void updateAppList() {
|
||||
final SpinnerDialog spinner = SpinnerDialog.displayDialog(this, "App List", "Refreshing app list...", true);
|
||||
final SpinnerDialog spinner = SpinnerDialog.displayDialog(this, "App List", "Refreshing apps...", true);
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -208,17 +212,12 @@ public class AppView extends Activity {
|
||||
AppView.this.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
appListAdapter.clear();
|
||||
if (appList.isEmpty()) {
|
||||
addListPlaceholder();
|
||||
}
|
||||
else {
|
||||
for (NvApp app : appList) {
|
||||
appListAdapter.add(new AppObject(generateString(app), app));
|
||||
}
|
||||
}
|
||||
|
||||
appListAdapter.notifyDataSetChanged();
|
||||
appGridAdapter.clear();
|
||||
for (NvApp app : appList) {
|
||||
appGridAdapter.addApp(new AppObject(generateString(app), app));
|
||||
}
|
||||
|
||||
appGridAdapter.notifyDataSetChanged();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -282,17 +281,15 @@ public class AppView extends Activity {
|
||||
}
|
||||
|
||||
public class AppObject {
|
||||
public String text;
|
||||
public NvApp app;
|
||||
|
||||
public AppObject(String text, NvApp app) {
|
||||
this.text = text;
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return text;
|
||||
return app.getAppName();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -676,7 +676,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
e.printStackTrace();
|
||||
|
||||
stopConnection();
|
||||
Dialog.displayDialog(this, "Connection Terminated", "The connection failed unexpectedly", true);
|
||||
Dialog.displayDialog(this, "Connection Terminated", "The connection was terminated", true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
155
app/src/main/java/com/limelight/grid/AppGridAdapter.java
Normal file
155
app/src/main/java/com/limelight/grid/AppGridAdapter.java
Normal file
@@ -0,0 +1,155 @@
|
||||
package com.limelight.grid;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.koushikdutta.async.future.FutureCallback;
|
||||
import com.koushikdutta.ion.Ion;
|
||||
import com.limelight.AppView;
|
||||
import com.limelight.R;
|
||||
import com.limelight.binding.PlatformBinding;
|
||||
import com.limelight.nvstream.http.LimelightCryptoProvider;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Principal;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.KeyManager;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509KeyManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
|
||||
|
||||
private InetAddress address;
|
||||
private String uniqueId;
|
||||
private LimelightCryptoProvider cryptoProvider;
|
||||
private SSLContext sslContext;
|
||||
private 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);
|
||||
|
||||
this.address = address;
|
||||
this.uniqueId = uniqueId;
|
||||
|
||||
cryptoProvider = PlatformBinding.getCryptoProvider(context);
|
||||
|
||||
sslContext = SSLContext.getInstance("SSL");
|
||||
sslContext.init(ourKeyman, trustAllCerts, new SecureRandom());
|
||||
}
|
||||
|
||||
TrustManager[] trustAllCerts = new TrustManager[] {
|
||||
new X509TrustManager() {
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return new X509Certificate[0];
|
||||
}
|
||||
public void checkClientTrusted(X509Certificate[] certs, String authType) {}
|
||||
public void checkServerTrusted(X509Certificate[] certs, String authType) {}
|
||||
}};
|
||||
|
||||
KeyManager[] ourKeyman = new KeyManager[] {
|
||||
new X509KeyManager() {
|
||||
public String chooseClientAlias(String[] keyTypes,
|
||||
Principal[] issuers, Socket socket) {
|
||||
return "Limelight-RSA";
|
||||
}
|
||||
|
||||
public String chooseServerAlias(String keyType, Principal[] issuers,
|
||||
Socket socket) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public X509Certificate[] getCertificateChain(String alias) {
|
||||
return new X509Certificate[] {cryptoProvider.getClientCertificate()};
|
||||
}
|
||||
|
||||
public String[] getClientAliases(String keyType, Principal[] issuers) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public PrivateKey getPrivateKey(String alias) {
|
||||
return cryptoProvider.getClientPrivateKey();
|
||||
}
|
||||
|
||||
public String[] getServerAliases(String keyType, Principal[] issuers) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Ignore differences between given hostname and certificate hostname
|
||||
HostnameVerifier hv = new HostnameVerifier() {
|
||||
public boolean verify(String hostname, SSLSession session) { return true; }
|
||||
};
|
||||
|
||||
public void addApp(AppView.AppObject app) {
|
||||
itemList.add(app);
|
||||
}
|
||||
|
||||
public void abortPendingRequests() {
|
||||
HashMap<ImageView, Future> tempMap;
|
||||
|
||||
synchronized (pendingRequests) {
|
||||
// Copy the pending requests under a lock
|
||||
tempMap = new HashMap<ImageView, Future>(pendingRequests);
|
||||
}
|
||||
|
||||
for (Future f : tempMap.values()) {
|
||||
f.cancel(true);
|
||||
}
|
||||
|
||||
synchronized (pendingRequests) {
|
||||
// Remove cancelled requests
|
||||
for (ImageView v : tempMap.keySet()) {
|
||||
pendingRequests.remove(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean populateTextView(TextView txtView, AppView.AppObject obj) {
|
||||
// Return false to use the app's toString method
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,10 @@ public abstract class GenericGridAdapter<T> extends BaseAdapter {
|
||||
this.inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
itemList.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return itemList.size();
|
||||
|
||||
@@ -10,7 +10,7 @@ import com.limelight.R;
|
||||
public class PcGridAdapter extends GenericGridAdapter<PcView.ComputerObject> {
|
||||
|
||||
public PcGridAdapter(Context context) {
|
||||
super(context, R.layout.generic_grid_item, R.drawable.computer);
|
||||
super(context, R.layout.pc_grid_item, R.drawable.computer);
|
||||
}
|
||||
|
||||
public void addComputer(PcView.ComputerObject computer) {
|
||||
|
||||
BIN
app/src/main/res/drawable/image_loading.png
Normal file
BIN
app/src/main/res/drawable/image_loading.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
@@ -8,18 +8,18 @@
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
tools:context=".AppView" >
|
||||
|
||||
<ListView
|
||||
android:id="@+id/pcListView"
|
||||
android:layout_width="match_parent"
|
||||
<GridView
|
||||
android:id="@+id/appGridView"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:numColumns="auto_fit"
|
||||
android:columnWidth="160dp"
|
||||
android:gravity="center"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_below="@+id/appListText"
|
||||
android:fastScrollEnabled="true"
|
||||
android:longClickable="false"
|
||||
android:stackFromBottom="false">
|
||||
</ListView>
|
||||
android:layout_below="@+id/appListText">
|
||||
</GridView>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/appListText"
|
||||
|
||||
22
app/src/main/res/layout/app_grid_item.xml
Normal file
22
app/src/main/res/layout/app_grid_item.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?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="30dp">
|
||||
<ImageView
|
||||
android:id="@+id/grid_image"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="150dp">
|
||||
</ImageView>
|
||||
<TextView
|
||||
android:id="@+id/grid_text"
|
||||
android:layout_width="125dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/grid_image"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:gravity="center"
|
||||
android:textSize="20sp" >
|
||||
</TextView>
|
||||
</RelativeLayout>
|
||||
Reference in New Issue
Block a user