Search code examples
javaandroidandroid-camera2

Implementing a separate Class into Activity


The problem In cleaning up my code I want to move my Android camera methods to a separate class, in line with what I believe are best practices. After searching all day, I'm still struggling to figure out how to do this exactly. Main problem is that the differences in implementation methods and moving from camera API to camera2 API lead to solutions found online which I can't replicate. Please note that I am quite a beginner in Java and as such, it is probably a very rookie mistake which I can't solve due to the variety of info on the web.

Current code My main problem is that SurfaceTexture texture = surfaceView.getSurfaceTexture(); in startCamera() says Cannot resolve method 'getSurfaceTexture()' and that previewBuilder.addTarget(texture); complains about addTarget (android.view.surface) in Builder cannot be applied to (android.graphics.SurfaceTexture).

public class CameraView extends TextureView implements TextureView.SurfaceTextureListener{

    private Size previewsize;
    private CameraDevice cameraDevice;
    private CaptureRequest.Builder previewBuilder;
    private CameraCaptureSession previewSession;
    private final Context context;
    public SurfaceView surfaceView;
    public TextureView textureView;

    public CameraView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
    }

    public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) {
        // Once the surface is created, simply open a handle to the camera hardware.
        openCamera();
    }

    public void onSurfaceTextureUpdated(SurfaceTexture texture) {
    }

    public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int     width, int height) {

        try {
            //cameraDevice.setPreviewDisplay(holder);

            //cameraDevice.startPreview();
        } catch (Exception e){
            e.printStackTrace();
        }
    }

    public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) {
        // stopPreview();
        cameraDevice.close();
        return true;
    }

    public void openCamera()
    {
        CameraManager manager = (CameraManager) context.getSystemService (Context.CAMERA_SERVICE);
        try
        {
            String cameraId = manager.getCameraIdList()[0];
            CameraCharacteristics     characteristics=manager.getCameraCharacteristics(cameraId);
            StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
            previewsize = map.getOutputSizes(SurfaceTexture.class)[0];
            try {
                manager.openCamera(cameraId, stateCallback, null);
            } catch (SecurityException e){
                e.printStackTrace();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(CameraDevice camera) {
            cameraDevice = camera;
            startCamera();
        }
        @Override
        public void onClosed(CameraDevice camera) {
            // nothing
        }
        @Override
        public void onDisconnected(CameraDevice camera) {
        }
        @Override
        public void onError(CameraDevice camera, int error) {
        }
    };

    void startCamera()
    {
        if (cameraDevice == null || previewsize==null)
        {
            return;
        }

        SurfaceTexture texture = surfaceView.getSurfaceTexture();
texture.setDefaultBufferSize(previewsize.getWidth(),previewsize.getHeight());

        Surface surface = new Surface(texture);
        try
        {
            // add all the standard stuff to the previewBuilder
            previewBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
        } catch (Exception e) {}
        previewBuilder.addTarget(texture);
        try
        {
            cameraDevice.createCaptureSession(Arrays.asList(surface), new CameraCaptureSession.StateCallback() {
            @Override
            public void onConfigured(CameraCaptureSession session) {
                previewSession = session;
                getChangedPreview();
            }
            @Override
            public void onConfigureFailed(CameraCaptureSession session{
            }
        },null);
        } catch (Exception e) {}
    }

    void getChangedPreview()
    {
        previewBuilder.set(CaptureRequest.CONTROL_AE_MODE, CameraMetadata.CONTROL_AE_MODE_ON);

        HandlerThread thread = new HandlerThread("changed Preview");
        thread.start();
        Handler handler = new Handler(thread.getLooper());
        try
        {
            previewSession.setRepeatingRequest(previewBuilder.build(), null, handler);
        }catch (Exception e){}
    }
}

The goal To keep my code clean and understandable I would like to limit the MainActivity class to switching between views instead of having tons of methods in there. I would like to activate my camera view in my app by switching the following object from INVISIBLE to VISIBLE. Other suggestions are appreciated.

cameraView = (CameraView) findViewById(R.id.camera);

MainActivity.java would then look like:

public class MainActivity extends AppCompatActivity {

    private TextView mTextMessage;
    private CameraView cameraView;
    private MainSurfaceView mGLView;
    private TextureView textureView;

    private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
        = new BottomNavigationView.OnNavigationItemSelectedListener() {

        @Override
        public boolean onNavigationItemSelected(@NonNull MenuItem item) {
            switch (item.getItemId()) {
                case R.id.navigation_home:
                    mTextMessage.setText(R.string.title_home);
                    return true;
                case R.id.navigation_dashboard:
                    mTextMessage.setText(R.string.title_dashboard);
                    return true;
                case R.id.navigation_notifications:
                    mTextMessage.setText(R.string.title_notifications);
                    return true;
            }
            return false;
        }

    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextMessage = (TextView) findViewById(R.id.message);
        cameraView = (CameraView) findViewById(R.id.camera);
        mGLView = (MainSurfaceView) findViewById(R.id.glSurfaceView);
        BottomNavigationView navigation = (BottomNavigationView) findViewById(R.id.navigation);
        navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);

    }
}

Your help is appreciated!


Solution

  • SurfaceTexture texture = surfaceView.getSurfaceTexture(); in startCamera() says Cannot resolve method 'getSurfaceTexture()'

    You call the method getSurfaceTexture of surfaceView. surfaceView is a SurfaceView. Let's take a look at the documentation: https://developer.android.com/reference/android/view/SurfaceView.html Apparently SurfaceView has no method called "getSurfaceTexture()". However, searching on Google for "getSurfaceTexture() Android" shows us that the method belongs to the TextureView class. Your class CameraView has a field called "textureView", so (I don't know what you want to achieve exactly) can call the method on that field if you want. Additionally your class CameraView is a TextureView itself (do you want that), so you could also just call getSurfaceTexture() if you want to invoke it on the class itself.

    previewBuilder.addTarget(texture); complains about addTarget (android.view.surface) in Builder cannot be applied to (android.graphics.SurfaceTexture). Let's have a look at the documentation again: https://developer.android.com/reference/android/hardware/camera2/CaptureRequest.Builder.html Apparently CaptureRequest.builder (the type of previewBuilder) has a method called addTarget, but that method only accepts a Surface! You're passing a texture. You probably want to change texture to surface