From 4d01e1afe600eaf565fbb37718b95bf519706e38 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Fri, 30 Jan 2015 18:49:01 -0500 Subject: [PATCH] Stub icon scaling and allow background updating of the applist --- app/libs/limelight-common.jar | Bin 956639 -> 956815 bytes app/src/main/java/com/limelight/AppView.java | 326 +++++++++++++----- app/src/main/java/com/limelight/PcView.java | 13 +- .../computers/ComputerManagerService.java | 113 ++++++ .../com/limelight/grid/AppGridAdapter.java | 76 ++-- .../limelight/grid/GenericGridAdapter.java | 19 +- .../com/limelight/grid/PcGridAdapter.java | 4 +- .../preferences/PreferenceConfiguration.java | 5 +- .../java/com/limelight/utils/CacheHelper.java | 55 +++ app/src/main/res/values/strings.xml | 5 +- app/src/main/res/xml/preferences.xml | 5 + 11 files changed, 467 insertions(+), 154 deletions(-) create mode 100644 app/src/main/java/com/limelight/utils/CacheHelper.java diff --git a/app/libs/limelight-common.jar b/app/libs/limelight-common.jar index c248abce216a9999b9e5eaabb3ca30b7b4857d19..63660c373a81e7edda5f5c7bd1ef4f2930331972 100644 GIT binary patch delta 11288 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 G3=9CB>Y^e5 diff --git a/app/src/main/java/com/limelight/AppView.java b/app/src/main/java/com/limelight/AppView.java index 3568c382..21ccbdf0 100644 --- a/app/src/main/java/com/limelight/AppView.java +++ b/app/src/main/java/com/limelight/AppView.java @@ -1,32 +1,45 @@ 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; @@ -35,26 +48,145 @@ import android.view.ContextMenu.ContextMenuInfo; import android.widget.AbsListView; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; +import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; import android.widget.AdapterView.AdapterContextMenuInfo; public class AppView extends Activity implements AdapterFragmentCallbacks { private AppGridAdapter appGridAdapter; - private InetAddress ipAddress; - private String uniqueId; - private boolean remote; - private boolean firstLoad = true; - + 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; private final static int START_WTIH_QUIT = 4; - - public final static String ADDRESS_EXTRA = "Address"; - public final static String UNIQUEID_EXTRA = "UniqueId"; - public final static String NAME_EXTRA = "Name"; - public final static String REMOTE_EXTRA = "Remote"; + + 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)); + + // Start updates + startComputerUpdates(); + + try { + appGridAdapter = new AppGridAdapter(AppView.this, 1.0, + PreferenceConfiguration.readPreferences(AppView.this).listMode, + computer, managerBinder.getUniqueId()); + } catch (Exception e) { + e.printStackTrace(); + finish(); + return; + } + + // Load the app grid with cached data (if possible) + populateAppGridWithCache(); + + getFragmentManager().beginTransaction() + .add(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; + } + + // 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) { @@ -70,41 +202,35 @@ public class AppView extends Activity implements AdapterFragmentCallbacks { 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; - } - try { - appGridAdapter = new AppGridAdapter(this, - PreferenceConfiguration.readPreferences(this).listMode, - ipAddress, uniqueId); - } catch (Exception e) { - e.printStackTrace(); - finish(); - return; - } - - getFragmentManager().beginTransaction() - .add(R.id.appFragmentContainer, new AdapterFragment()).commitAllowingStateLoss(); + // 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 + updateUiWithAppList(NvHTTP.getAppListByReader(new InputStreamReader(CacheHelper.openCacheFileForInput(getCacheDir(), "applist", uuidString)))); + LimeLog.info("Loaded applist from cache"); + } catch (Exception e) { + 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 +238,25 @@ public class AppView extends Activity implements AdapterFragmentCallbacks { 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; @@ -232,62 +365,62 @@ public class AppView extends Activity implements AdapterFragmentCallbacks { 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 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 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); } @@ -299,21 +432,26 @@ public class AppView extends Activity implements AdapterFragmentCallbacks { 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() { diff --git a/app/src/main/java/com/limelight/PcView.java b/app/src/main/java/com/limelight/PcView.java index e9dd5a6e..ebf64628 100644 --- a/app/src/main/java/com/limelight/PcView.java +++ b/app/src/main/java/com/limelight/PcView.java @@ -156,7 +156,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks { bindService(new Intent(PcView.this, ComputerManagerService.class), serviceConnection, Service.BIND_AUTO_CREATE); - pcGridAdapter = new PcGridAdapter(this, + pcGridAdapter = new PcGridAdapter(this, 1.0, PreferenceConfiguration.readPreferences(this).listMode); initializeViews(); @@ -474,16 +474,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks { 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); } diff --git a/app/src/main/java/com/limelight/computers/ComputerManagerService.java b/app/src/main/java/com/limelight/computers/ComputerManagerService.java index afafbe58..9af8808b 100644 --- a/app/src/main/java/com/limelight/computers/ComputerManagerService.java +++ b/app/src/main/java/com/limelight/computers/ComputerManagerService.java @@ -1,7 +1,11 @@ package com.limelight.computers; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; import java.net.InetAddress; import java.util.LinkedList; +import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import com.limelight.LimeLog; @@ -11,6 +15,7 @@ import com.limelight.nvstream.http.ComputerDetails; 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; @@ -179,10 +184,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 @@ -462,6 +483,98 @@ 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.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) { + 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(); + + // Open the cache file + LimeLog.info("Updating app list from "+computer.uuid.toString()); + 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 + listener.notifyComputerUpdated(computer); + } catch (IOException e) { + e.printStackTrace(); + } + } while (waitPollingDelay()); + } + }; + thread.start(); + } + + public void stop() { + if (thread != null) { + thread.interrupt(); + + try { + thread.join(); + } catch (InterruptedException e) {} + + thread = null; + } + } + } } class PollingTuple { diff --git a/app/src/main/java/com/limelight/grid/AppGridAdapter.java b/app/src/main/java/com/limelight/grid/AppGridAdapter.java index fb8476a6..481ae2be 100644 --- a/app/src/main/java/com/limelight/grid/AppGridAdapter.java +++ b/app/src/main/java/com/limelight/grid/AppGridAdapter.java @@ -13,7 +13,9 @@ 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; @@ -32,6 +34,7 @@ import java.security.SecureRandom; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; +import java.util.UUID; import java.util.concurrent.Future; import javax.net.ssl.HostnameVerifier; @@ -45,17 +48,16 @@ import java.security.cert.X509Certificate; public class AppGridAdapter extends GenericGridAdapter { - private boolean listMode; - private InetAddress address; + private ComputerDetails computer; private String uniqueId; private LimelightCryptoProvider cryptoProvider; private SSLContext sslContext; private final HashMap pendingRequests = new HashMap(); - public AppGridAdapter(Context context, boolean listMode, InetAddress address, String uniqueId) throws NoSuchAlgorithmException, KeyManagementException { - super(context, listMode ? R.layout.simple_row : R.layout.app_grid_item, R.drawable.image_loading); + public AppGridAdapter(Context context, double gridScaleFactor, boolean listMode, ComputerDetails computer, String uniqueId) throws NoSuchAlgorithmException, KeyManagementException { + super(context, listMode ? R.layout.simple_row : R.layout.app_grid_item, R.drawable.image_loading, gridScaleFactor); - this.address = address; + this.computer = computer; this.uniqueId = uniqueId; cryptoProvider = PlatformBinding.getCryptoProvider(context); @@ -117,6 +119,15 @@ public class AppGridAdapter extends GenericGridAdapter { }); } + 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(); @@ -144,47 +155,24 @@ public class AppGridAdapter extends GenericGridAdapter { } } - private Bitmap checkBitmapCache(String addrStr, int appId) { - File addrFolder = new File(context.getCacheDir(), addrStr); - if (addrFolder.isDirectory()) { - File bitmapFile = new File(addrFolder, appId+".png"); - if (bitmapFile.exists()) { - InputStream fileIn = null; - try { - fileIn = new BufferedInputStream(new FileInputStream(bitmapFile)); - Bitmap bm = BitmapFactory.decodeStream(fileIn); - if (bm == null) { - // The image seems corrupt - bitmapFile.delete(); - } - - return bm; - } catch (IOException e) { - e.printStackTrace(); - bitmapFile.delete(); - } finally { - if (fileIn != null) { - try { - fileIn.close(); - } catch (IOException ignored) {} - } - } - } - } - + private Bitmap checkBitmapCache(int appId) { + try { + InputStream in = CacheHelper.openCacheFileForInput(context.getCacheDir(), "boxart", computer.uuid.toString(), appId+".png"); + Bitmap bm = BitmapFactory.decodeStream(in); + in.close(); + return bm; + } catch (IOException e) {} return null; } // TODO: Handle pruning of bitmap cache - private void populateBitmapCache(String addrStr, int appId, Bitmap bitmap) { - File addrFolder = new File(context.getCacheDir(), addrStr); - addrFolder.mkdirs(); - - File bitmapFile = new File(addrFolder, appId+".png"); + private void populateBitmapCache(UUID uuid, int appId, Bitmap bitmap) { try { // PNG ignores quality setting - bitmap.compress(Bitmap.CompressFormat.PNG, 0, new FileOutputStream(bitmapFile)); - } catch (FileNotFoundException e) { + 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(); } } @@ -197,10 +185,10 @@ public class AppGridAdapter extends GenericGridAdapter { Ion.getDefault(imgView.getContext()).getHttpClient().getSSLSocketMiddleware().setSSLContext(sslContext); // Check the on-disk cache - Bitmap cachedBitmap = checkBitmapCache(address.getHostAddress(), obj.app.getAppId()); + Bitmap cachedBitmap = checkBitmapCache(obj.app.getAppId()); if (cachedBitmap != null) { // Cache hit; we're done - LimeLog.info("Image cache hit for ("+address.getHostAddress()+", "+obj.app.getAppId()+")"); + LimeLog.info("Image cache hit for ("+computer.uuid+", "+obj.app.getAppId()+")"); imgView.setImageBitmap(cachedBitmap); return true; } @@ -210,7 +198,7 @@ public class AppGridAdapter extends GenericGridAdapter { Future f = Ion.with(imgView) .placeholder(defaultImageRes) .error(defaultImageRes) - .load("https://" + address.getHostAddress() + ":47984/appasset?uniqueid=" + uniqueId + "&appid=" + + .load("https://" + getCurrentAddress().getHostAddress() + ":47984/appasset?uniqueid=" + uniqueId + "&appid=" + obj.app.getAppId() + "&AssetType=2&AssetIdx=0") .withBitmapInfo() .setCallback( @@ -225,7 +213,7 @@ public class AppGridAdapter extends GenericGridAdapter { if (result != null && result.getBitmapInfo() != null && result.getBitmapInfo().bitmap != null) { - populateBitmapCache(address.getHostAddress(), obj.app.getAppId(), + populateBitmapCache(computer.uuid, obj.app.getAppId(), result.getBitmapInfo().bitmap); } } diff --git a/app/src/main/java/com/limelight/grid/GenericGridAdapter.java b/app/src/main/java/com/limelight/grid/GenericGridAdapter.java index f0a17618..b6a416b6 100644 --- a/app/src/main/java/com/limelight/grid/GenericGridAdapter.java +++ b/app/src/main/java/com/limelight/grid/GenericGridAdapter.java @@ -18,11 +18,13 @@ public abstract class GenericGridAdapter extends BaseAdapter { protected int layoutId; protected ArrayList itemList = new ArrayList(); protected LayoutInflater inflater; + protected double gridSizeFactor; - public GenericGridAdapter(Context context, int layoutId, int defaultImageRes) { + public GenericGridAdapter(Context context, int layoutId, int defaultImageRes, double gridSizeFactor) { this.context = context; this.layoutId = layoutId; this.defaultImageRes = defaultImageRes; + this.gridSizeFactor = gridSizeFactor; this.inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); } @@ -64,11 +66,26 @@ public abstract class GenericGridAdapter extends BaseAdapter { if (!populateImageView(imgView, itemList.get(i))) { imgView.setImageResource(defaultImageRes); } + + ViewGroup.LayoutParams params = imgView.getLayoutParams(); + params.width *= gridSizeFactor; + params.height *= gridSizeFactor; + imgView.setLayoutParams(params); } if (!populateTextView(txtView, itemList.get(i))) { txtView.setText(itemList.get(i).toString()); + + ViewGroup.LayoutParams params = txtView.getLayoutParams(); + params.width *= gridSizeFactor; + params.height *= gridSizeFactor; + txtView.setLayoutParams(params); } if (overlayView != null) { + ViewGroup.LayoutParams params = overlayView.getLayoutParams(); + params.width *= gridSizeFactor; + params.height *= gridSizeFactor; + overlayView.setLayoutParams(params); + if (!populateOverlayView(overlayView, itemList.get(i))) { overlayView.setVisibility(View.INVISIBLE); } diff --git a/app/src/main/java/com/limelight/grid/PcGridAdapter.java b/app/src/main/java/com/limelight/grid/PcGridAdapter.java index 8e370de3..b2a63cf1 100644 --- a/app/src/main/java/com/limelight/grid/PcGridAdapter.java +++ b/app/src/main/java/com/limelight/grid/PcGridAdapter.java @@ -13,8 +13,8 @@ import java.util.Comparator; public class PcGridAdapter extends GenericGridAdapter { - public PcGridAdapter(Context context, boolean listMode) { - super(context, listMode ? R.layout.simple_row : R.layout.pc_grid_item, R.drawable.computer); + public PcGridAdapter(Context context, double gridScaleFactor, boolean listMode) { + super(context, listMode ? R.layout.simple_row : R.layout.pc_grid_item, R.drawable.computer, gridScaleFactor); } public void addComputer(PcView.ComputerObject computer) { diff --git a/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java b/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java index 0d850a0e..bcc8c00c 100644 --- a/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java +++ b/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java @@ -15,6 +15,7 @@ public class PreferenceConfiguration { 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; @@ -31,6 +32,7 @@ public class PreferenceConfiguration { private static final int DEFAULT_DEADZONE = 15; public static final String DEFAULT_LANGUAGE = "default"; private static final boolean DEFAULT_LIST_MODE = false; + private static final boolean DEFAULT_SMALL_ICON = false; public static final int FORCE_HARDWARE_DECODER = -1; public static final int AUTOSELECT_DECODER = 0; @@ -42,7 +44,7 @@ public class PreferenceConfiguration { public int deadzonePercentage; public boolean stretchVideo, enableSops, playHostAudio, disableWarnings; public String language; - public boolean listMode; + public boolean listMode, smallIconMode; public static int getDefaultBitrate(String resFpsString) { if (resFpsString.equals("720p30")) { @@ -149,6 +151,7 @@ public class PreferenceConfiguration { 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, DEFAULT_SMALL_ICON); return config; } diff --git a/app/src/main/java/com/limelight/utils/CacheHelper.java b/app/src/main/java/com/limelight/utils/CacheHelper.java new file mode 100644 index 00000000..b22bee93 --- /dev/null +++ b/app/src/main/java/com/limelight/utils/CacheHelper.java @@ -0,0 +1,55 @@ +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) { + Scanner s = new Scanner(in); + + StringBuilder sb = new StringBuilder(); + while (s.hasNext()) { + sb.append(s.next()); + } + + return sb.toString(); + } + + public static void writeStringToOutputStream(OutputStream out, String str) throws IOException { + out.write(str.getBytes("UTF-8")); + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2b9e1a68..d5258ef4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -58,7 +58,8 @@ Searching for PCs… Yes No - + Lost connection to PC + Apps on Resume Session @@ -102,6 +103,8 @@ Language to use for Limelight Use lists instead of grids Display apps and PCs in lists instead of grids + Use small icons + Use small icons in grid items to allow more items on screen Host Settings Optimize game settings diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 44f93e74..4e505719 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -54,6 +54,11 @@ android:entryValues="@array/language_values" android:summary="@string/summary_language_list" android:defaultValue="default" /> +