chromium/components/browser_ui/photo_picker/android/java/src/org/chromium/components/browser_ui/photo_picker/ImageDecoderTest.java

// Copyright 2019 The Chromium Authors
// 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.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;

import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;

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.CriteriaHelper;
import org.chromium.base.test.util.UrlUtils;

import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;

/** Tests for ImageDecoder and the aidl interfaces used for out-of-process decoding.. */
@RunWith(BaseJUnit4ClassRunner.class)
public class ImageDecoderTest {
    // By default, the test will wait for 3 seconds to create the decoder process, which (at least
    // in the emulators) brushes up against the actual time it takes to create the process, so these
    // tests are frequently flaky when run locally.
    private static final int DECODER_STARTUP_TIMEOUT_IN_MS = 7500;

    Context mContext;

    // Flag indicating whether we are bound to the service.
    private boolean mBound;

    private class DecoderServiceCallback extends IDecoderServiceCallback.Stub {
        // The returned bundle from the decoder.
        private Bundle mDecodedBundle;

        public boolean resolved() {
            return mDecodedBundle != null;
        }

        public Bundle getBundle() {
            return mDecodedBundle;
        }

        @Override
        public void onDecodeImageDone(final Bundle payload) {
            mDecodedBundle = payload;
        }
    }

    IDecoderService mIRemoteService;
    private ServiceConnection mConnection =
            new ServiceConnection() {
                @Override
                public void onServiceConnected(ComponentName className, IBinder service) {
                    mIRemoteService = IDecoderService.Stub.asInterface(service);
                    mBound = true;
                }

                @Override
                public void onServiceDisconnected(ComponentName className) {
                    mIRemoteService = null;
                    mBound = false;
                }
            };

    @Before
    public void setUp() throws Exception {
        mContext = InstrumentationRegistry.getTargetContext();
    }

    private void startDecoderService() {
        Intent intent = new Intent(mContext, TestImageDecoderService.class);
        intent.setAction(IDecoderService.class.getName());
        mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);

        CriteriaHelper.pollUiThread(
                () -> mBound,
                DECODER_STARTUP_TIMEOUT_IN_MS,
                CriteriaHelper.DEFAULT_POLLING_INTERVAL);
    }

    private void decode(
            String filePath, FileDescriptor fd, int width, final DecoderServiceCallback callback)
            throws Exception {
        Bundle bundle = new Bundle();
        bundle.putString(ImageDecoder.KEY_FILE_PATH, filePath);
        ParcelFileDescriptor pfd = null;
        if (fd != null) {
            pfd = ParcelFileDescriptor.dup(fd);
            Assert.assertTrue(pfd != null);
        }
        bundle.putParcelable(ImageDecoder.KEY_FILE_DESCRIPTOR, pfd);
        bundle.putInt(ImageDecoder.KEY_WIDTH, width);

        mIRemoteService.decodeImage(bundle, callback);
        CriteriaHelper.pollUiThread(() -> callback.resolved());
    }

    @Test
    @LargeTest
    public void testServiceDecodeNullFileDescriptor() throws Throwable {
        startDecoderService();

        // Attempt to decode to a 50x50 thumbnail without a valid FileDescriptor (null).
        DecoderServiceCallback callback = new DecoderServiceCallback();
        decode("path", null, 50, callback);

        Bundle bundle = callback.getBundle();
        Assert.assertFalse("Expected decode to fail", bundle.getBoolean(ImageDecoder.KEY_SUCCESS));
        Assert.assertEquals("path", bundle.getString(ImageDecoder.KEY_FILE_PATH));
        Assert.assertEquals(null, bundle.getParcelable(ImageDecoder.KEY_IMAGE_BITMAP));
        Assert.assertEquals(0, bundle.getLong(ImageDecoder.KEY_DECODE_TIME));
    }

    @Test
    @LargeTest
    public void testServiceDecodeSimple() throws Exception {
        startDecoderService();

        File file =
                new File(
                        UrlUtils.getIsolatedTestFilePath(
                                "chrome/test/data/android/photo_picker/blue100x100.jpg"));
        FileInputStream inStream = new FileInputStream(file);

        // Attempt to decode a valid 100x100 image file to a 50x50 thumbnail.
        DecoderServiceCallback callback = new DecoderServiceCallback();
        decode(file.getPath(), inStream.getFD(), 50, callback);

        Bundle bundle = callback.getBundle();
        Assert.assertTrue(
                "Expecting success being returned", bundle.getBoolean(ImageDecoder.KEY_SUCCESS));
        Assert.assertEquals(file.getPath(), bundle.getString(ImageDecoder.KEY_FILE_PATH));
        Assert.assertFalse(
                "Decoding should take a non-zero amount of time",
                0 == bundle.getLong(ImageDecoder.KEY_DECODE_TIME));

        Bitmap decodedBitmap = bundle.getParcelable(ImageDecoder.KEY_IMAGE_BITMAP);
        Assert.assertFalse("Decoded bitmap should not be null", null == decodedBitmap);
        Assert.assertEquals(50, decodedBitmap.getWidth());
        Assert.assertEquals(50, decodedBitmap.getHeight());
    }
}