Add new app view UI

This commit is contained in:
Cameron Gutman
2014-11-08 01:07:21 -08:00
parent 9ef577dbdd
commit 68c1aaf433
11 changed files with 232 additions and 48 deletions

View File

@@ -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>

View File

@@ -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')

View File

@@ -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();
}
}
}

View File

@@ -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);
}
}

View 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;
}
}

View File

@@ -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();

View File

@@ -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) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -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"

View 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>