Compare commits

...
16 Commits
10 changed files with 144 additions and 48 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 {
+41 -24
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,11 +1167,16 @@ public class Game extends Activity implements SurfaceHolder.Callback,
} }
@Override @Override
public void stageStarting(String stage) { public void stageStarting(final String stage) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (spinner != null) { if (spinner != null) {
spinner.setMessage(getResources().getString(R.string.conn_starting) + " " + stage); spinner.setMessage(getResources().getString(R.string.conn_starting) + " " + stage);
} }
} }
});
}
@Override @Override
public void stageComplete(String stage) { public void stageComplete(String stage) {
@@ -1191,36 +1200,39 @@ 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) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (spinner != null) { if (spinner != null) {
spinner.dismiss(); spinner.dismiss();
spinner = null; spinner = null;
} }
// Enable cursor visibility again
inputCaptureProvider.disableCapture();
if (!displayedFailureDialog) { if (!displayedFailureDialog) {
displayedFailureDialog = true; displayedFailureDialog = true;
LimeLog.severe(stage + " failed: " + errorCode); LimeLog.severe(stage + " failed: " + errorCode);
// If video initialization failed and the surface is still valid, display extra information for the user // If video initialization failed and the surface is still valid, display extra information for the user
if (stage.contains("video") && streamView.getHolder().getSurface().isValid()) { 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(Game.this, getResources().getString(R.string.conn_error_title),
getResources().getString(R.string.conn_error_msg) + " " + stage, true);
}
}
}); });
} }
Dialog.displayDialog(this, getResources().getString(R.string.conn_error_title),
getResources().getString(R.string.conn_error_msg)+" "+stage, true);
}
}
@Override @Override
public void connectionTerminated(long errorCode) { public void connectionTerminated(final long errorCode) {
runOnUiThread(new Runnable() {
@Override
public void run() {
// Let the display go to sleep now
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
// Enable cursor visibility again // Enable cursor visibility again
inputCaptureProvider.disableCapture(); inputCaptureProvider.disableCapture();
@@ -1229,13 +1241,18 @@ public class Game extends Activity implements SurfaceHolder.Callback,
LimeLog.severe("Connection terminated: " + errorCode); LimeLog.severe("Connection terminated: " + errorCode);
stopConnection(); stopConnection();
Dialog.displayDialog(this, getResources().getString(R.string.conn_terminated_title), Dialog.displayDialog(Game.this, getResources().getString(R.string.conn_terminated_title),
getResources().getString(R.string.conn_terminated_msg), true); getResources().getString(R.string.conn_terminated_msg), true);
} }
} }
});
}
@Override @Override
public void connectionStarted() { public void connectionStarted() {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (spinner != null) { if (spinner != null) {
spinner.dismiss(); spinner.dismiss();
spinner = null; spinner = null;
@@ -1244,18 +1261,18 @@ public class Game extends Activity implements SurfaceHolder.Callback,
connected = true; connected = true;
connecting = false; connecting = false;
runOnUiThread(new Runnable() {
@Override
public void run() {
// 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
public void displayMessage(final String message) { public void displayMessage(final String message) {
@@ -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,6 +79,10 @@ public class DiskAssetLoader {
return null; return null;
} }
Bitmap bmp;
// For OSes prior to P, we have to use the ugly BitmapFactory API
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
// Lookup bounds of the downloaded image // Lookup bounds of the downloaded image
BitmapFactory.Options decodeOnlyOptions = new BitmapFactory.Options(); BitmapFactory.Options decodeOnlyOptions = new BitmapFactory.Options();
decodeOnlyOptions.inJustDecodeBounds = true; decodeOnlyOptions.inJustDecodeBounds = true;
@@ -82,13 +99,36 @@ public class DiskAssetLoader {
options.inSampleSize = calculateInSampleSize(decodeOnlyOptions, options.inSampleSize = calculateInSampleSize(decodeOnlyOptions,
STANDARD_ASSET_WIDTH / sampleSize, STANDARD_ASSET_WIDTH / sampleSize,
STANDARD_ASSET_HEIGHT / sampleSize); STANDARD_ASSET_HEIGHT / sampleSize);
if (isLowRamDevice) {
options.inPreferredConfig = Bitmap.Config.RGB_565; options.inPreferredConfig = Bitmap.Config.RGB_565;
options.inDither = true; options.inDither = true;
Bitmap bmp = BitmapFactory.decodeFile(file.getAbsolutePath(), options); }
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
options.inPreferredConfig = Bitmap.Config.HARDWARE;
}
bmp = BitmapFactory.decodeFile(file.getAbsolutePath(), options);
if (bmp != null) { if (bmp != null) {
LimeLog.info("Tuple "+tuple+" decoded from disk cache with sample size: "+options.inSampleSize); LimeLog.info("Tuple "+tuple+" decoded from disk cache with sample size: "+options.inSampleSize);
} }
}
else {
// On P, we can get a bitmap back in one step with ImageDecoder
try {
bmp = ImageDecoder.decodeBitmap(ImageDecoder.createSource(file), new ImageDecoder.OnHeaderDecodedListener() {
@Override
public void onHeaderDecoded(ImageDecoder imageDecoder, ImageDecoder.ImageInfo imageInfo, ImageDecoder.Source source) {
imageDecoder.setTargetSize(STANDARD_ASSET_WIDTH, STANDARD_ASSET_HEIGHT);
if (isLowRamDevice) {
imageDecoder.setMemorySizePolicy(ImageDecoder.MEMORY_POLICY_LOW_RAM);
}
}
});
} catch (IOException e) {
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'
} }
} }