diff --git a/app/app.iml b/app/app.iml
index 436535aa..b9d6ee2a 100644
--- a/app/app.iml
+++ b/app/app.iml
@@ -101,12 +101,18 @@
+
+
+
+
+
+
diff --git a/app/build.gradle b/app/build.gradle
index 55348db7..c1d02b1b 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -11,8 +11,8 @@ android {
minSdkVersion 16
targetSdkVersion 21
- versionName "2.9"
- versionCode = 38
+ versionName "3.0-beta1.01"
+ versionCode = 41
}
productFlavors {
@@ -65,6 +65,10 @@ 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 group: 'com.squareup.okhttp', name: 'okhttp', version:'2.1.0-RC1'
+ compile group: 'com.squareup.okio', name:'okio', version:'1.0.1'
compile files('libs/jmdns-fixed.jar')
compile files('libs/limelight-common.jar')
compile files('libs/tinyrtsp.jar')
diff --git a/app/libs/limelight-common.jar b/app/libs/limelight-common.jar
index de1d908b..d6f99b3f 100644
Binary files a/app/libs/limelight-common.jar and b/app/libs/limelight-common.jar differ
diff --git a/app/src/main/java/com/limelight/AppView.java b/app/src/main/java/com/limelight/AppView.java
index aeff34de..c44b19a7 100644
--- a/app/src/main/java/com/limelight/AppView.java
+++ b/app/src/main/java/com/limelight/AppView.java
@@ -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 appListAdapter;
+ private GridView appGrid;
+ private AppGridAdapter appGridAdapter;
private InetAddress ipAddress;
private String uniqueId;
private boolean remote;
@@ -60,6 +61,7 @@ public class AppView extends Activity {
uniqueId = getIntent().getStringExtra(UNIQUEID_EXTRA);
remote = getIntent().getBooleanExtra(REMOTE_EXTRA, false);
if (address == null || uniqueId == null) {
+ finish();
return;
}
@@ -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(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;
}
@@ -135,25 +143,25 @@ public class AppView extends Activity {
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
-
- AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
- AppObject selectedApp = appListAdapter.getItem(info.position);
- if (selectedApp == null || selectedApp.app == null) {
- return;
- }
-
- 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, 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, CANCEL_ID, 2, getResources().getString(R.string.applist_menu_cancel));
- }
- }
- }
+
+ AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
+ AppObject selectedApp = (AppObject) appGridAdapter.getItem(info.position);
+ if (selectedApp == null || selectedApp.app == null) {
+ return;
+ }
+
+ 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, 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, CANCEL_ID, 2, getResources().getString(R.string.applist_menu_cancel));
+ }
+ }
+ }
@Override
public void onContextMenuClosed(Menu menu) {
@@ -162,42 +170,28 @@ public class AppView extends Activity {
@Override
public boolean onContextItemSelected(MenuItem item) {
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
- AppObject app = appListAdapter.getItem(info.position);
- switch (item.getItemId())
- {
- case RESUME_ID:
- // Resume is the same as start for us
- doStart(app.app);
- return true;
-
- case QUIT_ID:
- doQuit(app.app);
- return true;
-
- case CANCEL_ID:
- return true;
-
- default:
- return super.onContextItemSelected(item);
- }
- }
+ AppObject app = (AppObject) appGridAdapter.getItem(info.position);
+ switch (item.getItemId()) {
+ case RESUME_ID:
+ // Resume is the same as start for us
+ doStart(app.app);
+ return true;
- private static String generateString(NvApp app) {
- StringBuilder str = new StringBuilder();
- str.append(app.getAppName());
- if (app.getIsRunning()) {
- str.append(" - "+getResources().getString(R.string.applist_app_running));
- }
- return str.toString();
- }
-
- private void addListPlaceholder() {
- appListAdapter.add(new AppObject(getResources().getString(R.string.applist_no_apps), null));
+ case QUIT_ID:
+ doQuit(app.app);
+ return true;
+
+ case CANCEL_ID:
+ return true;
+
+ default:
+ return super.onContextItemSelected(item);
+ }
}
private void updateAppList() {
final SpinnerDialog spinner = SpinnerDialog.displayDialog(this, getResources().getString(R.string.applist_refresh_title),
- getResources().getString(R.string.applist_refresh_msg), true);
+ getResources().getString(R.string.applist_refresh_title), true);
new Thread() {
@Override
public void run() {
@@ -209,17 +203,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(app));
+ }
+
+ appGridAdapter.notifyDataSetChanged();
}
});
@@ -282,17 +271,15 @@ public class AppView extends Activity {
}
public class AppObject {
- public String text;
public NvApp app;
- public AppObject(String text, NvApp app) {
- this.text = text;
+ public AppObject(NvApp app) {
this.app = app;
}
@Override
public String toString() {
- return text;
+ return app.getAppName();
}
}
}
diff --git a/app/src/main/java/com/limelight/Game.java b/app/src/main/java/com/limelight/Game.java
index 3bb036c2..abb2bf12 100644
--- a/app/src/main/java/com/limelight/Game.java
+++ b/app/src/main/java/com/limelight/Game.java
@@ -678,8 +678,8 @@ public class Game extends Activity implements SurfaceHolder.Callback,
e.printStackTrace();
stopConnection();
- Dialog.displayDialog(this, getResources().getString(R.string.conn_fail_title),
- getResources().getString(R.string.conn_fail_msg), true);
+ Dialog.displayDialog(this, getResources().getString(R.string.conn_terminated_title),
+ getResources().getString(R.string.conn_terminated_msg), true);
}
}
diff --git a/app/src/main/java/com/limelight/PcView.java b/app/src/main/java/com/limelight/PcView.java
index b453596c..697104ab 100644
--- a/app/src/main/java/com/limelight/PcView.java
+++ b/app/src/main/java/com/limelight/PcView.java
@@ -9,6 +9,7 @@ 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.PcGridAdapter;
import com.limelight.nvstream.http.ComputerDetails;
import com.limelight.nvstream.http.NvHTTP;
import com.limelight.nvstream.http.PairingManager;
@@ -36,16 +37,18 @@ import android.view.ContextMenu.ContextMenuInfo;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
-import android.widget.ArrayAdapter;
import android.widget.Button;
-import android.widget.ListView;
+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 {
- private Button settingsButton, addComputerButton;
- private ListView pcList;
- private ArrayAdapter pcListAdapter;
+ private ImageButton settingsButton, addComputerButton;
+ private RelativeLayout noPcFoundLayout;
+ private GridView pcGrid;
+ private PcGridAdapter pcGridAdapter;
private ComputerManagerService.ComputerManagerBinder managerBinder;
private boolean freezeUpdates, runningPolling;
private ServiceConnection serviceConnection = new ServiceConnection() {
@@ -99,21 +102,19 @@ public class PcView extends Activity {
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
// Setup the list view
- settingsButton = (Button)findViewById(R.id.settingsButton);
- addComputerButton = (Button)findViewById(R.id.manuallyAddPc);
+ settingsButton = (ImageButton)findViewById(R.id.settingsButton);
+ addComputerButton = (ImageButton)findViewById(R.id.manuallyAddPc);
- pcList = (ListView)findViewById(R.id.pcListView);
- pcList.setAdapter(pcListAdapter);
- pcList.setItemsCanFocus(true);
- pcList.setOnItemClickListener(new OnItemClickListener() {
+ 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 = pcListAdapter.getItem(pos);
- if (computer.details == null) {
- // Placeholder item; no context menu for it
- return;
- }
+ 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);
@@ -127,7 +128,7 @@ public class PcView extends Activity {
}
}
});
- registerForContextMenu(pcList);
+ registerForContextMenu(pcGrid);
settingsButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
@@ -142,13 +143,15 @@ public class PcView extends Activity {
}
});
- if (pcListAdapter.isEmpty()) {
- addListPlaceholder();
- }
- else {
- pcListAdapter.notifyDataSetChanged();
- }
- }
+ noPcFoundLayout = (RelativeLayout) findViewById(R.id.no_pc_found_layout);
+ if (pcGridAdapter.getCount() == 0) {
+ noPcFoundLayout.setVisibility(View.VISIBLE);
+ }
+ else {
+ noPcFoundLayout.setVisibility(View.INVISIBLE);
+ }
+ pcGridAdapter.notifyDataSetChanged();
+ }
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -157,9 +160,8 @@ public class PcView extends Activity {
// Bind to the computer manager service
bindService(new Intent(PcView.this, ComputerManagerService.class), serviceConnection,
Service.BIND_AUTO_CREATE);
-
- pcListAdapter = new ArrayAdapter(this, R.layout.simplerow, R.id.rowTextView);
- pcListAdapter.setNotifyOnChange(false);
+
+ pcGridAdapter = new PcGridAdapter(this);
initializeViews();
}
@@ -178,7 +180,7 @@ public class PcView extends Activity {
PcView.this.runOnUiThread(new Runnable() {
@Override
public void run() {
- updateListView(details);
+ updateComputer(details);
}
});
}
@@ -242,30 +244,29 @@ public class PcView extends Activity {
// Call superclass
super.onCreateContextMenu(menu, v, menuInfo);
-
- AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
- ComputerObject computer = pcListAdapter.getItem(info.position);
- if (computer == null || computer.details == null) {
- startComputerUpdates();
- return;
- }
-
- // Inflate the context menu
- if (computer.details.reachability == ComputerDetails.Reachability.OFFLINE) {
- menu.add(Menu.NONE, WOL_ID, 1, getResources().getString(R.string.pcview_menu_send_wol));
- menu.add(Menu.NONE, DELETE_ID, 2, getResources().getString(R.string.pcview_menu_delete_pc));
- }
- else if (computer.details.pairState != PairState.PAIRED) {
- menu.add(Menu.NONE, PAIR_ID, 1, getResources().getString(R.string.pcview_menu_pair_pc));
- if (computer.details.reachability == ComputerDetails.Reachability.REMOTE) {
- menu.add(Menu.NONE, DELETE_ID, 2, getResources().getString(R.string.pcview_menu_delete_pc));
- }
- }
- else {
- menu.add(Menu.NONE, APP_LIST_ID, 1, getResources().getString(R.string.pcview_menu_app_list));
- menu.add(Menu.NONE, UNPAIR_ID, 2, getResources().getString(R.string.pcview_menu_unpair_pc));
- }
- }
+
+ AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
+ ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(info.position);
+ if (computer == null || computer.details == null ||
+ computer.details.reachability == ComputerDetails.Reachability.UNKNOWN) {
+ startComputerUpdates();
+ return;
+ }
+
+ // Inflate the context menu
+ if (computer.details.reachability == ComputerDetails.Reachability.OFFLINE) {
+ menu.add(Menu.NONE, WOL_ID, 1, getResources().getString(R.string.pcview_menu_send_wol));
+ menu.add(Menu.NONE, DELETE_ID, 2, getResources().getString(R.string.pcview_menu_delete_pc));
+ }
+ else if (computer.details.pairState != PairState.PAIRED) {
+ menu.add(Menu.NONE, PAIR_ID, 1, getResources().getString(R.string.pcview_menu_pair_pc));
+ menu.add(Menu.NONE, DELETE_ID, 2, getResources().getString(R.string.pcview_menu_delete_pc));
+ }
+ else {
+ menu.add(Menu.NONE, APP_LIST_ID, 1, getResources().getString(R.string.pcview_menu_app_list));
+ menu.add(Menu.NONE, UNPAIR_ID, 2, getResources().getString(R.string.pcview_menu_unpair_pc));
+ }
+ }
@Override
public void onContextMenuClosed(Menu menu) {
@@ -338,6 +339,7 @@ public class PcView extends Activity {
} catch (FileNotFoundException e) {
message = getResources().getString(R.string.error_404);
} catch (Exception e) {
+ e.printStackTrace();
message = e.getMessage();
}
@@ -476,104 +478,58 @@ public class PcView extends Activity {
startActivity(i);
}
- @Override
- public boolean onContextItemSelected(MenuItem item) {
- AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
- ComputerObject computer = pcListAdapter.getItem(info.position);
- switch (item.getItemId())
- {
- case PAIR_ID:
- doPair(computer.details);
- return true;
-
- case UNPAIR_ID:
- doUnpair(computer.details);
- return true;
-
- case WOL_ID:
- doWakeOnLan(computer.details);
- return true;
-
- case DELETE_ID:
- if (managerBinder == null) {
- Toast.makeText(PcView.this, getResources().getString(R.string.error_manager_not_running), Toast.LENGTH_LONG).show();
- return true;
- }
- managerBinder.removeComputer(computer.details.name);
- removeListView(computer.details);
- return true;
-
- case APP_LIST_ID:
- doAppList(computer.details);
- return true;
-
- default:
- return super.onContextItemSelected(item);
- }
- }
-
- private static String generateString(ComputerDetails details) {
- StringBuilder str = new StringBuilder();
- str.append(details.name).append(" - ");
- if (details.state == ComputerDetails.State.ONLINE) {
- str.append(getResources().getString(R.string.status_online)+" ");
- if (details.reachability == ComputerDetails.Reachability.LOCAL) {
- str.append("("+getResources().getString(R.string.status_local)+") - ");
- }
- else {
- str.append("("+getResources().getString(R.string.status_remote)+") - ");
- }
- if (details.pairState == PairState.PAIRED) {
- if (details.runningGameId == 0) {
- str.append(getResources().getString(R.string.status_available));
- }
- else {
- str.append(getResources().getString(R.string.status_ingame));
- }
- }
- else {
- str.append(getResources().getString(R.string.status_not_paired));
- }
- }
- else {
- str.append(getResources().getString(R.string.status_offline));
- }
- return str.toString();
- }
-
- private void addListPlaceholder() {
- pcListAdapter.add(new ComputerObject(getResources().getString(R.string.discovery_running), null));
- }
-
- private void removeListView(ComputerDetails details) {
- for (int i = 0; i < pcListAdapter.getCount(); i++) {
- ComputerObject computer = pcListAdapter.getItem(i);
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
+ ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(info.position);
+ switch (item.getItemId())
+ {
+ case PAIR_ID:
+ doPair(computer.details);
+ return true;
+
+ case UNPAIR_ID:
+ doUnpair(computer.details);
+ return true;
+
+ case WOL_ID:
+ doWakeOnLan(computer.details);
+ return true;
+
+ case DELETE_ID:
+ if (managerBinder == null) {
+ Toast.makeText(PcView.this, getResources().getString(R.string.error_manager_not_running), Toast.LENGTH_LONG).show();
+ return true;
+ }
+ managerBinder.removeComputer(computer.details.name);
+ removeComputer(computer.details);
+ return true;
+
+ case APP_LIST_ID:
+ doAppList(computer.details);
+ return true;
+
+ default:
+ return super.onContextItemSelected(item);
+ }
+ }
+
+ private void removeComputer(ComputerDetails details) {
+ for (int i = 0; i < pcGridAdapter.getCount(); i++) {
+ ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(i);
if (details.equals(computer.details)) {
- pcListAdapter.remove(computer);
+ pcGridAdapter.removeComputer(computer);
break;
}
}
-
- if (pcListAdapter.getCount() == 0) {
- // Add the placeholder if we're down to 0 computers
- addListPlaceholder();
- }
- }
-
- private void updateListView(ComputerDetails details) {
- String computerString = generateString(details);
+ }
+
+ private void updateComputer(ComputerDetails details) {
ComputerObject existingEntry = null;
- boolean placeholderPresent = false;
- for (int i = 0; i < pcListAdapter.getCount(); i++) {
- ComputerObject computer = pcListAdapter.getItem(i);
-
- // If there's a placeholder, there's nothing else
- if (computer.details == null) {
- placeholderPresent = true;
- break;
- }
+ for (int i = 0; i < pcGridAdapter.getCount(); i++) {
+ ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(i);
// Check if this is the same computer
if (details.equals(computer.details)) {
@@ -584,35 +540,30 @@ public class PcView extends Activity {
if (existingEntry != null) {
// Replace the information in the existing entry
- existingEntry.text = computerString;
existingEntry.details = details;
}
else {
- // If the placeholder is the only object, remove it
- if (placeholderPresent) {
- pcListAdapter.remove(pcListAdapter.getItem(0));
- }
-
// Add a new entry
- pcListAdapter.add(new ComputerObject(computerString, details));
+ pcGridAdapter.addComputer(new ComputerObject(details));
+
+ // Remove the "Discovery in progress" view
+ noPcFoundLayout.setVisibility(View.INVISIBLE);
}
-
- // Notify the view that the data has changed
- pcListAdapter.notifyDataSetChanged();
+
+ // Notify the view that the data has changed
+ pcGridAdapter.notifyDataSetChanged();
}
public class ComputerObject {
- public String text;
public ComputerDetails details;
- public ComputerObject(String text, ComputerDetails details) {
- this.text = text;
+ public ComputerObject(ComputerDetails details) {
this.details = details;
}
@Override
public String toString() {
- return text;
+ return details.name;
}
}
}
diff --git a/app/src/main/java/com/limelight/computers/ComputerDatabaseManager.java b/app/src/main/java/com/limelight/computers/ComputerDatabaseManager.java
index 78630166..701b1222 100644
--- a/app/src/main/java/com/limelight/computers/ComputerDatabaseManager.java
+++ b/app/src/main/java/com/limelight/computers/ComputerDatabaseManager.java
@@ -99,15 +99,17 @@ public class ComputerDatabaseManager {
details.macAddress = c.getString(4);
// This signifies we don't have dynamic state (like pair state)
- details.state = ComputerDetails.State.UNKNOWN;
+ details.state = ComputerDetails.State.UNKNOWN;
+ details.reachability = ComputerDetails.Reachability.UNKNOWN;
// If a field is corrupt or missing, skip the database entry
if (details.uuid == null || details.localIp == null || details.remoteIp == null ||
details.macAddress == null) {
continue;
}
-
- computerList.add(details);
+
+
+ computerList.add(details);
}
c.close();
@@ -151,6 +153,9 @@ public class ComputerDatabaseManager {
details.macAddress = c.getString(4);
c.close();
+
+ details.state = ComputerDetails.State.UNKNOWN;
+ details.reachability = ComputerDetails.Reachability.UNKNOWN;
// If a field is corrupt or missing, delete the database entry
if (details.uuid == null || details.localIp == null || details.remoteIp == null ||
diff --git a/app/src/main/java/com/limelight/computers/ComputerManagerService.java b/app/src/main/java/com/limelight/computers/ComputerManagerService.java
index fc08a440..fde9dd32 100644
--- a/app/src/main/java/com/limelight/computers/ComputerManagerService.java
+++ b/app/src/main/java/com/limelight/computers/ComputerManagerService.java
@@ -151,6 +151,9 @@ public class ComputerManagerService extends Service {
for (ComputerDetails computer : computerList) {
// This polling thread might already be there
if (!pollingThreads.containsKey(computer)) {
+ // Report this computer initially
+ listener.notifyComputerUpdated(computer);
+
Thread t = createPollingThread(computer);
pollingThreads.put(computer, t);
t.start();
diff --git a/app/src/main/java/com/limelight/grid/AppGridAdapter.java b/app/src/main/java/com/limelight/grid/AppGridAdapter.java
new file mode 100644
index 00000000..4a3a280c
--- /dev/null
+++ b/app/src/main/java/com/limelight/grid/AppGridAdapter.java
@@ -0,0 +1,167 @@
+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 {
+
+ private InetAddress address;
+ private String uniqueId;
+ private LimelightCryptoProvider cryptoProvider;
+ private SSLContext sslContext;
+ private HashMap pendingRequests = new HashMap();
+
+ 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 tempMap;
+
+ synchronized (pendingRequests) {
+ // Copy the pending requests under a lock
+ tempMap = new HashMap(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() {
+ @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;
+ }
+
+ @Override
+ public boolean populateOverlayView(ImageView overlayView, AppView.AppObject obj) {
+ if (obj.app.getIsRunning()) {
+ // Show the play button overlay
+ overlayView.setImageResource(R.drawable.play);
+ return true;
+ }
+
+ // No overlay
+ return false;
+ }
+}
diff --git a/app/src/main/java/com/limelight/grid/GenericGridAdapter.java b/app/src/main/java/com/limelight/grid/GenericGridAdapter.java
new file mode 100644
index 00000000..adaaa368
--- /dev/null
+++ b/app/src/main/java/com/limelight/grid/GenericGridAdapter.java
@@ -0,0 +1,79 @@
+package com.limelight.grid;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.limelight.PcView;
+import com.limelight.R;
+
+import java.util.ArrayList;
+
+public abstract class GenericGridAdapter extends BaseAdapter {
+ protected Context context;
+ protected int defaultImageRes;
+ protected int layoutId;
+ protected ArrayList itemList = new ArrayList();
+ protected LayoutInflater inflater;
+
+ public GenericGridAdapter(Context context, int layoutId, int defaultImageRes) {
+ this.context = context;
+ this.layoutId = layoutId;
+ this.defaultImageRes = defaultImageRes;
+
+ this.inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ }
+
+ public void clear() {
+ itemList.clear();
+ }
+
+ @Override
+ public int getCount() {
+ return itemList.size();
+ }
+
+ @Override
+ public Object getItem(int i) {
+ return itemList.get(i);
+ }
+
+ @Override
+ public long getItemId(int i) {
+ return i;
+ }
+
+ public abstract boolean populateImageView(ImageView imgView, T obj);
+ public abstract boolean populateTextView(TextView txtView, T obj);
+ public abstract boolean populateOverlayView(ImageView overlayView, T obj);
+
+ @Override
+ public View getView(int i, View convertView, ViewGroup viewGroup) {
+ if (convertView == null) {
+ convertView = inflater.inflate(layoutId, viewGroup, false);
+ }
+
+ ImageView imgView = (ImageView) convertView.findViewById(R.id.grid_image);
+ 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 (!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);
+ }
+
+ return convertView;
+ }
+}
diff --git a/app/src/main/java/com/limelight/grid/PcGridAdapter.java b/app/src/main/java/com/limelight/grid/PcGridAdapter.java
new file mode 100644
index 00000000..a191381c
--- /dev/null
+++ b/app/src/main/java/com/limelight/grid/PcGridAdapter.java
@@ -0,0 +1,62 @@
+package com.limelight.grid;
+
+import android.content.Context;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.limelight.PcView;
+import com.limelight.R;
+import com.limelight.nvstream.http.ComputerDetails;
+
+public class PcGridAdapter extends GenericGridAdapter {
+
+ public PcGridAdapter(Context context) {
+ super(context, R.layout.pc_grid_item, R.drawable.computer);
+ }
+
+ public void addComputer(PcView.ComputerObject computer) {
+ itemList.add(computer);
+ }
+
+ public boolean removeComputer(PcView.ComputerObject computer) {
+ return itemList.remove(computer);
+ }
+
+ @Override
+ public boolean populateImageView(ImageView imgView, PcView.ComputerObject obj) {
+ if (obj.details.reachability != ComputerDetails.Reachability.OFFLINE) {
+ imgView.setAlpha(1.0f);
+ }
+ else {
+ imgView.setAlpha(0.4f);
+ }
+
+ // Return false to use the default drawable
+ return false;
+ }
+
+ @Override
+ public boolean populateTextView(TextView txtView, PcView.ComputerObject obj) {
+ if (obj.details.reachability != ComputerDetails.Reachability.OFFLINE) {
+ txtView.setAlpha(1.0f);
+ }
+ else {
+ txtView.setAlpha(0.4f);
+ }
+
+ // Return false to use the computer's toString method
+ return false;
+ }
+
+ @Override
+ public boolean populateOverlayView(ImageView overlayView, PcView.ComputerObject obj) {
+ if (obj.details.reachability == ComputerDetails.Reachability.UNKNOWN) {
+ // Still refreshing this PC so display the overlay
+ overlayView.setImageResource(R.drawable.image_loading);
+ return true;
+ }
+
+ // No overlay
+ return false;
+ }
+}
diff --git a/app/src/main/java/com/limelight/preferences/AddComputerManually.java b/app/src/main/java/com/limelight/preferences/AddComputerManually.java
index 0cb46136..ebb3e717 100644
--- a/app/src/main/java/com/limelight/preferences/AddComputerManually.java
+++ b/app/src/main/java/com/limelight/preferences/AddComputerManually.java
@@ -7,6 +7,7 @@ import java.util.concurrent.LinkedBlockingQueue;
import com.limelight.computers.ComputerManagerService;
import com.limelight.R;
import com.limelight.utils.Dialog;
+import com.limelight.utils.SpinnerDialog;
import com.limelight.utils.UiHelper;
import android.app.Activity;
@@ -16,14 +17,17 @@ import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
+import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
+import android.view.inputmethod.EditorInfo;
import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
public class AddComputerManually extends Activity {
- private Button addPcButton;
private TextView hostText;
private ComputerManagerService.ComputerManagerBinder managerBinder;
private LinkedBlockingQueue computersToAdd = new LinkedBlockingQueue();
@@ -43,6 +47,9 @@ public class AddComputerManually extends Activity {
private void doAddPc(String host) {
String msg;
boolean finish = false;
+
+ SpinnerDialog dialog = SpinnerDialog.displayDialog(this, "Add PC Manually", "Connecting to the specified PC...", false);
+
try {
InetAddress addr = InetAddress.getByName(host);
@@ -56,8 +63,10 @@ public class AddComputerManually extends Activity {
} catch (UnknownHostException e) {
msg = getResources().getString(R.string.addpc_unknown_host);
}
-
- final boolean toastFinish = finish;
+
+ dialog.dismiss();
+
+ final boolean toastFinish = finish;
final String toastMsg = msg;
AddComputerManually.this.runOnUiThread(new Runnable() {
@Override
@@ -110,6 +119,7 @@ public class AddComputerManually extends Activity {
super.onStop();
Dialog.closeDialogs();
+ SpinnerDialog.closeDialogs(this);
}
@Override
@@ -130,24 +140,28 @@ public class AddComputerManually extends Activity {
UiHelper.notifyNewRootView(this);
- this.addPcButton = (Button) findViewById(R.id.addPc);
this.hostText = (TextView) findViewById(R.id.hostTextView);
+ hostText.setImeOptions(EditorInfo.IME_ACTION_DONE);
+ hostText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+ @Override
+ public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent) {
+ if (actionId == EditorInfo.IME_ACTION_DONE ||
+ keyEvent.getAction() == KeyEvent.ACTION_DOWN &&
+ keyEvent.getKeyCode() == KeyEvent.KEYCODE_ENTER) {
+ if (hostText.getText().length() == 0) {
+ Toast.makeText(AddComputerManually.this, getResources().getString(R.string.addpc_enter_ip), Toast.LENGTH_LONG).show();
+ return true;
+ }
+
+ computersToAdd.add(hostText.getText().toString());
+ }
+
+ return false;
+ }
+ });
// Bind to the ComputerManager service
bindService(new Intent(AddComputerManually.this,
- ComputerManagerService.class), serviceConnection, Service.BIND_AUTO_CREATE);
-
- addPcButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- if (hostText.getText().length() == 0) {
- Toast.makeText(AddComputerManually.this, getResources().getString(R.string.addpc_enter_ip), Toast.LENGTH_LONG).show();
- return;
- }
-
- Toast.makeText(AddComputerManually.this, getResources().getString(R.string.addpc_adding_pc), Toast.LENGTH_SHORT).show();
- computersToAdd.add(hostText.getText().toString());
- }
- });
+ ComputerManagerService.class), serviceConnection, Service.BIND_AUTO_CREATE);
}
}
diff --git a/app/src/main/res/drawable/add_computer.png b/app/src/main/res/drawable/add_computer.png
new file mode 100644
index 00000000..e228d32d
Binary files /dev/null and b/app/src/main/res/drawable/add_computer.png differ
diff --git a/app/src/main/res/drawable/computer.png b/app/src/main/res/drawable/computer.png
new file mode 100644
index 00000000..1b6abe7c
Binary files /dev/null and b/app/src/main/res/drawable/computer.png differ
diff --git a/app/src/main/res/drawable/image_loading.png b/app/src/main/res/drawable/image_loading.png
new file mode 100644
index 00000000..e03a5d77
Binary files /dev/null and b/app/src/main/res/drawable/image_loading.png differ
diff --git a/app/src/main/res/drawable/list_view_unselected.xml b/app/src/main/res/drawable/list_view_unselected.xml
deleted file mode 100644
index 5150c083..00000000
--- a/app/src/main/res/drawable/list_view_unselected.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
diff --git a/app/src/main/res/drawable/play.png b/app/src/main/res/drawable/play.png
new file mode 100644
index 00000000..d03e6e6e
Binary files /dev/null and b/app/src/main/res/drawable/play.png differ
diff --git a/app/src/main/res/drawable/settings.png b/app/src/main/res/drawable/settings.png
new file mode 100644
index 00000000..8d20bbb2
Binary files /dev/null and b/app/src/main/res/drawable/settings.png differ
diff --git a/app/src/main/res/layout-land/activity_pc_view.xml b/app/src/main/res/layout-land/activity_pc_view.xml
index 8c7b4df4..06ad7c52 100644
--- a/app/src/main/res/layout-land/activity_pc_view.xml
+++ b/app/src/main/res/layout-land/activity_pc_view.xml
@@ -8,48 +8,64 @@
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".PcView" >
-
-
-
-
-
+ android:layout_toLeftOf="@+id/manuallyAddPc"
+ android:layout_toRightOf="@+id/settingsButton">
+
+
+
+
+
+
-
-
-
+
+
+ android:layout_alignParentRight="true"
+ android:src="@drawable/add_computer"
+ style="?android:attr/borderlessButtonStyle"/>
\ No newline at end of file
diff --git a/app/src/main/res/layout-port/activity_pc_view.xml b/app/src/main/res/layout-port/activity_pc_view.xml
index e1f0d415..20a7cb60 100644
--- a/app/src/main/res/layout-port/activity_pc_view.xml
+++ b/app/src/main/res/layout-port/activity_pc_view.xml
@@ -8,46 +8,64 @@
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".PcView" >
-
-
-
-
-
+ android:layout_toLeftOf="@+id/manuallyAddPc"
+ android:layout_toRightOf="@+id/settingsButton">
+
+
+
+
+
+
-
+ android:src="@drawable/settings"
+ style="?android:attr/borderlessButtonStyle"/>
-
+ android:layout_width="70dp"
+ android:layout_height="65dp"
+ android:cropToPadding="false"
+ android:scaleType="fitXY"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentRight="true"
+ android:src="@drawable/add_computer"
+ style="?android:attr/borderlessButtonStyle"/>
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_add_computer_manually.xml b/app/src/main/res/layout/activity_add_computer_manually.xml
index 8a81268c..696d0297 100644
--- a/app/src/main/res/layout/activity_add_computer_manually.xml
+++ b/app/src/main/res/layout/activity_add_computer_manually.xml
@@ -5,16 +5,26 @@
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
- android:paddingTop="10dp"
- tools:context=".Connection" >
+ android:paddingTop="@dimen/activity_vertical_margin"
+ tools:context=".AddComputerManually" >
+
+
-
-
diff --git a/app/src/main/res/layout/activity_app_view.xml b/app/src/main/res/layout/activity_app_view.xml
index 93ba5a74..c7b3c825 100644
--- a/app/src/main/res/layout/activity_app_view.xml
+++ b/app/src/main/res/layout/activity_app_view.xml
@@ -8,19 +8,18 @@
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".AppView" >
-
-
+ android:layout_below="@+id/appListText">
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/pc_grid_item.xml b/app/src/main/res/layout/pc_grid_item.xml
new file mode 100644
index 00000000..18204684
--- /dev/null
+++ b/app/src/main/res/layout/pc_grid_item.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/simplerow.xml b/app/src/main/res/layout/simplerow.xml
deleted file mode 100644
index 6c40b68e..00000000
--- a/app/src/main/res/layout/simplerow.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
-
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 87329220..1c448103 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -43,13 +43,13 @@
- Online
+
Establishing Connection
@@ -61,37 +61,37 @@
Starting
Connection Error
Fail starting
- Connection Terminated
- The connection failed unexpectedly
+ Connection Terminated
+ The connection was terminated
Unable to connect to the specified computer. Make sure the required ports are allowed through the firewall.
Successfully added computer
Unable to resolve PC address. Make sure you didn\'t make a typo in the address.
You must enter an IP address
- Adding PC...
+
- Discovery is running. No computers found yet. If your PC doesn't show up in about 15 seconds,
+
IP address of GeForce PC
- PC List
+
- App List for
+ Apps on
Resume Session
Quit Session
Quit Current Game and Start
Cancel
- Running
- No apps found. Try rescanningfor games in GeForce Experience.
+
+
App List
- Refreshing app list...
+ Refreshing apps...
Error
Failed to get app list
Quitting
@@ -99,7 +99,8 @@
Failed to quit
- Manually Add PC
+ Add PC Manually
+ Add PC
Basic Settings