Incorporate Broadcast Event Senders/Receivers into the application. Use BootReciever to recieve boot event to start application service on launch. Use NetworkReveicer to stop/start service when network is starting/stopping. Use Broadcast Events for status updates from background service to TimelineActivity
Introduce this class to receive bootloader events:
class BootReceiver extends BroadcastReceiver
{
override onReceive(Context context, Intent intent)
{
context.startService(new Intent(context, typeof(UpdaterService)))
}
}
This will start the updater service when the device boots. It requires this permission in the manifest:
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<receiver android:name="com.marakana.yambax.BootReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
We can change our default service state to running:
@Property boolean serviceRunning = true
.. and also make some adjustments to the toggle service action:
val toggleService = [ | intent = new Intent(this, typeof(UpdaterService))
if (app.isServiceRunning)
stopService(intent)
else
startService(intent)
app.serviceRunning = !app.serviceRunning] as Command
You can inspect the list of running services on the device:
We can employ Android Broadcast events for the status updates, removing the custom event mechanism we have hand coded.
We first define some resources to describe the events:
<string name="send_timeline_notifications_permission_label">Sends Timeline Notifications</string>
<string name="send_timeline_notifications_permission_description">Allow this application to send timeline notifications to other applications</string>
<string name="receive_timeline_notifications_permission_label">Receive Timeline Notifications</string>
<string name="receive_timeline_notifications_permission_description">Allow this application to receive timeline notifications from other applications</string>
.. and then declare them in the manifest:
<uses-permission android:name="com.marakana.yamba.SEND_TIMELINE_NOTIFICATIONS" />
<uses-permission android:name="com.marakana.yamba.RECEIVE_TIMELINE_NOTIFICATIONS" />
<permission android:name="com.marakana.yamba.SEND_TIMELINE_NOTIFICATIONS"
android:label="@string/send_timeline_notifications_permission_label"
android:description="@string/send_timeline_notifications_permission_description"
android:permissionGroup="android.permission-group.PERSONAL_INFO"
android:protectionLevel="normal" />
<permission android:name="com.marakana.yamba.RECEIVE_TIMELINE_NOTIFICATIONS"
android:label="@string/receive_timeline_notifications_permission_label"
android:description="@string/receive_timeline_notifications_permission_description"
android:permissionGroup="android.permission-group.PERSONAL_INFO"
android:protectionLevel="normal" />
The strings need to be replicated in the UpdaterService class:
public static final String NEW_STATUS_INTENT = "com.marakana.yamba.NEW_STATUS"
public static final String SEND_TIMELINE_NOTIFICATIONS = "com.marakana.yamba.SEND_TIMELINE_NOTIFICATIONS";
public static final String RECEIVE_TIMELINE_NOTIFICATIONS = "com.marakana.yamba.RECEIVE_TIMELINE_NOTIFICATIONS"
.. and then dispatched :
override def void doBackgroundTask()
{
try
{
val List<Twitter.Status> timeline = twitter.getFriendsTimeline
newTweets = if (app.timeline.size == 0) timeline else timeline.filter [it.id > app.timeline.get(0).id]
Log.e("YAMBA", "number of new tweets= " + newTweets.size)
app.updateTimeline(newTweets)
sendBroadcast(new Intent(NEW_STATUS_INTENT), RECEIVE_TIMELINE_NOTIFICATIONS);
}
catch (TwitterException e)
{
Log.e("YAMBA", "Failed to connect to twitter service", e);
}
}
The knock on changes involve changes to the main application object:
def updateTimeline(Iterable<Twitter.Status> newTweets)
{
newTweets.forEach[timeline.add(0, it)]
}
def clearTimeline()
{
timeline.clear
// todo - clear timeline view
}
}
.. and the TimelineActivity, which gets an new receiver object:
class TimelineReceiver extends BroadcastReceiver
{
var TimelineActivity timelineActivity
new (TimelineActivity activity)
{
timelineActivity = activity;
}
override onReceive(Context context, Intent intent)
{
timelineActivity.timelineAdapter.notifyDataSetChanged
}
}
class TimelineActivity extends BaseActivity
{
@Property TimelineAdapter timelineAdapter
var TimelineReceiver receiver
var IntentFilter filter
override onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState)
setContentView(R.layout.timeline)
timelineAdapter = new TimelineAdapter(this, R.layout.row, app.timeline)
receiver = new TimelineReceiver (this)
filter = new IntentFilter( UpdaterService.NEW_STATUS_INTENT )
}
override onStart()
{
super.onStart
val listTimeline = findViewById(R.id.listTimeline) as ListView
listTimeline.setAdapter(timelineAdapter);
}
override onResume()
{
super.onResume
super.registerReceiver(receiver, filter, UpdaterService.SEND_TIMELINE_NOTIFICATIONS, null);
}
override onPause()
{
super.onPause();
unregisterReceiver(receiver)
}
}
We can also potentially improve the energy deficiency of our application by disabling the background task when the network is unavailable:
class NetworkReceiver extends BroadcastReceiver
{
override onReceive(Context context, Intent intent)
{
val isNetworkDown = intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
if (isNetworkDown)
{
if (YambaApplication.serviceRunning)
{
Log.d("YAMBA", "onReceive: NOT connected, stopping UpdaterService");
context.stopService(new Intent(context, typeof(UpdaterService)))
}
}
else
{
if (!YambaApplication.serviceRunning)
{
Log.d("YAMBA", "onReceive: connected, starting UpdaterService");
context.startService(new Intent(context, typeof(UpdaterService)))
}
}
}
}
This will involves setting our initial service back to false:
public static boolean serviceRunning = false
.. and other adjustments:
val toggleService = [ | intent = new Intent(this, typeof(UpdaterService))
if (YambaApplication.serviceRunning)
stopService(intent)
else
startService(intent)
YambaApplication.serviceRunning = !YambaApplication.serviceRunning] as Command
override onMenuOpened(int featureId, Menu menu)
{
val toggleItem = menu.findItem(R.id.itemToggleService)
toggleItem.title = if (YambaApplication.serviceRunning) R.string.titleServiceStop else R.string.titleServiceStart
toggleItem.icon = if (YambaApplication.serviceRunning) android.R.drawable.ic_media_pause else android.R.drawable.ic_media_play
true
}
override onStartCommand(Intent intent, int flags, int startId)
{
super.onStartCommand(intent, flags, startId)
startBackgroundTask
YambaApplication.serviceRunning = true
START_STICKY;
}
override onDestroy()
{
super.onDestroy
stopBackgroundTask
YambaApplication.serviceRunning = false
}
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<receiver android:name="com.marakana.yambax.NetworkReceiver">
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
</receiver>
Finally, our timeline should be cleared if a purge action received:
def clearTimeline()
{
timeline.clear
sendBroadcast(new Intent(UpdaterService.NEW_STATUS_INTENT), UpdaterService.RECEIVE_TIMELINE_NOTIFICATIONS);
}
These labs are based on the application developed using this text:
The Yamba application here uses android API largely compatible with Android 3.0 and earlier.
However, it has been substantially revised to use a full suite of modern facilities introduced in subsequent releases:
A full set up repos for all the code in the revised application is here:
(Toc is reproduce at the end of this page)
Specifically, the revised application includes:
Live Wallpapers
a number of minor improvements.
A version of YambaX (written in xtend) - fully updated to include the above features, would be an interesting and useful assignment topic