Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3d177e97e4 | |||
| 6c3aaedc83 | |||
| bf84ebef6d | |||
| 8991b29329 | |||
| fa84575be5 | |||
| 0432d5725b | |||
| 8e7b144339 | |||
| fc629db653 | |||
| d5863e1bef | |||
| c2c3a6b37c | |||
| e701699dea | |||
| 17179bd027 | |||
| b2f210700d | |||
| f0e85c4c53 | |||
| 92f8425ace | |||
| 6ad001e8be | |||
| b6e4d5528b | |||
| 0f0b83badc | |||
| 453fbb5f58 | |||
| e7dc3a4c11 | |||
| d68b2382cf | |||
| 1b5330323c | |||
| 8aba4888e1 | |||
| 1c3b9a3859 | |||
| e8f04f5a3b |
+2
-2
@@ -11,8 +11,8 @@ android {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 25
|
||||
|
||||
versionName "4.7.3"
|
||||
versionCode = 108
|
||||
versionName "4.8.1"
|
||||
versionCode = 113
|
||||
}
|
||||
|
||||
productFlavors {
|
||||
|
||||
Binary file not shown.
@@ -7,60 +7,85 @@
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
|
||||
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
|
||||
<uses-feature android:name="android.hardware.wifi" android:required="false" />
|
||||
<uses-feature android:name="android.hardware.gamepad" android:required="false" />
|
||||
<uses-feature android:name="android.hardware.usb.host" android:required="false" />
|
||||
<uses-feature android:name="android.software.leanback" android:required="false" />
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.touchscreen"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.wifi"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.gamepad"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.usb.host"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.software.leanback"
|
||||
android:required="false" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:banner="@drawable/atv_banner"
|
||||
android:theme="@style/AppTheme" >
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:theme="@style/AppTheme">
|
||||
|
||||
<!-- Samsung multi-window support -->
|
||||
<uses-library android:name="com.sec.android.app.multiwindow" android:required="false" />
|
||||
<meta-data android:name="com.sec.android.support.multiwindow" android:value="true" />
|
||||
|
||||
<uses-library
|
||||
android:name="com.sec.android.app.multiwindow"
|
||||
android:required="false" />
|
||||
|
||||
<meta-data
|
||||
android:name="com.sec.android.support.multiwindow"
|
||||
android:value="true" />
|
||||
|
||||
<activity
|
||||
android:name=".PcView"
|
||||
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<category android:name="android.intent.category.MULTIWINDOW_LAUNCHER" />
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||
<category android:name="tv.ouya.intent.category.APP" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Small hack to support launcher shortcuts without relaunching over and over again when the back button is pressed -->
|
||||
<activity
|
||||
android:name=".AppViewShortcutTrampoline"
|
||||
android:noHistory="true"
|
||||
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="com.limelight.PcView" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".AppView"
|
||||
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection" >
|
||||
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="com.limelight.PcView" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".preferences.StreamSettings"
|
||||
android:label="Streaming Settings" >
|
||||
android:label="Streaming Settings">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="com.limelight.PcView" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".preferences.AddComputerManually"
|
||||
android:label="Add Computer Manually" >
|
||||
android:label="Add Computer Manually">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="com.limelight.PcView" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".Game"
|
||||
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection"
|
||||
android:screenOrientation="sensorLandscape"
|
||||
android:theme="@style/StreamTheme"
|
||||
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection" >
|
||||
android:theme="@style/StreamTheme">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="com.limelight.AppView" />
|
||||
@@ -75,6 +100,8 @@
|
||||
<service
|
||||
android:name=".binding.input.driver.UsbDriverService"
|
||||
android:label="Usb Driver Service" />
|
||||
|
||||
<activity android:name=".HelpActivity"></activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
</manifest>
|
||||
@@ -49,12 +49,11 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
||||
|
||||
private ComputerDetails computer;
|
||||
private ComputerManagerService.ApplistPoller poller;
|
||||
private SpinnerDialog blockingLoadSpinner, blockingServerinfoSpinner;
|
||||
private SpinnerDialog blockingLoadSpinner;
|
||||
private String lastRawApplist;
|
||||
private int lastRunningAppId;
|
||||
private boolean suspendGridUpdates;
|
||||
private boolean inForeground;
|
||||
private boolean launchedFromShortcut;
|
||||
|
||||
private final static int START_OR_RESUME_ID = 1;
|
||||
private final static int QUIT_ID = 2;
|
||||
@@ -63,7 +62,6 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
||||
|
||||
public final static String NAME_EXTRA = "Name";
|
||||
public final static String UUID_EXTRA = "UUID";
|
||||
public final static String SHORTCUT_EXTRA = "Shortcut";
|
||||
|
||||
private ComputerManagerService.ComputerManagerBinder managerBinder;
|
||||
private final ServiceConnection serviceConnection = new ServiceConnection() {
|
||||
@@ -183,30 +181,6 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
||||
return;
|
||||
}
|
||||
|
||||
if (launchedFromShortcut) {
|
||||
if (details.state == ComputerDetails.State.ONLINE) {
|
||||
if (blockingServerinfoSpinner != null) {
|
||||
blockingServerinfoSpinner.dismiss();
|
||||
blockingServerinfoSpinner = null;
|
||||
}
|
||||
|
||||
if (details.runningGameId != 0) {
|
||||
AppView.this.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// We have to finish this activity here otherwise we'll get into a loop
|
||||
// when the user hits back
|
||||
finish();
|
||||
|
||||
// When launched from shortcut, resume the running game
|
||||
ServerHelper.doStart(AppView.this, new NvApp("app", details.runningGameId), computer, managerBinder);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// App list is the same or empty
|
||||
if (details.rawAppList == null || details.rawAppList.equals(lastRawApplist)) {
|
||||
|
||||
@@ -274,20 +248,17 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
||||
|
||||
uuidString = getIntent().getStringExtra(UUID_EXTRA);
|
||||
|
||||
launchedFromShortcut = getIntent().getBooleanExtra(SHORTCUT_EXTRA, false);
|
||||
if (launchedFromShortcut) {
|
||||
// Display blocking loading spinner
|
||||
blockingLoadSpinner = SpinnerDialog.displayDialog(this, getResources().getString(R.string.conn_establishing_title),
|
||||
getResources().getString(R.string.applist_connect_msg), true);
|
||||
}
|
||||
String computerName = getIntent().getStringExtra(NAME_EXTRA);
|
||||
|
||||
shortcutHelper.reportShortcutUsed(uuidString);
|
||||
|
||||
String labelText = getResources().getString(R.string.title_applist)+" "+getIntent().getStringExtra(NAME_EXTRA);
|
||||
String labelText = getResources().getString(R.string.title_applist)+" "+computerName;
|
||||
TextView label = (TextView) findViewById(R.id.appListText);
|
||||
setTitle(labelText);
|
||||
label.setText(labelText);
|
||||
|
||||
// Add a launcher shortcut for this PC (forced, since this is user interaction)
|
||||
shortcutHelper.createAppViewShortcut(uuidString, computerName, uuidString, true);
|
||||
shortcutHelper.reportShortcutUsed(uuidString);
|
||||
|
||||
// Bind to the computer manager service
|
||||
bindService(new Intent(this, ComputerManagerService.class), serviceConnection,
|
||||
Service.BIND_AUTO_CREATE);
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
package com.limelight;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Service;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
|
||||
import com.limelight.computers.ComputerManagerListener;
|
||||
import com.limelight.computers.ComputerManagerService;
|
||||
import com.limelight.nvstream.http.ComputerDetails;
|
||||
import com.limelight.nvstream.http.NvApp;
|
||||
import com.limelight.utils.Dialog;
|
||||
import com.limelight.utils.ServerHelper;
|
||||
import com.limelight.utils.SpinnerDialog;
|
||||
import com.limelight.utils.UiHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.UUID;
|
||||
|
||||
public class AppViewShortcutTrampoline extends Activity {
|
||||
private String uuidString;
|
||||
|
||||
private ComputerDetails computer;
|
||||
private SpinnerDialog blockingLoadSpinner;
|
||||
|
||||
public final static String UUID_EXTRA = "UUID";
|
||||
|
||||
private ComputerManagerService.ComputerManagerBinder managerBinder;
|
||||
private final 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));
|
||||
|
||||
// Force CMS to repoll this machine
|
||||
managerBinder.invalidateStateForComputer(computer.uuid);
|
||||
|
||||
// Start polling
|
||||
managerBinder.startPolling(new ComputerManagerListener() {
|
||||
@Override
|
||||
public void notifyComputerUpdated(final ComputerDetails details) {
|
||||
// Don't care about other computers
|
||||
if (!details.uuid.toString().equalsIgnoreCase(uuidString)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (details.state != ComputerDetails.State.UNKNOWN) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Stop showing the spinner
|
||||
if (blockingLoadSpinner != null) {
|
||||
blockingLoadSpinner.dismiss();
|
||||
blockingLoadSpinner = null;
|
||||
}
|
||||
|
||||
if (details.state == ComputerDetails.State.ONLINE) {
|
||||
// Close this activity
|
||||
finish();
|
||||
|
||||
// Create a new activity stack for this launch
|
||||
ArrayList<Intent> intentStack = new ArrayList<>();
|
||||
Intent i;
|
||||
|
||||
// Add the PC view at the back (and clear the task)
|
||||
i = new Intent(AppViewShortcutTrampoline.this, PcView.class);
|
||||
i.setAction(Intent.ACTION_MAIN);
|
||||
i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intentStack.add(i);
|
||||
|
||||
// Take this intent's data and create an intent to start the app view
|
||||
i = new Intent(getIntent());
|
||||
i.setClass(AppViewShortcutTrampoline.this, AppView.class);
|
||||
intentStack.add(i);
|
||||
|
||||
// If a game is running, we'll make the stream the top level activity
|
||||
if (details.runningGameId != 0) {
|
||||
intentStack.add(ServerHelper.createStartIntent(AppViewShortcutTrampoline.this,
|
||||
new NvApp("app", details.runningGameId), details, managerBinder));
|
||||
}
|
||||
|
||||
// Now start the activities
|
||||
startActivities(intentStack.toArray(new Intent[]{}));
|
||||
}
|
||||
else if (details.state == ComputerDetails.State.OFFLINE) {
|
||||
// Computer offline - display an error dialog
|
||||
Dialog.displayDialog(AppViewShortcutTrampoline.this,
|
||||
getResources().getString(R.string.conn_error_title),
|
||||
getResources().getString(R.string.error_pc_offline),
|
||||
true);
|
||||
}
|
||||
|
||||
// We don't want any more callbacks from now on, so go ahead
|
||||
// and unbind from the service
|
||||
if (managerBinder != null) {
|
||||
managerBinder.stopPolling();
|
||||
unbindService(serviceConnection);
|
||||
managerBinder = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
public void onServiceDisconnected(ComponentName className) {
|
||||
managerBinder = null;
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
UiHelper.notifyNewRootView(this);
|
||||
|
||||
uuidString = getIntent().getStringExtra(UUID_EXTRA);
|
||||
|
||||
// Bind to the computer manager service
|
||||
bindService(new Intent(this, ComputerManagerService.class), serviceConnection,
|
||||
Service.BIND_AUTO_CREATE);
|
||||
|
||||
blockingLoadSpinner = SpinnerDialog.displayDialog(this, getResources().getString(R.string.conn_establishing_title),
|
||||
getResources().getString(R.string.applist_connect_msg), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
|
||||
if (blockingLoadSpinner != null) {
|
||||
blockingLoadSpinner.dismiss();
|
||||
blockingLoadSpinner = null;
|
||||
}
|
||||
|
||||
Dialog.closeDialogs();
|
||||
|
||||
if (managerBinder != null) {
|
||||
managerBinder.stopPolling();
|
||||
unbindService(serviceConnection);
|
||||
managerBinder = null;
|
||||
}
|
||||
|
||||
finish();
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import com.limelight.preferences.PreferenceConfiguration;
|
||||
import com.limelight.ui.GameGestures;
|
||||
import com.limelight.ui.StreamView;
|
||||
import com.limelight.utils.Dialog;
|
||||
import com.limelight.utils.ShortcutHelper;
|
||||
import com.limelight.utils.SpinnerDialog;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
@@ -97,6 +98,8 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
private boolean grabComboDown = false;
|
||||
private StreamView streamView;
|
||||
|
||||
private ShortcutHelper shortcutHelper;
|
||||
|
||||
private EnhancedDecoderRenderer decoderRenderer;
|
||||
|
||||
private WifiManager.WifiLock wifiLock;
|
||||
@@ -123,11 +126,15 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
public static final String EXTRA_APP_ID = "AppId";
|
||||
public static final String EXTRA_UNIQUEID = "UniqueId";
|
||||
public static final String EXTRA_STREAMING_REMOTE = "Remote";
|
||||
public static final String EXTRA_PC_UUID = "UUID";
|
||||
public static final String EXTRA_PC_NAME = "PcName";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
shortcutHelper = new ShortcutHelper(this);
|
||||
|
||||
String locale = PreferenceConfiguration.readPreferences(this).language;
|
||||
if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
|
||||
Configuration config = new Configuration(getResources().getConfiguration());
|
||||
@@ -193,12 +200,18 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
int appId = Game.this.getIntent().getIntExtra(EXTRA_APP_ID, StreamConfiguration.INVALID_APP_ID);
|
||||
String uniqueId = Game.this.getIntent().getStringExtra(EXTRA_UNIQUEID);
|
||||
boolean remote = Game.this.getIntent().getBooleanExtra(EXTRA_STREAMING_REMOTE, false);
|
||||
String uuid = Game.this.getIntent().getStringExtra(EXTRA_PC_UUID);
|
||||
String pcName = Game.this.getIntent().getStringExtra(EXTRA_PC_NAME);
|
||||
|
||||
if (appId == StreamConfiguration.INVALID_APP_ID) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
// Add a launcher shortcut for this PC (forced, since this is user interaction)
|
||||
shortcutHelper.createAppViewShortcut(uuid, pcName, uuid, true);
|
||||
shortcutHelper.reportShortcutUsed(uuid);
|
||||
|
||||
// Initialize the MediaCodec helper before creating the decoder
|
||||
MediaCodecHelper.initializeWithContext(this);
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.limelight;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Bundle;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
|
||||
import com.limelight.utils.SpinnerDialog;
|
||||
|
||||
public class HelpActivity extends Activity {
|
||||
|
||||
private SpinnerDialog loadingDialog;
|
||||
private WebView webView;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
webView = new WebView(this);
|
||||
setContentView(webView);
|
||||
|
||||
// These allow the user to zoom the page
|
||||
webView.getSettings().setBuiltInZoomControls(true);
|
||||
webView.getSettings().setDisplayZoomControls(false);
|
||||
|
||||
// This sets the view to display the whole page by default
|
||||
webView.getSettings().setUseWideViewPort(true);
|
||||
webView.getSettings().setLoadWithOverviewMode(true);
|
||||
|
||||
// This allows the links to places on the same page to work
|
||||
webView.getSettings().setJavaScriptEnabled(true);
|
||||
|
||||
webView.setWebViewClient(new WebViewClient() {
|
||||
@Override
|
||||
public void onPageStarted(WebView view, String url, Bitmap favicon) {
|
||||
if (loadingDialog == null) {
|
||||
loadingDialog = SpinnerDialog.displayDialog(HelpActivity.this,
|
||||
getResources().getString(R.string.help_loading_title),
|
||||
getResources().getString(R.string.help_loading_msg), false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageFinished(WebView view, String url) {
|
||||
if (loadingDialog != null) {
|
||||
loadingDialog.dismiss();
|
||||
loadingDialog = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
if (url.toUpperCase().startsWith("https://github.com/moonlight-stream/moonlight-docs/wiki/".toUpperCase()) ||
|
||||
url.toUpperCase().startsWith("http://github.com/moonlight-stream/moonlight-docs/wiki/".toUpperCase())) {
|
||||
// Allow navigation to Moonlight docs
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
webView.loadUrl(getIntent().getData().toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
// Back goes back through the WebView history
|
||||
// until no more history remains
|
||||
if (webView.canGoBack()) {
|
||||
webView.goBack();
|
||||
}
|
||||
else {
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import com.limelight.preferences.StreamSettings;
|
||||
import com.limelight.ui.AdapterFragment;
|
||||
import com.limelight.ui.AdapterFragmentCallbacks;
|
||||
import com.limelight.utils.Dialog;
|
||||
import com.limelight.utils.HelpLauncher;
|
||||
import com.limelight.utils.ServerHelper;
|
||||
import com.limelight.utils.ShortcutHelper;
|
||||
import com.limelight.utils.UiHelper;
|
||||
@@ -111,6 +112,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
// Setup the list view
|
||||
ImageButton settingsButton = (ImageButton) findViewById(R.id.settingsButton);
|
||||
ImageButton addComputerButton = (ImageButton) findViewById(R.id.manuallyAddPc);
|
||||
ImageButton helpButton = (ImageButton) findViewById(R.id.helpButton);
|
||||
|
||||
settingsButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
@@ -125,6 +127,12 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
startActivity(i);
|
||||
}
|
||||
});
|
||||
helpButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
HelpLauncher.launchSetupGuide(PcView.this);
|
||||
}
|
||||
});
|
||||
|
||||
getFragmentManager().beginTransaction()
|
||||
.replace(R.id.pcFragmentContainer, new AdapterFragment())
|
||||
@@ -345,6 +353,10 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
// Just navigate to the app view without displaying a toast
|
||||
message = null;
|
||||
success = true;
|
||||
|
||||
// Invalidate reachability information after pairing to force
|
||||
// a refresh before reading pair state again
|
||||
managerBinder.invalidateStateForComputer(computer.uuid);
|
||||
}
|
||||
else {
|
||||
// Should be no other values
|
||||
@@ -373,7 +385,6 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
|
||||
if (toastSuccess) {
|
||||
// Open the app list after a successful pairing attempt
|
||||
computer.pairState = PairState.PAIRED;
|
||||
doAppList(computer);
|
||||
}
|
||||
else {
|
||||
@@ -494,8 +505,6 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
return;
|
||||
}
|
||||
|
||||
shortcutHelper.createAppViewShortcut(computer.uuid.toString(), computer);
|
||||
|
||||
Intent i = new Intent(this, AppView.class);
|
||||
i.putExtra(AppView.NAME_EXTRA, computer.name);
|
||||
i.putExtra(AppView.UUID_EXTRA, computer.uuid.toString());
|
||||
@@ -598,6 +607,11 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
}
|
||||
}
|
||||
|
||||
// Add a launcher shortcut for this PC
|
||||
if (details.pairState == PairState.PAIRED) {
|
||||
shortcutHelper.createAppViewShortcut(details.uuid.toString(), details, false);
|
||||
}
|
||||
|
||||
if (existingEntry != null) {
|
||||
// Replace the information in the existing entry
|
||||
existingEntry.details = details;
|
||||
|
||||
@@ -404,9 +404,19 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
context.isServal = true;
|
||||
context.ignoreBack = true;
|
||||
}
|
||||
// The Xbox One S Bluetooth controller has some mappings that need fixing up
|
||||
// The Xbox One S Bluetooth controller has some mappings that need fixing up.
|
||||
// However, Microsoft released a firmware update with no change to VID/PID
|
||||
// or device name that fixed the mappings for Android. Since there's
|
||||
// no good way to detect this, we'll use the presence of GAS/BRAKE axes
|
||||
// that were added in the latest firmware. If those are present, the only
|
||||
// required fixup is ignoring the select button.
|
||||
else if (devName.equals("Xbox Wireless Controller")) {
|
||||
context.isXboxBtController = true;
|
||||
if (gasRange == null) {
|
||||
context.isXboxBtController = true;
|
||||
}
|
||||
else {
|
||||
context.ignoreBack = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -564,41 +574,38 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
}
|
||||
|
||||
if (context.isDualShock4) {
|
||||
switch (event.getKeyCode()) {
|
||||
case KeyEvent.KEYCODE_BUTTON_Y:
|
||||
return KeyEvent.KEYCODE_BUTTON_L1;
|
||||
|
||||
case KeyEvent.KEYCODE_BUTTON_Z:
|
||||
return KeyEvent.KEYCODE_BUTTON_R1;
|
||||
|
||||
case KeyEvent.KEYCODE_BUTTON_C:
|
||||
return KeyEvent.KEYCODE_BUTTON_B;
|
||||
|
||||
case KeyEvent.KEYCODE_BUTTON_X:
|
||||
return KeyEvent.KEYCODE_BUTTON_Y;
|
||||
|
||||
case KeyEvent.KEYCODE_BUTTON_B:
|
||||
return KeyEvent.KEYCODE_BUTTON_A;
|
||||
|
||||
case KeyEvent.KEYCODE_BUTTON_A:
|
||||
return KeyEvent.KEYCODE_BUTTON_X;
|
||||
|
||||
case KeyEvent.KEYCODE_BUTTON_SELECT:
|
||||
return KeyEvent.KEYCODE_BUTTON_THUMBL;
|
||||
|
||||
case KeyEvent.KEYCODE_BUTTON_START:
|
||||
return KeyEvent.KEYCODE_BUTTON_THUMBR;
|
||||
|
||||
case KeyEvent.KEYCODE_BUTTON_L2:
|
||||
return KeyEvent.KEYCODE_BUTTON_SELECT;
|
||||
|
||||
case KeyEvent.KEYCODE_BUTTON_R2:
|
||||
return KeyEvent.KEYCODE_BUTTON_START;
|
||||
|
||||
// These are duplicate trigger events
|
||||
case KeyEvent.KEYCODE_BUTTON_R1:
|
||||
case KeyEvent.KEYCODE_BUTTON_L1:
|
||||
return 0;
|
||||
switch (event.getScanCode()) {
|
||||
case 304:
|
||||
return KeyEvent.KEYCODE_BUTTON_X;
|
||||
case 305:
|
||||
return KeyEvent.KEYCODE_BUTTON_A;
|
||||
case 306:
|
||||
return KeyEvent.KEYCODE_BUTTON_B;
|
||||
case 307:
|
||||
return KeyEvent.KEYCODE_BUTTON_Y;
|
||||
case 308:
|
||||
return KeyEvent.KEYCODE_BUTTON_L1;
|
||||
case 309:
|
||||
return KeyEvent.KEYCODE_BUTTON_R1;
|
||||
/*
|
||||
**** Using analog triggers instead ****
|
||||
case 310:
|
||||
return KeyEvent.KEYCODE_BUTTON_L2;
|
||||
case 311:
|
||||
return KeyEvent.KEYCODE_BUTTON_R2;
|
||||
*/
|
||||
case 312:
|
||||
return KeyEvent.KEYCODE_BUTTON_SELECT;
|
||||
case 313:
|
||||
return KeyEvent.KEYCODE_BUTTON_START;
|
||||
case 314:
|
||||
return KeyEvent.KEYCODE_BUTTON_THUMBL;
|
||||
case 315:
|
||||
return KeyEvent.KEYCODE_BUTTON_THUMBR;
|
||||
case 316:
|
||||
return KeyEvent.KEYCODE_BUTTON_MODE;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
// If this is a Serval controller sending an unknown key code, it's probably
|
||||
@@ -1102,11 +1109,11 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
context.emulatingButtonFlags |= ControllerHandler.EMULATING_SPECIAL;
|
||||
}
|
||||
|
||||
// Send a new input packet if this is the first instance of a button down event
|
||||
// or anytime if we're emulating a button
|
||||
if (event.getRepeatCount() == 0 || context.emulatingButtonFlags != 0) {
|
||||
sendControllerInputPacket(context);
|
||||
}
|
||||
|
||||
// We don't need to send repeat key down events, but the platform
|
||||
// sends us events that claim to be repeats but they're from different
|
||||
// devices, so we just send them all and deal with some duplicates.
|
||||
sendControllerInputPacket(context);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -114,13 +114,13 @@ public abstract class AbstractXboxController extends AbstractController {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Report that we're added _before_ starting the input thread
|
||||
notifyDeviceAdded();
|
||||
|
||||
// Start listening for controller input
|
||||
inputThread = createInputThread();
|
||||
inputThread.start();
|
||||
|
||||
// Now report we're added
|
||||
notifyDeviceAdded();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -137,11 +137,11 @@ public abstract class AbstractXboxController extends AbstractController {
|
||||
inputThread = null;
|
||||
}
|
||||
|
||||
// Report the device removed
|
||||
notifyDeviceRemoved();
|
||||
|
||||
// Close the USB connection
|
||||
connection.close();
|
||||
|
||||
// Report the device removed
|
||||
notifyDeviceRemoved();
|
||||
}
|
||||
|
||||
protected abstract boolean handleRead(ByteBuffer buffer);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.limelight.binding.input.evdev;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Build;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.limelight.LimeLog;
|
||||
@@ -47,26 +48,42 @@ public class EvdevCaptureProvider extends InputCaptureProvider {
|
||||
return;
|
||||
}
|
||||
|
||||
// Launch a su shell
|
||||
ProcessBuilder builder = new ProcessBuilder("su");
|
||||
builder.redirectErrorStream(true);
|
||||
final String evdevReaderCmd = libraryPath+File.separatorChar+"libevdev_reader.so "+servSock.getLocalPort();
|
||||
|
||||
try {
|
||||
su = builder.start();
|
||||
} catch (IOException e) {
|
||||
reportDeviceNotRooted();
|
||||
e.printStackTrace();
|
||||
return;
|
||||
// On Nougat and later, we'll need to pass the command directly to SU.
|
||||
// Writing to SU's input stream after it has started doesn't seem to work anymore.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
// Launch evdev_reader directly via SU
|
||||
try {
|
||||
su = Runtime.getRuntime().exec("su -c "+evdevReaderCmd);
|
||||
} catch (IOException e) {
|
||||
reportDeviceNotRooted();
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Launch a SU shell on Marshmallow and earlier
|
||||
ProcessBuilder builder = new ProcessBuilder("su");
|
||||
builder.redirectErrorStream(true);
|
||||
|
||||
// Start evdevreader
|
||||
DataOutputStream suOut = new DataOutputStream(su.getOutputStream());
|
||||
try {
|
||||
suOut.writeChars(libraryPath+File.separatorChar+"libevdev_reader.so "+servSock.getLocalPort()+"\n");
|
||||
} catch (IOException e) {
|
||||
reportDeviceNotRooted();
|
||||
e.printStackTrace();
|
||||
return;
|
||||
try {
|
||||
su = builder.start();
|
||||
} catch (IOException e) {
|
||||
reportDeviceNotRooted();
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
// Start evdevreader
|
||||
DataOutputStream suOut = new DataOutputStream(su.getOutputStream());
|
||||
try {
|
||||
suOut.writeChars(evdevReaderCmd+"\n");
|
||||
} catch (IOException e) {
|
||||
reportDeviceNotRooted();
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for evdevreader's connection
|
||||
|
||||
@@ -25,6 +25,8 @@ import android.view.SurfaceHolder;
|
||||
|
||||
public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
|
||||
|
||||
private static final boolean USE_FRAME_RENDER_TIME = false;
|
||||
|
||||
// Used on versions < 5.0
|
||||
private ByteBuffer[] legacyInputBuffers;
|
||||
|
||||
@@ -202,9 +204,31 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
|
||||
videoFormat.setInteger(MediaFormat.KEY_MAX_HEIGHT, height);
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
// Operate at maximum rate to lower latency as much as possible on
|
||||
// some Qualcomm platforms. We could also set KEY_PRIORITY to 0 (realtime)
|
||||
// but that will actually result in the decoder crashing if it can't satisfy
|
||||
// our (ludicrous) operating rate requirement.
|
||||
videoFormat.setInteger(MediaFormat.KEY_OPERATING_RATE, Short.MAX_VALUE);
|
||||
}
|
||||
|
||||
videoDecoder.configure(videoFormat, ((SurfaceHolder)renderTarget).getSurface(), null, 0);
|
||||
videoDecoder.setVideoScalingMode(MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT);
|
||||
|
||||
if (USE_FRAME_RENDER_TIME && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
videoDecoder.setOnFrameRenderedListener(new MediaCodec.OnFrameRenderedListener() {
|
||||
@Override
|
||||
public void onFrameRendered(MediaCodec mediaCodec, long presentationTimeUs, long renderTimeNanos) {
|
||||
long delta = (renderTimeNanos / 1000000L) - (presentationTimeUs / 1000);
|
||||
if (delta >= 0 && delta < 1000) {
|
||||
if (USE_FRAME_RENDER_TIME) {
|
||||
totalTimeMs += delta;
|
||||
}
|
||||
}
|
||||
}
|
||||
}, null);
|
||||
}
|
||||
|
||||
LimeLog.info("Using codec "+selectedDecoderName+" for hardware decoding "+mimeType);
|
||||
|
||||
return true;
|
||||
@@ -265,7 +289,9 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
|
||||
long delta = MediaCodecHelper.getMonotonicMillis() - (presentationTimeUs / 1000);
|
||||
if (delta >= 0 && delta < 1000) {
|
||||
decoderTimeMs += delta;
|
||||
totalTimeMs += delta;
|
||||
if (!USE_FRAME_RENDER_TIME) {
|
||||
totalTimeMs += delta;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch (outIndex) {
|
||||
@@ -413,7 +439,9 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
|
||||
long delta = MediaCodecHelper.getMonotonicMillis()-(presentationTimeUs/1000);
|
||||
if (delta >= 0 && delta < 1000) {
|
||||
decoderTimeMs += delta;
|
||||
totalTimeMs += delta;
|
||||
if (!USE_FRAME_RENDER_TIME) {
|
||||
totalTimeMs += delta;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch (outIndex) {
|
||||
|
||||
@@ -37,7 +37,9 @@ public class ComputerManagerService extends Service {
|
||||
private static final int MDNS_QUERY_PERIOD_MS = 1000;
|
||||
private static final int FAST_POLL_TIMEOUT = 500;
|
||||
private static final int OFFLINE_POLL_TRIES = 5;
|
||||
private static final int INITIAL_POLL_TRIES = 2;
|
||||
private static final int EMPTY_LIST_THRESHOLD = 3;
|
||||
private static final int POLL_DATA_TTL_MS = 30000;
|
||||
|
||||
private final ComputerManagerBinder binder = new ComputerManagerBinder();
|
||||
|
||||
@@ -76,12 +78,15 @@ public class ComputerManagerService extends Service {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int pollTriesBeforeOffline = details.state == ComputerDetails.State.UNKNOWN ?
|
||||
INITIAL_POLL_TRIES : OFFLINE_POLL_TRIES;
|
||||
|
||||
activePolls.incrementAndGet();
|
||||
|
||||
// Poll the machine
|
||||
try {
|
||||
if (!pollComputer(details)) {
|
||||
if (!newPc && offlineCount < OFFLINE_POLL_TRIES) {
|
||||
if (!newPc && offlineCount < pollTriesBeforeOffline) {
|
||||
// Return without calling the listener
|
||||
releaseLocalDatabaseReference();
|
||||
return false;
|
||||
@@ -136,6 +141,7 @@ public class ComputerManagerService extends Service {
|
||||
LimeLog.warning(tuple.computer.name + " is offline (try " + offlineCount + ")");
|
||||
offlineCount++;
|
||||
} else {
|
||||
tuple.lastSuccessfulPollMs = System.currentTimeMillis();
|
||||
offlineCount = 0;
|
||||
}
|
||||
}
|
||||
@@ -165,11 +171,18 @@ public class ComputerManagerService extends Service {
|
||||
|
||||
synchronized (pollingTuples) {
|
||||
for (PollingTuple tuple : pollingTuples) {
|
||||
// Enforce the poll data TTL
|
||||
if (System.currentTimeMillis() - tuple.lastSuccessfulPollMs > POLL_DATA_TTL_MS) {
|
||||
LimeLog.info("Timing out polled state for "+tuple.computer.name);
|
||||
tuple.computer.state = ComputerDetails.State.UNKNOWN;
|
||||
tuple.computer.reachability = ComputerDetails.Reachability.UNKNOWN;
|
||||
}
|
||||
|
||||
// Report this computer initially
|
||||
listener.notifyComputerUpdated(tuple.computer);
|
||||
|
||||
// This polling thread might already be there
|
||||
if (tuple.thread == null) {
|
||||
// Report this computer initially
|
||||
listener.notifyComputerUpdated(tuple.computer);
|
||||
|
||||
tuple.thread = createPollingThread(tuple);
|
||||
tuple.thread.start();
|
||||
}
|
||||
@@ -229,6 +242,21 @@ public class ComputerManagerService extends Service {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void invalidateStateForComputer(UUID uuid) {
|
||||
synchronized (pollingTuples) {
|
||||
for (PollingTuple tuple : pollingTuples) {
|
||||
if (uuid.equals(tuple.computer.uuid)) {
|
||||
// We need the network lock to prevent a concurrent poll
|
||||
// from wiping this change out
|
||||
synchronized (tuple.networkLock) {
|
||||
tuple.computer.state = ComputerDetails.State.UNKNOWN;
|
||||
tuple.computer.reachability = ComputerDetails.Reachability.UNKNOWN;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -778,6 +806,7 @@ class PollingTuple {
|
||||
public Thread thread;
|
||||
public final ComputerDetails computer;
|
||||
public final Object networkLock;
|
||||
public long lastSuccessfulPollMs;
|
||||
|
||||
public PollingTuple(ComputerDetails computer, Thread thread) {
|
||||
this.computer = computer;
|
||||
|
||||
@@ -5,6 +5,9 @@ import java.util.ArrayList;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.widget.Button;
|
||||
|
||||
import com.limelight.R;
|
||||
|
||||
public class Dialog implements Runnable {
|
||||
private final String title;
|
||||
@@ -55,7 +58,7 @@ public class Dialog implements Runnable {
|
||||
alert.setCancelable(false);
|
||||
alert.setCanceledOnTouchOutside(false);
|
||||
|
||||
alert.setButton(AlertDialog.BUTTON_NEUTRAL, "OK", new DialogInterface.OnClickListener() {
|
||||
alert.setButton(AlertDialog.BUTTON_POSITIVE, activity.getResources().getText(android.R.string.ok), new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
synchronized (rundownDialogs) {
|
||||
rundownDialogs.remove(Dialog.this);
|
||||
@@ -67,6 +70,31 @@ public class Dialog implements Runnable {
|
||||
}
|
||||
}
|
||||
});
|
||||
alert.setButton(AlertDialog.BUTTON_NEUTRAL, activity.getResources().getText(R.string.help), new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
synchronized (rundownDialogs) {
|
||||
rundownDialogs.remove(Dialog.this);
|
||||
alert.dismiss();
|
||||
}
|
||||
|
||||
if (endAfterDismiss) {
|
||||
activity.finish();
|
||||
}
|
||||
|
||||
HelpLauncher.launchTroubleshooting(activity);
|
||||
}
|
||||
});
|
||||
alert.setOnShowListener(new DialogInterface.OnShowListener(){
|
||||
|
||||
@Override
|
||||
public void onShow(DialogInterface dialog) {
|
||||
// Set focus to the OK button by default
|
||||
Button button = alert.getButton(AlertDialog.BUTTON_POSITIVE);
|
||||
button.setFocusable(true);
|
||||
button.setFocusableInTouchMode(true);
|
||||
button.requestFocus();
|
||||
}
|
||||
});
|
||||
|
||||
synchronized (rundownDialogs) {
|
||||
rundownDialogs.add(this);
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.limelight.utils;
|
||||
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
|
||||
import com.limelight.HelpActivity;
|
||||
|
||||
public class HelpLauncher {
|
||||
|
||||
private static void launchUrl(Context context, String url) {
|
||||
// Try to launch the default browser
|
||||
try {
|
||||
// Fire TV devices will lie and say they do have a browser
|
||||
// even though the OS just shows an error dialog if we
|
||||
// try to use it.
|
||||
if (!"Amazon".equalsIgnoreCase(Build.MANUFACTURER)) {
|
||||
Intent i = new Intent(Intent.ACTION_VIEW);
|
||||
i.setData(Uri.parse(url));
|
||||
context.startActivity(i);
|
||||
return;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// This is only supposed to throw ActivityNotFoundException but
|
||||
// it can (at least) also throw SecurityException if a user's default
|
||||
// browser is not exported. We'll catch everything to workaround this.
|
||||
|
||||
// Fall through
|
||||
}
|
||||
|
||||
// This platform has no browser (possibly a leanback device)
|
||||
// We'll launch our WebView activity
|
||||
Intent i = new Intent(context, HelpActivity.class);
|
||||
i.setData(Uri.parse(url));
|
||||
context.startActivity(i);
|
||||
}
|
||||
|
||||
public static void launchSetupGuide(Context context) {
|
||||
launchUrl(context, "https://github.com/moonlight-stream/moonlight-docs/wiki/Setup-Guide");
|
||||
}
|
||||
|
||||
public static void launchTroubleshooting(Context context) {
|
||||
launchUrl(context, "https://github.com/moonlight-stream/moonlight-docs/wiki/Troubleshooting");
|
||||
}
|
||||
}
|
||||
@@ -23,8 +23,8 @@ public class ServerHelper {
|
||||
computer.localIp : computer.remoteIp;
|
||||
}
|
||||
|
||||
public static void doStart(Activity parent, NvApp app, ComputerDetails computer,
|
||||
ComputerManagerService.ComputerManagerBinder managerBinder) {
|
||||
public static Intent createStartIntent(Activity parent, NvApp app, ComputerDetails computer,
|
||||
ComputerManagerService.ComputerManagerBinder managerBinder) {
|
||||
Intent intent = new Intent(parent, Game.class);
|
||||
intent.putExtra(Game.EXTRA_HOST,
|
||||
computer.reachability == ComputerDetails.Reachability.LOCAL ?
|
||||
@@ -34,7 +34,14 @@ public class ServerHelper {
|
||||
intent.putExtra(Game.EXTRA_UNIQUEID, managerBinder.getUniqueId());
|
||||
intent.putExtra(Game.EXTRA_STREAMING_REMOTE,
|
||||
computer.reachability != ComputerDetails.Reachability.LOCAL);
|
||||
parent.startActivity(intent);
|
||||
intent.putExtra(Game.EXTRA_PC_UUID, computer.uuid.toString());
|
||||
intent.putExtra(Game.EXTRA_PC_NAME, computer.name);
|
||||
return intent;
|
||||
}
|
||||
|
||||
public static void doStart(Activity parent, NvApp app, ComputerDetails computer,
|
||||
ComputerManagerService.ComputerManagerBinder managerBinder) {
|
||||
parent.startActivity(createStartIntent(parent, app, computer, managerBinder));
|
||||
}
|
||||
|
||||
public static void doQuit(final Activity parent,
|
||||
|
||||
@@ -5,13 +5,15 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.content.pm.ShortcutManager;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.os.Build;
|
||||
|
||||
import com.limelight.AppView;
|
||||
import com.limelight.AppViewShortcutTrampoline;
|
||||
import com.limelight.R;
|
||||
import com.limelight.nvstream.http.ComputerDetails;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -22,7 +24,7 @@ public class ShortcutHelper {
|
||||
|
||||
public ShortcutHelper(Context context) {
|
||||
this.context = context;
|
||||
if (Build.VERSION.SDK_INT >= 25) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
||||
sm = context.getSystemService(ShortcutManager.class);
|
||||
}
|
||||
else {
|
||||
@@ -30,7 +32,7 @@ public class ShortcutHelper {
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(25)
|
||||
@TargetApi(Build.VERSION_CODES.N_MR1)
|
||||
private void reapShortcutsForDynamicAdd() {
|
||||
List<ShortcutInfo> dynamicShortcuts = sm.getDynamicShortcuts();
|
||||
while (dynamicShortcuts.size() >= sm.getMaxShortcutCountPerActivity()) {
|
||||
@@ -40,11 +42,11 @@ public class ShortcutHelper {
|
||||
maxRankShortcut = scut;
|
||||
}
|
||||
}
|
||||
sm.removeDynamicShortcuts(Arrays.asList(maxRankShortcut.getId()));
|
||||
sm.removeDynamicShortcuts(Collections.singletonList(maxRankShortcut.getId()));
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(25)
|
||||
@TargetApi(Build.VERSION_CODES.N_MR1)
|
||||
private List<ShortcutInfo> getAllShortcuts() {
|
||||
LinkedList<ShortcutInfo> list = new LinkedList<>();
|
||||
list.addAll(sm.getDynamicShortcuts());
|
||||
@@ -52,7 +54,7 @@ public class ShortcutHelper {
|
||||
return list;
|
||||
}
|
||||
|
||||
@TargetApi(25)
|
||||
@TargetApi(Build.VERSION_CODES.N_MR1)
|
||||
private ShortcutInfo getInfoForId(String id) {
|
||||
List<ShortcutInfo> shortcuts = getAllShortcuts();
|
||||
|
||||
@@ -65,8 +67,19 @@ public class ShortcutHelper {
|
||||
return null;
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.N_MR1)
|
||||
private boolean isExistingDynamicShortcut(String id) {
|
||||
for (ShortcutInfo si : sm.getDynamicShortcuts()) {
|
||||
if (si.getId().equals(id)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void reportShortcutUsed(String id) {
|
||||
if (Build.VERSION.SDK_INT >= 25) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
||||
ShortcutInfo sinfo = getInfoForId(id);
|
||||
if (sinfo != null) {
|
||||
sm.reportShortcutUsed(id);
|
||||
@@ -74,41 +87,51 @@ public class ShortcutHelper {
|
||||
}
|
||||
}
|
||||
|
||||
public void createAppViewShortcut(String id, ComputerDetails details) {
|
||||
if (Build.VERSION.SDK_INT >= 25) {
|
||||
Intent i = new Intent(context, AppView.class);
|
||||
i.putExtra(AppView.NAME_EXTRA, details.name);
|
||||
i.putExtra(AppView.UUID_EXTRA, details.uuid.toString());
|
||||
i.putExtra(AppView.SHORTCUT_EXTRA, true);
|
||||
public void createAppViewShortcut(String id, String computerName, String computerUuid, boolean forceAdd) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
||||
Intent i = new Intent(context, AppViewShortcutTrampoline.class);
|
||||
i.putExtra(AppView.NAME_EXTRA, computerName);
|
||||
i.putExtra(AppView.UUID_EXTRA, computerUuid);
|
||||
i.setAction(Intent.ACTION_DEFAULT);
|
||||
|
||||
ShortcutInfo sinfo = new ShortcutInfo.Builder(context, id)
|
||||
.setIntent(i)
|
||||
.setShortLabel(details.name)
|
||||
.setLongLabel(details.name)
|
||||
.setShortLabel(computerName)
|
||||
.setLongLabel(computerName)
|
||||
.setIcon(Icon.createWithResource(context, R.mipmap.ic_pc_scut))
|
||||
.build();
|
||||
|
||||
ShortcutInfo existingSinfo = getInfoForId(id);
|
||||
if (existingSinfo != null) {
|
||||
// Update in place
|
||||
sm.updateShortcuts(Arrays.asList(sinfo));
|
||||
sm.enableShortcuts(Arrays.asList(id));
|
||||
sm.updateShortcuts(Collections.singletonList(sinfo));
|
||||
sm.enableShortcuts(Collections.singletonList(id));
|
||||
}
|
||||
else {
|
||||
// Reap shortcuts to make space for this new one
|
||||
reapShortcutsForDynamicAdd();
|
||||
|
||||
// Add the new shortcut
|
||||
//TODO: Testing and proper icon - sm.addDynamicShortcuts(Arrays.asList(sinfo));
|
||||
// Reap shortcuts to make space for this if it's new
|
||||
// NOTE: This CAN'T be an else on the above if, because it's
|
||||
// possible that we have an existing shortcut but it's not a dynamic one.
|
||||
if (!isExistingDynamicShortcut(id)) {
|
||||
// To avoid a random carousel of shortcuts popping in and out based on polling status,
|
||||
// we only add shortcuts if it's not at the limit or the user made a conscious action
|
||||
// to interact with this PC.
|
||||
if (forceAdd || sm.getDynamicShortcuts().size() < sm.getMaxShortcutCountPerActivity()) {
|
||||
reapShortcutsForDynamicAdd();
|
||||
sm.addDynamicShortcuts(Collections.singletonList(sinfo));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void createAppViewShortcut(String id, ComputerDetails details, boolean forceAdd) {
|
||||
createAppViewShortcut(id, details.name, details.uuid.toString(), forceAdd);
|
||||
}
|
||||
|
||||
public void disableShortcut(String id, CharSequence reason) {
|
||||
if (Build.VERSION.SDK_INT >= 25) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
||||
ShortcutInfo sinfo = getInfoForId(id);
|
||||
if (sinfo != null) {
|
||||
sm.disableShortcuts(Arrays.asList(id), reason);
|
||||
sm.disableShortcuts(Collections.singletonList(id), reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="256dp"
|
||||
android:height="256dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,19h-2v-2h2v2zM15.07,11.25l-0.9,0.92C13.45,12.9 13,13.5 13,15h-2v-0.5c0,-1.1 0.45,-2.1 1.17,-2.83l1.24,-1.26c0.37,-0.36 0.59,-0.86 0.59,-1.41 0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2L8,9c0,-2.21 1.79,-4 4,-4s4,1.79 4,4c0,0.88 -0.36,1.68 -0.93,2.25z"/>
|
||||
</vector>
|
||||
@@ -9,41 +9,40 @@
|
||||
tools:context=".PcView" >
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/no_pc_found_layout"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_centerHorizontal="true">
|
||||
<ProgressBar
|
||||
android:id="@+id/pcs_loading"
|
||||
android:layout_width="75dp"
|
||||
android:layout_height="75dp"
|
||||
android:indeterminate="true"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toRightOf="@+id/pcs_loading"
|
||||
android:layout_toEndOf="@+id/pcs_loading"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:gravity="center"
|
||||
android:text="@string/searching_pc"/>
|
||||
</RelativeLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/pcFragmentContainer"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_toLeftOf="@+id/manuallyAddPc"
|
||||
android:layout_toStartOf="@+id/manuallyAddPc"
|
||||
android:layout_toRightOf="@+id/settingsButton"
|
||||
android:layout_toEndOf="@+id/settingsButton"/>
|
||||
android:layout_toEndOf="@+id/settingsButton">
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/no_pc_found_layout"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_centerHorizontal="true">
|
||||
<ProgressBar
|
||||
android:id="@+id/pcs_loading"
|
||||
android:layout_width="75dp"
|
||||
android:layout_height="75dp"
|
||||
android:indeterminate="true"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toRightOf="@+id/pcs_loading"
|
||||
android:layout_toEndOf="@+id/pcs_loading"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:gravity="center"
|
||||
android:text="@string/searching_pc"/>
|
||||
</RelativeLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/settingsButton"
|
||||
@@ -51,23 +50,33 @@
|
||||
android:layout_height="65dp"
|
||||
android:cropToPadding="false"
|
||||
android:scaleType="fitXY"
|
||||
android:nextFocusDown="@+id/pcGridView"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:src="@drawable/ic_settings"
|
||||
style="?android:attr/borderlessButtonStyle"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/helpButton"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="65dp"
|
||||
android:cropToPadding="false"
|
||||
android:scaleType="fitXY"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_below="@id/settingsButton"
|
||||
android:src="@drawable/ic_help"
|
||||
style="?android:attr/borderlessButtonStyle"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/manuallyAddPc"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="65dp"
|
||||
android:cropToPadding="false"
|
||||
android:scaleType="fitXY"
|
||||
android:nextFocusDown="@+id/pcGridView"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_below="@id/helpButton"
|
||||
android:src="@drawable/ic_add"
|
||||
style="?android:attr/borderlessButtonStyle"/>
|
||||
|
||||
|
||||
@@ -9,41 +9,44 @@
|
||||
tools:context=".PcView" >
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/no_pc_found_layout"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_centerHorizontal="true">
|
||||
<ProgressBar
|
||||
android:id="@+id/pcs_loading"
|
||||
android:layout_width="75dp"
|
||||
android:layout_height="75dp"
|
||||
android:indeterminate="true"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toRightOf="@+id/pcs_loading"
|
||||
android:layout_toEndOf="@+id/pcs_loading"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:gravity="center"
|
||||
android:text="@string/searching_pc"/>
|
||||
</RelativeLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/pcFragmentContainer"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:layout_toLeftOf="@+id/manuallyAddPc"
|
||||
android:layout_toStartOf="@+id/manuallyAddPc"
|
||||
android:layout_toRightOf="@+id/settingsButton"
|
||||
android:layout_toEndOf="@+id/settingsButton"
|
||||
android:layout_below="@+id/settingsButton"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_alignParentTop="true"/>
|
||||
>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/no_pc_found_layout"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_centerHorizontal="true">
|
||||
<ProgressBar
|
||||
android:id="@+id/pcs_loading"
|
||||
android:layout_width="75dp"
|
||||
android:layout_height="75dp"
|
||||
android:indeterminate="true"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toRightOf="@+id/pcs_loading"
|
||||
android:layout_toEndOf="@+id/pcs_loading"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:gravity="center"
|
||||
android:text="@string/searching_pc"/>
|
||||
</RelativeLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/settingsButton"
|
||||
@@ -56,6 +59,18 @@
|
||||
android:layout_alignParentTop="true"
|
||||
android:src="@drawable/ic_settings"
|
||||
style="?android:attr/borderlessButtonStyle"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/helpButton"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="65dp"
|
||||
android:cropToPadding="false"
|
||||
android:scaleType="fitXY"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_toRightOf="@+id/settingsButton"
|
||||
android:layout_toEndOf="@+id/settingsButton"
|
||||
android:src="@drawable/ic_help"
|
||||
style="?android:attr/borderlessButtonStyle"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/manuallyAddPc"
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/activity_help"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
tools:context="com.limelight.HelpActivity">
|
||||
|
||||
<WebView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true" />
|
||||
</RelativeLayout>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.6 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 8.6 KiB |
@@ -4,6 +4,10 @@
|
||||
<string name="scut_deleted_pc">PC deleted</string>
|
||||
<string name="scut_not_paired">PC not paired</string>
|
||||
|
||||
<!-- Help strings -->
|
||||
<string name="help_loading_title">Help Viewer</string>
|
||||
<string name="help_loading_msg">Loading help page…</string>
|
||||
|
||||
<!-- PC view menu entries -->
|
||||
<string name="pcview_menu_app_list">View Game List</string>
|
||||
<string name="pcview_menu_pair_pc">Pair with PC</string>
|
||||
@@ -64,6 +68,7 @@
|
||||
<string name="yes">Yes</string>
|
||||
<string name="no">No</string>
|
||||
<string name="lost_connection">Lost connection to PC</string>
|
||||
<string name="help">Help</string>
|
||||
|
||||
<!-- AppList activity -->
|
||||
<string name="title_applist">Apps on</string>
|
||||
|
||||
Reference in New Issue
Block a user