/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*- * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.mozilla.gecko; import android.app.Activity; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.telephony.SmsManager; import android.telephony.SmsMessage; import android.util.Log; import java.util.ArrayList; /** * This class is returning unique ids for PendingIntent requestCode attribute. * There are only |Integer.MAX_VALUE - Integer.MIN_VALUE| unique IDs available, * and they wrap around. */ class PendingIntentUID { static private int sUID = Integer.MIN_VALUE; static public int generate() { return sUID++; } } /** * The envelope class contains all information that are needed to keep track of * a sent SMS. */ class Envelope { enum SubParts { SENT_PART, DELIVERED_PART } protected int mId; protected int mMessageId; protected long mMessageTimestamp; /** * Number of sent/delivered remaining parts. * @note The array has much slots as SubParts items. */ protected int[] mRemainingParts; /** * Whether sending/delivering is currently failing. * @note The array has much slots as SubParts items. */ protected boolean[] mFailing; /** * Error type (only for sent). */ protected int mError; public Envelope(int aId, int aParts) { mId = aId; mMessageId = -1; mMessageTimestamp = 0; mError = GeckoSmsManager.kNoError; int size = Envelope.SubParts.values().length; mRemainingParts = new int[size]; mFailing = new boolean[size]; for (int i=0; i mRemainingParts[SubParts.DELIVERED_PART.ordinal()]) { Log.e("GeckoSmsManager", "Delivered more parts than we sent!?"); } } public boolean arePartsRemaining(Envelope.SubParts aType) { return mRemainingParts[aType.ordinal()] != 0; } public void markAsFailed(Envelope.SubParts aType) { mFailing[aType.ordinal()] = true; } public boolean isFailing(Envelope.SubParts aType) { return mFailing[aType.ordinal()]; } public int getMessageId() { return mMessageId; } public void setMessageId(int aMessageId) { mMessageId = aMessageId; } public long getMessageTimestamp() { return mMessageTimestamp; } public void setMessageTimestamp(long aMessageTimestamp) { mMessageTimestamp = aMessageTimestamp; } public int getError() { return mError; } public void setError(int aError) { mError = aError; } } /** * Postman class is a singleton that manages Envelope instances. */ class Postman { public static final int kUnknownEnvelopeId = -1; private static final Postman sInstance = new Postman(); private ArrayList mEnvelopes = new ArrayList(1); private Postman() {} public static Postman getInstance() { return sInstance; } public int createEnvelope(int aParts) { /* * We are going to create the envelope in the first empty slot in the array * list. If there is no empty slot, we create a new one. */ int size = mEnvelopes.size(); for (int i=0; i mCursors = new ArrayList(0); public int add(Cursor aCursor) { int size = mCursors.size(); for (int i=0; i parts = sm.divideMessage(aMessage); envelopeId = Postman.getInstance().createEnvelope(parts.size()); bundle.putInt("envelopeId", envelopeId); sentIntent.putExtras(bundle); deliveredIntent.putExtras(bundle); ArrayList sentPendingIntents = new ArrayList(parts.size()); ArrayList deliveredPendingIntents = new ArrayList(parts.size()); for (int i=0; i Integer.MAX_VALUE) { throw new IdTooHighException(); } return (int)id; } catch (IdTooHighException e) { Log.e("GeckoSmsManager", "The id we received is higher than the higher allowed value."); return -1; } catch (Exception e) { Log.e("GeckoSmsManager", "Something went wrong when trying to write a sent message", e); return -1; } } public void getMessage(int aMessageId, int aRequestId, long aProcessId) { class GetMessageRunnable implements Runnable { private int mMessageId; private int mRequestId; private long mProcessId; GetMessageRunnable(int aMessageId, int aRequestId, long aProcessId) { mMessageId = aMessageId; mRequestId = aRequestId; mProcessId = aProcessId; } @Override public void run() { class NotFoundException extends Exception { } class UnmatchingIdException extends Exception { } class TooManyResultsException extends Exception { } class InvalidTypeException extends Exception { } Cursor cursor = null; try { ContentResolver cr = GeckoApp.mAppContext.getContentResolver(); Uri message = ContentUris.withAppendedId(kSmsContentUri, mMessageId); cursor = cr.query(message, kRequiredMessageRows, null, null, null); if (cursor == null || cursor.getCount() == 0) { throw new NotFoundException(); } if (cursor.getCount() != 1) { throw new TooManyResultsException(); } cursor.moveToFirst(); if (cursor.getInt(cursor.getColumnIndex("_id")) != mMessageId) { throw new UnmatchingIdException(); } int type = cursor.getInt(cursor.getColumnIndex("type")); String sender = ""; String receiver = ""; if (type == kSmsTypeInbox) { sender = cursor.getString(cursor.getColumnIndex("address")); } else if (type == kSmsTypeSentbox) { receiver = cursor.getString(cursor.getColumnIndex("address")); } else { throw new InvalidTypeException(); } GeckoAppShell.notifyGetSms(cursor.getInt(cursor.getColumnIndex("_id")), receiver, sender, cursor.getString(cursor.getColumnIndex("body")), cursor.getLong(cursor.getColumnIndex("date")), mRequestId, mProcessId); } catch (NotFoundException e) { Log.i("GeckoSmsManager", "Message id " + mMessageId + " not found"); GeckoAppShell.notifyGetSmsFailed(kNotFoundError, mRequestId, mProcessId); } catch (UnmatchingIdException e) { Log.e("GeckoSmsManager", "Requested message id (" + mMessageId + ") is different from the one we got."); GeckoAppShell.notifyGetSmsFailed(kUnknownError, mRequestId, mProcessId); } catch (TooManyResultsException e) { Log.e("GeckoSmsManager", "Get too many results for id " + mMessageId); GeckoAppShell.notifyGetSmsFailed(kUnknownError, mRequestId, mProcessId); } catch (InvalidTypeException e) { Log.i("GeckoSmsManager", "Message has an invalid type, we ignore it."); GeckoAppShell.notifyGetSmsFailed(kNotFoundError, mRequestId, mProcessId); } catch (Exception e) { Log.e("GeckoSmsManager", "Error while trying to get message", e); GeckoAppShell.notifyGetSmsFailed(kUnknownError, mRequestId, mProcessId); } finally { if (cursor != null) { cursor.close(); } } } } if (!SmsIOThread.getInstance().execute(new GetMessageRunnable(aMessageId, aRequestId, aProcessId))) { Log.e("GeckoSmsManager", "Failed to add GetMessageRunnable to the SmsIOThread"); GeckoAppShell.notifyGetSmsFailed(kUnknownError, aRequestId, aProcessId); } } public void deleteMessage(int aMessageId, int aRequestId, long aProcessId) { class DeleteMessageRunnable implements Runnable { private int mMessageId; private int mRequestId; private long mProcessId; DeleteMessageRunnable(int aMessageId, int aRequestId, long aProcessId) { mMessageId = aMessageId; mRequestId = aRequestId; mProcessId = aProcessId; } @Override public void run() { class TooManyResultsException extends Exception { } try { ContentResolver cr = GeckoApp.mAppContext.getContentResolver(); Uri message = ContentUris.withAppendedId(kSmsContentUri, mMessageId); int count = cr.delete(message, null, null); if (count > 1) { throw new TooManyResultsException(); } GeckoAppShell.notifySmsDeleted(count == 1, mRequestId, mProcessId); } catch (TooManyResultsException e) { Log.e("GeckoSmsManager", "Delete more than one message?", e); GeckoAppShell.notifySmsDeleteFailed(kUnknownError, mRequestId, mProcessId); } catch (Exception e) { Log.e("GeckoSmsManager", "Error while trying to delete a message", e); GeckoAppShell.notifySmsDeleteFailed(kUnknownError, mRequestId, mProcessId); } } } if (!SmsIOThread.getInstance().execute(new DeleteMessageRunnable(aMessageId, aRequestId, aProcessId))) { Log.e("GeckoSmsManager", "Failed to add GetMessageRunnable to the SmsIOThread"); GeckoAppShell.notifySmsDeleteFailed(kUnknownError, aRequestId, aProcessId); } } public void createMessageList(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, int aDeliveryState, boolean aReverse, int aRequestId, long aProcessId) { class CreateMessageListRunnable implements Runnable { private long mStartDate; private long mEndDate; private String[] mNumbers; private int mNumbersCount; private int mDeliveryState; private boolean mReverse; private int mRequestId; private long mProcessId; CreateMessageListRunnable(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, int aDeliveryState, boolean aReverse, int aRequestId, long aProcessId) { mStartDate = aStartDate; mEndDate = aEndDate; mNumbers = aNumbers; mNumbersCount = aNumbersCount; mDeliveryState = aDeliveryState; mReverse = aReverse; mRequestId = aRequestId; mProcessId = aProcessId; } @Override public void run() { class UnexpectedDeliveryStateException extends Exception { }; class InvalidTypeException extends Exception { } Cursor cursor = null; boolean closeCursor = true; try { // TODO: should use the |selectionArgs| argument in |ContentResolver.query()|. ArrayList restrictions = new ArrayList(); if (mStartDate != 0) { restrictions.add("date >= " + mStartDate); } if (mEndDate != 0) { restrictions.add("date <= " + mEndDate); } if (mNumbersCount > 0) { String numberRestriction = "address IN ('" + mNumbers[0] + "'"; for (int i=1; i