diff --git a/libs/limelight-common.jar b/libs/limelight-common.jar index c7747799..1e437dcc 100644 Binary files a/libs/limelight-common.jar and b/libs/limelight-common.jar differ diff --git a/src/com/limelight/AdvancedSettings.java b/src/com/limelight/AdvancedSettings.java index 86bbacff..40941ed7 100644 --- a/src/com/limelight/AdvancedSettings.java +++ b/src/com/limelight/AdvancedSettings.java @@ -9,6 +9,7 @@ import android.os.Bundle; import android.widget.CompoundButton; import android.widget.RadioButton; import android.widget.SeekBar; +import android.widget.SeekBar.OnSeekBarChangeListener; import android.widget.TextView; import android.widget.CompoundButton.OnCheckedChangeListener; @@ -17,6 +18,9 @@ public class AdvancedSettings extends Activity { private RadioButton forceSoftDec, autoDec, forceHardDec; private SeekBar bitrateSlider; private TextView bitrateLabel; + + private static final int BITRATE_FLOOR = 1; + private static final int BITRATE_CEILING = 100; @Override public void onPause() { @@ -49,7 +53,7 @@ public class AdvancedSettings extends Activity { prefs = getSharedPreferences(Game.PREFS_FILE_NAME, Context.MODE_MULTI_PROCESS); - bitrateSlider.setMax(Game.BITRATE_CEILING); + bitrateSlider.setMax(BITRATE_CEILING); bitrateSlider.setProgress(prefs.getInt(Game.BITRATE_PREF_STRING, Game.DEFAULT_BITRATE)); updateBitrateLabel(); @@ -94,6 +98,31 @@ public class AdvancedSettings extends Activity { forceSoftDec.setOnCheckedChangeListener(occl); forceHardDec.setOnCheckedChangeListener(occl); autoDec.setOnCheckedChangeListener(occl); + + this.bitrateSlider.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, + boolean fromUser) { + + // Verify the user's selection + if (fromUser) { + if (progress < BITRATE_FLOOR) { + seekBar.setProgress(BITRATE_FLOOR); + return; + } + } + + updateBitrateLabel(); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + } + }); } private void updateBitrateLabel() { diff --git a/src/com/limelight/AppView.java b/src/com/limelight/AppView.java index bf6f5ac6..ef2787f8 100644 --- a/src/com/limelight/AppView.java +++ b/src/com/limelight/AppView.java @@ -35,6 +35,7 @@ public class AppView extends Activity { private ArrayAdapter appListAdapter; private InetAddress ipAddress; private String uniqueId; + private boolean remote; private final static int RESUME_ID = 1; private final static int QUIT_ID = 2; @@ -42,6 +43,7 @@ public class AppView extends Activity { 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"; @Override protected void onCreate(Bundle savedInstanceState) { @@ -50,6 +52,7 @@ public class AppView extends Activity { byte[] address = getIntent().getByteArrayExtra(ADDRESS_EXTRA); uniqueId = getIntent().getStringExtra(UNIQUEID_EXTRA); + remote = getIntent().getBooleanExtra(REMOTE_EXTRA, false); if (address == null || uniqueId == null) { return; } @@ -195,6 +198,7 @@ public class AppView extends Activity { intent.putExtra(Game.EXTRA_HOST, ipAddress.getHostAddress()); intent.putExtra(Game.EXTRA_APP, app.getAppName()); intent.putExtra(Game.EXTRA_UNIQUEID, uniqueId); + intent.putExtra(Game.EXTRA_STREAMING_REMOTE, remote); startActivity(intent); } diff --git a/src/com/limelight/Game.java b/src/com/limelight/Game.java index c359069c..5c7e7cf4 100644 --- a/src/com/limelight/Game.java +++ b/src/com/limelight/Game.java @@ -1,11 +1,5 @@ package com.limelight; -import java.net.Inet4Address; -import java.net.InetAddress; -import java.net.InterfaceAddress; -import java.net.NetworkInterface; -import java.net.SocketException; -import java.util.Enumeration; import com.limelight.binding.PlatformBinding; import com.limelight.binding.input.ControllerHandler; @@ -26,7 +20,6 @@ import android.content.SharedPreferences; import android.graphics.Point; import android.media.AudioManager; import android.net.ConnectivityManager; -import android.net.NetworkInfo; import android.net.wifi.WifiManager; import android.os.Bundle; import android.os.Handler; @@ -74,6 +67,7 @@ public class Game extends Activity implements SurfaceHolder.Callback, OnGenericM public static final String EXTRA_HOST = "Host"; public static final String EXTRA_APP = "App"; public static final String EXTRA_UNIQUEID = "UniqueId"; + public static final String EXTRA_STREAMING_REMOTE = "Remote"; public static final String PREFS_FILE_NAME = "gameprefs"; @@ -83,18 +77,11 @@ public class Game extends Activity implements SurfaceHolder.Callback, OnGenericM public static final String DECODER_PREF_STRING = "Decoder"; public static final String BITRATE_PREF_STRING = "Bitrate"; - public static final int BITRATE_FLOOR_720_30 = 2; - public static final int BITRATE_FLOOR_720_60 = 4; - public static final int BITRATE_FLOOR_1080_30 = 4; - public static final int BITRATE_FLOOR_1080_60 = 10; - public static final int BITRATE_DEFAULT_720_30 = 5; public static final int BITRATE_DEFAULT_720_60 = 10; public static final int BITRATE_DEFAULT_1080_30 = 10; public static final int BITRATE_DEFAULT_1080_60 = 30; - - public static final int BITRATE_CEILING = 50; - + public static final int DEFAULT_WIDTH = 1280; public static final int DEFAULT_HEIGHT = 720; public static final int DEFAULT_REFRESH_RATE = 60; @@ -179,21 +166,7 @@ public class Game extends Activity implements SurfaceHolder.Callback, OnGenericM String host = Game.this.getIntent().getStringExtra(EXTRA_HOST); String app = Game.this.getIntent().getStringExtra(EXTRA_APP); String uniqueId = Game.this.getIntent().getStringExtra(EXTRA_UNIQUEID); - InetAddress addr; - boolean enableLargePackets; - try { - // If this does a DNS lookup, it could cause a NetworkOnMainThread exception - // Chances are if it has to do this, we're not on the same network anyways - addr = InetAddress.getByName(host); - - // Check if we can enable large packets if we get this far - enableLargePackets = shouldEnableLargePackets(addr); - } catch (Exception e) { - // We don't want to deal with any exceptions here. The user will be notified - // when the connection fails - enableLargePackets = false; - } - + boolean enableLargePackets = !Game.this.getIntent().getBooleanExtra(EXTRA_STREAMING_REMOTE, true); LimeLog.info("Using large packets? "+enableLargePackets); // Start the connection @@ -208,89 +181,6 @@ public class Game extends Activity implements SurfaceHolder.Callback, OnGenericM sh.addCallback(this); } - private boolean shouldEnableLargePackets(InetAddress targetAddress) { - ConnectivityManager connMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo activeNetworkInfo = connMgr.getActiveNetworkInfo(); - String matchingPrefix; - - if (activeNetworkInfo == null) { - return false; - } - - switch (activeNetworkInfo.getType()) - { - case ConnectivityManager.TYPE_ETHERNET: - matchingPrefix = "eth"; - break; - case ConnectivityManager.TYPE_WIFI: - matchingPrefix = "wlan"; - break; - - default: - // Must be on Ethernet or Wifi to consider that we can send large packets - return false; - } - - // Try to find the interface that corresponds to the active network - try { - Enumeration ifaceList = NetworkInterface.getNetworkInterfaces(); - while (ifaceList.hasMoreElements()) { - NetworkInterface iface = ifaceList.nextElement(); - - // Look for an interface that matches the prefix we expect - if (iface.isUp() && iface.getName().startsWith(matchingPrefix)) { - // Find the IPv4 address for the interface - for (InterfaceAddress addr : iface.getInterfaceAddresses()) { - if (!(addr.getAddress() instanceof Inet4Address)) { - // Skip non-IPv4 addresses - continue; - } - - // Found the right address on the right interface - return isOnSameSubnet(targetAddress, addr.getAddress(), addr.getNetworkPrefixLength()); - } - } - } - } catch (SocketException e) { - e.printStackTrace(); - } - - // We didn't find the interface or something else went wrong - return false; - } - - private boolean isOnSameSubnet(InetAddress targetAddress, InetAddress localAddress, short networkPrefixLength) { - byte[] targetBytes = targetAddress.getAddress(); - byte[] localBytes = localAddress.getAddress(); - - for (int byteIndex = 0; networkPrefixLength > 0; byteIndex++) { - byte target = targetBytes[byteIndex]; - byte local = localBytes[byteIndex]; - - if (networkPrefixLength >= 8) { - // Do a full byte comparison - if (target != local) { - return false; - } - - networkPrefixLength -= 8; - } - else { - target &= (byte)(0xFF << (8 - networkPrefixLength)); - local &= (byte)(0xFF << (8 - networkPrefixLength)); - - // Do a masked comparison - if (target != local) { - return false; - } - - networkPrefixLength = 0; - } - } - - return true; - } - private void checkDataConnection() { ConnectivityManager mgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); diff --git a/src/com/limelight/PcView.java b/src/com/limelight/PcView.java index be8096e0..d3ac3da1 100644 --- a/src/com/limelight/PcView.java +++ b/src/com/limelight/PcView.java @@ -223,6 +223,11 @@ public class PcView extends Activity { Toast.makeText(PcView.this, "Computer is offline", Toast.LENGTH_SHORT).show(); return; } + if (computer.runningGameId != 0) { + Toast.makeText(PcView.this, "Computer is currently in a game. " + + "You must close the game before pairing.", Toast.LENGTH_LONG).show(); + return; + } Toast.makeText(PcView.this, "Pairing...", Toast.LENGTH_SHORT).show(); new Thread(new Runnable() { @@ -305,7 +310,7 @@ public class PcView extends Activity { message = "It may take a few seconds for your PC to wake up. " + "If it doesn't, make sure it's configured properly for Wake-On-LAN."; } catch (IOException e) { - message = "Failed to send wake-on-lan packets"; + message = "Failed to send Wake-On-LAN packets"; } final String toastMessage = message; @@ -388,9 +393,11 @@ public class PcView extends Activity { 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); } startActivity(i); } @@ -459,8 +466,8 @@ public class PcView extends Activity { } private void addListPlaceholder() { - pcListAdapter.add(new ComputerObject("No computers found yet. Make sure your computer is running GFE " + - "or add your PC manually on the settings page.", null)); + pcListAdapter.add(new ComputerObject("Discovery is running. No computers found yet. " + + "Make sure your computer is running GFE or add your PC manually on the settings page.", null)); } private void removeListView(ComputerDetails details) { diff --git a/src/com/limelight/computers/ComputerManagerService.java b/src/com/limelight/computers/ComputerManagerService.java index 9d49a646..b9c6702b 100644 --- a/src/com/limelight/computers/ComputerManagerService.java +++ b/src/com/limelight/computers/ComputerManagerService.java @@ -1,6 +1,11 @@ package com.limelight.computers; +import java.net.Inet4Address; import java.net.InetAddress; +import java.net.InterfaceAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.Enumeration; import java.util.List; import java.util.Timer; import java.util.TimerTask; @@ -18,8 +23,11 @@ import com.limelight.nvstream.mdns.MdnsDiscoveryListener; import android.app.Service; import android.content.ComponentName; +import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; import android.os.Binder; import android.os.IBinder; @@ -180,41 +188,179 @@ public class ComputerManagerService extends Service { }; } + private int getActiveNetworkType() { + ConnectivityManager connMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo activeNetworkInfo = connMgr.getActiveNetworkInfo(); + if (activeNetworkInfo == null) { + return -1; + } + + return activeNetworkInfo.getType(); + } + + private InterfaceAddress getActiveInterfaceAddress() { + String matchingPrefix; + + switch (getActiveNetworkType()) + { + case ConnectivityManager.TYPE_ETHERNET: + matchingPrefix = "eth"; + break; + case ConnectivityManager.TYPE_WIFI: + matchingPrefix = "wlan"; + break; + + default: + // Must be on Ethernet or Wifi to consider that we can send large packets + return null; + } + + // Try to find the interface that corresponds to the active network + try { + Enumeration ifaceList = NetworkInterface.getNetworkInterfaces(); + while (ifaceList.hasMoreElements()) { + NetworkInterface iface = ifaceList.nextElement(); + + // Look for an interface that matches the prefix we expect + if (iface.isUp() && iface.getName().startsWith(matchingPrefix)) { + // Find the IPv4 address for the interface + for (InterfaceAddress addr : iface.getInterfaceAddresses()) { + if (!(addr.getAddress() instanceof Inet4Address)) { + // Skip non-IPv4 addresses + continue; + } + + // Found the right address on the right interface + return addr; + } + } + } + } catch (SocketException e) { + e.printStackTrace(); + } + + // We didn't find the interface or something else went wrong + return null; + } + + private boolean isOnSameSubnet(InetAddress targetAddress, InetAddress localAddress, short networkPrefixLength) { + byte[] targetBytes = targetAddress.getAddress(); + byte[] localBytes = localAddress.getAddress(); + + for (int byteIndex = 0; networkPrefixLength > 0; byteIndex++) { + byte target = targetBytes[byteIndex]; + byte local = localBytes[byteIndex]; + + if (networkPrefixLength >= 8) { + // Do a full byte comparison + if (target != local) { + return false; + } + + networkPrefixLength -= 8; + } + else { + target &= (byte)(0xFF << (8 - networkPrefixLength)); + local &= (byte)(0xFF << (8 - networkPrefixLength)); + + // Do a masked comparison + if (target != local) { + return false; + } + + networkPrefixLength = 0; + } + } + + return true; + } + + private ComputerDetails tryPollIp(InetAddress ipAddr) { + try { + NvHTTP http = new NvHTTP(ipAddr, idManager.getUniqueId(), + null, PlatformBinding.getCryptoProvider(ComputerManagerService.this)); + + return http.getComputerDetails(); + } catch (Exception e) { + return null; + } + } + + private boolean pollComputer(ComputerDetails details, boolean localFirst) { + ComputerDetails polledDetails; + + if (localFirst) { + polledDetails = tryPollIp(details.localIp); + } + else { + polledDetails = tryPollIp(details.remoteIp); + } + + if (polledDetails == null) { + // Failed, so let's try the fallback + if (!localFirst) { + polledDetails = tryPollIp(details.localIp); + } + else { + polledDetails = tryPollIp(details.remoteIp); + } + + // The fallback poll worked + if (polledDetails != null) { + polledDetails.reachability = !localFirst ? ComputerDetails.Reachability.LOCAL : + ComputerDetails.Reachability.REMOTE; + } + } else { + polledDetails.reachability = localFirst ? ComputerDetails.Reachability.LOCAL : + ComputerDetails.Reachability.REMOTE; + } + + // Machine was unreachable both tries + if (polledDetails == null) { + return false; + } + + // If we got here, it's reachable + details.update(polledDetails); + return true; + } + + private boolean doPollMachine(ComputerDetails details) { + // Get the network type + int networkType = getActiveNetworkType(); + switch (networkType) { + // We'll check local first on these if we find + // we're on the same subnet + case ConnectivityManager.TYPE_ETHERNET: + case ConnectivityManager.TYPE_WIFI: + InterfaceAddress ifaceAddr = getActiveInterfaceAddress(); + if (ifaceAddr != null) { + if (isOnSameSubnet(details.localIp, ifaceAddr.getAddress(), ifaceAddr.getNetworkPrefixLength())) { + // It's on the same subnet, so poll local first + LimeLog.info("Machine looks local; trying local IP first"); + return pollComputer(details, true); + } + } + // Fall through to remote first + default: + LimeLog.info("Machine looks remote; trying remote IP first"); + return pollComputer(details, false); + } + } + private Runnable getPollingRunnable(final ComputerDetails details) { return new Runnable() { @Override public void run() { - boolean newPc = details.name == null; + boolean newPc = (details.name == null); - try { - // Try the local IP first - NvHTTP http = new NvHTTP(details.localIp, idManager.getUniqueId(), - null, PlatformBinding.getCryptoProvider(ComputerManagerService.this)); - - ComputerDetails localDetails = http.getComputerDetails(); - - // If we got here, it's reachable - details.reachability = ComputerDetails.Reachability.LOCAL; - details.update(localDetails); - } catch (Exception e) { - // This isn't horrible yet; we'll try remote - try { - NvHTTP http = new NvHTTP(details.remoteIp, idManager.getUniqueId(), - null, PlatformBinding.getCryptoProvider(ComputerManagerService.this)); - - ComputerDetails remoteDetails = http.getComputerDetails(); - - // If we got here, it's reachable - details.reachability = ComputerDetails.Reachability.REMOTE; - details.update(remoteDetails); - } catch (Exception e1) { - // No good, it's offline - details.state = ComputerDetails.State.OFFLINE; - details.reachability = ComputerDetails.Reachability.OFFLINE; - } + // Poll the machine + if (!doPollMachine(details)) { + details.state = ComputerDetails.State.OFFLINE; + details.reachability = ComputerDetails.Reachability.OFFLINE; } - + // If it's online, update our persistent state if (details.state == ComputerDetails.State.ONLINE) { if (!newPc) {