Android Open Source - SMSSentTime Sms Time Fix Service






From Project

Back to project page SMSSentTime.

License

The source code is released under:

GNU General Public License

If you think the Android project SMSSentTime listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

package at.zweng.smssenttimefix;
/*from  w  w w  . java  2 s  .  c om*/
import static at.zweng.smssenttimefix.Constants.COL_ADDRESS;
import static at.zweng.smssenttimefix.Constants.COL_BODY;
import static at.zweng.smssenttimefix.Constants.COL_DATE;
import static at.zweng.smssenttimefix.Constants.COL_ID;
import static at.zweng.smssenttimefix.Constants.COL_SERVICE_CENTER;
import static at.zweng.smssenttimefix.Constants.COL_TYPE;
import static at.zweng.smssenttimefix.Constants.CONTENT_SMS_URI;
import static at.zweng.smssenttimefix.Constants.DEBUG_ENABLED_DEFAULT_VALUE;
import static at.zweng.smssenttimefix.Constants.EXTRA_BODIES;
import static at.zweng.smssenttimefix.Constants.EXTRA_ORIGINATORS;
import static at.zweng.smssenttimefix.Constants.EXTRA_SC_ADRESSES;
import static at.zweng.smssenttimefix.Constants.EXTRA_SC_TIMESTAMPS;
import static at.zweng.smssenttimefix.Constants.PREF_DATE_FORMAT;
import static at.zweng.smssenttimefix.Constants.PREF_DBG_DEBUG_ENABLED;
import static at.zweng.smssenttimefix.Constants.PREF_DBG_FILENAME;
import static at.zweng.smssenttimefix.Constants.PREF_TEXT_TO_APPEND;
import static at.zweng.smssenttimefix.Constants.PREF_TIMEZONE_FIX_1;
import static at.zweng.smssenttimefix.Constants.PREF_USE24H_FORMAT;
import static at.zweng.smssenttimefix.Constants.TAG;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

import android.app.Service;
import android.content.ContentValues;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.util.Log;

/**
 * This service gets startet from the SMS broadcast receiver and correc ts the
 * time in the SMS database after the SMS got stored.
 * 
 * @author John Zweng
 * 
 */
public class SmsTimeFixService extends Service {

  private static final int NUM_RETRIES = 6;

  /**
   * SD card logger
   */
  private static SDCardLogger SD;

  /**
   * @see android.app.Service#onCreate()
   */
  @Override
  public void onCreate() {
    super.onCreate();
    SD = SDCardLogger.getLogger(this.getSharedPreferences(
        PREF_DBG_FILENAME, 0).getBoolean(PREF_DBG_DEBUG_ENABLED,
        DEBUG_ENABLED_DEFAULT_VALUE));
    SD.log("SmsTimeFixService: onCreate - Service instance created.");
  }

  /**
   * Our worker thread
   */
  private Thread workerThread = null;

  /**
   * Our work queue
   */
  private BlockingQueue<Intent> queue = new LinkedBlockingQueue<Intent>();

  /**
   * Constructor
   * 
   * @param name
   *            Used to name the worker thread, important only for debugging
   */
  public SmsTimeFixService() {
    super();
  }

  /**
   * Called when binding to this service
   * 
   * @see android.app.Service#onBind(android.content.Intent)
   */
  @Override
  public IBinder onBind(Intent intent) {
    // Nobody binds to this service
    return null;
  }

  /**
   * This is the old onStart method that will be called on the pre-2.0
   * platform. On 2.0 or later we override onStartCommand() so this method
   * will not be called.
   */
  @Override
  public void onStart(Intent intent, int startId) {
    // Log.d(TAG, "Service: Old (pre-2.0) onStart was called.");
    SD.log("SmsTimeFixService: onStart was called.");
    handleIntent(intent);
  }

  /**
   * This is the new onStart method (since 2.0 platform), called when the
   * service gets started.
   */
  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {
    // Log.d(TAG, "Service: New (post-2.0) onStartCommand was called.");
    SD.log("SmsTimeFixService: onStartCommand was called.");
    handleIntent(intent);
    // We want this service to be restarted again if
    // it gets killed before it has done its work.
    return START_REDELIVER_INTENT;
  }

  /**
   * Performs the work in a separate thread, we change the sms time stamp
   * here.
   * 
   * @see android.app.IntentService#onHandleIntent(android.content.Intent)
   */
  private void handleIntent(Intent intent) {
    // Log.d(TAG,
    // "Service: handleIntent was called. Will put intent in queue and start workerthread");
    SD.log("SmsTimeFixService: handleIntent was called.");

    try {
      SD.log("SmsTimeFixService: adding intent to work queue.");
      queue.put(intent);
    } catch (InterruptedException e) {
      // do nothing, should never happen as this queue has maximum
      // capacity and therefore we never shoud have to wait
      Log.w(TAG, "Catched exception while adding intent to workqueue", e);
      SD.log("SmsTimeFixService: Catched exception while adding intent to workqueue: "
          + e + ": " + e.getMessage());
    }
    if (workerThread == null || !workerThread.isAlive()) {
      workerThread = new WorkerThread();
      SD.log("SmsTimeFixService: Created new worker thread and will start it NOW.");
      workerThread.start();
    } else {
      SD.log("SmsTimeFixService: A worker thread exists already and is running. It will take the work from queue.");
    }
  }

  /**
   * @see android.app.IntentService#onDestroy()
   */
  @Override
  public void onDestroy() {
    Log.d(TAG, "Service: onDestroy was called! BYE BYE..");
    SD.log("SmsTimeFixService: onDestroy was called! BYE BYE!");
    super.onDestroy();
  }

  /**
   * Inner class as datastructure, represents one sms to fix
   * 
   * @author John Zweng
   * 
   */
  private class SmsToFix {

    private String scAddress;
    private String body;
    private String originator;
    private long scTimestamp;

    public SmsToFix(String scAddress, String body, String originator,
        long scTimestamp) {
      super();
      this.scAddress = scAddress;
      this.body = body;
      this.originator = originator;
      this.scTimestamp = scTimestamp;
    }

  }

  /**
   * Workerthread, performs the work in a separate thread.
   * 
   * @author John Zweng
   * 
   */
  private class WorkerThread extends Thread {

    public WorkerThread() {
      super();
      this.setName("Workerthread-SMSSentTimeFix");
      SD.log("SmsTimeFixService [WorkerThread]: Thread constructor.");
    }

    /**
     * Perform the SMS date fix in this separate thread
     */
    @Override
    public void run() {
      SD.log("SmsTimeFixService [WorkerThread]: Thread is starting now.");
      SD.log("SmsTimeFixService [WorkerThread]: There are "
          + queue.size() + " elements in the work queue.");
      Intent intentToProcess;
      // work as long we have intents in the workqueue.
      while ((intentToProcess = queue.poll()) != null) {
        SD.log("SmsTimeFixService [WorkerThread]: Yippie, got work from the queue!");
        Log.i(TAG, "Service: WorkerThread got some work from queue");

        try {
          List<SmsToFix> smsList = parseSmsFromIntent(intentToProcess);
          processSmsList(smsList);
        } catch (SmsFixException e) {
          Log.w(TAG,
              "Service: Some error occured during sms fix. Message was: "
                  + e.getMessage(), e);
          SD.log("SmsTimeFixService [WorkerThread]: An exception occured during sms fix. Message was: "
              + e + ": " + e.getMessage());
        }
      }
      Log.i(TAG,
          "Service: Workerthread: No more work in queue. Will exit thread and stop service. GOOD BYE!, Thread: "
              + Thread.currentThread().getName());
      SD.log("SmsTimeFixService [WorkerThread]: Found no more work in the queue. Will stop working and go for a beer now. BYE BYE..");
      stopSelf();
      workerThread = null;
    }

    /**
     * Gets out the extra data from the intent and constructs a list object
     * 
     * @param intent
     * @return list with sms to fix
     * @throws SmsFixException
     *             if something went wrong (eg. missing data in Intent)
     */
    private List<SmsToFix> parseSmsFromIntent(Intent intent)
        throws SmsFixException {
      try {
        List<SmsToFix> list = new ArrayList<SmsToFix>();
        String scAddress[] = intent
            .getStringArrayExtra(EXTRA_SC_ADRESSES);
        String body[] = intent.getStringArrayExtra(EXTRA_BODIES);
        String originator[] = intent
            .getStringArrayExtra(EXTRA_ORIGINATORS);
        long scTimestamp[] = intent
            .getLongArrayExtra(EXTRA_SC_TIMESTAMPS);

        for (int i = 0; i < body.length; i++) {
          SmsToFix fixMe = new SmsToFix(scAddress[i], body[i],
              originator[i], scTimestamp[i]);
          list.add(fixMe);
        }
        // Log.d(TAG, "Service: parseSmsFromIntent returns " +
        // list.size()
        // + " sms");
        return list;
      } catch (Exception e) {
        Log.w(TAG, "Service: parseSmsFromIntent catched an exception: "
            + e + " " + e.getMessage(), e);
        throw new SmsFixException(e.getMessage());
      }
    }

    /**
     * Preocess the list of sms to fix
     * 
     * @param list
     * @return <code>true</code> if all SMS could get fixed,
     *         <code>false</code> otherwise
     */
    private boolean processSmsList(List<SmsToFix> list) {
      // Log.d(TAG, "Service: processSmsList got " + list.size()
      // + " sms to fix.");
      for (int i = 0; i < NUM_RETRIES; i++) {
        SD.log("SmsTimeFixService [WorkerThread]: Waiting until SMS is inserted in database....");
        // wait some initial time, until sms is stored into database
        goSleeping(5000);
        SD.log("SmsTimeFixService [WorkerThread]: processSmsList starting run #"
            + i);
        Log.v(TAG, "Service: processSmsList starting run #" + i);

        for (SmsToFix sms : list) {
          if (fixSms(sms)) {
            list.remove(sms);
          }
        }
        if (list.isEmpty()) {
          Log.v(TAG, "Service: processSmsList after run #" + i
              + " list is empty and we will exit");
          SD.log("SmsTimeFixService [WorkerThread]: processSmsList after run #"
              + i + ": list is empty and we will exit");
          return true;
        } else {
          Log.v(TAG,
              "Service: processSmsList after run #"
                  + i
                  + " list still not empty and we will go sleeping and try again");
          SD.log("SmsTimeFixService [WorkerThread]: processSmsList after run #"
              + i
              + ": list still not empty and we will go sleeping and try again");

          // increase sleep duration: 5s, 10s, 15s, 20s, 25s
          int sleepDuration = 5000 + (5000 * i);
          goSleeping(sleepDuration);
        }
      }
      Log.d(TAG,
          "Service: processSmsList finished. Sms todo list has now length: "
              + list.size());
      SD.log("SmsTimeFixService [WorkerThread]: Sms todo list has now length: "
          + list.size());
      return (list.isEmpty());
    }

    /**
     * Fixes a single sms
     * 
     * @param sms
     * @return <code>true</code> if SMS could be found and fixed,
     *         <code>false</code> otherwise
     */
    private boolean fixSms(SmsToFix sms) {
      try {
        Cursor cur = findSmsInDb(sms);
        return updateSmsInDB(cur, sms);
      } catch (SmsNotFoundException se) {
        Log.w(TAG, "SMS not found in DB: " + se.getMessage());
        SD.log("SmsTimeFixService [WorkerThread]: SMS not found in DB: "
            + se.getMessage());
        return false;
      } catch (SmsFixException e) {
        Log.w(TAG, "Service: fix sms catched exception.", e);
        SD.log("SmsTimeFixService [WorkerThread]: fix sms catched an exception. "
            + e + ": " + e.getMessage());
        return false;
      }
    }

    /**
     * Tries to find a matching SMS in the database based on the given
     * infos. Only returns the id if the sms could be identified in DB
     * unambigous.
     * 
     * @param sms
     * @return the id of the sms in the SMS database
     * @throws SmsFixException
     *             if the sms could not be found
     */
    private Cursor findSmsInDb(SmsToFix sms) throws SmsFixException {
      Cursor cur = null;
      try {
        Uri smsUri = Uri.parse(CONTENT_SMS_URI);

        // ASSUMPTION: critical fields are not null and empty (body,
        // timestamp)
        // (we check this before in the filter method in the receiver)

        // if text is shorter than 145 chars, we do not use the LIKE
        // operator but = instead.
        //
        // (Info: We need the LIKE operator for
        // multi-sms messages, because we can search only with the text
        // of the first sms-part.
        // But on the other side we want prevent that "Hello" and
        // "Hallo there!" are matched (with the LIKE operator).
        // Therefore the length-based branch.)

        // Log.v(TAG, "Service: looking for SMS in DB: body:'" +
        // sms.body
        // + "', origin:" + sms.originator + ", scAddr:"
        // + sms.scAddress + ", timest:" + sms.scTimestamp);

        String selection;
        String selectionArgBody;
        String[] selectionArgs;

        // create the body selection string according to length
        String selBodyString;
        if (sms.body.length() < 145) {
          selBodyString = COL_BODY + "=?";
          selectionArgBody = sms.body;
        } else {
          selBodyString = COL_BODY + " LIKE ?";
          selectionArgBody = sms.body + "%";
        }
        // create selection string depending on which fields are
        // available
        if (sms.originator != null && sms.scAddress != null) {
          selection = COL_TYPE + "=? AND " + COL_ADDRESS + "=? AND "
              + COL_SERVICE_CENTER + "=? AND " + selBodyString;
          selectionArgs = new String[] { "1", sms.originator,
              sms.scAddress, selectionArgBody };
        } else if (sms.originator == null && sms.scAddress != null) {
          selection = COL_TYPE + "=? AND " + COL_SERVICE_CENTER
              + "=? AND " + selBodyString;
          selectionArgs = new String[] { "1", sms.scAddress,
              selectionArgBody };
        } else if (sms.originator != null && sms.scAddress == null) {
          selection = COL_TYPE + "=? AND " + COL_ADDRESS + "=? AND "
              + selBodyString;
          selectionArgs = new String[] { "1", sms.originator,
              selectionArgBody };
        }
        // both are null
        else {
          selection = COL_TYPE + "=? AND " + selBodyString;
          selectionArgs = new String[] { "1", selectionArgBody };
        }

        cur = getContentResolver().query(
            smsUri,
            new String[] { COL_ID, COL_ADDRESS, COL_DATE, COL_BODY,
                COL_SERVICE_CENTER }, selection, selectionArgs,
            "_id DESC LIMIT 1");
      } catch (Exception e) {
        // try to close the cursor
        if (cur != null) {
          try {
            cur.close();
          } catch (Exception e1) {
            // do nothing if fails
          }
        }
        throw new SmsFixException(
            "Service: Exception while looking for SMS in DB.", e);
      }
      if (cur.getCount() == 1) {
        return cur;
      } else {
        throw new SmsNotFoundException(
            "Service: SMS not found or ambigous: Cursor contained "
                + cur.getCount() + " results");
      }
    }

    /**
     * Tries to update the sms in the database with the correct timestamp.
     * We will close the cursor in this method afterwards.
     * 
     * @param cur
     *            cursor for update in database, should only contain one row
     * @param sms
     * @return <code>true</code> if everything went ok, <code>false</code>
     *         otherwise
     */
    private boolean updateSmsInDB(Cursor cur, SmsToFix sms)
        throws SmsFixException {
      // Log.d(TAG, "Service: updateSmsInDB called");
      try {
        cur.moveToFirst();
        int bodyColumn = cur.getColumnIndex(COL_BODY);
        int idColumn = cur.getColumnIndex(COL_ID);
        int id = cur.getInt(idColumn);
        String bodyTxt = cur.getString(bodyColumn);
        ContentValues values = new ContentValues();

        // Only if logging to SD is enabled (to save work otherwise)
        if (SD.isLoggingEnabled()) {
          SimpleDateFormat sdf = new SimpleDateFormat(
              "yyyy-MM-dd'T'HH:mm:ss.SSSZ");
          SD.log("SmsTimeFixService [WorkerThread]: SMS details: sent_time as received from the SMSC: '"
              + sdf.format(new Date(sms.scTimestamp))
              + "', SMSC-number: '"
              + sms.scAddress
              + "', originator: '" + sms.originator + "'");
        }
        String stringToAdd = getStringToAdd(
            correctTimestampOffset(sms.scTimestamp), sms.scAddress);
        values.put(COL_BODY, bodyTxt + " " + stringToAdd);

        if (SD.isLoggingEnabled()) {
          SD.log("SmsTimeFixService [WorkerThread]: Will add the following string: '"
              + stringToAdd + "'");
        }

        Uri smsUri = Uri.parse(CONTENT_SMS_URI);
        // Log.d(TAG,
        // "Service: updateSmsInDB: will update entry in DB now. MSG-Id: "
        // + id);
        SD.log("SmsTimeFixService [WorkerThread]: Will perform database update on SMS with id: "
            + id);
        getContentResolver().update(smsUri, values, COL_ID + "=?",
            new String[] { Integer.toString(id) });
        // Log.d(TAG, "Service: updateSmsInDB: update DONE!");
        return true;
      } catch (Exception e) {
        Log.w(TAG, "Service: Exception while DB update: ", e);
        SD.log("SmsTimeFixService [WorkerThread]: Sorry, there was an exception while updating the SMS DB: "
            + e + ": " + e.getMessage());
        return false;
      } finally {
        // try to close the cursor
        if (cur != null) {
          try {
            cur.close();
          } catch (Exception e1) {
            // do nothing if fails
          }
        }
      }
    }

    /**
     * Repairs the timestamp (correct the offset) if enabled in settings.
     * 
     * @param timestamp
     * @return
     */
    private long correctTimestampOffset(long timestamp) {
      SharedPreferences prefs = PreferenceManager
          .getDefaultSharedPreferences(getApplicationContext());
      Calendar cal = Calendar.getInstance();
      if (SD.isLoggingEnabled()) {
        SD.log("SmsTimeFixService [WorkerThread]: Timezone details: ZONE_OFFSET="
            + (cal.get(Calendar.ZONE_OFFSET) / 60000)
            + ", DST_OFFSET="
            + (cal.get(Calendar.DST_OFFSET) / 60000));
      }
      if (prefs.getBoolean(PREF_TIMEZONE_FIX_1, false)) {
        SD.log("SmsTimeFixService [WorkerThread]: Timezonefix#1 is ENABLED. Will modify SMS timestamp by timezone/DST offset.");
        timestamp = timestamp
            - (cal.get(Calendar.ZONE_OFFSET) + cal
                .get(Calendar.DST_OFFSET));
      } else {
        SD.log("SmsTimeFixService [WorkerThread]: Timezonefix#1 is DISABLED. Will keep SMS timestamp as is.");
      }
      return timestamp;
    }

    /**
     * Returns the string to add, according to apps prefernces
     * 
     * @param scTimestamp
     * @return
     */
    private String getStringToAdd(long scTimestamp, String smscAddr) {
      // Log.v(TAG, "Service: formatDateTime");
      SharedPreferences prefs = PreferenceManager
          .getDefaultSharedPreferences(getApplicationContext());
      Date smscTime = new Date(scTimestamp);
      String dateStr = new SimpleDateFormat(prefs.getString(
          PREF_DATE_FORMAT,
          getString(R.string.p_default_date_pattern)))
          .format(smscTime);
      SimpleDateFormat timeFormatter;
      if (prefs.getBoolean(PREF_USE24H_FORMAT, true)) {
        timeFormatter = new SimpleDateFormat("HH:mm:ss");
      } else {
        timeFormatter = new SimpleDateFormat("h:mm:ss a");
      }

      // check if smsc is null (like on emulator)
      if (smscAddr == null) {
        smscAddr = getString(R.string.g_smsc_unknown);
      }

      // Some tested defaults for "inspiration" :)
      // en-UK: "14 Feb 2011 17:34:53"
      // en-US: "Feb 14, 2011 5:38:27 PM"
      // de-AT/DE: "14.02.2011 17:40:29"

      String timeStr = timeFormatter.format(smscTime);
      String textToAppend = prefs.getString(PREF_TEXT_TO_APPEND,
          getString(R.string.p_default_text_append));
      textToAppend = textToAppend.replaceAll("%DATE%", dateStr);
      textToAppend = textToAppend.replaceAll("%TIME%", timeStr);
      textToAppend = textToAppend.replaceAll("%SMSC%", smscAddr);
      // Log.v(TAG, "Service: formatDateTime returning: " + textToAppend);
      return textToAppend;
    }

    /**
     * Send thread to sleep
     * 
     * @param ms
     *            milliseconds
     */
    private void goSleeping(int ms) {
      try {
        Thread.sleep(ms);
      } catch (InterruptedException e) {
        Log.w(TAG, "Service: Thread sleep was interrupted.");
      }
    }

  }

}




Java Source Code List

at.zweng.smssenttimefix.Constants.java
at.zweng.smssenttimefix.DebugSettingsActivity.java
at.zweng.smssenttimefix.SDCardLogger.java
at.zweng.smssenttimefix.SDNotMountedException.java
at.zweng.smssenttimefix.SmsFixException.java
at.zweng.smssenttimefix.SmsNotFoundException.java
at.zweng.smssenttimefix.SmsReceiver.java
at.zweng.smssenttimefix.SmsSentTimeFixActivity.java
at.zweng.smssenttimefix.SmsTimeFixPrefs.java
at.zweng.smssenttimefix.SmsTimeFixService.java
at.zweng.smssenttimefix.Util.java