@ActivityObject
@Permissions(values={"android.permission.CAMERA"})
@ShortName("Camera")
@Version(2.01f)
@Events(values={"Ready (Success As Boolean)", "PictureTaken (Data() As Byte)",
"Preview (Data() As Byte)"})
public class CameraW {
@Hide
public Camera camera;
private static ConcurrentHashMap<Integer, Camera> closingCameras = new ConcurrentHashMap<Integer, Camera>();
private SurfaceView sv;
private String eventName;
private BA ba;
private int currentId;
private AtomicInteger readyCount = new AtomicInteger();
/**
* Initializes the back camera.
*Panel - The preview images will be displayed on the panel.
*EventName - Events subs prefix.
*The Ready event will be raised when the camera has finished opening.
*/
public void Initialize(final BA ba, ViewGroup Panel, String EventName) {
shared(ba, Panel, EventName, -1);
}
/**
* Same as Initialize. CameraId is the id of the hardware camera.
*<b>This method is only available from Android 2.3+.</b>
*/
public void Initialize2(final BA ba, ViewGroup Panel, String EventName, int CameraId) {
shared(ba, Panel, EventName, CameraId);
}
private void shared(final BA ba, ViewGroup Panel, String EventName, final int CameraId) {
this.ba = ba;
currentId = CameraId;
readyCount.set(0);
this.eventName = EventName.toLowerCase(BA.cul);
sv = new SurfaceView(ba.context);
anywheresoftware.b4a.BALayout.LayoutParams lp = new anywheresoftware.b4a.BALayout.LayoutParams(0, 0,
Panel.getLayoutParams().width, Panel.getLayoutParams().height);
Panel.addView(sv, lp);
camera = closingCameras.get(CameraId);
closingCameras.remove(CameraId);
if (camera == null) {
//release other cached cameras (relevant when switching cameras)
synchronized (closingCameras) { //make sure that release is only called once.
for (Camera c : closingCameras.values()) {
c.release();
}
closingCameras.clear();
}
}
sv.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
sv.getHolder().setFixedSize(Panel.getLayoutParams().width, Panel.getLayoutParams().height);
sv.getHolder().addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceChanged(SurfaceHolder holder, int format,
int width, int height) {
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
if (readyCount.addAndGet(1) == 2) {
ba.raiseEvent(null, eventName + "_ready", true);
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
});
ba.submitRunnable(new Runnable() {
@Override
public void run() {
try {
if (camera == null) {
if (CameraId == -1)
camera = Camera.open();
else {
Method m = Camera.class.getMethod("open", int.class);
camera = (Camera)m.invoke(null, CameraId);
}
}
if (readyCount.addAndGet(1) == 2) {
ba.raiseEventFromDifferentThread(null, CameraW.this, -1,eventName + "_ready", false, new Object[] {true});
}
if (ba.subExists(eventName + "_preview")) {
camera.setPreviewCallback(new Camera.PreviewCallback() {
@Override
public void onPreviewFrame(byte[] data,
Camera camera) {
ba.raiseEvent(null, eventName + "_preview", data);
}
});
}
}
catch (Exception e) {
e.printStackTrace();
Release();
ba.raiseEventFromDifferentThread(null, CameraW.this, -1,eventName + "_ready", false, new Object[] {false});
}
}
}, this, -1);
}
/**
* Starts displaying the preview images.
*/
public void StartPreview() throws IOException {
camera.setPreviewDisplay(sv.getHolder());
camera.startPreview();
}
/**
* Stops displaying the preview images.
*/
public void StopPreview() {
if (camera != null)
camera.stopPreview();
}
/**
* Releases the camera object and allows other processes to access the camera.
*/
public void Release() {
if (camera != null) {
camera.setPreviewCallback(null);
camera.stopPreview();
closingCameras.put(currentId, camera);
final Camera c = camera;
camera = null;
Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5000);
synchronized (closingCameras) {
if (closingCameras.remove(currentId) != null) {
c.release();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
t.start();
}
if (sv != null) {
ViewGroup vg = (ViewGroup) sv.getParent();
if (vg != null)
vg.removeView(sv);
sv = null;
}
}
/**
* Takes a picture. When the picture is ready, the PictureTaken event will be raised.
*You should not call TakePicture while another picture is currently taken.
*The preview images are stopped after calling this method. You can call StartPreview to restart the preview images.
*/
public void TakePicture() {
camera.takePicture(null , null, new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
ba.raiseEvent(null, eventName + "_picturetaken", data);
}
});
}
}