Well Behaved Widgetry
When we left it, my battery widget was working but wasn’t particularly well behaved. There was nothing much wrong with the functionality, but plenty wrong with the implementation.
Despite their impressive specifications, mobile devices have one very limiting factor. Battery Life.
Indeed, the impressive specifications are part of the reason that battery life can be a problem, but as application developers targeting mobile platforms there are things we can – and should – do, to ensure that we don’t make the problem worse than it needs to be.
So let’s look at the problems with my battery widget.
System Scheduled Updates
For simplicity and convenience, I declared an update interval in the meta-data for my widget.
This is bad for two reasons:
- Declared update intervals are subject to a maximum frequency (i.e. they cannot be schedule more frequently than a prescribed limit). There is a good reason for this, which is the second problem with such declared updates
- For updates scheduled in this way, the system will wake the device in order to perform the requested update. For my widget this is pointless – if the device is asleep the screen is off, so having an up-to-date battery %’age displayed is neither here nor there. But it’s worse than that. Not only will my widget wake up the device needlessly, but also when it does any other apps or widgets that were waiting patiently for the user to wake the device will also potentially take this as their cue to do some work that they otherwise needn’t
All in all, my simple widget is a real trouble maker.
Fortunately there is an alternative to scheduling updates in this way. Alarms.
Be Alarmed
Not to be confused with an “alarm clock“, on Android there is a system AlarmManager which can be used to scheduled recurring (or one off) events.
Even better, when setting up an alarm using AlarmManager, we can specify whether or not our alarm is important enough to wake up the device. If not, and if the device is asleep when our alarm would have been triggered, it will instead be triggered immediately that the device is first awoken after our alarm had elapsed.
And even better still, with the AlarmManager there are no constraints on the frequency of any recurring alarm we may wish to set (other than our own common sense and consideration for others).
Perfect.
So, let’s adapt the battery widget to use an alarm.
First of all, we set the update period in the meta-data to “0” to indicate that we do not wish to receive any system generated updates:
android:updatePeriodMillis="0"
Next, we add a class var (static member) to our BatteryWidgetProvider class, to hold a reference to the AlarmManager. As we shall see, this will both act as a cache for the reference to the AlarmManager and also serve as a flag that we have an alarm set.
private class var fAlarm: AlarmManager;
Now we need to extend BatteryWidgetProvider further. Previously we only responded to ACTION_APPWIDGET_UPDATE intents, but now we need to respond to two other intents in the widget lifecycle:
- ACTION_APPWIDGET_ENABLED – an intent received when the first instance of the widget is placed on the home screen. We will use this intent to set our update alarm.
- ACTION_APPWIDGET_DISABLED – an intent received when the last instance of the widget is removed from the home screen. No prizes for guessing that we will use this intent to cancel our update alarm.
To receive these intents we must add them to the intent-filter
for the BatteryWidgetProvider receiver
entity in the AndroidManifest.
<intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> <action android:name="android.appwidget.action.APPWIDGET_ENABLED" /> <action android:name="android.appwidget.action.APPWIDGET_DISABLED" /> </intent-filter>
Although not as important for recurring updates, we will leave the APPWIDGET_UPDATE intent in place to ensure that new widget instances are updated immediately when initially placed.
Now we can override the onEnabled
and onDisabled
methods of the BatteryWidgetProvider class. These methods are introduced by the AppWidgetProvider ancestor class – mapping the intents onto these virtual methods is one of the conveniences it provides. Handling intents more directly is something we will come to later.
method onDisabled(aContext: Context); override; method onEnabled(aContext: Context); override;
In the implementation of the onEnabled method, we will obtain a reference to the AlarmManager and establish our alarm. If we already have a reference to the AlarmManager then our alarm is already in place and does not need to be re-established.
method BatteryWidgetProvider.onEnabled(aContext: Context); begin if NOT assigned(fAlarm) then begin fAlarm := aContext.SystemService[Service.ALARM_SERVICE] as AlarmManager; fAlarm.setRepeating(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime, 1000, updateIntent(aContext)); end; end;
We obtain a reference to the AlarmManager from the SystemService
member of a context, in this case the Context supplied with the Enabled intent.
As with just about everything in Android, Intents are involved again.
Rather than providing a callback function or any other such contrivance, an alarm is configured with an intent. In this way you can directly configure an alarm to perform (almost) any intent based action in the system, even going to far as to launch applications if you wish (not advisable – users might be confused).
In this case the intent supplied to the alarm is build by another method we shall add to the provider class, updateIntent(Context)
. It is important that we have a method for reliably constructing the appropriate intent because when we cancel an alarm we do so by identifying the intent whose alarm is to be cancelled, so it must match.
But before we get to that, let’s look at the other aspects of establishing this alarm.
Alarm Parameters
First, I am using the setRepeating()
method to set the alarm. As the name implies, this will establish a repeating alarm. The set()
method establishes a one-time alarm. The setInexactRepeating()
method also establishes a repeating alarm, but allows the system to vary the schedule of the alarm slightly, so as to coalesce alarms to maximise efficiency.
The first parameter to this call specified the type of alarm. There are two basic types, ELAPSED_REALTIME and RTC. ELAPSED_REALTIME alarms are specified in terms of intervals (trigger alarm in 5 seconds time), RTC alarms are specified in terms of actual clock time (trigger alarm at 1pm).
In addition, both types of alarm have a WAKEUP (ELAPSED_REALTIME_WAKEUP / RTC_WAKEUP) variant which indicates that the device should be awoken to ensure delivery of the alarm intent at the appropriate time. I specifically do not want to do this for this widget.
The next parameter indicates the initial time at which the alarm should fire. For an elapsed time alarm this should be based on SystemClock.elapsedTime
(the current-time). You could apply an offset to this time if you do not need the alarm to fire immediately, but in this case right now is as good a time as any.
The third parameter identifies the repeating interval in milliseconds. Very straight-forward.
In this case I have set a ridiculous interval of just 1 second.
Make no mistake. This is bonkers !!
Battery level is not going to alter significantly – if at all – in such a time frame and by updating so frequently all I am doing is contributing to battery drain. But I want to be certain that my alarm is firing at this stage. I will add some variation to the display of the battery percentage so that I can see the updates occuring without having to hang around waiting for the battery level to actually fall (or rise, if charging).
Once I am satisfied that everything is working I will remove that additional visual feedback and set a more appropriate interval. I might even allow the user to choose one that suits them and their device.
The final parameter is the intent associated with my new alarm. Actually it is a special sort of Intent called a PendingIntent. So let’s look at how we construct this in the updateIntent()
method.
Building a PendingIntent
You can think of a PendingIntent as a sort of envelope containing an actual Intent that will be delivered at some point. Like any envelope that you pop in the post, you have to address it so that the postal service know where to send it.
First let’s consider the Intent we will trigger with our alarm.
It just so happens that in the case of this widget, I already have an ideal intent in mind. Remember that we implemented the update code for the widget in a Service, and we start a Service using… an Intent. We can just use exactly the same specification of intent for our alarm as well. All that changes is the way we deliver it.
After we have created our Intent, we then obtain a stamped, addressed envelope and slip our Intent inside it. This is achieved using an appropriate method of the PendingIntent class.
For an Intent to be broadcast to an arbitrary broadcast receiver, we would use getBroadcast
.
For an Intent to be send to an Activity, we would use getActivity
.
Since our Intent is intended [sic] to be delivered to a Service, we use .. again, no prizes for having guessed already .. the getService
method.
method BatteryWidgetProvider.updateIntent(const aContext: Context): PendingIntent; begin var action := new Intent(aContext, typeOf(UpdateService)); result := PendingIntent.Service[aContext, 0, action, PendingIntent.FLAG_UPDATE_CURRENT]; end;
There are some parameters supplied to the getService
method (exposed by the magic of Oxygene syntax as an indexed property called, simply, Service
).
First is a Context (a common requirement which I have still yet to fully grasp myself which is why I haven’t yet explained it).
Next is an arbitrary ID value. I am not using this so 0 will do.
Then comes the Intent we wish to put inside the ‘envelope’.
And finally a flag which determines what should happen to any existing PendingIntent that matches the specification. In this case I simply update it, though nothing will ever change.
That is almost enough to get our alarm performing updates for us. There is a change we need to make to the service before it will work however, but before we do that I will finish the changes to the widget provider itself.
I still need to implement onDisabled()
to cancel any alarm that has been set.
Cancelling an Alarm
Cancelling an alarm is very straightforward:
method BatteryWidgetProvider.onDisabled(aContext: Context); begin if assigned(fAlarm) then begin fAlarm.cancel(updateIntent(aContext)); fAlarm := NIL; end; end;
We only cancel the alarm if an alarm has been set which we can determine by the fact that we have a cached reference to the AlarmManager (in fAlarm).
We cancel the alarm by calling the cancel()
method and passing in a PendingIntent. Any alarm that is set with a matching PendingIntent will be cancelled.
Having cancelled the alarm, we NIL our reference to the AlarmManager so that if/when the widget is again placed on the home screen we will know that we need to re-set the alarm in the onEnabled
method.
All that’s left is two minor changes to make to the UpdateService and we’re done.
All Part of the Service
The first and simplest change is to introduce the visual feedback that will help identify that things are working as intended.
I shall use a unit variable (an “implementation global” if you prefer). I could have used another class var (static member) on the UpdateService class, but this seemed like a good opportunity to show once again how Oxygene targets Java without being entirely constrained by the rules of that language, since Java of course doesn’t support global variables at all as directly as this.
But bear in mind, this isn’t a recommendation just a demonstration.
The unit variable will be a simple boolean which I will toggle each time the service builds an update and use the state to alternate the color of the text of the widget (arbitrarily I chose RED and WHITE):
implementation var ticktock: Boolean; function UpdateService.buildUpdate: RemoteViews; begin ... ticktock := NOT ticktock; result := new RemoteViews(PackageName, R.layout.widgetlayout); result.setTextViewText(R.id.lblInfo, pct.toString + '%'); result.setTextColor(R.id.lblInfo, if ticktock then COLOR.RED else COLOR.WHITE); end;
Finally, we have to make an important change to the onStart
method of the UpdateService itself.
First of all, I was following an out of date example when I first implemented my service. The onStart()
method is deprecated and we should now implement onStartCommand()
instead. This is important because the return value of onStartCommand
determines how the system manages our service.
The default implementation of onStartCommand()
calls onStart()
and returns START_STICKY, indicating that our service should be left hanging around for as long as possible. I am guessing this replicates some early, fairly crude system management behaviour from an early Android version.
I want to be a bit more accommodating and return START_NOT_STICKY since our service is not long-running and doesn’t need to be kept hanging around and the system should be able to get rid of it if necessary. The service will simply be restarted by my widget if and when needed.
In the onStartCommand implementation itself I also need to change the way I update my widget(s). Previously the service was always started with an intent that came packaged with an array of widget ID’s identifying the widgets to be updated. This is still the case when the service is started in response to an onUpdate() intent, resulting from the placement of a new instance of the widget.
But when the service is started by my alarm, there is no array of widget ID’s. The service instead should update all instances of the widget, and so it must use a slightly different version of the AppWidgetManager.updateAppWidget() method, once which identifies the target widgets not by ID but by class name:
method UpdateService.onStartCommand(aIntent: Intent; aFlags: Integer; aStartID: Integer): Integer; begin var update := buildUpdate; if aIntent.hasExtra('ids') then begin var ids := aIntent.Extras.IntArray['ids']; AppWidgetManager.Instance[self].updateAppWidget(ids, update); end else AppWidgetManager.Instance[self].updateAppWidget(new ComponentName(self, typeOf(BatteryWidgetProvider)), update); result := Service.START_NOT_STICKY; end;
And there we go. A much more well behaved widget that will do it’s best to not drain the battery it is so carefully monitoring for us.
Well, not quite everything.
There is (at least) one more refinement we can make. For those not already au fait with such things, here’s a clue: Just because the screen on your device is off, does not mean that the device is necessarily yet asleep.
Update: This morning I also discovered a bug which is that my widget update alarm did not get re-set when my phone was awoken from what I presume was a deep sleep state that it entered over-night. I have yet to figure out why this is, but I suspect that my service and broadcast receiver are being cleaned up by the system. I am being a little bit too conservative somewhere.
please can you show how you have to do it in free pascal?
thx in adv
Sorry, no. a) I don’t know how and b) I’m too busy (and having too much fun) learning Oxygene. π
A minor nitpick, but…
since Java of course doesnβt support global variables at all
Hmm, sort of. Static variables, and if you want unqualified naming, static variables with static imports gets you the same thing.
My bad. I should of course have said doesn’t support global variables as directly/easily. Clearly Java (in the form of the JVM) supports global variables by some device since without that device Oxygene for Java could not support them itself. π
Out of interest, does Oxygene for Java support any/all of the following?
– var parameters
– Overriding a method as something that has to be marked explicitly (I know Java has the @Override attribute, but it’s use is merely optional).
– Proper class references (Java’s own Class type, even in its generic form, is pathetic IMO).
– Connected to the previous point, virtual class methods…
– … and virtual constructors.
– Avoidance of the ridiculous ‘checked’ exception system (admittedly, this is not as big an issue as it might have been when coding native Java Android apps given Google clearly doesn’t like it either!).
– Record types.
– Set types, and if so what are they mapped to – bitflags or
EnumSet…?
I haven’t yet come across all of the things you list but would be curious to find out myself so will look into some of them specifically a.s.a.p – it will make an interesting subject I think.
For now though:
var parameters – yes, but it requires an RTL support library (com.remobjects.oxygene.rtl.jar) reference in the project. Why ? I don’t know.
overrides – if you do not explicitly mark these then you get the same “hides an inherited method” warning that you would get on Delphi, for example.
Side note: you do not have to explicitly mark overloads.
virtual class methods – they compile. Whether they work as you would expect I have yet to explore further
class references and virtual constructors – Oxygene supports “class of ..” and the Delphi code you would write to use class references and virtual constructor all compiles, but as to how/whether it works… watch this space. π
Records – yes, but again it requires that RTL support library
set types – no (enums yes). Compiler rejects “set of” type declarations as unsupported on the target platform (Java)
Thanks. I’m a bit surprised about sets – would that be because sets (including enum sets) are a library not language feature on the Java side…?
I don’t know. I think the language/framework aspect could be part of it, yes. The support for sets in java.util seems to go way, way beyond what could be achieved with the “set of X” syntax, especially considering that “set” here could – in Java – be one of any number of Set collection types (Navigable, Sorted).
Any Java specific extensions would not be portable and having a simplified an incomplete “set of” syntax would be too limited for Java.
So it might simply be that RemObjects don’t see either solution as appropriate or necessary, given that if you want/need to you can simply use the native java.util set support directly anyway. But this is just my speculation. I don’t actually know why RO haven’t done anything here.
MySet = EnumSet<MyEnum>
vsMySet = set of MyEnum
Hardly seems worth the effort. π
Perhaps they are still figuring out whether there is a useful way to approach it. They do they seem to prefer to wait and do such things right, rather than to do the first/most obvious thing that pops into their head and then hope that it turns out to be right in the end.
Hello,
would it be possible to post a small code piece to the use the alarm manager under Delphi ?
The android wrapper units of delphi and oxygene differ something.
I would be very grateful to you if you could help me
I’m not sure how useful that would be. You cannot implement a service in Delphi so you can’t use an alarm manager to invoke a service in your own application. You cannot implement additional activities to respond to intents either. You may be able to declare additional intents on your NativeActivity in the manifest, though I don’t know how you would obtain any such intent when invoked on that activity, though this probably is possible somehow.
However, you should be able to use the alarm manager to send intents to other applications quite easily I think.
Perhaps if you tell me exactly what it is you wish to do I can take a look at precisely those areas involved ?
What parts of the JNI wrapper are giving you trouble ?
Hello – in the unit Androidapi.JNI.app this part:
JAlarmManagerClass = interface(JObjectClass)
[‘{B26537D0-A769-4DD1-A5C3-2E2D9344193B}’]
…..
[JavaSignature(‘android/app/AlarmManager’)]
JAlarmManager = interface(JObject)
[‘{D4B2A2E3-48AD-491C-823B-DC301F6DA456}’]
I try to develop an alarm clock application. A sound shall be played if a particular time and date is reached.
I have tried it with the system timers already, would work also, only should the application run permanently.
The timer becomes event but isn’t triggered if the application runs in the background.
The Alarmmanger is exact for this there anyway or do I misunderstand this?
How are you attempting to trigger the sound ?
An Alarm triggers an intent – the key is what type of intent you are setting up and how you expect to respond to it.
At the moment still not at all.
I try to understand the mechanism first.
As intent I introduce a receive function which is jumped at at the event.
How this shall work if the alarm clocks application shall not permanently run ?
Well, I found that using an IntentService derived class was necessary in my widget in order for my alarms to continue to be received after the device entered deep sleep. I suspect this was because my WidgetProvider class had been cleaned up, a similar situation to your application activity not running.
If that is the case, then your only solution is to implement an IntentService class yourself, and this is not something you can do with the NDK alone. You would have to implement an IntentService derived class using Java to call your NDK code via JNI.
BUt I’m not sure that you can create “standalone” NDK code using Delphi for Android – it only supports generating an NDK based Activity (using the NativeActivity class built in to the Android platform itself, designed specifically to wrap around NDK based activities)
So if that is what you need to do, then you simply cannot do it with Delphi.
This is the single biggest problem with the approach taken to Android support in Delphi. I have been trying to warn people of this but nobody seems to want to listen. π
Nice post!
Hello Deltics,
Thank you for your efforts, Delphi for android is still relatively new.
The documentation is currently very low.
So, I must wait, or learn java π
This isn’t a question of the maturity of FireMonkey or the Android support. For Delphi to support these things, the entire architecture of FireMonkey on Android would have to change.
I don’t understand why you think your only alternative is Java.
Why not check out Oxygene ? That way you can learn and make full use of the Android SDK (and Cocoa for iOS/OS X and .NET framework).
If you then decide that you want to use Java (and/or Objective-C and/or C#) later, then you still can, and everything you have learned about the platforms and frameworks will still be 100% useful and relevant.