Compare commits

..
16 Commits
10 changed files with 189 additions and 93 deletions

No files matched your search

+11 -4
View File
@@ -1,14 +1,15 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
android { android {
compileSdkVersion 27 buildToolsVersion '28.0.0'
compileSdkVersion 28
defaultConfig { defaultConfig {
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 27 targetSdkVersion 28
versionName "5.8" versionName "5.8.1"
versionCode = 161 versionCode = 166
} }
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 {
+66 -49
View File
@@ -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);
@@ -1163,10 +1167,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 +1200,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
@@ -452,7 +452,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() {
@@ -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;
}
} }
@@ -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"
@@ -35,4 +35,14 @@
<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>
+1 -2
View File
@@ -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-alpha17'
classpath 'com.android.tools.build:gradle:3.2.0-alpha15'
} }
} }