Commit 5e65dc64 authored by Peter Kotwicz's avatar Peter Kotwicz Committed by Commit Bot

Fix splash screen pixelation for new-style WebAPKs

This CL:
- Increases the maximum screenshot size (prior to PNG/JPEG encoding) that
  we transfer to Chrome. The new max will avoid downscaling on most phones
  (e.g. no downscaling on Pixel 3)
- Uses JPEG encoding for large screenshots because JPEG encoding is
  faster than PNG encoding on Android. Although JPEG encoding is not
  lossless it is preferable to downscaling

BUG=1001154

Change-Id: Ib9d61cee565b6d9a392272300bac71d6da46a861
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1787162
Commit-Queue: Peter Kotwicz <pkotwicz@chromium.org>
Reviewed-by: default avatarGlenn Hartmann <hartmanng@chromium.org>
Cr-Commit-Position: refs/heads/master@{#696990}
parent ff9ee2b6
......@@ -12,4 +12,4 @@
# //chrome/android/webapk/shell_apk:webapk is changed. This includes
# Java files, Android resource files and AndroidManifest.xml. Does not affect
# Chrome.apk
current_shell_apk_version = 105
current_shell_apk_version = 106
......@@ -13,6 +13,7 @@ import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.util.Pair;
import android.view.View;
import android.view.ViewTreeObserver;
......@@ -213,44 +214,52 @@ public class SplashActivity extends Activity {
/**
* Launches the host browser on top of {@link SplashActivity}.
* @param splashPngEncoded PNG-encoded screenshot of {@link mSplashView}.
* @param splashEncoded Encoded screenshot of {@link mSplashView}.
* @param encodingFormat The screenshot's encoding format.
*/
private void launch(byte[] splashPngEncoded) {
SplashContentProvider.cache(
this, splashPngEncoded, mSplashView.getWidth(), mSplashView.getHeight());
private void launch(byte[] splashEncoded, Bitmap.CompressFormat encodingFormat) {
SplashContentProvider.cache(this, splashEncoded, encodingFormat, mSplashView.getWidth(),
mSplashView.getHeight());
H2OLauncher.launch(this, mParams);
mParams = null;
}
/**
* Screenshots and PNG-encodes {@link mSplashView} on a background thread.
* Screenshots and encodes {@link mSplashView} on a background thread.
*/
@SuppressWarnings("NoAndroidAsyncTaskCheck")
private void screenshotAndEncodeSplashInBackground() {
final Bitmap bitmap = SplashUtils.screenshotView(
mSplashView, SplashContentProvider.MAX_TRANSFER_SIZE_BYTES);
if (bitmap == null) {
launch(null);
launch(null, Bitmap.CompressFormat.PNG);
return;
}
mScreenshotSplashTask =
new android.os
.AsyncTask<Void, Void, byte[]>() {
.AsyncTask<Void, Void, Pair<byte[], Bitmap.CompressFormat>>() {
@Override
protected byte[] doInBackground(Void... args) {
protected Pair<byte[], Bitmap.CompressFormat> doInBackground(
Void... args) {
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
return out.toByteArray();
Bitmap.CompressFormat encodingFormat =
SplashUtils.selectBitmapEncoding(
bitmap.getWidth(), bitmap.getHeight());
bitmap.compress(encodingFormat, 100, out);
return Pair.create(out.toByteArray(), encodingFormat);
} catch (IOException e) {
}
return null;
}
@Override
protected void onPostExecute(byte[] splashPngEncoded) {
protected void onPostExecute(
Pair<byte[], Bitmap.CompressFormat> splashEncoded) {
mScreenshotSplashTask = null;
launch(splashPngEncoded);
launch((splashEncoded == null) ? null : splashEncoded.first,
(splashEncoded == null) ? Bitmap.CompressFormat.PNG
: splashEncoded.second);
}
// Do nothing if task was cancelled.
......
......@@ -54,12 +54,14 @@ public class SplashContentProvider
* downsampled to fit. Capping the maximum size of the screenshot decreases bitmap encoding
* time and image transfer time.
*/
public static final int MAX_TRANSFER_SIZE_BYTES = 1024 * 1024 * 4;
public static final int MAX_TRANSFER_SIZE_BYTES = 1024 * 1024 * 12;
/** Mime type of transferred screenshot. */
private static final String SPLASH_MIME_TYPE = "image/png";
/**
* The encoding type of the last image vended by the ContentProvider.
*/
private static Bitmap.CompressFormat sEncodingFormat;
private static AtomicReference<ExpiringData> sCachedSplashPngBytes = new AtomicReference<>();
private static AtomicReference<ExpiringData> sCachedSplashBytes = new AtomicReference<>();
/** The URI handled by this content provider. */
private String mContentProviderUri;
......@@ -68,14 +70,15 @@ public class SplashContentProvider
* Temporarily caches the passed-in splash screen screenshot. To preserve memory, the cached
* data is cleared after a delay.
*/
public static void cache(
Context context, byte[] splashPngBytes, int splashWidth, int splashHeight) {
public static void cache(Context context, byte[] splashBytes,
Bitmap.CompressFormat encodingFormat, int splashWidth, int splashHeight) {
SharedPreferences.Editor editor = WebApkSharedPreferences.getPrefs(context).edit();
editor.putInt(WebApkSharedPreferences.PREF_SPLASH_WIDTH, splashWidth);
editor.putInt(WebApkSharedPreferences.PREF_SPLASH_HEIGHT, splashHeight);
editor.apply();
getAndSetCachedData(splashPngBytes);
sEncodingFormat = encodingFormat;
getAndSetCachedData(splashBytes);
}
public static void clearCache() {
......@@ -86,12 +89,12 @@ public class SplashContentProvider
* Sets the cached splash screen screenshot and returns the old one.
* Thread safety: Can be called from any thread.
*/
private static byte[] getAndSetCachedData(byte[] newSplashPngBytes) {
private static byte[] getAndSetCachedData(byte[] newSplashBytes) {
ExpiringData newData = null;
if (newSplashPngBytes != null) {
newData = new ExpiringData(newSplashPngBytes, SplashContentProvider::clearCache);
if (newSplashBytes != null) {
newData = new ExpiringData(newSplashBytes, SplashContentProvider::clearCache);
}
ExpiringData oldCachedData = sCachedSplashPngBytes.getAndSet(newData);
ExpiringData oldCachedData = sCachedSplashBytes.getAndSet(newData);
if (oldCachedData == null) return null;
oldCachedData.removeCallbacks();
......@@ -117,16 +120,18 @@ public class SplashContentProvider
public void writeDataToPipe(
ParcelFileDescriptor output, Uri uri, String mimeType, Bundle opts, Void unused) {
try (OutputStream out = new FileOutputStream(output.getFileDescriptor())) {
byte[] cachedSplashPngBytes = getAndSetCachedData(null);
if (cachedSplashPngBytes != null) {
out.write(cachedSplashPngBytes);
byte[] cachedSplashBytes = getAndSetCachedData(null);
if (cachedSplashBytes != null) {
out.write(cachedSplashBytes);
} else {
// One way that this case gets hit is when the WebAPK is brought to the foreground
// via Android Recents after the Android OOM killer has killed the host browser but
// not SplashActivity.
Bitmap splashScreenshot = recreateAndScreenshotSplash();
if (splashScreenshot != null) {
splashScreenshot.compress(Bitmap.CompressFormat.PNG, 100, out);
sEncodingFormat = SplashUtils.selectBitmapEncoding(
splashScreenshot.getWidth(), splashScreenshot.getHeight());
splashScreenshot.compress(sEncodingFormat, 100, out);
}
}
out.flush();
......@@ -137,7 +142,18 @@ public class SplashContentProvider
@Override
public String getType(Uri uri) {
if (uri != null && uri.toString().equals(mContentProviderUri)) {
return SPLASH_MIME_TYPE;
if (sEncodingFormat == null) {
Context context = getContext().getApplicationContext();
SharedPreferences prefs = WebApkSharedPreferences.getPrefs(context);
int splashWidth = prefs.getInt(WebApkSharedPreferences.PREF_SPLASH_WIDTH, -1);
int splashHeight = prefs.getInt(WebApkSharedPreferences.PREF_SPLASH_HEIGHT, -1);
sEncodingFormat = SplashUtils.selectBitmapEncoding(splashWidth, splashHeight);
}
if (sEncodingFormat == Bitmap.CompressFormat.PNG) {
return "image/png";
} else if (sEncodingFormat == Bitmap.CompressFormat.JPEG) {
return "image/jpeg";
}
}
return null;
}
......
......@@ -18,6 +18,12 @@ import org.chromium.webapk.shell_apk.WebApkUtils;
/** Contains splash screen related utility methods. */
public class SplashUtils {
/**
* The maximum image size to PNG-encode. JPEG encoding is > 2 times faster on large bitmaps.
* JPEG encoding is preferable to downscaling the screenshot.
*/
private static final int MAX_SIZE_ENCODE_PNG = 1024 * 1024;
/** Creates view with splash screen. */
public static View createSplashView(Context context) {
Resources resources = context.getResources();
......@@ -28,6 +34,7 @@ public class SplashUtils {
SplashLayout.createLayout(context, layout, icon, false /* isIconAdaptive */,
false /* isIconGenerated */, resources.getString(R.string.name),
WebApkUtils.shouldUseLightForegroundOnBackground(backgroundColor));
layout.setBackgroundColor(backgroundColor);
return layout;
}
......@@ -57,6 +64,12 @@ public class SplashUtils {
return bitmap;
}
/** Selects encoding for the bitmap based on its size. */
public static Bitmap.CompressFormat selectBitmapEncoding(int width, int height) {
return (width * height <= MAX_SIZE_ENCODE_PNG) ? Bitmap.CompressFormat.PNG
: Bitmap.CompressFormat.JPEG;
}
/** Creates splash view with the passed-in dimensions and screenshots it. */
public static Bitmap createAndImmediatelyScreenshotSplashView(
Context context, int splashWidth, int splashHeight, int maxSizeBytes) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment