Export SMS Example

This example shows how to export SMS messages 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
from android.content import Context
from android.net import Uri
from android.os import Environment, Handler, Looper
from android.provider import Telephony
from android.view import View
from android.widget import Button, ScrollView, TextView

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

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

The activity class represents the application. It will be used to present a graphical interface to the user.

class ExportSMSActivity(Activity):

    __interfaces__ = [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 creates an output directory to store messages in, and constructs a user interface consisting of a button and a scrollable text view.

    def onCreate(self, bundle):
    
        Activity.onCreate(self, bundle)
        
        self.outputDir = self.createDir()
        
        self.exportButton = Button(self)
        self.exportButton.setText("Export messages")
        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(self.exportButton)
        vbox.addView(self.scrollView)

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

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

    def onClick(self, view):
    
        if not self.running:

If the export button is pressed, we update the user interface to allow the export process to be stopped then start a new thread with this instance as its runnable object.

            self.exportButton.setText("Stop")
            self.textView.setText("")
            
            self.thread = Thread(self)
            self.thread.start()
        else:
            self.stopWorker()

The following method is called by the message handler to update the log view in the main GUI thread with information about exported messages.

    @args(void, [str])
    def addLogText(self, new_text):
    
        self.textView.setText(new_text)
    
    def stopWorker(self):

If the stop button is pressed, we ask the thread to stop by clearing an instance attribute, wait for it to finish, then reset the user interface.

        self.running = False
        self.thread.join()
        self.exportButton.setText("Export messages")
        
        if self.outputDir != "":
            self.textView.setText("Messages saved in " + self.outputDir)

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 messages to be exported.

    def run(self):
    
        if self.outputDir == "":
            message = self.handler.obtainMessage(self.STOP_WORKER, None)
            message.sendToTarget()
            return
        
        self.running = True
        
        message = self.handler.obtainMessage(
            self.ADD_LOG_TEXT, "Querying " + str(Telephony.Sms.CONTENT_URI))
        message.sendToTarget()
        
        resolver = self.getContentResolver()
        cursor = resolver.query(Telephony.Sms.CONTENT_URI, None, None, None, None)
        
        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)
                if cursor.getType(j) == cursor.FIELD_TYPE_BLOB:
                    value = "Blob in field " + name
                else:
                    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["thread_id"], d["date"] + ".sms", d)
            
            message = self.handler.obtainMessage(
                self.ADD_LOG_TEXT, "Messages 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, "ExportSMS")
        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 directories to hold the file if required.

    @args(void, [str, str, HashMap(str, str)])
    def saveFile(self, thread, name, data):
    
        subdir = File(self.outputDir, thread)
        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, ExportSMSActivity])
    def __init__(self, looper, activity):
    
        Handler.__init__(self, looper)
        self.activity = activity
    
    def handleMessage(self, inputMessage):
    
        if inputMessage.what == ExportSMSActivity.ADD_LOG_TEXT:
            self.activity.addLogText(str(inputMessage.obj))
        
        elif inputMessage.what == ExportSMSActivity.STOP_WORKER:
            self.activity.stopWorker()

Files