/*
 * Copyright 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.camera.camera2.internal.compat;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;

import android.Manifest;
import android.content.Context;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Size;
import android.view.Surface;

import androidx.annotation.NonNull;
import androidx.camera.camera2.internal.compat.params.OutputConfigurationCompat;
import androidx.camera.camera2.internal.compat.params.SessionConfigurationCompat;
import androidx.core.os.HandlerCompat;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.rule.GrantPermissionRule;

import org.junit.After;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.Collections;
import java.util.concurrent.Semaphore;


/**
 * Tests some of the methods of {@link CameraDeviceCompat} on device.
 *
 * <p>These need to run on device since they rely on native implementation details of the
 * {@link CameraDevice} class on some API levels.
 */
@MediumTest
@RunWith(AndroidJUnit4.class)
public final class CameraDeviceCompatDeviceTest {

    private final Semaphore mOpenCloseSemaphore = new Semaphore(0);
    @Rule
    public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(
            Manifest.permission.CAMERA);
    private CameraDevice mCameraDevice;
    private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(@NonNull CameraDevice camera) {
            mCameraDevice = camera;
            mOpenCloseSemaphore.release();
        }

        @Override
        public void onDisconnected(@NonNull CameraDevice camera) {
            mCameraDevice = camera;
            mOpenCloseSemaphore.release();
        }

        @Override
        public void onError(@NonNull CameraDevice camera, int error) {
            mCameraDevice = camera;
            mOpenCloseSemaphore.release();
        }

        @Override
        public void onClosed(@NonNull CameraDevice camera) {
            mCameraDevice = null;
            mOpenCloseSemaphore.release();
        }
    };
    private SurfaceTexture mSurfaceTexture;
    private Surface mSurface;
    private HandlerThread mCompatHandlerThread;
    private Handler mCompatHandler;

    @Before
    public void setUp() throws CameraAccessException, InterruptedException {
        CameraManager cameraManager =
                (CameraManager) ApplicationProvider.getApplicationContext().getSystemService(
                        Context.CAMERA_SERVICE);

        String[] cameraIds = cameraManager.getCameraIdList();
        Assume.assumeTrue("No cameras found on device.", cameraIds.length > 0);
        String cameraId = cameraIds[0];

        mCompatHandlerThread = new HandlerThread("DispatchThread");
        mCompatHandlerThread.start();
        mCompatHandler = HandlerCompat.createAsync(mCompatHandlerThread.getLooper());

        cameraManager.openCamera(cameraId, mStateCallback, mCompatHandler);
        mOpenCloseSemaphore.acquire();

        if (mCameraDevice == null) {
            throw new AssertionError("Unable to open camera.");
        }

        StreamConfigurationMap streamConfigurationMap = cameraManager.getCameraCharacteristics(
                cameraId).get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
        Size[] validSizes = streamConfigurationMap.getOutputSizes(SurfaceTexture.class);

        Assume.assumeTrue("No valid sizes available for SurfaceTexture.", validSizes.length > 0);

        mSurfaceTexture = new SurfaceTexture(0);
        mSurfaceTexture.setDefaultBufferSize(validSizes[0].getWidth(), validSizes[0].getHeight());

        mSurface = new Surface(mSurfaceTexture);
    }

    @After
    public void tearDown() throws InterruptedException {
        if (mCameraDevice != null) {
            mCameraDevice.close();
            mOpenCloseSemaphore.acquire();
        }

        if (mSurface != null) {
            mSurface.release();
        }

        if (mSurfaceTexture != null) {
            mSurfaceTexture.release();
        }

        mCompatHandlerThread.quitSafely();
    }

    @Test
    public void canConfigureCaptureSession() throws CameraAccessException {
        OutputConfigurationCompat outputConfig = new OutputConfigurationCompat(mSurface);

        CameraCaptureSession.StateCallback stateCallback =
                mock(CameraCaptureSession.StateCallback.class);

        SessionConfigurationCompat sessionConfig = new SessionConfigurationCompat(
                SessionConfigurationCompat.SESSION_REGULAR,
                Collections.singletonList(outputConfig), AsyncTask.THREAD_POOL_EXECUTOR,
                stateCallback);

        CameraDeviceCompat deviceCompat = CameraDeviceCompat.toCameraDeviceCompat(mCameraDevice,
                mCompatHandler);
        try {
            deviceCompat.createCaptureSession(sessionConfig);
        } catch (CameraAccessException e) {
            // If the camera has been disconnected during the test (likely due to another process
            // stealing the camera), then we will skip the test.
            Assume.assumeTrue("Camera disconnected during test.",
                    e.getReason() != CameraAccessException.CAMERA_DISCONNECTED);

            // This is not an error we expect should reasonably happen. Rethrow the exception.
            throw e;
        }

        verify(stateCallback, timeout(3000)).onConfigured(any(CameraCaptureSession.class));
    }

}

@@ -5362,32 +5362,80 @@
 e();%0A        %7D%0A%0A
+        if (mCompatHandlerThread != null) %7B%0A    
         mCompatH
@@ -5457,24 +5457,34 @@
 itSafely();%0A
+        %7D%0A
     %7D%0A%0A    @

Fix failing postsubmit test: androidx.camera.camera2.internal.compat.CameraDeviceCompatDeviceTest