3. import com.wonderkiln.camerakit.*;
import com.codename1.impl.android.AndroidNativeUtil;
import com.codename1.impl.android.AndroidImplementation;
import java.io.File;
CameraNativeAccessImpl
The Android port is trivial. Once we have the native interface we can right click the project and choose "Generate Native Stubs" which produces the skeleton code for
this implementation class.
Unlike before I'll start with the imports as some of them are a bit challenging.
I just imported everything as it was simpler
6. public class CameraNativeAccessImpl {
private CameraView view;
private CameraKitEventListener listener =
new CameraKitEventListener() {
public void onEvent(CameraKitEvent event) {}
public void onError(CameraKitError error) {
String errorMessage = "";
if(error.getException() != null) {
errorMessage = error.getException().toString();
com.codename1.io.Log.e(error.getException());
com.codename1.io.Log.sendLogAsync();
}
CameraCallbacks.onError(error.getType(),
error.getMessage(), errorMessage);
}
public void onImage(CameraKitImage image) {
CameraCallbacks.onImage(image.getJpeg());
}
public void onVideo(CameraKitVideo video) {
CameraCallbacks.onVideo("file://" +
video.getVideoFile().getAbsolutePath());
}
};
CameraNativeAccessImpl
Lets get into the code itself.
The view is the camera preview UI, I keep it as a member to return later.
7. public class CameraNativeAccessImpl {
private CameraView view;
private CameraKitEventListener listener =
new CameraKitEventListener() {
public void onEvent(CameraKitEvent event) {}
public void onError(CameraKitError error) {
String errorMessage = "";
if(error.getException() != null) {
errorMessage = error.getException().toString();
com.codename1.io.Log.e(error.getException());
com.codename1.io.Log.sendLogAsync();
}
CameraCallbacks.onError(error.getType(),
error.getMessage(), errorMessage);
}
public void onImage(CameraKitImage image) {
CameraCallbacks.onImage(image.getJpeg());
}
public void onVideo(CameraKitVideo video) {
CameraCallbacks.onVideo("file://" +
video.getVideoFile().getAbsolutePath());
}
};
CameraNativeAccessImpl
The listener is bound later in the code, it just delegates all the calls into the callback. Notice I have only one listener on the native side.
All calls are effectively the same they just invoke the `CameraCallbacks` class methods
8. public class CameraNativeAccessImpl {
private CameraView view;
private CameraKitEventListener listener =
new CameraKitEventListener() {
public void onEvent(CameraKitEvent event) {}
public void onError(CameraKitError error) {
String errorMessage = "";
if(error.getException() != null) {
errorMessage = error.getException().toString();
com.codename1.io.Log.e(error.getException());
com.codename1.io.Log.sendLogAsync();
}
CameraCallbacks.onError(error.getType(),
error.getMessage(), errorMessage);
}
public void onImage(CameraKitImage image) {
CameraCallbacks.onImage(image.getJpeg());
}
public void onVideo(CameraKitVideo video) {
CameraCallbacks.onVideo("file://" +
video.getVideoFile().getAbsolutePath());
}
};
CameraNativeAccessImpl
Android native file paths should be prepended with file:// to work in Codename One's FileSystemStorage.
9. public void start() {
AndroidImplementation.runOnUiThreadAndBlock(new Runnable() {
public void run() {
if(view == null) {
view = new CameraView(AndroidNativeUtil.getContext());
view.addCameraKitListener(listener);
}
view.start();
}
});
}
public void stop() {
AndroidNativeUtil.getActivity().runOnUiThread(new Runnable() {
public void run() {
view.stop();
}
});
}
public boolean isStarted() {
if(view == null) {
return false;
}
return view.isStarted();
}
CameraNativeAccessImpl
The next step is lifecycle and view methods. After the start method is invoked I'd like the view object to be valid otherwise we might have a problem. This code MUST run
on Androids native EDT hence the need for runOnUiThreadAndBlock()
10. public void start() {
AndroidImplementation.runOnUiThreadAndBlock(new Runnable() {
public void run() {
if(view == null) {
view = new CameraView(AndroidNativeUtil.getContext());
view.addCameraKitListener(listener);
}
view.start();
}
});
}
public void stop() {
AndroidNativeUtil.getActivity().runOnUiThread(new Runnable() {
public void run() {
view.stop();
}
});
}
public boolean isStarted() {
if(view == null) {
return false;
}
return view.isStarted();
}
CameraNativeAccessImpl
We initialize the view, bind the listener and start(). Notice we can start & stop() more than once
11. public void start() {
AndroidImplementation.runOnUiThreadAndBlock(new Runnable() {
public void run() {
if(view == null) {
view = new CameraView(AndroidNativeUtil.getContext());
view.addCameraKitListener(listener);
}
view.start();
}
});
}
public void stop() {
AndroidNativeUtil.getActivity().runOnUiThread(new Runnable() {
public void run() {
view.stop();
}
});
}
public boolean isStarted() {
if(view == null) {
return false;
}
return view.isStarted();
}
CameraNativeAccessImpl
We don't need to "wait" for stop() to complete but it still needs to run on Android's dispatch thread
12. public void run() {
view.stop();
}
});
}
public boolean isStarted() {
if(view == null) {
return false;
}
return view.isStarted();
}
public void setMethod(final int param) {
AndroidNativeUtil.getActivity().runOnUiThread(new Runnable() {
public void run() {
view.setMethod(param);
}
});
}
public android.view.View getView() {
return view;
}
public boolean isSupported() {
return true;
}
CameraNativeAccessImpl
This method returns a PeerComponent in the Java side. The Android view is translated automatically
13. public void run() {
view.stop();
}
});
}
public boolean isStarted() {
if(view == null) {
return false;
}
return view.isStarted();
}
public void setMethod(final int param) {
AndroidNativeUtil.getActivity().runOnUiThread(new Runnable() {
public void run() {
view.setMethod(param);
}
});
}
public android.view.View getView() {
return view;
}
public boolean isSupported() {
return true;
}
CameraNativeAccessImpl
The isSupported method is a standard method in native interfaces which defaults to false. When you implement a version in a platform set it to true
14. public void setPermissions(final int param) {
AndroidNativeUtil.getActivity().runOnUiThread(new Runnable() {
public void run() {
view.setPermissions(param);
}
});
}
public void setZoom(final float param) {
AndroidNativeUtil.getActivity().runOnUiThread(new Runnable() {
public void run() {
view.setZoom(param);
}
});
}
public void setFocus(final int param) {
// ... same runOnUiThread code
}
public void setFlash(final int param) {
// ... same runOnUiThread code
}
public void setFacing(final int param) {
// ... same runOnUiThread code
}
CameraNativeAccessImpl
Next lets do a quick recap of the setter methods in the native interface. We can't use a Lambda yet because we are still using Java 5 for Android native code. This might
change by the time you see this!
We hope to upgrade Android native code for newer builds to use Java 8 syntax which will shorten the boilerplate here significantly.
15. public void setPermissions(final int param) {
AndroidNativeUtil.getActivity().runOnUiThread(new Runnable() {
public void run() {
view.setPermissions(param);
}
});
}
public void setZoom(final float param) {
AndroidNativeUtil.getActivity().runOnUiThread(new Runnable() {
public void run() {
view.setZoom(param);
}
});
}
public void setFocus(final int param) {
// ... same runOnUiThread code
}
public void setFlash(final int param) {
// ... same runOnUiThread code
}
public void setFacing(final int param) {
// ... same runOnUiThread code
}
CameraNativeAccessImpl
All of the methods here work roughly in the same way by delegating to the native call via the runOnUiThread call.
I could go for a couple of additional pages of setters but you get the drift, it’s more of the same…
16. public int getFacing() {
return view.getFacing();
}
public boolean isFacingFront() {
return view.isFacingFront();
}
public boolean isFacingBack() {
return view.isFacingBack();
}
public float getVerticalViewingAngle() {
return view.getCameraProperties().verticalViewingAngle;
}
public float getHorizontalViewingAngle() {
return view.getCameraProperties().horizontalViewingAngle;
}
public int getFlash() {
return view.getFlash();
}
public int getPreviewWidth() {
return view.getPreviewSize().getWidth();
}
public int getPreviewHeight() {
return view.getPreviewSize().getHeight();
CameraNativeAccessImpl
Next we can review the getters which are shorter. Notice we don't need to use runOnUiThread as the getters aren't as sensitive to threading
17. public int getFacing() {
return view.getFacing();
}
public boolean isFacingFront() {
return view.isFacingFront();
}
public boolean isFacingBack() {
return view.isFacingBack();
}
public float getVerticalViewingAngle() {
return view.getCameraProperties().verticalViewingAngle;
}
public float getHorizontalViewingAngle() {
return view.getCameraProperties().horizontalViewingAngle;
}
public int getFlash() {
return view.getFlash();
}
public int getPreviewWidth() {
return view.getPreviewSize().getWidth();
}
public int getPreviewHeight() {
return view.getPreviewSize().getHeight();
CameraNativeAccessImpl
As you may recall some of the methods here map to properties of the returned objects as is the case here and with the preview/capture methods below.
Like the setters this goes on a bit further so I’ll spare you an additional slide where I don’t have anything interesting to say…
18. public void captureVideoFile(final String param) {
AndroidNativeUtil.getActivity().runOnUiThread(new Runnable() {
public void run() {
String f = param;
if (param.startsWith("file://")) {
f = param.substring(7);
}
view.captureVideo(new File(f));
}
});
}
public void captureImage() {
AndroidNativeUtil.getActivity().runOnUiThread(new Runnable() {
public void run() {
view.captureImage();
}
});
}
public void captureVideo() {
AndroidNativeUtil.getActivity().runOnUiThread(new Runnable() {
public void run() {
view.captureVideo();
}
});
}
public void stopVideo() {
AndroidNativeUtil.getActivity().runOnUiThread(new Runnable() {
CameraNativeAccessImpl
Finally we have the capture method calls. We need runOnUiThread as capture is thread sensitive
19. public void captureVideoFile(final String param) {
AndroidNativeUtil.getActivity().runOnUiThread(new Runnable() {
public void run() {
String f = param;
if (param.startsWith("file://")) {
f = param.substring(7);
}
view.captureVideo(new File(f));
}
});
}
public void captureImage() {
AndroidNativeUtil.getActivity().runOnUiThread(new Runnable() {
public void run() {
view.captureImage();
}
});
}
public void captureVideo() {
AndroidNativeUtil.getActivity().runOnUiThread(new Runnable() {
public void run() {
view.captureVideo();
}
});
}
public void stopVideo() {
AndroidNativeUtil.getActivity().runOnUiThread(new Runnable() {
CameraNativeAccessImpl
Files in Codename One have file URL prepended so they will act as URL's we remove this to translate the file into an Android file.
20. public void captureVideoFile(final String param) {
AndroidNativeUtil.getActivity().runOnUiThread(new Runnable() {
public void run() {
String f = param;
if (param.startsWith("file://")) {
f = param.substring(7);
}
view.captureVideo(new File(f));
}
});
}
public void captureImage() {
AndroidNativeUtil.getActivity().runOnUiThread(new Runnable() {
public void run() {
view.captureImage();
}
});
}
public void captureVideo() {
AndroidNativeUtil.getActivity().runOnUiThread(new Runnable() {
public void run() {
view.captureVideo();
}
});
}
public void stopVideo() {
AndroidNativeUtil.getActivity().runOnUiThread(new Runnable() {
CameraNativeAccessImpl
All the other methods are exactly the same. They just invoke the native Android version. This is it for the code but it won't work yet…