Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b79d328961 | |||
| c313797d93 | |||
| c8cb8e1346 | |||
| 6a9f8da14e | |||
| ff9260a0fd | |||
| 62bedb1609 | |||
| a519723d44 | |||
| 36191781ed | |||
| 61b6a49669 | |||
| e97845e46e | |||
| 6bba68207d | |||
| 0e17cccc06 | |||
| 918e922e40 | |||
| a08854ddfd | |||
| eb6f15c2b7 | |||
| 2cd9e31684 | |||
| 791d6624e2 | |||
| af41021271 | |||
| d726d939f4 | |||
| 748085e7bb | |||
| d57d19174b | |||
| efebe1828a | |||
| 06007e0597 | |||
| 3a868045d7 | |||
| e0a7ff1880 | |||
| 88d43bbd40 | |||
| 30ff319b13 | |||
| 9a0f48b799 | |||
| b52c8a1a8f | |||
| 3fde115670 | |||
| b6f4d8ff1e | |||
| a7d85a7dd5 | |||
| 9b238ab6c3 | |||
| f82ee97c05 |
+4
-1
@@ -1,6 +1,9 @@
|
|||||||
#built application files
|
# built application files
|
||||||
*.apk
|
*.apk
|
||||||
*.ap_
|
*.ap_
|
||||||
|
*.aab
|
||||||
|
output.json
|
||||||
|
out/
|
||||||
|
|
||||||
# files for the dex VM
|
# files for the dex VM
|
||||||
*.dex
|
*.dex
|
||||||
|
|||||||
+11
-4
@@ -1,14 +1,15 @@
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 27
|
buildToolsVersion '28.0.1'
|
||||||
|
compileSdkVersion 28
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 27
|
targetSdkVersion 28
|
||||||
|
|
||||||
versionName "5.8"
|
versionName "5.9"
|
||||||
versionCode = 161
|
versionCode = 168
|
||||||
}
|
}
|
||||||
|
|
||||||
flavorDimensions "root"
|
flavorDimensions "root"
|
||||||
@@ -51,6 +52,12 @@ android {
|
|||||||
// to manually switch language in settings.
|
// to manually switch language in settings.
|
||||||
enableSplit = false
|
enableSplit = false
|
||||||
}
|
}
|
||||||
|
density {
|
||||||
|
// FIXME: This should not be neccessary but we get
|
||||||
|
// weird crashes due to missing drawable resources
|
||||||
|
// when this split is enabled.
|
||||||
|
enableSplit = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
|||||||
@@ -67,8 +67,9 @@
|
|||||||
</activity>
|
</activity>
|
||||||
<!-- Small hack to support launcher shortcuts without relaunching over and over again when the back button is pressed -->
|
<!-- Small hack to support launcher shortcuts without relaunching over and over again when the back button is pressed -->
|
||||||
<activity
|
<activity
|
||||||
android:name=".AppViewShortcutTrampoline"
|
android:name=".ShortcutTrampoline"
|
||||||
android:noHistory="true"
|
android:noHistory="true"
|
||||||
|
android:exported="true"
|
||||||
android:resizeableActivity="true"
|
android:resizeableActivity="true"
|
||||||
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
|
<meta-data
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ import android.app.Service;
|
|||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.ServiceConnection;
|
import android.content.ServiceConnection;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.view.ContextMenu;
|
import android.view.ContextMenu;
|
||||||
@@ -36,6 +39,7 @@ import android.view.ContextMenu.ContextMenuInfo;
|
|||||||
import android.widget.AbsListView;
|
import android.widget.AbsListView;
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
import android.widget.AdapterView.OnItemClickListener;
|
import android.widget.AdapterView.OnItemClickListener;
|
||||||
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
import android.widget.AdapterView.AdapterContextMenuInfo;
|
import android.widget.AdapterView.AdapterContextMenuInfo;
|
||||||
@@ -56,7 +60,9 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
private final static int START_OR_RESUME_ID = 1;
|
private final static int START_OR_RESUME_ID = 1;
|
||||||
private final static int QUIT_ID = 2;
|
private final static int QUIT_ID = 2;
|
||||||
private final static int CANCEL_ID = 3;
|
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 NAME_EXTRA = "Name";
|
||||||
public final static String UUID_EXTRA = "UUID";
|
public final static String UUID_EXTRA = "UUID";
|
||||||
@@ -331,10 +337,15 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
menu.add(Menu.NONE, QUIT_ID, 2, getResources().getString(R.string.applist_menu_quit));
|
menu.add(Menu.NONE, QUIT_ID, 2, getResources().getString(R.string.applist_menu_quit));
|
||||||
}
|
}
|
||||||
else {
|
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, 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) {
|
||||||
|
menu.add(Menu.NONE, CREATE_SHORTCUT_ID, 4, getResources().getString(R.string.applist_menu_scut));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -346,7 +357,7 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
|
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
|
||||||
final AppObject app = (AppObject) appGridAdapter.getItem(info.position);
|
final AppObject app = (AppObject) appGridAdapter.getItem(info.position);
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case START_WTIH_QUIT:
|
case START_WITH_QUIT:
|
||||||
// Display a confirmation dialog first
|
// Display a confirmation dialog first
|
||||||
UiHelper.displayQuitConfirmationDialog(this, new Runnable() {
|
UiHelper.displayQuitConfirmationDialog(this, new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
@@ -386,6 +397,19 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
case CANCEL_ID:
|
case CANCEL_ID:
|
||||||
return true;
|
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:
|
default:
|
||||||
return super.onContextItemSelected(item);
|
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -146,10 +146,8 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
// We don't want a title bar
|
// We don't want a title bar
|
||||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||||
|
|
||||||
// Full-screen and don't let the display go off
|
// Full-screen
|
||||||
getWindow().addFlags(
|
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||||
WindowManager.LayoutParams.FLAG_FULLSCREEN |
|
|
||||||
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
|
||||||
|
|
||||||
// If we're going to use immersive mode, we want to have
|
// If we're going to use immersive mode, we want to have
|
||||||
// the entire screen
|
// the entire screen
|
||||||
@@ -185,6 +183,12 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
prefConfig = PreferenceConfiguration.readPreferences(this);
|
prefConfig = PreferenceConfiguration.readPreferences(this);
|
||||||
tombstonePrefs = Game.this.getSharedPreferences("DecoderTombstone", 0);
|
tombstonePrefs = Game.this.getSharedPreferences("DecoderTombstone", 0);
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && prefConfig.stretchVideo) {
|
||||||
|
// Allow the activity to layout under notches if the fill-screen option
|
||||||
|
// was turned on by the user
|
||||||
|
getWindow().getAttributes().layoutInDisplayCutoutMode =
|
||||||
|
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
|
||||||
|
}
|
||||||
|
|
||||||
// Listen for events on the game surface
|
// Listen for events on the game surface
|
||||||
streamView = findViewById(R.id.surfaceView);
|
streamView = findViewById(R.id.surfaceView);
|
||||||
@@ -830,8 +834,12 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle a synthetic back button event that some Android OS versions
|
// Handle a synthetic back button event that some Android OS versions
|
||||||
// create as a result of a right-click.
|
// create as a result of a right-click. This event WILL repeat if
|
||||||
if (event.getSource() == InputDevice.SOURCE_MOUSE && event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
|
// 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);
|
conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_RIGHT);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -881,7 +889,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
|
|
||||||
// Handle a synthetic back button event that some Android OS versions
|
// Handle a synthetic back button event that some Android OS versions
|
||||||
// create as a result of a right-click.
|
// 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);
|
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_RIGHT);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -949,6 +959,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
{
|
{
|
||||||
// This case is for mice
|
// This case is for mice
|
||||||
if (event.getSource() == InputDevice.SOURCE_MOUSE ||
|
if (event.getSource() == InputDevice.SOURCE_MOUSE ||
|
||||||
|
event.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE ||
|
||||||
(event.getPointerCount() >= 1 &&
|
(event.getPointerCount() >= 1 &&
|
||||||
event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE))
|
event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE))
|
||||||
{
|
{
|
||||||
@@ -964,6 +975,13 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
byte vScrollClicks = (byte) event.getAxisValue(MotionEvent.AXIS_VSCROLL);
|
byte vScrollClicks = (byte) event.getAxisValue(MotionEvent.AXIS_VSCROLL);
|
||||||
conn.sendMouseScroll(vScrollClicks);
|
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 ((changedButtons & MotionEvent.BUTTON_PRIMARY) != 0) {
|
||||||
if ((event.getButtonState() & MotionEvent.BUTTON_PRIMARY) != 0) {
|
if ((event.getButtonState() & MotionEvent.BUTTON_PRIMARY) != 0) {
|
||||||
@@ -1010,12 +1028,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
lastMouseY = (int)event.getY();
|
lastMouseY = (int)event.getY();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// First process the history
|
// Don't process the history. We just want the current position now.
|
||||||
for (int i = 0; i < event.getHistorySize(); i++) {
|
|
||||||
updateMousePosition((int)event.getHistoricalX(i), (int)event.getHistoricalY(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now process the current values
|
|
||||||
updateMousePosition((int)event.getX(), (int)event.getY());
|
updateMousePosition((int)event.getX(), (int)event.getY());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1163,10 +1176,15 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stageStarting(String stage) {
|
public void stageStarting(final String stage) {
|
||||||
if (spinner != null) {
|
runOnUiThread(new Runnable() {
|
||||||
spinner.setMessage(getResources().getString(R.string.conn_starting)+" "+stage);
|
@Override
|
||||||
}
|
public void run() {
|
||||||
|
if (spinner != null) {
|
||||||
|
spinner.setMessage(getResources().getString(R.string.conn_starting) + " " + stage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1191,70 +1209,78 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stageFailed(String stage, long errorCode) {
|
public void stageFailed(final String stage, final long errorCode) {
|
||||||
if (spinner != null) {
|
runOnUiThread(new Runnable() {
|
||||||
spinner.dismiss();
|
@Override
|
||||||
spinner = null;
|
public void run() {
|
||||||
}
|
if (spinner != null) {
|
||||||
|
spinner.dismiss();
|
||||||
|
spinner = null;
|
||||||
|
}
|
||||||
|
|
||||||
// Enable cursor visibility again
|
if (!displayedFailureDialog) {
|
||||||
inputCaptureProvider.disableCapture();
|
displayedFailureDialog = true;
|
||||||
|
LimeLog.severe(stage + " failed: " + errorCode);
|
||||||
|
|
||||||
if (!displayedFailureDialog) {
|
// If video initialization failed and the surface is still valid, display extra information for the user
|
||||||
displayedFailureDialog = true;
|
if (stage.contains("video") && streamView.getHolder().getSurface().isValid()) {
|
||||||
LimeLog.severe(stage+" failed: "+errorCode);
|
|
||||||
|
|
||||||
// If video initialization failed and the surface is still valid, display extra information for the user
|
|
||||||
if (stage.contains("video") && streamView.getHolder().getSurface().isValid()) {
|
|
||||||
runOnUiThread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
Toast.makeText(Game.this, "Video decoder failed to initialize. Your device may not support the selected resolution.", Toast.LENGTH_LONG).show();
|
Toast.makeText(Game.this, "Video decoder failed to initialize. Your device may not support the selected resolution.", Toast.LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Dialog.displayDialog(this, getResources().getString(R.string.conn_error_title),
|
Dialog.displayDialog(Game.this, getResources().getString(R.string.conn_error_title),
|
||||||
getResources().getString(R.string.conn_error_msg)+" "+stage, true);
|
getResources().getString(R.string.conn_error_msg) + " " + stage, true);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void connectionTerminated(long errorCode) {
|
public void connectionTerminated(final long errorCode) {
|
||||||
// Enable cursor visibility again
|
runOnUiThread(new Runnable() {
|
||||||
inputCaptureProvider.disableCapture();
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// Let the display go to sleep now
|
||||||
|
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||||
|
|
||||||
if (!displayedFailureDialog) {
|
// Enable cursor visibility again
|
||||||
displayedFailureDialog = true;
|
inputCaptureProvider.disableCapture();
|
||||||
LimeLog.severe("Connection terminated: "+errorCode);
|
|
||||||
stopConnection();
|
|
||||||
|
|
||||||
Dialog.displayDialog(this, getResources().getString(R.string.conn_terminated_title),
|
if (!displayedFailureDialog) {
|
||||||
getResources().getString(R.string.conn_terminated_msg), true);
|
displayedFailureDialog = true;
|
||||||
}
|
LimeLog.severe("Connection terminated: " + errorCode);
|
||||||
|
stopConnection();
|
||||||
|
|
||||||
|
Dialog.displayDialog(Game.this, getResources().getString(R.string.conn_terminated_title),
|
||||||
|
getResources().getString(R.string.conn_terminated_msg), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void connectionStarted() {
|
public void connectionStarted() {
|
||||||
if (spinner != null) {
|
|
||||||
spinner.dismiss();
|
|
||||||
spinner = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
connected = true;
|
|
||||||
connecting = false;
|
|
||||||
|
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
if (spinner != null) {
|
||||||
|
spinner.dismiss();
|
||||||
|
spinner = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
connected = true;
|
||||||
|
connecting = false;
|
||||||
|
|
||||||
// Hide the mouse cursor now. Doing it before
|
// Hide the mouse cursor now. Doing it before
|
||||||
// dismissing the spinner seems to be undone
|
// dismissing the spinner seems to be undone
|
||||||
// when the spinner gets displayed.
|
// when the spinner gets displayed.
|
||||||
inputCaptureProvider.enableCapture();
|
inputCaptureProvider.enableCapture();
|
||||||
|
|
||||||
|
// Keep the display on
|
||||||
|
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||||
|
|
||||||
|
hideSystemUi(1000);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
hideSystemUi(1000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import com.limelight.utils.ShortcutHelper;
|
|||||||
import com.limelight.utils.UiHelper;
|
import com.limelight.utils.UiHelper;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.app.ActivityManager;
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Intent;
|
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 DELETE_ID = 5;
|
||||||
private final static int RESUME_ID = 6;
|
private final static int RESUME_ID = 6;
|
||||||
private final static int QUIT_ID = 7;
|
private final static int QUIT_ID = 7;
|
||||||
|
private final static int VIEW_DETAILS_ID = 8;
|
||||||
|
|
||||||
private void initializeViews() {
|
private void initializeViews() {
|
||||||
setContentView(R.layout.activity_pc_view);
|
setContentView(R.layout.activity_pc_view);
|
||||||
@@ -332,6 +334,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
// it with delete which actually work
|
// 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, 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
|
@Override
|
||||||
@@ -452,7 +455,6 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Toast.makeText(PcView.this, getResources().getString(R.string.wol_waking_pc), Toast.LENGTH_SHORT).show();
|
|
||||||
new Thread(new Runnable() {
|
new Thread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
@@ -561,6 +563,10 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
case DELETE_ID:
|
case DELETE_ID:
|
||||||
|
if (ActivityManager.isUserAMonkey()) {
|
||||||
|
LimeLog.info("Ignoring delete PC request from monkey");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (managerBinder == null) {
|
if (managerBinder == null) {
|
||||||
Toast.makeText(PcView.this, getResources().getString(R.string.error_manager_not_running), Toast.LENGTH_LONG).show();
|
Toast.makeText(PcView.this, getResources().getString(R.string.error_manager_not_running), Toast.LENGTH_LONG).show();
|
||||||
return true;
|
return true;
|
||||||
@@ -599,6 +605,10 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
}, null);
|
}, null);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
case VIEW_DETAILS_ID:
|
||||||
|
Dialog.displayDialog(PcView.this, getResources().getString(R.string.title_details), computer.details.toString(), false);
|
||||||
|
return true;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return super.onContextItemSelected(item);
|
return super.onContextItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,263 @@
|
|||||||
|
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 {
|
||||||
|
UiHelper.displayQuitConfirmationDialog(ShortcutTrampoline.this, new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
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[]{}));
|
||||||
|
}
|
||||||
|
}, 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.input.InputManager;
|
||||||
import android.hardware.usb.UsbDevice;
|
import android.hardware.usb.UsbDevice;
|
||||||
import android.hardware.usb.UsbManager;
|
import android.hardware.usb.UsbManager;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
import android.view.InputDevice;
|
import android.view.InputDevice;
|
||||||
@@ -22,6 +23,7 @@ import com.limelight.preferences.PreferenceConfiguration;
|
|||||||
import com.limelight.ui.GameGestures;
|
import com.limelight.ui.GameGestures;
|
||||||
import com.limelight.utils.Vector2d;
|
import com.limelight.utils.Vector2d;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.util.Timer;
|
import java.util.Timer;
|
||||||
import java.util.TimerTask;
|
import java.util.TimerTask;
|
||||||
|
|
||||||
@@ -308,6 +310,79 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
return context;
|
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) {
|
private InputDeviceContext createInputDeviceContextForDevice(InputDevice dev) {
|
||||||
InputDeviceContext context = new InputDeviceContext();
|
InputDeviceContext context = new InputDeviceContext();
|
||||||
String devName = dev.getName();
|
String devName = dev.getName();
|
||||||
@@ -440,6 +515,8 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
context.ignoreBack = shouldIgnoreBack(dev);
|
||||||
|
|
||||||
if (devName != null) {
|
if (devName != null) {
|
||||||
// For the Nexus Player (and probably other ATV devices), we should
|
// 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
|
// 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
|
// so we increase the deadzone on them to minimize this
|
||||||
context.triggerDeadzone = 0.30f;
|
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
|
// SHIELD controllers will use small stick deadzones
|
||||||
else if (devName.contains("SHIELD")) {
|
else if (devName.contains("SHIELD")) {
|
||||||
context.leftStickDeadzoneRadius = 0.07f;
|
context.leftStickDeadzoneRadius = 0.07f;
|
||||||
context.rightStickDeadzoneRadius = 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
|
// 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.
|
// a back button which we want to ignore since there's already a select button.
|
||||||
else if (devName.contains("Razer Serval")) {
|
else if (devName.contains("Razer Serval")) {
|
||||||
context.isServal = true;
|
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
|
// However, Microsoft released a firmware update with no change to VID/PID
|
||||||
|
|||||||
+10
-2
@@ -43,11 +43,19 @@ public class AndroidNativePointerCaptureProvider extends InputCaptureProvider {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float getRelativeAxisX(MotionEvent event) {
|
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
|
@Override
|
||||||
public float getRelativeAxisY(MotionEvent event) {
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -60,6 +60,7 @@ public class VirtualController {
|
|||||||
|
|
||||||
buttonConfigure = new Button(context);
|
buttonConfigure = new Button(context);
|
||||||
buttonConfigure.setAlpha(0.25f);
|
buttonConfigure.setAlpha(0.25f);
|
||||||
|
buttonConfigure.setFocusable(false);
|
||||||
buttonConfigure.setBackgroundResource(R.drawable.ic_settings);
|
buttonConfigure.setBackgroundResource(R.drawable.ic_settings);
|
||||||
buttonConfigure.setOnClickListener(new View.OnClickListener() {
|
buttonConfigure.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -131,8 +131,8 @@ public class MediaCodecHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sony ATVs have broken MediaTek codecs (decoder hangs after rendering the first frame).
|
// 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
|
// I know the Fire TV 2 and 3 works, so I'll just whitelist Amazon devices which seem
|
||||||
// to actually be tested. Ugh...
|
// to actually be tested.
|
||||||
if (Build.MANUFACTURER.equalsIgnoreCase("Amazon")) {
|
if (Build.MANUFACTURER.equalsIgnoreCase("Amazon")) {
|
||||||
whitelistedHevcDecoders.add("omx.mtk");
|
whitelistedHevcDecoders.add("omx.mtk");
|
||||||
whitelistedHevcDecoders.add("omx.amlogic");
|
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) {
|
private static String getAdrenoVersionString(String glRenderer) {
|
||||||
glRenderer = glRenderer.toLowerCase().trim();
|
glRenderer = glRenderer.toLowerCase().trim();
|
||||||
|
|
||||||
@@ -259,6 +263,18 @@ public class MediaCodecHelper {
|
|||||||
else {
|
else {
|
||||||
blacklistedDecoderPrefixes.add("OMX.qcom.video.decoder.hevc");
|
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;
|
initialized = true;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.limelight.grid;
|
package com.limelight.grid;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.graphics.BitmapFactory;
|
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
@@ -46,13 +45,10 @@ public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
|
|||||||
}
|
}
|
||||||
LimeLog.info("Art scaling divisor: " + scalingDivisor);
|
LimeLog.info("Art scaling divisor: " + scalingDivisor);
|
||||||
|
|
||||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
|
||||||
options.inSampleSize = (int) scalingDivisor;
|
|
||||||
|
|
||||||
this.loader = new CachedAppAssetLoader(computer, scalingDivisor,
|
this.loader = new CachedAppAssetLoader(computer, scalingDivisor,
|
||||||
new NetworkAssetLoader(context, uniqueId),
|
new NetworkAssetLoader(context, uniqueId),
|
||||||
new MemoryAssetLoader(),
|
new MemoryAssetLoader(),
|
||||||
new DiskAssetLoader(context.getCacheDir()));
|
new DiskAssetLoader(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cancelQueuedOperations() {
|
public void cancelQueuedOperations() {
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
package com.limelight.grid.assets;
|
package com.limelight.grid.assets;
|
||||||
|
|
||||||
|
import android.app.ActivityManager;
|
||||||
|
import android.content.Context;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.graphics.ImageDecoder;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
import com.limelight.LimeLog;
|
import com.limelight.LimeLog;
|
||||||
import com.limelight.utils.CacheHelper;
|
import com.limelight.utils.CacheHelper;
|
||||||
@@ -19,10 +23,19 @@ public class DiskAssetLoader {
|
|||||||
private static final int STANDARD_ASSET_WIDTH = 300;
|
private static final int STANDARD_ASSET_WIDTH = 300;
|
||||||
private static final int STANDARD_ASSET_HEIGHT = 400;
|
private static final int STANDARD_ASSET_HEIGHT = 400;
|
||||||
|
|
||||||
|
private final boolean isLowRamDevice;
|
||||||
private final File cacheDir;
|
private final File cacheDir;
|
||||||
|
|
||||||
public DiskAssetLoader(File cacheDir) {
|
public DiskAssetLoader(Context context) {
|
||||||
this.cacheDir = cacheDir;
|
this.cacheDir = context.getCacheDir();
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||||
|
this.isLowRamDevice =
|
||||||
|
((ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE)).isLowRamDevice();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Use conservative low RAM behavior on very old devices
|
||||||
|
this.isLowRamDevice = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean checkCacheExists(CachedAppAssetLoader.LoaderTuple tuple) {
|
public boolean checkCacheExists(CachedAppAssetLoader.LoaderTuple tuple) {
|
||||||
@@ -66,28 +79,55 @@ public class DiskAssetLoader {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lookup bounds of the downloaded image
|
Bitmap bmp;
|
||||||
BitmapFactory.Options decodeOnlyOptions = new BitmapFactory.Options();
|
|
||||||
decodeOnlyOptions.inJustDecodeBounds = true;
|
// For OSes prior to P, we have to use the ugly BitmapFactory API
|
||||||
BitmapFactory.decodeFile(file.getAbsolutePath(), decodeOnlyOptions);
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
|
||||||
if (decodeOnlyOptions.outWidth <= 0 || decodeOnlyOptions.outHeight <= 0) {
|
// Lookup bounds of the downloaded image
|
||||||
// Dimensions set to -1 on error. Return value always null.
|
BitmapFactory.Options decodeOnlyOptions = new BitmapFactory.Options();
|
||||||
return null;
|
decodeOnlyOptions.inJustDecodeBounds = true;
|
||||||
|
BitmapFactory.decodeFile(file.getAbsolutePath(), decodeOnlyOptions);
|
||||||
|
if (decodeOnlyOptions.outWidth <= 0 || decodeOnlyOptions.outHeight <= 0) {
|
||||||
|
// Dimensions set to -1 on error. Return value always null.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
LimeLog.info("Tuple "+tuple+" has cached art of size: "+decodeOnlyOptions.outWidth+"x"+decodeOnlyOptions.outHeight);
|
||||||
|
|
||||||
|
// Load the image scaled to the appropriate size
|
||||||
|
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||||
|
options.inSampleSize = calculateInSampleSize(decodeOnlyOptions,
|
||||||
|
STANDARD_ASSET_WIDTH / sampleSize,
|
||||||
|
STANDARD_ASSET_HEIGHT / sampleSize);
|
||||||
|
if (isLowRamDevice) {
|
||||||
|
options.inPreferredConfig = Bitmap.Config.RGB_565;
|
||||||
|
options.inDither = true;
|
||||||
|
}
|
||||||
|
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
options.inPreferredConfig = Bitmap.Config.HARDWARE;
|
||||||
|
}
|
||||||
|
|
||||||
|
bmp = BitmapFactory.decodeFile(file.getAbsolutePath(), options);
|
||||||
|
if (bmp != null) {
|
||||||
|
LimeLog.info("Tuple "+tuple+" decoded from disk cache with sample size: "+options.inSampleSize);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
LimeLog.info("Tuple "+tuple+" has cached art of size: "+decodeOnlyOptions.outWidth+"x"+decodeOnlyOptions.outHeight);
|
// On P, we can get a bitmap back in one step with ImageDecoder
|
||||||
|
try {
|
||||||
// Load the image scaled to the appropriate size
|
bmp = ImageDecoder.decodeBitmap(ImageDecoder.createSource(file), new ImageDecoder.OnHeaderDecodedListener() {
|
||||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
@Override
|
||||||
options.inSampleSize = calculateInSampleSize(decodeOnlyOptions,
|
public void onHeaderDecoded(ImageDecoder imageDecoder, ImageDecoder.ImageInfo imageInfo, ImageDecoder.Source source) {
|
||||||
STANDARD_ASSET_WIDTH / sampleSize,
|
imageDecoder.setTargetSize(STANDARD_ASSET_WIDTH, STANDARD_ASSET_HEIGHT);
|
||||||
STANDARD_ASSET_HEIGHT / sampleSize);
|
if (isLowRamDevice) {
|
||||||
options.inPreferredConfig = Bitmap.Config.RGB_565;
|
imageDecoder.setMemorySizePolicy(ImageDecoder.MEMORY_POLICY_LOW_RAM);
|
||||||
options.inDither = true;
|
}
|
||||||
Bitmap bmp = BitmapFactory.decodeFile(file.getAbsolutePath(), options);
|
}
|
||||||
|
});
|
||||||
if (bmp != null) {
|
} catch (IOException e) {
|
||||||
LimeLog.info("Tuple "+tuple+" decoded from disk cache with sample size: "+options.inSampleSize);
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return bmp;
|
return bmp;
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import android.content.ServiceConnection;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
|
import android.view.View;
|
||||||
import android.view.inputmethod.EditorInfo;
|
import android.view.inputmethod.EditorInfo;
|
||||||
import android.view.inputmethod.InputMethodManager;
|
import android.view.inputmethod.InputMethodManager;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
@@ -196,12 +197,7 @@ public class AddComputerManually extends Activity {
|
|||||||
(keyEvent != null &&
|
(keyEvent != null &&
|
||||||
keyEvent.getAction() == KeyEvent.ACTION_DOWN &&
|
keyEvent.getAction() == KeyEvent.ACTION_DOWN &&
|
||||||
keyEvent.getKeyCode() == KeyEvent.KEYCODE_ENTER)) {
|
keyEvent.getKeyCode() == KeyEvent.KEYCODE_ENTER)) {
|
||||||
if (hostText.getText().length() == 0) {
|
return handleDoneEvent();
|
||||||
Toast.makeText(AddComputerManually.this, getResources().getString(R.string.addpc_enter_ip), Toast.LENGTH_LONG).show();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
computersToAdd.add(hostText.getText().toString().trim());
|
|
||||||
}
|
}
|
||||||
else if (actionId == EditorInfo.IME_ACTION_PREVIOUS) {
|
else if (actionId == EditorInfo.IME_ACTION_PREVIOUS) {
|
||||||
// This is how the Fire TV dismisses the keyboard
|
// This is how the Fire TV dismisses the keyboard
|
||||||
@@ -214,8 +210,28 @@ public class AddComputerManually extends Activity {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
findViewById(R.id.addPcButton).setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
handleDoneEvent();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Bind to the ComputerManager service
|
// Bind to the ComputerManager service
|
||||||
bindService(new Intent(AddComputerManually.this,
|
bindService(new Intent(AddComputerManually.this,
|
||||||
ComputerManagerService.class), serviceConnection, Service.BIND_AUTO_CREATE);
|
ComputerManagerService.class), serviceConnection, Service.BIND_AUTO_CREATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns true if the event should be eaten
|
||||||
|
private boolean handleDoneEvent() {
|
||||||
|
String hostAddress = hostText.getText().toString().trim();
|
||||||
|
|
||||||
|
if (hostAddress.length() == 0) {
|
||||||
|
Toast.makeText(AddComputerManually.this, getResources().getString(R.string.addpc_enter_ip), Toast.LENGTH_LONG).show();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
computersToAdd.add(hostAddress);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,13 +5,15 @@ import android.content.Context;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.ShortcutInfo;
|
import android.content.pm.ShortcutInfo;
|
||||||
import android.content.pm.ShortcutManager;
|
import android.content.pm.ShortcutManager;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.drawable.Icon;
|
import android.graphics.drawable.Icon;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
|
||||||
import com.limelight.AppView;
|
import com.limelight.AppView;
|
||||||
import com.limelight.AppViewShortcutTrampoline;
|
import com.limelight.ShortcutTrampoline;
|
||||||
import com.limelight.R;
|
import com.limelight.R;
|
||||||
import com.limelight.nvstream.http.ComputerDetails;
|
import com.limelight.nvstream.http.ComputerDetails;
|
||||||
|
import com.limelight.nvstream.http.NvApp;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
@@ -80,8 +82,7 @@ public class ShortcutHelper {
|
|||||||
|
|
||||||
public void reportShortcutUsed(String id) {
|
public void reportShortcutUsed(String id) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
||||||
ShortcutInfo sinfo = getInfoForId(id);
|
if (getInfoForId(id) != null) {
|
||||||
if (sinfo != null) {
|
|
||||||
sm.reportShortcutUsed(id);
|
sm.reportShortcutUsed(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -89,7 +90,7 @@ public class ShortcutHelper {
|
|||||||
|
|
||||||
public void createAppViewShortcut(String id, String computerName, String computerUuid, boolean forceAdd) {
|
public void createAppViewShortcut(String id, String computerName, String computerUuid, boolean forceAdd) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
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.NAME_EXTRA, computerName);
|
||||||
i.putExtra(AppView.UUID_EXTRA, computerUuid);
|
i.putExtra(AppView.UUID_EXTRA, computerUuid);
|
||||||
i.setAction(Intent.ACTION_DEFAULT);
|
i.setAction(Intent.ACTION_DEFAULT);
|
||||||
@@ -127,10 +128,42 @@ public class ShortcutHelper {
|
|||||||
createAppViewShortcut(id, details.name, details.uuid.toString(), forceAdd);
|
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) {
|
public void disableShortcut(String id, CharSequence reason) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
||||||
ShortcutInfo sinfo = getInfoForId(id);
|
if (getInfoForId(id) != null) {
|
||||||
if (sinfo != null) {
|
|
||||||
sm.disableShortcuts(Collections.singletonList(id), reason);
|
sm.disableShortcuts(Collections.singletonList(id), reason);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import android.content.Context;
|
|||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
|
import android.os.Build;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
|
||||||
import com.limelight.R;
|
import com.limelight.R;
|
||||||
import com.limelight.preferences.PreferenceConfiguration;
|
import com.limelight.preferences.PreferenceConfiguration;
|
||||||
@@ -57,6 +59,16 @@ public class UiHelper {
|
|||||||
rootView.setPadding(horizontalPaddingPixels, verticalPaddingPixels,
|
rootView.setPadding(horizontalPaddingPixels, verticalPaddingPixels,
|
||||||
horizontalPaddingPixels, verticalPaddingPixels);
|
horizontalPaddingPixels, verticalPaddingPixels);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
|
// Allow this non-streaming activity to layout under notches.
|
||||||
|
//
|
||||||
|
// We should NOT do this for the Game activity unless
|
||||||
|
// the user specifically opts in, because it can obscure
|
||||||
|
// parts of the streaming surface.
|
||||||
|
activity.getWindow().getAttributes().layoutInDisplayCutoutMode =
|
||||||
|
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void showDecoderCrashDialog(Activity activity) {
|
public static void showDecoderCrashDialog(Activity activity) {
|
||||||
|
|||||||
@@ -22,11 +22,11 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@+id/manuallyAddPcText"
|
android:layout_below="@+id/manuallyAddPcText"
|
||||||
|
android:layout_toLeftOf="@+id/addPcButton"
|
||||||
|
android:layout_toStartOf="@+id/addPcButton"
|
||||||
android:layout_marginTop="25dp"
|
android:layout_marginTop="25dp"
|
||||||
android:layout_alignParentLeft="true"
|
android:layout_alignParentLeft="true"
|
||||||
android:layout_alignParentStart="true"
|
android:layout_alignParentStart="true"
|
||||||
android:layout_alignParentRight="true"
|
|
||||||
android:layout_alignParentEnd="true"
|
|
||||||
android:ems="10"
|
android:ems="10"
|
||||||
android:singleLine="true"
|
android:singleLine="true"
|
||||||
android:inputType="textNoSuggestions"
|
android:inputType="textNoSuggestions"
|
||||||
@@ -34,5 +34,15 @@
|
|||||||
|
|
||||||
<requestFocus />
|
<requestFocus />
|
||||||
</EditText>
|
</EditText>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/addPcButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="25dp"
|
||||||
|
android:layout_below="@+id/manuallyAddPcText"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:text="@android:string/ok"/>
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|||||||
@@ -6,6 +6,9 @@
|
|||||||
<!-- Shortcut strings -->
|
<!-- Shortcut strings -->
|
||||||
<string name="scut_deleted_pc">PC deleted</string>
|
<string name="scut_deleted_pc">PC deleted</string>
|
||||||
<string name="scut_not_paired">PC not paired</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 -->
|
<!-- Help strings -->
|
||||||
<string name="help_loading_title">Help Viewer</string>
|
<string name="help_loading_title">Help Viewer</string>
|
||||||
@@ -17,6 +20,7 @@
|
|||||||
<string name="pcview_menu_unpair_pc">Unpair</string>
|
<string name="pcview_menu_unpair_pc">Unpair</string>
|
||||||
<string name="pcview_menu_send_wol">Send Wake-On-LAN request</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_delete_pc">Delete PC</string>
|
||||||
|
<string name="pcview_menu_details">View Details</string>
|
||||||
|
|
||||||
<!-- Pair messages -->
|
<!-- Pair messages -->
|
||||||
<string name="pairing">Pairing…</string>
|
<string name="pairing">Pairing…</string>
|
||||||
@@ -55,6 +59,7 @@
|
|||||||
<string name="title_decoding_reset">Video Settings Reset</string>
|
<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="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="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 -->
|
<!-- Start application messages -->
|
||||||
<string name="conn_establishing_title">Establishing Connection</string>
|
<string name="conn_establishing_title">Establishing Connection</string>
|
||||||
@@ -76,6 +81,7 @@
|
|||||||
<string name="yes">Yes</string>
|
<string name="yes">Yes</string>
|
||||||
<string name="no">No</string>
|
<string name="no">No</string>
|
||||||
<string name="lost_connection">Lost connection to PC</string>
|
<string name="lost_connection">Lost connection to PC</string>
|
||||||
|
<string name="title_details">Details</string>
|
||||||
<string name="help">Help</string>
|
<string name="help">Help</string>
|
||||||
|
|
||||||
<!-- AppList activity -->
|
<!-- AppList activity -->
|
||||||
@@ -85,6 +91,8 @@
|
|||||||
<string name="applist_menu_quit">Quit 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_quit_and_start">Quit Current Game and Start</string>
|
||||||
<string name="applist_menu_cancel">Cancel</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_title">App List</string>
|
||||||
<string name="applist_refresh_msg">Refreshing apps…</string>
|
<string name="applist_refresh_msg">Refreshing apps…</string>
|
||||||
<string name="applist_refresh_error_title">Error</string>
|
<string name="applist_refresh_error_title">Error</string>
|
||||||
@@ -93,6 +101,7 @@
|
|||||||
<string name="applist_quit_success">Successfully quit</string>
|
<string name="applist_quit_success">Successfully quit</string>
|
||||||
<string name="applist_quit_fail">Failed to 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_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 -->
|
<!-- Add computer manually activity -->
|
||||||
<string name="title_add_pc">Add PC Manually</string>
|
<string name="title_add_pc">Add PC Manually</string>
|
||||||
|
|||||||
+1
-2
@@ -5,8 +5,7 @@ buildscript {
|
|||||||
google()
|
google()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath "com.android.tools:r8:1.0.25"
|
classpath 'com.android.tools.build:gradle:3.2.0-beta04'
|
||||||
classpath 'com.android.tools.build:gradle:3.2.0-alpha15'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -1,9 +1,9 @@
|
|||||||
This file serves to document some of the decoder errata when using MediaCodec hardware decoders on certain devices.
|
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.
|
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
|
- 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
|
3. Some decoders strictly require that you pass BUFFER_FLAG_CODEC_CONFIG and crash upon the IDR frame if you don't
|
||||||
|
|||||||
+1
-1
Submodule moonlight-common updated: d87f24c617...416105b12f
Reference in New Issue
Block a user