blob: d2380a006c2fbfb39c80ead67a1b6779be00d5cf [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.components.browser_ui.photo_picker;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import android.support.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
import androidx.test.filters.SmallTest;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.base.test.BaseJUnit4ClassRunner;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.base.test.util.MinAndroidSdkLevel;
import org.chromium.base.test.util.UrlUtils;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import java.io.File;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* Tests for the DecoderServiceHost.
*/
@RunWith(BaseJUnit4ClassRunner.class)
@MinAndroidSdkLevel(Build.VERSION_CODES.N)
public class DecoderServiceHostTest implements DecoderServiceHost.DecoderStatusCallback,
DecoderServiceHost.ImagesDecodedCallback {
// The timeout (in milliseconds) to wait for the decoding.
private static final int WAIT_TIMEOUT_MS = 7500;
// The base test file path.
private static final String TEST_FILE_PATH = "chrome/test/data/android/photo_picker/";
private Context mContext;
// A callback that fires when the decoder is ready.
public final CallbackHelper mOnDecoderReadyCallback = new CallbackHelper();
// A callback that fires when something is finished decoding in the dialog.
public final CallbackHelper mOnDecodedCallback = new CallbackHelper();
private String mLastDecodedPath;
private boolean mLastIsVideo;
private Bitmap mLastInitialFrame;
private int mLastFrameCount;
private String mLastVideoDuration;
private float mLastRatio;
@Before
public void setUp() throws Exception {
mContext = InstrumentationRegistry.getTargetContext();
TestThreadUtils.runOnUiThreadBlocking(() -> {
DecoderServiceHost.setIntentSupplier(
() -> { return new Intent(mContext, TestImageDecoderService.class); });
});
DecoderServiceHost.setStatusCallback(this);
}
// DecoderServiceHost.DecoderStatusCallback:
@Override
public void serviceReady() {
mOnDecoderReadyCallback.notifyCalled();
}
@Override
public void decoderIdle() {}
// DecoderServiceHost.ImagesDecodedCallback:
@Override
public void imagesDecodedCallback(String filePath, boolean isVideo, boolean isZoomedIn,
List<Bitmap> bitmaps, String videoDuration, float ratio) {
mLastDecodedPath = filePath;
mLastIsVideo = isVideo;
mLastFrameCount = bitmaps != null ? bitmaps.size() : -1;
mLastInitialFrame = bitmaps != null ? bitmaps.get(0) : null;
mLastVideoDuration = videoDuration;
mLastRatio = ratio;
mOnDecodedCallback.notifyCalled();
}
private void waitForDecoder() throws Exception {
int callCount = mOnDecoderReadyCallback.getCallCount();
mOnDecoderReadyCallback.waitForCallback(
callCount, 1, WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
}
private void waitForThumbnailDecode() throws Exception {
int callCount = mOnDecodedCallback.getCallCount();
mOnDecodedCallback.waitForCallback(callCount, 1, WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
}
private void decodeImage(DecoderServiceHost host, Uri uri, @PickerBitmap.TileTypes int fileType,
int width, boolean fullWidth, DecoderServiceHost.ImagesDecodedCallback callback) {
TestThreadUtils.runOnUiThreadBlocking(
() -> host.decodeImage(uri, fileType, width, fullWidth, callback));
}
private void cancelDecodeImage(DecoderServiceHost host, String filePath) {
TestThreadUtils.runOnUiThreadBlocking(() -> host.cancelDecodeImage(filePath));
}
@Test
@SmallTest
public void testRequestComparator() throws Throwable {
Uri uri = Uri.parse("http://example.com");
int width = 100;
boolean fullWidth = true;
DecoderServiceHost.ImagesDecodedCallback callback = null;
DecoderServiceHost.DecoderServiceParams higherPri;
DecoderServiceHost.DecoderServiceParams lowerPri;
// Still image decoding has higher priority than first frame video decoding.
higherPri = new DecoderServiceHost.DecoderServiceParams(uri, width, fullWidth,
PickerBitmap.TileTypes.PICTURE,
/* firstFrame= */ true, callback);
lowerPri = new DecoderServiceHost.DecoderServiceParams(uri, width, fullWidth,
PickerBitmap.TileTypes.VIDEO,
/* firstFrame= */ true, callback);
DecoderServiceHost host = new DecoderServiceHost(this, mContext);
Assert.assertTrue("Still images have priority over requests for initial video frame",
host.mRequestComparator.compare(higherPri, lowerPri) < 0);
// Still image decoding has higher priority than decoding remaining video frames.
higherPri = new DecoderServiceHost.DecoderServiceParams(uri, width, fullWidth,
PickerBitmap.TileTypes.PICTURE,
/* firstFrame= */ true, callback);
lowerPri = new DecoderServiceHost.DecoderServiceParams(uri, width, fullWidth,
PickerBitmap.TileTypes.VIDEO,
/* firstFrame= */ false, callback);
Assert.assertTrue("Still images have priority over requests for remaining video frames",
host.mRequestComparator.compare(higherPri, lowerPri) < 0);
// First frame video request have priority over remaining video frames.
higherPri = new DecoderServiceHost.DecoderServiceParams(uri, width, fullWidth,
PickerBitmap.TileTypes.VIDEO,
/* firstFrame= */ true, callback);
lowerPri = new DecoderServiceHost.DecoderServiceParams(uri, width, fullWidth,
PickerBitmap.TileTypes.VIDEO,
/* firstFrame= */ false, callback);
Assert.assertTrue("Initial video frames have priority over remaining video frames",
host.mRequestComparator.compare(higherPri, lowerPri) < 0);
// Enforce FIFO principle for two identical still image requests.
higherPri = new DecoderServiceHost.DecoderServiceParams(uri, width, fullWidth,
PickerBitmap.TileTypes.PICTURE,
/* firstFrame= */ true, callback);
lowerPri = new DecoderServiceHost.DecoderServiceParams(uri, width, fullWidth,
PickerBitmap.TileTypes.PICTURE,
/* firstFrame= */ true, callback);
Assert.assertTrue("Identical still image requests should be processed FIFO",
host.mRequestComparator.compare(higherPri, lowerPri) < 0);
// Enforce FIFO principle for two identical video requests (initial frames).
higherPri = new DecoderServiceHost.DecoderServiceParams(uri, width, fullWidth,
PickerBitmap.TileTypes.VIDEO,
/* firstFrame= */ true, callback);
lowerPri = new DecoderServiceHost.DecoderServiceParams(uri, width, fullWidth,
PickerBitmap.TileTypes.VIDEO,
/* firstFrame= */ true, callback);
Assert.assertTrue("Identical video requests (initial frames) should be processed FIFO",
host.mRequestComparator.compare(higherPri, lowerPri) < 0);
// Enforce FIFO principle for two identical video requests (remaining frames).
higherPri = new DecoderServiceHost.DecoderServiceParams(uri, width, fullWidth,
PickerBitmap.TileTypes.VIDEO,
/* firstFrame= */ false, callback);
lowerPri = new DecoderServiceHost.DecoderServiceParams(uri, width, fullWidth,
PickerBitmap.TileTypes.VIDEO,
/* firstFrame= */ false, callback);
Assert.assertTrue("Identical video requests (remanining frames) should be processed FIFO",
host.mRequestComparator.compare(higherPri, lowerPri) < 0);
}
@Test
@LargeTest
public void testDecodingOrder() throws Throwable {
DecoderServiceHost host = new DecoderServiceHost(this, mContext);
host.bind(mContext);
waitForDecoder();
String video1 = "noogler.mp4";
String video2 = "noogler2.mp4";
String jpg1 = "blue100x100.jpg";
File file1 = new File(UrlUtils.getIsolatedTestFilePath(TEST_FILE_PATH + video1));
File file2 = new File(UrlUtils.getIsolatedTestFilePath(TEST_FILE_PATH + video2));
File file3 = new File(UrlUtils.getIsolatedTestFilePath(TEST_FILE_PATH + jpg1));
decodeImage(host, Uri.fromFile(file1), PickerBitmap.TileTypes.VIDEO, 10,
/*fullWidth=*/false, this);
decodeImage(host, Uri.fromFile(file2), PickerBitmap.TileTypes.VIDEO, 10,
/*fullWidth=*/false, this);
decodeImage(host, Uri.fromFile(file3), PickerBitmap.TileTypes.PICTURE, 10,
/*fullWidth=*/false, this);
// First decoding result should be first frame of video 1. Even though still images take
// priority over video decoding, video 1 will be the only item in the queue when the first
// decoding request is kicked off (as a result of calling decodeImage).
waitForThumbnailDecode();
Assert.assertTrue(mLastDecodedPath.contains(video1));
Assert.assertEquals(true, mLastIsVideo);
Assert.assertEquals("0:00", mLastVideoDuration);
Assert.assertEquals(1, mLastFrameCount);
// When the decoder is finished with the first frame of video 1, there will be two new
// requests available for processing. Video2 was added first, but that will be skipped in
// favor of the still image, so the jpg is expected to be decoded next.
waitForThumbnailDecode();
Assert.assertTrue(mLastDecodedPath.contains(jpg1));
Assert.assertEquals(false, mLastIsVideo);
Assert.assertEquals(null, mLastVideoDuration);
Assert.assertEquals(1, mLastFrameCount);
// Third decoding result is first frame of video 2, because that's higher priority than the
// rest of video 1.
waitForThumbnailDecode();
Assert.assertTrue(mLastDecodedPath.contains(video2));
Assert.assertEquals(true, mLastIsVideo);
Assert.assertEquals("0:00", mLastVideoDuration);
Assert.assertEquals(1, mLastFrameCount);
// Remaining frames of video 1.
waitForThumbnailDecode();
Assert.assertTrue(mLastDecodedPath.contains(video1));
Assert.assertEquals(true, mLastIsVideo);
Assert.assertEquals("0:00", mLastVideoDuration);
Assert.assertEquals(10, mLastFrameCount);
// Remaining frames of video 2.
waitForThumbnailDecode();
Assert.assertTrue(mLastDecodedPath.contains(video2));
Assert.assertEquals(true, mLastIsVideo);
Assert.assertEquals("0:00", mLastVideoDuration);
Assert.assertEquals(10, mLastFrameCount);
host.unbind(mContext);
}
@Test
@LargeTest
public void testDecodingSizes() throws Throwable {
DecoderServiceHost host = new DecoderServiceHost(this, mContext);
host.bind(mContext);
waitForDecoder();
String video1 = "noogler.mp4"; // 1920 x 1080 video.
String jpg1 = "blue100x100.jpg";
File file1 = new File(UrlUtils.getIsolatedTestFilePath(TEST_FILE_PATH + video1));
File file2 = new File(UrlUtils.getIsolatedTestFilePath(TEST_FILE_PATH + jpg1));
// Thumbnail photo. 100 x 100 -> 10 x 10.
decodeImage(host, Uri.fromFile(file2), PickerBitmap.TileTypes.PICTURE, 10,
/*fullWidth=*/false, this);
waitForThumbnailDecode();
Assert.assertTrue(mLastDecodedPath.contains(jpg1));
Assert.assertEquals(false, mLastIsVideo);
Assert.assertEquals(null, mLastVideoDuration);
Assert.assertEquals(1, mLastFrameCount);
Assert.assertEquals(1.0f, mLastRatio, 0.1f);
Assert.assertEquals(10, mLastInitialFrame.getWidth());
Assert.assertEquals(10, mLastInitialFrame.getHeight());
// Full-width photo. 100 x 100 -> 200 x 200.
decodeImage(host, Uri.fromFile(file2), PickerBitmap.TileTypes.PICTURE, 200,
/*fullWidth=*/true, this);
waitForThumbnailDecode();
Assert.assertTrue(mLastDecodedPath.contains(jpg1));
Assert.assertEquals(false, mLastIsVideo);
Assert.assertEquals(null, mLastVideoDuration);
Assert.assertEquals(1, mLastFrameCount);
Assert.assertEquals(1.0f, mLastRatio, 0.1f);
Assert.assertEquals(200, mLastInitialFrame.getWidth());
Assert.assertEquals(200, mLastInitialFrame.getHeight());
// Thumbnail video. 1920 x 1080 -> 10 x 10.
decodeImage(host, Uri.fromFile(file1), PickerBitmap.TileTypes.VIDEO, 10,
/*fullWidth=*/false, this);
waitForThumbnailDecode(); // Initial frame.
Assert.assertTrue(mLastDecodedPath.contains(video1));
Assert.assertEquals(true, mLastIsVideo);
Assert.assertEquals("0:00", mLastVideoDuration);
Assert.assertEquals(1, mLastFrameCount);
Assert.assertEquals(0.5625f, mLastRatio, 0.0001f);
Assert.assertEquals(10, mLastInitialFrame.getWidth());
Assert.assertEquals(10, mLastInitialFrame.getHeight());
waitForThumbnailDecode(); // Rest of frames.
Assert.assertTrue(mLastDecodedPath.contains(video1));
Assert.assertEquals(true, mLastIsVideo);
Assert.assertEquals("0:00", mLastVideoDuration);
Assert.assertEquals(10, mLastFrameCount);
Assert.assertEquals(0.5625f, mLastRatio, 0.0001f);
Assert.assertEquals(10, mLastInitialFrame.getWidth());
Assert.assertEquals(10, mLastInitialFrame.getHeight());
// Full-width video. 1920 x 1080 -> 2000 x 1125.
decodeImage(host, Uri.fromFile(file1), PickerBitmap.TileTypes.VIDEO, 2000,
/*fullWidth=*/true, this);
waitForThumbnailDecode(); // Initial frame.
Assert.assertTrue(mLastDecodedPath.contains(video1));
Assert.assertEquals(true, mLastIsVideo);
Assert.assertEquals("0:00", mLastVideoDuration);
Assert.assertEquals(1, mLastFrameCount);
Assert.assertEquals(0.5625f, mLastRatio, 0.0001f);
Assert.assertEquals(2000, mLastInitialFrame.getWidth());
Assert.assertEquals(1125, mLastInitialFrame.getHeight());
waitForThumbnailDecode(); // Rest of frames.
Assert.assertTrue(mLastDecodedPath.contains(video1));
Assert.assertEquals(true, mLastIsVideo);
Assert.assertEquals("0:00", mLastVideoDuration);
Assert.assertEquals(10, mLastFrameCount);
Assert.assertEquals(0.5625f, mLastRatio, 0.0001f);
Assert.assertEquals(2000, mLastInitialFrame.getWidth());
Assert.assertEquals(1125, mLastInitialFrame.getHeight());
host.unbind(mContext);
}
@Test
@LargeTest
public void testCancelation() throws Throwable {
DecoderServiceHost host = new DecoderServiceHost(this, mContext);
host.bind(mContext);
waitForDecoder();
String green = "green100x100.jpg";
String yellow = "yellow100x100.jpg";
String red = "red100x100.jpg";
String greenPath = UrlUtils.getIsolatedTestFilePath(TEST_FILE_PATH + green);
String yellowPath = UrlUtils.getIsolatedTestFilePath(TEST_FILE_PATH + yellow);
String redPath = UrlUtils.getIsolatedTestFilePath(TEST_FILE_PATH + red);
decodeImage(host, Uri.fromFile(new File(greenPath)), PickerBitmap.TileTypes.PICTURE, 10,
/*fullWidth=*/false, this);
decodeImage(host, Uri.fromFile(new File(yellowPath)), PickerBitmap.TileTypes.PICTURE, 10,
/*fullWidth=*/false, this);
// Now add and subsequently remove the request.
decodeImage(host, Uri.fromFile(new File(redPath)), PickerBitmap.TileTypes.PICTURE, 10,
/*fullWidth=*/false, this);
cancelDecodeImage(host, redPath);
// First decoding result should be the green image.
waitForThumbnailDecode();
Assert.assertEquals(greenPath, mLastDecodedPath);
// Next is the yellow image, and asserts in DecoderServiceHost (designed to catch when
// multiple simultaneous decoding requests are started) should not fire.
waitForThumbnailDecode();
Assert.assertEquals(yellowPath, mLastDecodedPath);
host.unbind(mContext);
}
@Test
@LargeTest
public void testNoConnectionFailureMode() throws Throwable {
DecoderServiceHost host = new DecoderServiceHost(this, mContext);
// Try decoding without a connection to the decoder.
String green = "green100x100.jpg";
String greenPath = UrlUtils.getIsolatedTestFilePath(TEST_FILE_PATH + green);
decodeImage(host, Uri.fromFile(new File(greenPath)), PickerBitmap.TileTypes.PICTURE, 10,
/*fullWidth=*/false, this);
Assert.assertEquals(greenPath, mLastDecodedPath);
Assert.assertEquals(null, mLastInitialFrame);
}
@Test
@LargeTest
public void testFileNotFoundFailureMode() throws Throwable {
DecoderServiceHost host = new DecoderServiceHost(this, mContext);
host.bind(mContext);
waitForDecoder();
// Try decoding a file that doesn't exist.
String noPath = "/nonexistentpath/nonexistentfile";
decodeImage(host, Uri.fromFile(new File(noPath)), PickerBitmap.TileTypes.PICTURE, 10,
/*fullWidth=*/false, this);
Assert.assertEquals(noPath, mLastDecodedPath);
Assert.assertEquals(null, mLastInitialFrame);
host.unbind(mContext);
}
}