diff -r 000000000000 -r 6474c204b198 embedding/android/GeckoSmsManager.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/embedding/android/GeckoSmsManager.java Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,988 @@ +/* -*- 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 java.util.ArrayList; +import java.util.Iterator; + +import android.util.Log; + +import android.app.PendingIntent; +import android.app.Activity; + +import android.database.Cursor; + +import android.content.Intent; +import android.content.IntentFilter; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.ContentUris; + +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 static android.telephony.SmsMessage.MessageClass; + +/** + * 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) { + class GetMessageRunnable implements Runnable { + private int mMessageId; + private int mRequestId; + + GetMessageRunnable(int aMessageId, int aRequestId) { + mMessageId = aMessageId; + mRequestId = aRequestId; + } + + @Override + public void run() { + 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")); + int deliveryStatus; + String sender = ""; + String receiver = ""; + + if (type == kSmsTypeInbox) { + deliveryStatus = kDeliveryStatusSuccess; + sender = cursor.getString(cursor.getColumnIndex("address")); + } else if (type == kSmsTypeSentbox) { + deliveryStatus = getGeckoDeliveryStatus(cursor.getInt(cursor.getColumnIndex("status"))); + receiver = cursor.getString(cursor.getColumnIndex("address")); + } else { + throw new InvalidTypeException(); + } + + GeckoAppShell.notifyGetSms(cursor.getInt(cursor.getColumnIndex("_id")), + deliveryStatus, + receiver, sender, + cursor.getString(cursor.getColumnIndex("body")), + cursor.getLong(cursor.getColumnIndex("date")), + mRequestId); + } catch (NotFoundException e) { + Log.i("GeckoSmsManager", "Message id " + mMessageId + " not found"); + GeckoAppShell.notifyGetSmsFailed(kNotFoundError, mRequestId); + } catch (UnmatchingIdException e) { + Log.e("GeckoSmsManager", "Requested message id (" + mMessageId + + ") is different from the one we got."); + GeckoAppShell.notifyGetSmsFailed(kUnknownError, mRequestId); + } catch (TooManyResultsException e) { + Log.e("GeckoSmsManager", "Get too many results for id " + mMessageId); + GeckoAppShell.notifyGetSmsFailed(kUnknownError, mRequestId); + } catch (InvalidTypeException e) { + Log.i("GeckoSmsManager", "Message has an invalid type, we ignore it."); + GeckoAppShell.notifyGetSmsFailed(kNotFoundError, mRequestId); + } catch (Exception e) { + Log.e("GeckoSmsManager", "Error while trying to get message: " + e); + GeckoAppShell.notifyGetSmsFailed(kUnknownError, mRequestId); + } finally { + if (cursor != null) { + cursor.close(); + } + } + } + } + + if (!SmsIOThread.getInstance().execute(new GetMessageRunnable(aMessageId, aRequestId))) { + Log.e("GeckoSmsManager", "Failed to add GetMessageRunnable to the SmsIOThread"); + GeckoAppShell.notifyGetSmsFailed(kUnknownError, aRequestId); + } + } + + public void deleteMessage(int aMessageId, int aRequestId) { + class DeleteMessageRunnable implements Runnable { + private int mMessageId; + private int mRequestId; + + DeleteMessageRunnable(int aMessageId, int aRequestId) { + mMessageId = aMessageId; + mRequestId = aRequestId; + } + + @Override + public void run() { + 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); + } catch (TooManyResultsException e) { + Log.e("GeckoSmsManager", "Delete more than one message? " + e); + GeckoAppShell.notifySmsDeleteFailed(kUnknownError, mRequestId); + } catch (Exception e) { + Log.e("GeckoSmsManager", "Error while trying to delete a message: " + e); + GeckoAppShell.notifySmsDeleteFailed(kUnknownError, mRequestId); + } + } + } + + if (!SmsIOThread.getInstance().execute(new DeleteMessageRunnable(aMessageId, aRequestId))) { + Log.e("GeckoSmsManager", "Failed to add GetMessageRunnable to the SmsIOThread"); + GeckoAppShell.notifySmsDeleteFailed(kUnknownError, aRequestId); + } + } + + public void createMessageList(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, int aDeliveryState, boolean aReverse, int aRequestId) { + 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; + + CreateMessageListRunnable(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, int aDeliveryState, boolean aReverse, int aRequestId) { + mStartDate = aStartDate; + mEndDate = aEndDate; + mNumbers = aNumbers; + mNumbersCount = aNumbersCount; + mDeliveryState = aDeliveryState; + mReverse = aReverse; + mRequestId = aRequestId; + } + + @Override + public void run() { + 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= kInternalDeliveryStatusFailed) { + return kDeliveryStatusError; + } + if (aDeliveryStatus >= kInternalDeliveryStatusPending) { + return kDeliveryStatusPending; + } + return kDeliveryStatusSuccess; + } + + private int getGeckoMessageClass(MessageClass aMessageClass) { + switch (aMessageClass) { + case UNKNOWN: + return kMessageClassNormal; + case CLASS_0: + return kMessageClassClass0; + case CLASS_1: + return kMessageClassClass1; + case CLASS_2: + return kMessageClassClass2; + case CLASS_3: + return kMessageClassClass3; + } + } + + class IdTooHighException extends Exception { + private static final long serialVersionUID = 395697882128640L; + } + + class InvalidTypeException extends Exception { + private static final long serialVersionUID = 23359904803795434L; + } + + class NotFoundException extends Exception { + private static final long serialVersionUID = 266226999371957426L; + } + + class TooManyResultsException extends Exception { + private static final long serialVersionUID = 48899777673841920L; + } + + class UnexpectedDeliveryStateException extends Exception { + private static final long serialVersionUID = 5044567998961920L; + } + + class UnmatchingIdException extends Exception { + private static final long serialVersionUID = 1935649715512128L; + } +}