Export Calendar Example

This example shows how to export calendar events on a suitably equipped device.

A screenshot of the application.

We begin by importing the classes and modules needed by our application.

from java.io import File, FileWriter
from java.lang import Runnable, Thread
from java.util import HashMap, List
from android.content import Context
from android.net import Uri
from android.os import Environment, Handler, Looper
from android.provider import CalendarContract
from android.view import View
from android.widget import AdapterView, Button, ScrollView, Spinner, TextView

We also use a custom activity class and a convenience widget from the serpentine package.

from serpentine.activities import Activity
from serpentine.adapters import StringListAdapter
from serpentine.widgets import VBox

We define a class based indirectly on the standard Activity class. This represents the application, and will be used to present a graphical interface to the user.

class ExportCalendarActivity(Activity):

    __interfaces__ = [AdapterView.OnItemSelectedListener, Runnable,
                      View.OnClickListener]
    
    ADD_LOG_TEXT = 0
    STOP_WORKER = 1

The initialisation method only needs to call the corresponding method in the base class.

    def __init__(self):
    
        Activity.__init__(self)

The onCreate method is called when the activity is created. Our implementation queries the available calendars, creates an output directory to store events in, and constructs a user interface.

    def onCreate(self, bundle):
    
        Activity.onCreate(self, bundle)
        
        calendars = self.getCalendars()
        if len(calendars) > 0:
            self.currentCalendar = calendars[0]
        else:
            self.currentCalendar = ""
        
        self.outputDir = self.createDir()

A spinner allows the user to select the calendar to export.

        spinner = Spinner(self)
        self.adapter = StringListAdapter(calendars)
        spinner.setAdapter(self.adapter)
        spinner.setOnItemSelectedListener(self)
        
        self.exportButton = Button(self)
        self.exportButton.setText("Export calendar")
        self.exportButton.setEnabled(True)
        self.exportButton.setOnClickListener(self)
        
        self.scrollView = ScrollView(self)
        self.textView = TextView(self)
        self.scrollView.addView(self.textView)

The widgets are added to a convenience widget from the serpentine package.

        vbox = VBox(self)
        vbox.addView(spinner)
        vbox.addWeightedView(self.scrollView, 1)
        vbox.addView(self.exportButton)

The VBox widget is used as the main user interface in the activity.

        self.setContentView(vbox)

We also create a handler that will receive messages sent from the worker thread and initialise an attribute that records whether the worker thread is running.

        self.handler = MessageHandler(Looper.getMainLooper(), self)
        self.running = False

If an item is selected in the spinner then the current calendar string is updated.

    def onItemSelected(self, parent, view, position, id):
    
        self.currentCalendar = self.adapter.items[position]

If no item is selected then we do nothing.

    def onNothingSelected(self, parent):
        pass
    
    @args(List(str), [])
    def getCalendars(self):
    
        resolver = self.getContentResolver()
        
        cursor = resolver.query(CalendarContract.Calendars.CONTENT_URI,
            array([CalendarContract.Calendars.ACCOUNT_NAME]), None, None, None)
        
        calendars = []
        
        l = cursor.getCount()
        i = 0
        while i < l:
            cursor.moveToPosition(i)
            calendars.add(cursor.getString(0))
            i += 1
        
        return calendars

We implement the following method to implement the View.OnClickListener interface. The method is called when the user clicks the export button.

    def onClick(self, view):
    
        if not self.running:
            self.exportButton.setText("Stop")
            self.textView.setText("")
            
            self.thread = Thread(self)
            self.thread.start()
        else:
            self.stopWorker()
    
    @args(void, [str])
    def addLogText(self, new_text):
    
        self.textView.setText(new_text)
    
    def stopWorker(self):
    
        self.running = False
        self.thread.join()
        self.exportButton.setText("Export calendar")
        
        if self.outputDir != "":
            self.textView.setText("Events saved in " + self.outputDir + "/" + \
                self.currentCalendar)

We define methods that run in the worker thread. The first of these sets an instance attribute to indicate that it is running and iterates over the events to be exported.

    def run(self):
    
        if self.outputDir == "":
            message = self.handler.obtainMessage(self.STOP_WORKER, None)
            message.sendToTarget()
            return
        
        message = self.handler.obtainMessage(
            self.ADD_LOG_TEXT, "Reading " + self.currentCalendar + "...")
        message.sendToTarget()
        
        resolver = self.getContentResolver()
        
        cursor = resolver.query(CalendarContract.Events.CONTENT_URI, None,
            CalendarContract.Calendars.ACCOUNT_NAME + "=?",
            array([self.currentCalendar]), None)
        
        self.running = True
        l = cursor.getCount()
        i = 0
        
        while i < l and self.running:
            cursor.moveToPosition(i)
            columns = cursor.getColumnCount()
            
            d = {}
            
            j = 0
            while j < columns:
                name = cursor.getColumnName(j)
                value = cursor.getString(j)
                
                # Avoid storing null values in the dictionary to prevent later
                # problems extracting them.
                if value != None:
                    d[name] = value
                
                j += 1
            i += 1
            
            self.saveFile(d["_id"], self.currentCalendar, d)
            
            message = self.handler.obtainMessage(
                self.ADD_LOG_TEXT, "Events saved: " + str(i))
            message.sendToTarget()
        
        message = self.handler.obtainMessage(self.STOP_WORKER, None)
        message.sendToTarget()

The following method creates a directory for this example in the external storage.

    @args(str, [])
    def createDir(self):
    
        if Environment.getExternalStorageState() != Environment.MEDIA_MOUNTED:
            return ""
        
        storageDir = Environment.getExternalStoragePublicDirectory(
            Environment.DIRECTORY_DOWNLOADS)
        
        dir = File(storageDir, "ExportCalendar")
        if not dir.exists():
            if not dir.mkdirs():
                return ""
        
        return str(dir)

The saveFile method is a simple utility method that creates and writes a file in the device's external storage area, creating a directory to hold the file if required.

    @args(void, [str, str, HashMap(str, str)])
    def saveFile(self, name, calendar, data):
    
        subdir = File(self.outputDir, calendar)
        if not subdir.exists():
            if not subdir.mkdirs():
                return
        
        f = File(subdir, name)
        
        stream = FileWriter(f, False)
        
        for key in data.keySet().toArray():
            
            stream.write(key + "\n")
            value = data[key]
            if value != None:
                stream.write(value + "\n\n")
        
        stream.flush()
        stream.close()


class MessageHandler(Handler):

    @args(void, [Looper, ExportCalendarActivity])
    def __init__(self, looper, activity):
    
        Handler.__init__(self, looper)
        self.activity = activity
    
    def handleMessage(self, inputMessage):
    
        if inputMessage.what == ExportCalendarActivity.ADD_LOG_TEXT:
            self.activity.addLogText(str(inputMessage.obj))
        
        elif inputMessage.what == ExportCalendarActivity.STOP_WORKER:
            self.activity.stopWorker()

Files