[Estimated Reading Time: 3 minutes]

In a previous post I noted the absence of the BatteryManager class in the AndroidAPI.JNI units. This class contains some constants useful when reading battery information. I showed how to use a suitably massaged literal in place of these missing constants, but in response to observations from Paul and Brian decided to look at how to import the missing class itself.

First of all if you want the class declared in the most appropriate namespace – AndroidAPI.JNI.OS – you will have to do the usual juggling of RTL units to ensure that any modifications you make are actually used.

Alternatively we can just import the class in a unit of our own.

This unit will become redundant should Embarcadero ever fill in these gaps in their AndroidAPI units, but if we use a modified copy of the existing AndroidAPI.JNI.OS unit then we’ll have to reconcile any future Embarcadero revisions with our own. Either way there will potentially be housekeeping to deal with in the future.

You’ll have to decide which approach suits you best, but for this exercise we’ll just put the newly imported class in a separate unit.

The first thing you’ll need is to use some core units with basic Java type support and the unit that contains the bridging generics classes themselves:


  unit Deltics.Android.OS;

interface

  uses
    AndroidAPI.JavaTypes,
    AndroidAPI.JNIBridge;

Next you will need to declare two interfaces. One interface represents the static members of the Java Class being imported. The other represents the members of an instance (object) of that class. We need to decorate the object interface with a JavaSignature attribute identifying the target SDK class using a path qualified reference to that class in the SDK namespaces.

Each interface requires a GUID since the Delphi runtime still requires this for interface resolution. The GUID’s do not relate to anything in the Android SDK though, so any old GUID will do. Ctrl+Shift+G gives us some nice fresh GUIDs to use, as usual.

Following the J prefix and Class suffix conventions in the existing AndroidAPI.JNI units, this leads us to:

  type
    JBatteryManagerClass = interface(JObjectClass);
    ['{4E061F9B-42D4-4A1B-837E-D722556952EA}']
    end;

    [JavaSignature('android/os/BatteryManager')]
    JBatteryManager = interface(JObject);
    ['{483F66A1-266E-47AD-822B-6A6193358AD9}']
    end;

implementation

  // Nothing to see here

end.

Now we need to add members of the class with suitable declarations. This does not have to be complete – anything we don’t add simply won’t be accessible. For now we’ll just add the two constants required by the previous code snippet – EXTRA_LEVEL and EXTRA_SCALE.

These are class members so are declared on the Class interface:

  type
    JBatteryManagerClass = interface(JObjectClass);
    ['{4E061F9B-42D4-4A1B-837E-D722556952EA}']
      function _GetEXTRA_LEVEL: JString;
      function _GetEXTRA_SCALE: JString;

      property EXTRA_LEVEL: JString read _GetEXTRA_LEVEL;
      property EXTRA_SCALE: JString read _GetEXTRA_SCALE;
    end;

Some methods in the import interfaces are decorated with the cdecl calling convention. This does not seem to apply to property accessor functions so I have omitted them here.

Similarly I haven’t yet determined whether the names of the accessor functions are important so for safety I have again just followed the convention divined from the existing import declarations. In any event, since interface members are always public, the leading underscore acts to hide these functions from code completion, leaving only the property names.

Without any of the advances in syntax that I’ve already become used to in Oxygene, this was laborious and cumbersome, but it gets the job done. Not the whole job of course. The BatteryManager class contains dozens of constants that will all have to be declared in this way if you wish to use them via this mechanism.

Note that the string properties are directly declared as JString‘s.

The final step is to declare the class that will tie all of this together and actually create the import class declaration. This is a generic class that accepts a class interface and an object interface as type parameters:

  TJBatteryManager = class(TJavaGenericImport<JBatteryManagerClass, JBatterManager>) end;

To demonstrate the use of the newly imported class, let’s apply it to the previous example for reading the battery level. We’ll also apply the init() constructor pattern for the IntentFilter:

function BatteryPercent(const aContext: JContext): Integer;
var
  filter: JIntentFilter;
  battery: JIntent;
  level, scale: Integer;
begin
  filter := TJIntentFilter.JavaClass.init(TJIntent.JavaClass.ACTION_BATTERY_CHANGED);
 
  battery := aContext.registerReceiver(NIL, filter);
  level := battery.getIntExtra(TJBatteryManager.JavaClass.EXTRA_LEVEL, -1);
  scale := battery.getIntExtra(TJBatteryManager.JavaClass.EXTRA_SCALE, -1);
 
  result := (100 * level) div scale;
end;

The JavaClass member of our import class derives from the TJavaGenericImport class and provides access to the JObjectClass interface (containing our ‘constants’);

Not particularly difficult but a bit laborious.

If you don’t fancy having to do all this by hand you might want to keep your eye on something that CHUA Chee Wee is apparently working on.

5 thoughts on “Importing an Android Class For Use in Delphi”

  1. I’ve almost got this working, but what parameter do I pass in BatteryPercent() ?

    Thanks.

    procedure TForm1.Button1Click(Sender: TObject);
    begin
    memo1.Lines.Add(IntToStr(BatteryPercent(????????)));
    end;

    1. It needs to be a JContext. The MainActivity global in FMX.Platform.Android ultimately derives from JContext so this should work:

      pct := BatteryPercent(MainActivity);

      You will need to use that FMX.Platform.Android unit as well of course, if you aren’t already.

Comments are closed.