Compare commits

..

34 Commits

Author SHA1 Message Date
Cameron Gutman 7dea322bbd Update build tools to 28.0.3 2018-09-29 15:37:31 -07:00
Cameron Gutman 349ecb16ab Increment version code 2018-09-29 15:36:21 -07:00
Cameron Gutman a3867735c1 Update to AGP 3.2 2018-09-29 15:31:13 -07:00
Cameron Gutman 5b087e9f70 Update common-c with split encode change 2018-09-22 20:22:59 -07:00
Cameron Gutman eed18223eb Version 5.9.2 2018-09-18 20:36:53 -07:00
Cameron Gutman 30d4d2a918 Update moonlight-common 2018-09-18 20:27:08 -07:00
Cameron Gutman 30f666c70e Update AGP to 3.2 rc3 2018-09-18 20:23:25 -07:00
Cameron Gutman 209fead0e8 Only add the create shortcut option if the box art is present to avoid crashing 2018-09-18 20:22:16 -07:00
Cameron Gutman 5c6889bf6d Version 5.9.1 2018-08-12 00:45:19 -07:00
Cameron Gutman 7d24900756 Update build tools to 28.0.2 2018-08-12 00:44:09 -07:00
Cameron Gutman 79a75b9d19 Update common with audio fix and game launch fix 2018-08-12 00:40:56 -07:00
Cameron Gutman 29b64992bd Enable stale and no-response bots to reduce inactive issues 2018-08-12 00:20:03 -07:00
Cameron Gutman c9b14540f2 Remove reference to old Moonlight Java project 2018-08-10 22:29:59 -07:00
Cameron Gutman 546843a26c Fix crash on quit confirmation prompt 2018-07-28 00:13:20 -07:00
Cameron Gutman d03d260535 Add status bar and navigation bar color on L+ 2018-07-27 23:48:20 -07:00
Cameron Gutman 6946e3c7a2 Just use the PC name as the app list title 2018-07-27 23:42:57 -07:00
Cameron Gutman b79d328961 Version 5.9 2018-07-16 18:46:01 -07:00
Cameron Gutman c313797d93 Make OSC reconfigure button non-focusable so it doesn't eat hardware enter/space presses. Fixes #611 2018-07-16 18:40:19 -07:00
Cameron Gutman c8cb8e1346 Update build tools to 28.0.1 2018-07-16 18:25:33 -07:00
Cameron Gutman 6a9f8da14e Update common to reduce syscall overhead 2018-07-16 18:21:48 -07:00
Cameron Gutman ff9260a0fd Update AGP to 3.2-beta4 2018-07-16 18:16:35 -07:00
zacklmoore 62bedb1609 Pinned Game Shortcuts (Android Oreo) (#603)
* Initial changes to add game shortcuts.

* Initial working cut.

* Cleanup and converting strings to resource strings.

* Additional cleanup.

* Removed a blank line

* Changes based on review feedback.

* Forgot to save some changes before commiting...

* Standardized dialogs and tried to fix the dialogs auto-closing when the PCView is already opened.
2018-07-06 21:53:19 -07:00
Cameron Gutman a519723d44 Monkey-proof Moonlight 2018-06-20 01:26:59 -07:00
Cameron Gutman 36191781ed Version 5.8.2 2018-06-16 17:05:44 -07:00
Cameron Gutman 61b6a49669 Correct MT8176 errata 2018-06-16 16:37:50 -07:00
Cameron Gutman e97845e46e Add comments and documentation on MT8176 testing 2018-06-16 16:31:06 -07:00
Cameron Gutman 6bba68207d Ignore spurious ACTION_HOVER_ENTER with wrong coordinates and KEYCODE_BACK repeats. Fixes #554 2018-06-16 15:57:44 -07:00
Cameron Gutman 0e17cccc06 Process historical values for relative mouse events 2018-06-16 15:22:01 -07:00
Cameron Gutman 918e922e40 Avoid processing mouse move history 2018-06-16 15:14:16 -07:00
Cameron Gutman a08854ddfd Properly handle SOURCE_MOUSE_RELATIVE in the mouse back button hack. Fixes #424 2018-06-16 15:01:11 -07:00
Cameron Gutman eb6f15c2b7 Add dynamic method for allowing back buttons for navigation 2018-06-16 14:32:07 -07:00
Cameron Gutman 2cd9e31684 Update gitignore to handle app bundles and other new files dropped by AS 2018-06-16 14:27:54 -07:00
Cameron Gutman 791d6624e2 Update to AGP 3.2 alpha 18 2018-06-16 14:21:14 -07:00
Cameron Gutman af41021271 Use HEVC by default on MediaTek SoCs with PowerVR graphics 2018-06-14 22:55:10 -07:00
30 changed files with 523 additions and 233 deletions
+8
View File
@@ -0,0 +1,8 @@
# ProBot No Response (https://probot.github.io/apps/no-response/)
daysUntilClose: 7
responseRequiredLabel: 'need more info'
closeComment: >
This issue has been automatically closed because there was no response to a
request for more information from the issue opener. Please leave a comment or
open a new issue if you have additional information related to this issue.
+14
View File
@@ -0,0 +1,14 @@
# ProBot Stale (https://probot.github.io/apps/stale/)
daysUntilStale: 90
daysUntilClose: 7
exemptLabels:
- accepted
- bug
- enhancement
- meta
staleLabel: stale
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs.
closeComment: false
+4 -1
View File
@@ -1,6 +1,9 @@
#built application files
# built application files
*.apk
*.ap_
*.aab
output.json
out/
# files for the dex VM
*.dex
+1 -3
View File
@@ -1,4 +1,4 @@
# Moonlight
# Moonlight Android
[Moonlight](http://moonlight-stream.com) is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield.
We reverse engineered the Shield streaming software and created a version that can be run on any Android device.
@@ -6,8 +6,6 @@ We reverse engineered the Shield streaming software and created a version that c
Moonlight will allow you to stream your full collection of games from your Windows PC to your Android device,
whether in your own home or over the internet.
[Moonlight-pc](https://github.com/moonlight-stream/moonlight-pc) is also currently in development for Windows, OS X and Linux. Versions for [iOS](https://github.com/moonlight-stream/moonlight-ios) and [Windows and Windows Phone](https://github.com/moonlight-stream/moonlight-windows) are also in development.
Check our [wiki](https://github.com/moonlight-stream/moonlight-docs/wiki) for more detailed information or a troubleshooting guide.
## Features
+3 -3
View File
@@ -1,15 +1,15 @@
apply plugin: 'com.android.application'
android {
buildToolsVersion '28.0.0'
buildToolsVersion '28.0.3'
compileSdkVersion 28
defaultConfig {
minSdkVersion 16
targetSdkVersion 28
versionName "5.8.1"
versionCode = 166
versionName "5.9.2"
versionCode = 171
}
flavorDimensions "root"
+2 -1
View File
@@ -67,8 +67,9 @@
</activity>
<!-- Small hack to support launcher shortcuts without relaunching over and over again when the back button is pressed -->
<activity
android:name=".AppViewShortcutTrampoline"
android:name=".ShortcutTrampoline"
android:noHistory="true"
android:exported="true"
android:resizeableActivity="true"
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection">
<meta-data
+35 -6
View File
@@ -26,6 +26,10 @@ import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.view.ContextMenu;
@@ -36,6 +40,7 @@ import android.view.ContextMenu.ContextMenuInfo;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.AdapterView.AdapterContextMenuInfo;
@@ -56,7 +61,9 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
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;
private final static int START_WITH_QUIT = 4;
private final static int VIEW_DETAILS_ID = 5;
private final static int CREATE_SHORTCUT_ID = 6;
public final static String NAME_EXTRA = "Name";
public final static String UUID_EXTRA = "UUID";
@@ -251,10 +258,9 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
String computerName = getIntent().getStringExtra(NAME_EXTRA);
String labelText = getResources().getString(R.string.title_applist)+" "+computerName;
TextView label = findViewById(R.id.appListText);
setTitle(labelText);
label.setText(labelText);
setTitle(computerName);
label.setText(computerName);
// Add a launcher shortcut for this PC (forced, since this is user interaction)
shortcutHelper.createAppViewShortcut(uuidString, computerName, uuidString, true);
@@ -331,10 +337,20 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
menu.add(Menu.NONE, QUIT_ID, 2, getResources().getString(R.string.applist_menu_quit));
}
else {
menu.add(Menu.NONE, START_WTIH_QUIT, 1, getResources().getString(R.string.applist_menu_quit_and_start));
menu.add(Menu.NONE, START_WITH_QUIT, 1, getResources().getString(R.string.applist_menu_quit_and_start));
menu.add(Menu.NONE, CANCEL_ID, 2, getResources().getString(R.string.applist_menu_cancel));
}
}
menu.add(Menu.NONE, VIEW_DETAILS_ID, 3, getResources().getString(R.string.applist_menu_details));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Only add an option to create shortcut if box art is loaded
ImageView appImageView = info.targetView.findViewById(R.id.grid_image);
BitmapDrawable drawable = (BitmapDrawable)appImageView.getDrawable();
if (drawable != null && drawable.getBitmap() != null) {
menu.add(Menu.NONE, CREATE_SHORTCUT_ID, 4, getResources().getString(R.string.applist_menu_scut));
}
}
}
@Override
@@ -346,7 +362,7 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
final AppObject app = (AppObject) appGridAdapter.getItem(info.position);
switch (item.getItemId()) {
case START_WTIH_QUIT:
case START_WITH_QUIT:
// Display a confirmation dialog first
UiHelper.displayQuitConfirmationDialog(this, new Runnable() {
@Override
@@ -386,6 +402,19 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
case CANCEL_ID:
return true;
case VIEW_DETAILS_ID:
Dialog.displayDialog(AppView.this, getResources().getString(R.string.title_details),
getResources().getString(R.string.applist_details_id) + " " + app.app.getAppId(), false);
return true;
case CREATE_SHORTCUT_ID:
ImageView appImageView = info.targetView.findViewById(R.id.grid_image);
Bitmap appBits = ((BitmapDrawable)appImageView.getDrawable()).getBitmap();
if (!shortcutHelper.createPinnedGameShortcut(uuidString + Integer.valueOf(app.app.getAppId()).toString(), appBits, computer, app.app)) {
Toast.makeText(AppView.this, getResources().getString(R.string.unable_to_pin_shortcut), Toast.LENGTH_LONG).show();
}
return true;
default:
return super.onContextItemSelected(item);
}
@@ -1,171 +0,0 @@
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 the managerBinder was destroyed before this callback,
// just finish the activity.
if (managerBinder == null) {
finish();
return;
}
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, false), 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();
}
}
+18 -9
View File
@@ -834,8 +834,12 @@ public class Game extends Activity implements SurfaceHolder.Callback,
}
// Handle a synthetic back button event that some Android OS versions
// create as a result of a right-click.
if (event.getSource() == InputDevice.SOURCE_MOUSE && event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
// create as a result of a right-click. This event WILL repeat if
// the right mouse button is held down, so we ignore those.
if ((event.getSource() == InputDevice.SOURCE_MOUSE ||
event.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE) &&
event.getKeyCode() == KeyEvent.KEYCODE_BACK &&
event.getRepeatCount() == 0) {
conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_RIGHT);
return true;
}
@@ -885,7 +889,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
// Handle a synthetic back button event that some Android OS versions
// create as a result of a right-click.
if (event.getSource() == InputDevice.SOURCE_MOUSE && event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
if ((event.getSource() == InputDevice.SOURCE_MOUSE ||
event.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE) &&
event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_RIGHT);
return true;
}
@@ -953,6 +959,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
{
// This case is for mice
if (event.getSource() == InputDevice.SOURCE_MOUSE ||
event.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE ||
(event.getPointerCount() >= 1 &&
event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE))
{
@@ -968,6 +975,13 @@ public class Game extends Activity implements SurfaceHolder.Callback,
byte vScrollClicks = (byte) event.getAxisValue(MotionEvent.AXIS_VSCROLL);
conn.sendMouseScroll(vScrollClicks);
}
else if (event.getActionMasked() == MotionEvent.ACTION_HOVER_ENTER ||
event.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT) {
// On some devices (Galaxy S8 without Oreo pointer capture), we can
// get spurious ACTION_HOVER_ENTER events when right clicking with
// incorrect X and Y coordinates. Just eat this event without processing it.
return true;
}
if ((changedButtons & MotionEvent.BUTTON_PRIMARY) != 0) {
if ((event.getButtonState() & MotionEvent.BUTTON_PRIMARY) != 0) {
@@ -1014,12 +1028,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
lastMouseY = (int)event.getY();
}
else {
// First process the history
for (int i = 0; i < event.getHistorySize(); i++) {
updateMousePosition((int)event.getHistoricalX(i), (int)event.getHistoricalY(i));
}
// Now process the current values
// Don't process the history. We just want the current position now.
updateMousePosition((int)event.getX(), (int)event.getY());
}
@@ -28,6 +28,7 @@ import com.limelight.utils.ShortcutHelper;
import com.limelight.utils.UiHelper;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
@@ -112,6 +113,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
private final static int DELETE_ID = 5;
private final static int RESUME_ID = 6;
private final static int QUIT_ID = 7;
private final static int VIEW_DETAILS_ID = 8;
private void initializeViews() {
setContentView(R.layout.activity_pc_view);
@@ -332,6 +334,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
// it with delete which actually work
menu.add(Menu.NONE, DELETE_ID, 4, getResources().getString(R.string.pcview_menu_delete_pc));
}
menu.add(Menu.NONE, VIEW_DETAILS_ID, 5, getResources().getString(R.string.pcview_menu_details));
}
@Override
@@ -560,6 +563,10 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
return true;
case DELETE_ID:
if (ActivityManager.isUserAMonkey()) {
LimeLog.info("Ignoring delete PC request from monkey");
return true;
}
if (managerBinder == null) {
Toast.makeText(PcView.this, getResources().getString(R.string.error_manager_not_running), Toast.LENGTH_LONG).show();
return true;
@@ -598,6 +605,10 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
}, null);
return true;
case VIEW_DETAILS_ID:
Dialog.displayDialog(PcView.this, getResources().getString(R.string.title_details), computer.details.toString(), false);
return true;
default:
return super.onContextItemSelected(item);
}
@@ -0,0 +1,267 @@
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.nvstream.http.PairingManager;
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 ShortcutTrampoline extends Activity {
private String uuidString;
private String appIdString;
private ArrayList<Intent> intentStack = new ArrayList<>();
private ComputerDetails computer;
private SpinnerDialog blockingLoadSpinner;
public final static String APP_ID_EXTRA = "AppId";
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));
if (computer == null) {
Dialog.displayDialog(ShortcutTrampoline.this,
getResources().getString(R.string.conn_error_title),
getResources().getString(R.string.scut_pc_not_found),
true);
if (blockingLoadSpinner != null) {
blockingLoadSpinner.dismiss();
blockingLoadSpinner = null;
}
if (managerBinder != null) {
unbindService(serviceConnection);
managerBinder = null;
}
return;
}
// 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 the managerBinder was destroyed before this callback,
// just finish the activity.
if (managerBinder == null) {
finish();
return;
}
if (details.state == ComputerDetails.State.ONLINE && details.pairState == PairingManager.PairState.PAIRED) {
// Launch game if provided app ID, otherwise launch app view
if (appIdString != null && appIdString.length() > 0) {
if (details.runningGameId == 0 || details.runningGameId == Integer.parseInt(appIdString)) {
intentStack.add(ServerHelper.createStartIntent(ShortcutTrampoline.this,
new NvApp("app", Integer.parseInt(appIdString), false), details, managerBinder));
// Close this activity
finish();
// Now start the activities
startActivities(intentStack.toArray(new Intent[]{}));
} else {
// Create the start intent immediately, so we can safely unbind the managerBinder
// below before we return.
final Intent startIntent = ServerHelper.createStartIntent(ShortcutTrampoline.this,
new NvApp("app", Integer.parseInt(appIdString), false), details, managerBinder);
UiHelper.displayQuitConfirmationDialog(ShortcutTrampoline.this, new Runnable() {
@Override
public void run() {
intentStack.add(startIntent);
// Close this activity
finish();
// Now start the activities
startActivities(intentStack.toArray(new Intent[]{}));
}
}, new Runnable() {
@Override
public void run() {
// Close this activity
finish();
}
});
}
} else {
// Close this activity
finish();
// Add the PC view at the back (and clear the task)
Intent i;
i = new Intent(ShortcutTrampoline.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(ShortcutTrampoline.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(ShortcutTrampoline.this,
new NvApp("app", details.runningGameId, false), 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(ShortcutTrampoline.this,
getResources().getString(R.string.conn_error_title),
getResources().getString(R.string.error_pc_offline),
true);
} else if (details.pairState != PairingManager.PairState.PAIRED) {
// Computer not paired - display an error dialog
Dialog.displayDialog(ShortcutTrampoline.this,
getResources().getString(R.string.conn_error_title),
getResources().getString(R.string.scut_not_paired),
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;
}
};
protected boolean validateInput() {
// Validate UUID
try {
UUID.fromString(uuidString);
} catch (IllegalArgumentException ex) {
Dialog.displayDialog(ShortcutTrampoline.this,
getResources().getString(R.string.conn_error_title),
getResources().getString(R.string.scut_invalid_uuid),
true);
return false;
}
// Validate App ID (if provided)
if (appIdString != null && !appIdString.isEmpty()) {
try {
Integer.parseInt(appIdString);
} catch (NumberFormatException ex) {
Dialog.displayDialog(ShortcutTrampoline.this,
getResources().getString(R.string.conn_error_title),
getResources().getString(R.string.scut_invalid_app_id),
true);
return false;
}
}
return true;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
UiHelper.notifyNewRootView(this);
uuidString = getIntent().getStringExtra(AppView.UUID_EXTRA);
appIdString = getIntent().getStringExtra(APP_ID_EXTRA);
if (validateInput()) {
// 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 onStop() {
super.onStop();
if (blockingLoadSpinner != null) {
blockingLoadSpinner.dismiss();
blockingLoadSpinner = null;
}
Dialog.closeDialogs();
if (managerBinder != null) {
managerBinder.stopPolling();
unbindService(serviceConnection);
managerBinder = null;
}
finish();
}
}
@@ -4,6 +4,7 @@ import android.content.Context;
import android.hardware.input.InputManager;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
import android.os.Build;
import android.os.SystemClock;
import android.util.SparseArray;
import android.view.InputDevice;
@@ -22,6 +23,7 @@ import com.limelight.preferences.PreferenceConfiguration;
import com.limelight.ui.GameGestures;
import com.limelight.utils.Vector2d;
import java.lang.reflect.InvocationTargetException;
import java.util.Timer;
import java.util.TimerTask;
@@ -308,6 +310,79 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
return context;
}
private static boolean isExternal(InputDevice dev) {
try {
// Landroid/view/InputDevice;->isExternal()Z is on the light graylist in Android P
return (Boolean)dev.getClass().getMethod("isExternal").invoke(dev);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (ClassCastException e) {
e.printStackTrace();
}
// Answer true if we don't know
return true;
}
private boolean shouldIgnoreBack(InputDevice dev) {
String devName = dev.getName();
// The Serval has a Select button but the framework doesn't
// know about that because it uses a non-standard scancode.
if (devName.contains("Razer Serval")) {
return true;
}
// Classify this device as a remote by name if it has no joystick axes
if (getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_X) == null &&
getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_Y) == null &&
devName.toLowerCase().contains("remote")) {
return true;
}
// Otherwise, dynamically try to determine whether we should allow this
// back button to function for navigation.
//
// First, check if this is an internal device we're being called on.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && !isExternal(dev)) {
InputManager im = (InputManager) activityContext.getSystemService(Context.INPUT_SERVICE);
boolean foundInternalGamepad = false;
boolean foundInternalSelect = false;
for (int id : im.getInputDeviceIds()) {
InputDevice currentDev = im.getInputDevice(id);
// Ignore external devices
if (currentDev == null || isExternal(currentDev)) {
continue;
}
// Note that we are explicitly NOT excluding the current device we're examining here,
// since the other gamepad buttons may be on our current device and that's fine.
boolean[] keys = currentDev.hasKeys(KeyEvent.KEYCODE_BUTTON_SELECT, KeyEvent.KEYCODE_BUTTON_A);
if (keys[0]) {
foundInternalSelect = true;
}
if (keys[1]) {
foundInternalGamepad = true;
}
}
// Allow the back button to function for navigation if we either:
// a) have no internal gamepad (most phones)
// b) have an internal gamepad but also have an internal select button (GPD XD)
// but not:
// c) have an internal gamepad but no internal select button (NVIDIA SHIELD Portable)
return !foundInternalGamepad || foundInternalSelect;
}
return false;
}
private InputDeviceContext createInputDeviceContextForDevice(InputDevice dev) {
InputDeviceContext context = new InputDeviceContext();
String devName = dev.getName();
@@ -440,6 +515,8 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
}
}
context.ignoreBack = shouldIgnoreBack(dev);
if (devName != null) {
// For the Nexus Player (and probably other ATV devices), we should
// use the back button as start since it doesn't have a start/menu button
@@ -459,30 +536,15 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
// so we increase the deadzone on them to minimize this
context.triggerDeadzone = 0.30f;
}
// Classify this device as a remote by name
else if (devName.toLowerCase().contains("remote")) {
// It's only a remote if it doesn't any sticks
if (!context.hasJoystickAxes) {
context.ignoreBack = true;
}
}
// SHIELD controllers will use small stick deadzones
else if (devName.contains("SHIELD")) {
context.leftStickDeadzoneRadius = 0.07f;
context.rightStickDeadzoneRadius = 0.07f;
}
// Samsung's face buttons appear as a non-virtual button so we'll explicitly ignore
// back presses on this device. The Goodix buttons on the Nokia 6 also appear
// non-virtual so we'll ignore those too.
else if (devName.equals("sec_touchscreen") || devName.equals("sec_touchkey") ||
devName.equals("goodix_fp")) {
context.ignoreBack = true;
}
// The Serval has a couple of unknown buttons that are start and select. It also has
// a back button which we want to ignore since there's already a select button.
else if (devName.contains("Razer Serval")) {
context.isServal = true;
context.ignoreBack = true;
}
// 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
@@ -43,11 +43,19 @@ public class AndroidNativePointerCaptureProvider extends InputCaptureProvider {
@Override
public float getRelativeAxisX(MotionEvent event) {
return event.getX();
float x = event.getX();
for (int i = 0; i < event.getHistorySize(); i++) {
x += event.getHistoricalX(i);
}
return x;
}
@Override
public float getRelativeAxisY(MotionEvent event) {
return event.getY();
float y = event.getY();
for (int i = 0; i < event.getHistorySize(); i++) {
y += event.getHistoricalY(i);
}
return y;
}
}
@@ -60,6 +60,7 @@ public class VirtualController {
buttonConfigure = new Button(context);
buttonConfigure.setAlpha(0.25f);
buttonConfigure.setFocusable(false);
buttonConfigure.setBackgroundResource(R.drawable.ic_settings);
buttonConfigure.setOnClickListener(new View.OnClickListener() {
@Override
@@ -131,8 +131,8 @@ public class MediaCodecHelper {
}
// Sony ATVs have broken MediaTek codecs (decoder hangs after rendering the first frame).
// I know the Fire TV 2 works, so I'll just whitelist Amazon devices which seem
// to actually be tested. Ugh...
// I know the Fire TV 2 and 3 works, so I'll just whitelist Amazon devices which seem
// to actually be tested.
if (Build.MANUFACTURER.equalsIgnoreCase("Amazon")) {
whitelistedHevcDecoders.add("omx.mtk");
whitelistedHevcDecoders.add("omx.amlogic");
@@ -166,6 +166,10 @@ public class MediaCodecHelper {
}
}
private static boolean isPowerVR(String glRenderer) {
return glRenderer.toLowerCase().contains("powervr");
}
private static String getAdrenoVersionString(String glRenderer) {
glRenderer = glRenderer.toLowerCase().trim();
@@ -259,6 +263,18 @@ public class MediaCodecHelper {
else {
blacklistedDecoderPrefixes.add("OMX.qcom.video.decoder.hevc");
}
// Older MediaTek SoCs have issues with HEVC rendering but the newer chips with
// PowerVR GPUs have good HEVC support.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && isPowerVR(glRenderer)) {
LimeLog.info("Added omx.mtk to HEVC decoders based on PowerVR GPU");
whitelistedHevcDecoders.add("omx.mtk");
// This SoC (MT8176 in GPD XD+) supports AVC RFI too, but the maxNumReferenceFrames setting
// required to make it work adds a huge amount of latency.
LimeLog.info("Added omx.mtk to RFI list for HEVC");
refFrameInvalidationHevcPrefixes.add("omx.mtk");
}
}
initialized = true;
@@ -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.Bitmap;
import android.graphics.drawable.Icon;
import android.os.Build;
import com.limelight.AppView;
import com.limelight.AppViewShortcutTrampoline;
import com.limelight.ShortcutTrampoline;
import com.limelight.R;
import com.limelight.nvstream.http.ComputerDetails;
import com.limelight.nvstream.http.NvApp;
import java.util.Collections;
import java.util.LinkedList;
@@ -80,8 +82,7 @@ public class ShortcutHelper {
public void reportShortcutUsed(String id) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
ShortcutInfo sinfo = getInfoForId(id);
if (sinfo != null) {
if (getInfoForId(id) != null) {
sm.reportShortcutUsed(id);
}
}
@@ -89,7 +90,7 @@ public class ShortcutHelper {
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);
Intent i = new Intent(context, ShortcutTrampoline.class);
i.putExtra(AppView.NAME_EXTRA, computerName);
i.putExtra(AppView.UUID_EXTRA, computerUuid);
i.setAction(Intent.ACTION_DEFAULT);
@@ -127,10 +128,42 @@ public class ShortcutHelper {
createAppViewShortcut(id, details.name, details.uuid.toString(), forceAdd);
}
@TargetApi(Build.VERSION_CODES.O)
public boolean createPinnedGameShortcut(String id, Bitmap iconBits, String computerName, String computerUuid, String appName, String appId) {
if (sm.isRequestPinShortcutSupported()) {
Icon appIcon;
Intent i = new Intent(context, ShortcutTrampoline.class);
i.putExtra(AppView.NAME_EXTRA, computerName);
i.putExtra(AppView.UUID_EXTRA, computerUuid);
i.putExtra(ShortcutTrampoline.APP_ID_EXTRA, appId);
i.setAction(Intent.ACTION_DEFAULT);
if (iconBits != null) {
appIcon = Icon.createWithAdaptiveBitmap(iconBits);
} else {
appIcon = Icon.createWithResource(context, R.mipmap.ic_pc_scut);
}
ShortcutInfo sInfo = new ShortcutInfo.Builder(context, id)
.setIntent(i)
.setShortLabel(appName + " (" + computerName + ")")
.setIcon(appIcon)
.build();
return sm.requestPinShortcut(sInfo, null);
} else {
return false;
}
}
public boolean createPinnedGameShortcut(String id, Bitmap iconBits, ComputerDetails cDetails, NvApp app) {
return createPinnedGameShortcut(id, iconBits, cDetails.name, cDetails.uuid.toString(), app.getAppName(), Integer.valueOf(app.getAppId()).toString());
}
public void disableShortcut(String id, CharSequence reason) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
ShortcutInfo sinfo = getInfoForId(id);
if (sinfo != null) {
if (getInfoForId(id) != null) {
sm.disableShortcuts(Collections.singletonList(id), reason);
}
}
-1
View File
@@ -61,7 +61,6 @@
<string name="lost_connection">Conexión perdida</string>
<!-- AppList activity -->
<string name="title_applist">Aplicaciones en</string>
<string name="applist_menu_resume">Reanudar sesión</string>
<string name="applist_menu_quit">Cerrar sesión</string>
<string name="applist_menu_quit_and_start">Cerrar juego actual e iniciar</string>
-1
View File
@@ -71,7 +71,6 @@
<string name="help">Aide</string>
<!-- AppList activity -->
<string name="title_applist">Applications sur</string>
<string name="applist_connect_msg">Connexion au PC…</string>
<string name="applist_menu_resume">Reprise de la session</string>
<string name="applist_menu_quit">Quitter la session</string>
-1
View File
@@ -76,7 +76,6 @@
<string name="help">Assistenza</string>
<!-- AppList activity -->
<string name="title_applist">Applicazioni su</string>
<string name="applist_connect_msg">Connessione al PC in corso…</string>
<string name="applist_menu_resume">Riprendi sessione</string>
<string name="applist_menu_quit">Chiudi sessione</string>
-1
View File
@@ -57,7 +57,6 @@
<string name="lost_connection">コンピュータとの接続が失われました</string>
<!-- AppList activity -->
<string name="title_applist">ゲーム</string>
<string name="applist_menu_resume">セッションを続ける</string>
<string name="applist_menu_quit">セッションを終了する</string>
<string name="applist_menu_quit_and_start">現在のゲームを終了して新しいゲームを始める</string>
-1
View File
@@ -71,7 +71,6 @@
<string name="help">도움말</string>
<!-- AppList activity -->
<string name="title_applist">앱 사용 가능</string>
<string name="applist_connect_msg">PC에 연결중…</string>
<string name="applist_menu_resume">세션 계속</string>
<string name="applist_menu_quit">세션 종료</string>
-1
View File
@@ -61,7 +61,6 @@
<string name="lost_connection">Verbinding met PC verloren</string>
<!-- AppList activity -->
<string name="title_applist">Apps op</string>
<string name="applist_menu_resume">Hervat Sessie</string>
<string name="applist_menu_quit">Stop Sessie</string>
<string name="applist_menu_quit_and_start">Stop Huidige Spel en Start</string>
-1
View File
@@ -62,7 +62,6 @@
<string name="lost_connection">Потеряно соединение с PC</string>
<!-- AppList activity -->
<string name="title_applist">Приложения на</string>
<string name="applist_menu_resume">Возобновить сессию</string>
<string name="applist_menu_quit">Выйти из сессии</string>
<string name="applist_menu_quit_and_start">Выйти из текущей игры и запустить</string>
+2
View File
@@ -7,6 +7,8 @@
-->
<style name="AppBaseTheme" parent="android:Theme.Material">
<!-- API 21 theme customizations can go here. -->
<item name="android:statusBarColor">#212121</item>
<item name="android:navigationBarColor">#212121</item>
</style>
</resources>
@@ -71,7 +71,6 @@
<string name="help">帮助</string>
<!-- AppList activity -->
<string name="title_applist">Apps on</string>
<string name="applist_connect_msg"> 连接到电脑中…… </string>
<string name="applist_menu_resume"> 恢复串流 </string>
<string name="applist_menu_quit"> 退出串流 </string>
@@ -71,7 +71,6 @@
<string name="help">幫助</string>
<!-- AppList activity -->
<string name="title_applist">Apps on</string>
<string name="applist_connect_msg"> 連接到電腦中…… </string>
<string name="applist_menu_resume"> 恢復串流 </string>
<string name="applist_menu_quit"> 退出串流 </string>
+9 -1
View File
@@ -6,6 +6,9 @@
<!-- Shortcut strings -->
<string name="scut_deleted_pc">PC deleted</string>
<string name="scut_not_paired">PC not paired</string>
<string name="scut_pc_not_found">PC not found</string>
<string name="scut_invalid_uuid">Provided PC is not valid</string>
<string name="scut_invalid_app_id">Provided App is not valid</string>
<!-- Help strings -->
<string name="help_loading_title">Help Viewer</string>
@@ -17,6 +20,7 @@
<string name="pcview_menu_unpair_pc">Unpair</string>
<string name="pcview_menu_send_wol">Send Wake-On-LAN request</string>
<string name="pcview_menu_delete_pc">Delete PC</string>
<string name="pcview_menu_details">View Details</string>
<!-- Pair messages -->
<string name="pairing">Pairing…</string>
@@ -55,6 +59,7 @@
<string name="title_decoding_reset">Video Settings Reset</string>
<string name="message_decoding_reset">Your device\'s video decoder continues to crash at your selected streaming settings. Your streaming settings have been reset to default.</string>
<string name="error_usb_prohibited">USB access is prohibited by your device administrator. Check your Knox or MDM settings.</string>
<string name="unable_to_pin_shortcut">Your current launcher does not allow for creating pinned shortcuts.</string>
<!-- Start application messages -->
<string name="conn_establishing_title">Establishing Connection</string>
@@ -76,15 +81,17 @@
<string name="yes">Yes</string>
<string name="no">No</string>
<string name="lost_connection">Lost connection to PC</string>
<string name="title_details">Details</string>
<string name="help">Help</string>
<!-- AppList activity -->
<string name="title_applist">Apps on</string>
<string name="applist_connect_msg">Connecting to PC…</string>
<string name="applist_menu_resume">Resume Session</string>
<string name="applist_menu_quit">Quit Session</string>
<string name="applist_menu_quit_and_start">Quit Current Game and Start</string>
<string name="applist_menu_cancel">Cancel</string>
<string name="applist_menu_details">View Details</string>
<string name="applist_menu_scut">Create Shortcut</string>
<string name="applist_refresh_title">App List</string>
<string name="applist_refresh_msg">Refreshing apps…</string>
<string name="applist_refresh_error_title">Error</string>
@@ -93,6 +100,7 @@
<string name="applist_quit_success">Successfully quit</string>
<string name="applist_quit_fail">Failed to quit</string>
<string name="applist_quit_confirmation">Are you sure you want to quit the running app? All unsaved data will be lost.</string>
<string name="applist_details_id">App ID:</string>
<!-- Add computer manually activity -->
<string name="title_add_pc">Add PC Manually</string>
+1 -1
View File
@@ -5,7 +5,7 @@ buildscript {
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.0-alpha17'
classpath 'com.android.tools.build:gradle:3.2.0'
}
}
+2 -2
View File
@@ -1,9 +1,9 @@
This file serves to document some of the decoder errata when using MediaCodec hardware decoders on certain devices.
1. num_ref_frames is set to 16 by NVENC which causes decoders to allocate 16+ buffers. This can cause an error or lag on some devices.
- Affected decoders: TI OMAP4, Allwinner A20
- Affected decoders: TI OMAP4 crashes, Allwinner A20, MT8176 lags (HEVC not affected)
2. Some decoders have a huge per-frame latency with the unmodified SPS sent from NVENC. Setting max_dec_frame_buffering fixes this latency issue.
2. Some H.264 decoders have a huge per-frame latency with the unmodified SPS sent from NVENC. Setting max_dec_frame_buffering=1 fixes this latency issue.
- Affected decoders: NVIDIA Tegra 3 and 4, Broadcom VideoCore IV
3. Some decoders strictly require that you pass BUFFER_FLAG_CODEC_CONFIG and crash upon the IDR frame if you don't