docs: @SuppressWarnings("MissingPermission")
  	protected void openCamera(int widthTexture, int heightTexture) {
  		if( verbose )
  			Log.i(TAG,"openCamera( texture: "+widthTexture+"x"+heightTexture+") activity="+getClass().getSimpleName());
  
  		if (Looper.getMainLooper().getThread() != Thread.currentThread()) {
  			throw new RuntimeException("Attempted to openCamera() when not in the main looper thread!");
  		}
  
  		if (isFinishing()) {
  			return;
  		}
  		CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
  		if( manager == null )
  			throw new RuntimeException("Null camera manager");
  
  		// The camera should be released here until a camera has been successfully initialized
  		boolean releaseLock = true;
  		try {
  			if (!open.mLock.tryLock(2500, TimeUnit.MILLISECONDS)) {
  				throw new RuntimeException("Time out waiting to lock camera opening.");
  			}
  
  			if( open.state == CameraState.CLOSING) {
  				if( verbose )
  					Log.d(TAG, "Close request was made after the open request. Aborting and closing. device="
  					+(open.mCameraDevice==null));
  				if( open.mCameraDevice != null ) {
  					open.closeCamera();
  				}
  				open.state = CameraState.CLOSED;
  				open.clearCamera();
  				return;
  			} else if( open.state == CameraState.CLOSED || open.state == CameraState.OPENING) {
  				// These are the two states it should be in. if it wasn't opening before it is now
  				open.state = CameraState.OPENING;
  			} else {
  				throw new RuntimeException("Unexpected state="+open.state);
  			}
  
  			if( mBackgroundHandler == null ) {
  				if( verbose )
  					Log.i(TAG,"Background handler is null. Aborting.");
  				return;
  			}
  
  			if( open.mCameraDevice != null ) {
  				throw new RuntimeException("Tried to open camera with one already open");
  			}
  
  			// Save the size of the component the camera feed is being displayed inside of
  			this.viewWidth = widthTexture;
  			this.viewHeight = heightTexture;
  			this.cameraToDisplayDensity = 0;
  			this.firstFrame = true;
  
  			String[] cameras = manager.getCameraIdList();
  			for( String cameraId : cameras ) {
  				CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
  				if(!selectCamera(cameraId,characteristics))
  					continue;
  
  				StreamConfigurationMap map = characteristics.
  						get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
  				if (map == null) {
  					continue;
  				}
  
  				Size[] sizes = map.getOutputSizes(ImageFormat.YUV_420_888);
  				int which = selectResolution(widthTexture, heightTexture,sizes);
  				if( which < 0 || which >= sizes.length )
  					continue;
  				open.mCameraSize = sizes[which];
  				open.cameraId = cameraId;
  				open.mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
  
  				this.cameraToDisplayDensity = displayDensityAdjusted();
  
  				if( verbose )
  					Log.i(TAG,"selected cameraId="+cameraId+" orientation="+open.mSensorOrientation+
  							" res="+open.mCameraSize.getWidth()+"x"+open.mCameraSize.getHeight());
  
  				open.mCameraCharacterstics = characteristics;
  				onCameraResolutionChange(
  						open.mCameraSize.getWidth(), open.mCameraSize.getHeight(),
  						open.mSensorOrientation);
  
  				open.mPreviewReader = ImageReader.newInstance(
  						open.mCameraSize.getWidth(), open.mCameraSize.getHeight(),
  						ImageFormat.YUV_420_888, 2);
  				// Do the processing inside the the handler thread instead of the looper thread to avoid
  				// grinding the UI to a halt
  				open.mPreviewReader.setOnImageAvailableListener(onAvailableListener, mBackgroundHandler);
  				configureTransform(widthTexture, heightTexture);
  				manager.openCamera(cameraId, mStateCallback, null);
  				releaseLock = false;
  				return;
  			}
  
  			if( handleNoCameraSelected() ) {
  				Toast.makeText(this, "No camera selected!", Toast.LENGTH_LONG).show();
  				finish();
  			}
  		} catch (CameraAccessException e) {
  			Toast.makeText(this, "Cannot access the camera.", Toast.LENGTH_SHORT).show();
  			if( handleCameraOpenException(e)) {
  				finish();
  			}
  		} catch (NullPointerException e) {
  			e.printStackTrace();
  			Log.e(TAG,"Null pointer in openCamera()");
  			if( handleCameraOpenException(e)) {
  				// Currently an NPE is thrown when the Camera2API is used but not supported on the
  				// device this code runs.
  				Toast.makeText(this, "Null pointer. Camera2 API not supported?", Toast.LENGTH_LONG).show();
  				finish();
  			}
  		} catch (InterruptedException e) {
  			throw new RuntimeException("Interrupted while trying to lock camera opening.");
  		} finally {
  			if( releaseLock )
  				open.mLock.unlock();
  		}
  	}