[Estimated Reading Time: 8 minutes]

Not a Merchant Ivory production, but Part 3 in the Oxygene for Java camera app for Android series.

So far we have seen that we can work directly with the Android platform manifest and layout files and how the Oxygene language is a first class citizen in the Java platform and just one way in which it simplifies and improves the business of writing Java code, in Pascal.

Now it’s time to make the app do something useful.

First Things Third

A camera is not very useful without some way of seeing what it is you are taking a picture of (with some exceptions). We need some sort of preview or viewfinder.

In Part 1 we created a layout element – a FrameLayout – as a placeholder for a camera preview control, and now we need to create that control and place it in that UI element. We shall do that in code. But first we need to implement the preview control itself. To keep things organised I put this new control class in a separate file (ViewFinder.pas) but declared the contents as part of the same namespace as the rest of the app.

For what we have in mind I need to sub-class the Android SurfaceView class and implement the SurfaceHolder.Callback interface. Our control will need to maintain a reference to the camera which will be supplied when we construct our viewfinder class. A Camera.CameraInfo object will also be useful later on. Finally we will need a reference to an entity known as a SurfaceHolder.

Remember, this exercise has been a learning experience for me so I am no expert on the Android framework. But if I have understood things correctly, SurfaceView is to TCustomControl as SurfaceHolder is to the TCanvas. It’s not quite as direct a relationship as that, but for our purposes this will suffice. The SurfaceView – via the SurfaceHolder – provides the “canvas” on which we will render our camera preview. Our initial class declaration looks like this:


  namespace nz.co.deltics.demo.camera;

interface

  uses 
    android.content,
    android.graphics,
    android.hardware,
    android.util,
    android.view;

  type
    ViewFinder = public class(SurfaceView, SurfaceHolder.Callback)
    private
      mCamera: Camera;
      mCameraInfo: Camera.CameraInfo;
      mSurfaceHolder: SurfaceHolder;
    public
      constructor(aContext: Context; aCamera: Camera);

    private // SurfaceHolder.Callback
      method surfaceCreated(aHolder: SurfaceHolder);
      method surfaceDestroyed(aHolder: SurfaceHolder);
      method surfaceChanged(aHolder: SurfaceHolder; aFormat, aWidth, aHeight: Integer);
    end;


implementation

  // ...

I have added some Android namespaces to the uses list to avoid having to fully qualify references made to members of those namespaces. This isn’t strictly necessary, and you can of course continue to fully qualify any references if necessary (or desired).

For example, anywhere that Camera is referenced I could instead fully qualify the reference as android.hardware.Camera and then I would not need to “use” the android.hardware namespace.

This is obviously similar to the concept of using a unit in Delphi, except that with Delphi you must use a unit in order to reference it’s contents where the same is not the case in Oxygene. Oxygene is more similar to more modern languages such as Java and C# in this respect.

So now let us look at the implementation of the class itself.

First, the constructor.

Anonymous Constructors and Non-Existent Properties

Since an Oxygene class in Oxygene for Java is compiled to a Java class, it must conform to the rules of the Java language and accordingly our constructor has no name. Oxygene optionally allows us to redundantly use a name of Create for our constructors if we wish but this is optional (on a per project basis, according to a Compatibility setting in the compiler options) and even with this option enabled Create is the only name allowed.

I have opted to adopt the Java convention in this case and so no name is used.

The implementation of the constructor itself is very straightforward.

  constructor ViewFinder(aContext: Context; aCamera: Camera);
  begin
    inherited constructor(aContext);

    mCamera := aCamera;

    mCameraInfo := new Camera.CameraInfo;
    Camera.getCameraInfo(0, mCameraInfo);

    mSurfaceHolder := self.Holder;
    mSurfaceHolder.addCallback(self);
  end;

After invoking the inherited constructor of the SurfaceView parent class a reference to the supplied Camera object is stored in the mCamera member which we will need later on.

Next we create the Camera.CameraInfo object and obtain this info from the Camera. Notice in this case that the “getCameraInfo” method does not conform to the semantics of a property (it modifies a passed parameter rather than returning a result) and we must call it as a method. In a real application we would need to be more sensible about how we determine which camera to get info for, but for my purposes in this app I know that the camera I want (the rear facing camera) is the first camera on my device (an ASUS TF101). So I’m being quick and dirty and just specifying camera 0 (zero). Not recommended, obviously. 🙂

We will use the information in this info object later on to ensure our preview image is correctly oriented with respect to the device orientation.

We then obtain a reference to the SurfaceHolder of the SurfaceView via the Holder property and we add a reference to the ViewFinder class itself as a callback handler for the SurfaceHolder.

Yet again, this is where Oxygene does not simply provide access to the Java classes but allows us to work with them in a manner more natural to an ObjectPascal developer. As far as I know, Java still does not formally support the notion of a “property” on a class, as a Delphi developer would be used to. The “Holder” property we are reading in this constructor is in fact a regular Java method named getHolder. As I understand it however, it is a well established convention in Java (stemming from Java Beans) to name accessor methods for notional properties in this way. Oxygene takes that convention and presents it to the developer as if the Java class really did have a property formally declared.

But again, you have the choice. Although the code completion in Visual Studio offers only the “property name”, the compiler does not force you to use the property syntax. If you prefer you can still use the formal method name. The two are exactly equivalent:

    mSurfaceHolder := self.Holder;
    mSurfaceHolder := self.getHolder;

Now let’s look at the implementation of the SurfaceHolder.Callback interface, which is where all the real work is done.

It’s For You-hooo

The surfaceCreated and surfaceDestroyed methods of the callback interface notify us when the Surface of our SurfaceView control has been, predictably enough, created or destroyed. In our case these notifications tell us when to start and stop displaying preview images taken through the camera.

First, surfaceCreated:

  method ViewFinder.surfaceCreated(aHolder: SurfaceHolder);
  begin
    // The Surface has been created, now tell the camera where to draw the preview.
    try 
      mCamera.PreviewDisplay := mSurfaceHolder;
      mCamera.startPreview;

    except
      on e: IOException do
        Log.d("Demo Camera", "Error setting camera preview: " + e.getMessage());
    end;
  end;

When our control’s surface is created we assign our SurfaceHolder to the PreviewDisplay property of the Camera object reference (stored by our constructor). After assigning the PreviewDisplay we then call startPreview on the camera to, well, start capturing preview images, which the camera will then automatically start presenting for us.

In a manner that will be familiar to any Delphi developer, We keep a wary eye open for an IOException and if we get one we log it for potential diagnostic purposes.

Quickly going back to the PreviewDisplay property. This is Java, remember. No such thing as properties. And that’s right. Again, there is no such “property” called “PreviewDisplay” on the Camera. There are getPreviewDisplay and setPreviewDisplay methods however.

Oxygene doesn’t just sanitise get methods into readable properties, it does the same for writable ones (with a set method) too ! 🙂 Just as before however, you do still have the option of using the explicit setPreviewDisplay() method call syntax if you prefer:

      mCamera.setPreviewDisplay(mSurfaceHolder);

Worth mentioning at this point is that as well as setting up automatic drawing of preview frames, if we were interested in working with the images captured during preview there are mechanisms that enable us to receive additional notifications which receive each image captured during preview, but that is not something that we are concerned with here.

So much for getting the preview going.

When our surface is destroyed we must be sure to stop the preview as well, and that is take care of in surfaceDestroyed:

  method ViewFinder.surfaceDestroyed(aHolder: SurfaceHolder);
  begin
    mCamera.stopPreview;
    mCamera.release;
  end;

We tell the camera to stop the preview and release the camera.

The final notification from the SurfaceHolder that we need to respond to, is when the surface has changed. One of the reasons that our surface may change is when the device orientation changes, causing our layout to be rotated. When this occurs I would like to ensure that the preview image is oriented correctly with respect to the camera – this is not something that occurs automatically (and perhaps is not always appropriate though it is in my case and I can’t think off-hand of a situation when it might not be).

In any event, to determine the orientation to be applied to the camera we must determine the current orientation of the device and make the appropriate adjustment according to the natural orientation of the camera. The information about the camera is fixed and is contained in the Camera.CameraInfo object we obtained in our constructor. We did that so that we would not need to query the camera for that information every time the device orientation changes and so can respond that bit more quickly.

Obtaining the current device orientation involves a bit of hoop jumping to obtain a reference to the system WindowManager so that we may obtain the current rotation from the DefaultDisplay.

With the current device rotation we can calculate the adjustment for the camera, based on the camera’s natural orientation. Which is all a very wordy way of saying:

  method ViewFinder.surfaceChanged(aHolder: SurfaceHolder; aFormat, aWidth, aHeight: Integer);
  var
    wndmgr: WindowManager;
    rotation: Integer;
    angle: Integer;
  begin
    if NOT assigned(mSurfaceHolder.Surface) then
      EXIT; // preview surface does not exist
    
    wndmgr   := WindowManager(self.Context.getSystemService(Context.WINDOW_SERVICE));
    rotation := wndmgr.DefaultDisplay.Rotation;

    case rotation of
      Surface.ROTATION_0  : angle := 0;
      Surface.ROTATION_90 : angle := 90;
      Surface.ROTATION_180: angle := 180;
      Surface.ROTATION_270: angle := 270; 
    end;
     
    mCamera.DisplayOrientation := (mCameraInfo.orientation - angle + 360) mod 360;
  end;

There are a lot of assumptions here.

First of all, there’s no error checking so if something should unexpectedly go awry this app is simply going to crash out ungracefully. This isn’t an exercise in implementing a fully robust app for use on any and all Android devices though, so again, I can get away with it here.

Similarly, changing the camera DisplayOrientation is something that on older versions of Android (prior to SDK revision 14, Android 4.0) can only be done when there is no active preview. On the version of Android on my device – 4.1.2 – this isn’t an issue. But to be safe I could wrap this method in code which stops the preview (mCamera.stopPreview) and then restarts it only after applying the orientation change (re-assigning PreviewDisplay and calling startPreview again).

In the real world this would be performed conditionally, following a test of the Android version on which the app is running.

But for now, this is almost all I need for a functioning camera preview in my app. The only step remaining at this point is to actually instantiate the ViewFinder class and place it in the FrameLayout container we created at the beginning.

Creating The ViewFinder

So, returning back to the MainActivity class we already have, we add two new members. One will hold a reference to the Camera, the other to the Viewfinder.

    MainActivity = public class(Activity, Camera.PictureCallback)
    private
      mCamera: Camera;
      mViewFinder: ViewFinder;
    public
      method onCreate(savedInstanceState: Bundle); override;
      method CaptureClick(v: View);
    end;

I do not need to add anything to the uses clause of the MainActivity unit. The ViewFinder class is declared in the same namespace as the MainActivity class already – nz.co.deltics.demo.camera – even though it is in a separate file.

Finally, we need to add a little code to the onCreate method to turn on the Camera, instantiate the ViewFinder and place it in our UI.

  method MainActivity.onCreate(savedInstanceState: Bundle);
  var
    layout: FrameLayout;
    btnCapture: Button;
  begin
    inherited;

    ContentView := R.layout.main;

    mCamera := Camera.open;
    mViewFinder := new ViewFinder(self, mCamera);

    layout := FrameLayout(findViewById(R.id.camera_preview));
    layout.addView(mViewFinder);

    btnCapture := Button(findViewById(R.id.button_capture));
    btnCapture.OnClickListener := new interface View.OnClickListener(onClick := @CaptureClick);
  end;

After turning the Camera on with the call to open (without a specific camera ID, this opens the default camera – again, quick and dirty but adequate for my purposes) we create our ViewFinder, passing the reference to the Camera that the ViewFinder needs.

We then obtain a reference to the FrameLayout we placed in our layout and add the ViewFinder view that we just created.

That’s it.

All Over Bar The Shouting

In this state, this application is a functioning camera preview app.

The only thing it lacks is the ability to actually take a picture, which is what we will look at in the next and final (in this series) instalment.

11 thoughts on “An App With View”

  1. Can you please include a screenshot of you app? For those of us strictly reading your experience. Thanks 🙂

    1. I shall do one for the next post, but there really isn’t much to see. 🙂

      I will also make the source available once the series is complete.

  2. Camera.getCameraInfo(0, info);
    should be Camera.getCameraInfo(0, mCameraInfo);
    or I took it wrong? Have no Oxygene here to check it.

    1. Great David. It’s not quite the same of course since that post deliberately skips over the UI part rather than walking through it in detail to show what’s involved. Some questions about that sample app:

      Does a FireMonkey XE5 app always demand camera hardware ?

      If not, what happens if you try to run (or install) your app on a device that doesn’t have a camera ?

      Does a FireMonkey app always require camera and write storage permissions ?

      Or do you also need to specify these things if your app needs them ?

      If you do, is it the same for iOS and Android as well or is this something that isn’t covered by the “do it once and it runs everywhere” framework ?

      How big is the resulting app on Android ? (mine is just 48KB – and that’s the debug build)

      Will it run on an ASUS TF101 or other Tegra2 device ? (mine does)

      You also won’t like what’s coming next quite so much I don’t think…

      1. The tutorial on the wiki page goes through creating the UI and according to the footer, was last updated on 4th September which is before your first post on the 15th of your camera app.

        I’m not concerned about the size of the apps I write but am concerned with how much time I spend developing them.

        There is no need to state which devices the app won’t run on when that is already documented for the framework. Just like there is no need for you to state every time that the code to do the same thing on iOS could be different to the code in your Android version.

        1. I’m not sure what point the dates are supposed to make. David I’s post that appropriated my article title, referenced my articles without linking (bad blogging form apart from anything else) came only after part 3. I wasn’t even aware that there was a tutorial that did the same thing.

          But I just took a look now, and notice that the tutorial does not cover device hardware dependency declarations or application permissions.

          If you follow the steps in the tutorial do you actually have an app that works ? I can’t try it because my devices aren’t supported.

          Where does the app declare it’s need for a hardware camera ? Where does it request the permissions required to use the camera and write to storage ?

          If these are set in a platform independent way and/or automatically based on some sort of analysis of the code/components used in an app this would surely be something to boast about, no?

          But it isn’t mentioned at all, which either means that FireMonkey applications demand ALL features and ALL permissions or that there are platform specific wrinkles in the satin smooth “one codebase” marketing message which are glossed over.

          Which is it ? Why will/can no-one answer this very simple question ?

          The size of apps may not concern you, but there are maximum limits on app sizes in the app stores so this may concern someone who is building apps with a view to selling them in the store and it may concern users with limited storage on their devices. The iWD app previously referenced by Embarcadero is 10MB. It is a very simple app.

          It won’t matter very much how quickly you are able to develop your app if it ends up being too big to be allowed in a store.

          There is a need for greater clarity on supported devices since the Feature Matrix implies 5.1 device support (from XE4, no clarification w.r.t XE5 specifically), the wiki platform requirements state iOS 6.0 and the FAQ states 6.0 for development but 5.16 for deployment. Which is right ?

          As you say, when discussing clearly Android code there is no need to say that it won’t run on iOS. But when you say “will run on Android and iOS” when in fact it won’t run on all Android devices/versions and you have confused information about which iOS devices are supported, then some additional clarification might reasonably be expected.

          1. I cannot answer the question about Firemonkey and features as I’m at work and don’t have access to Delphi XE5 to run through the steps. If I get some time tonight, I’ll check it out.

    1. That stackoverflow question you link to simply states the case that the executable file size is limited to 60MB, regardless of delivery mechanism. The 50MB over-the-air limit is separate to that as far as I know (and since it is presumably imposed by the device – not the store – may even be something you can override in device settings?).

      On Android the limit is 4G but with the APK still limited to 50MB.

      The other concern of course is devices that have constrained, unexpandable storage (which is one reason for the limit of course, particularly on iOS – see “constrained, unexpandable storage”).

      Of course, if a potential customer has to wait before they can get to a wifi hotspot before they can download your app, they will probably just choose a smaller one instead.

      Interesting fact: File New > Empty App > Build for Android = 48MB

      That’s not even “Hello World”. Just an empty app. (debug build)

Comments are closed.