Objectives

BroadcastReciever - Boot

Introduce this class to receive bootloader events:

YambaApplication.xtend

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:

AndroidManifest

    <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:

YambaApplication.xtend

  @Property boolean                serviceRunning = true

.. and also make some adjustments to the toggle service action:

BaseActivity.xtend

  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:


Source


BaroadcastReciever - tweets

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:

res/values/strings.xml

  <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:

AndroidManifest.xml

    <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:

UpdaterService

  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:

YambaApplication.xtend

  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:

TimelineActivity

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) 
  }
}

Source


BroadcastReceiver - Network Events

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:

YambaApplication.xtend

  public static boolean            serviceRunning = false

.. and other adjustments:

BaseActivity.xtend

  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
  } 

UpdaterService

  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
  }

AndroidManifest

  <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>

Source


Purge Timeline

Finally, our timeline should be cleared if a purge action received:

YambaApplication.xtend

  def clearTimeline()
  {
    timeline.clear
    sendBroadcast(new Intent(UpdaterService.NEW_STATUS_INTENT), UpdaterService.RECEIVE_TIMELINE_NOTIFICATIONS);   
  }

Source


Exercises (Assignment Ideas)

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:

A version of YambaX (written in xtend) - fully updated to include the above features, would be an interesting and useful assignment topic

ToC for Learning Android 2nd Edition

Chapter 1 Android Overview

Chapter 2 Java Review

Chapter 3 The Stack

Chapter 4 Installing and Beginning Use of Android Tools

Chapter 5 Main Building Blocks

Chapter 6 Yamba Project Overview

Chapter 7 Android User Interface

Chapter 8 Fragments

Chapter 9 Intents, Action Bar, and More

Chapter 10 Services

Chapter 11 Content Providers

Chapter 12 Lists and Adapters

Chapter 13 Broadcast Receivers

Chapter 14 App Widgets

Chapter 16 Interaction and Animation: Live Wallpaper and Handlers