summaryrefslogtreecommitdiffstats
path: root/src/com/android/messaging/sms
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/messaging/sms')
-rw-r--r--src/com/android/messaging/sms/ApnDatabase.java374
-rw-r--r--src/com/android/messaging/sms/ApnsXmlProcessor.java329
-rw-r--r--src/com/android/messaging/sms/BugleApnSettingsLoader.java646
-rw-r--r--src/com/android/messaging/sms/BugleCarrierConfigValuesLoader.java201
-rw-r--r--src/com/android/messaging/sms/BugleUserAgentInfoLoader.java96
-rw-r--r--src/com/android/messaging/sms/DatabaseMessages.java1006
-rwxr-xr-xsrc/com/android/messaging/sms/MmsConfig.java309
-rw-r--r--src/com/android/messaging/sms/MmsFailureException.java102
-rw-r--r--src/com/android/messaging/sms/MmsSender.java312
-rw-r--r--src/com/android/messaging/sms/MmsSmsUtils.java204
-rw-r--r--src/com/android/messaging/sms/MmsUtils.java2747
-rw-r--r--src/com/android/messaging/sms/SmsException.java59
-rw-r--r--src/com/android/messaging/sms/SmsReleaseStorage.java166
-rw-r--r--src/com/android/messaging/sms/SmsSender.java315
-rw-r--r--src/com/android/messaging/sms/SmsStorageStatusManager.java102
-rw-r--r--src/com/android/messaging/sms/SystemProperties.java54
16 files changed, 0 insertions, 7022 deletions
diff --git a/src/com/android/messaging/sms/ApnDatabase.java b/src/com/android/messaging/sms/ApnDatabase.java
deleted file mode 100644
index a8d0d0c..0000000
--- a/src/com/android/messaging/sms/ApnDatabase.java
+++ /dev/null
@@ -1,374 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.sms;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.XmlResourceParser;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteException;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.provider.Telephony;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.PhoneUtils;
-import com.google.common.collect.Lists;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-
-/*
- * Database helper class for looking up APNs. This database has a single table
- * which stores the APNs that are initially created from an xml file.
- */
-public class ApnDatabase extends SQLiteOpenHelper {
- private static final int DB_VERSION = 3; // added sub_id columns
-
- private static final String TAG = LogUtil.BUGLE_TAG;
-
- private static final boolean DEBUG = false;
-
- private static Context sContext;
- private static ApnDatabase sApnDatabase;
-
- private static final String APN_DATABASE_NAME = "apn.db";
-
- /** table for carrier APN's */
- public static final String APN_TABLE = "apn";
-
- // APN table
- private static final String APN_TABLE_SQL =
- "CREATE TABLE " + APN_TABLE +
- "(_id INTEGER PRIMARY KEY," +
- Telephony.Carriers.NAME + " TEXT," +
- Telephony.Carriers.NUMERIC + " TEXT," +
- Telephony.Carriers.MCC + " TEXT," +
- Telephony.Carriers.MNC + " TEXT," +
- Telephony.Carriers.APN + " TEXT," +
- Telephony.Carriers.USER + " TEXT," +
- Telephony.Carriers.SERVER + " TEXT," +
- Telephony.Carriers.PASSWORD + " TEXT," +
- Telephony.Carriers.PROXY + " TEXT," +
- Telephony.Carriers.PORT + " TEXT," +
- Telephony.Carriers.MMSPROXY + " TEXT," +
- Telephony.Carriers.MMSPORT + " TEXT," +
- Telephony.Carriers.MMSC + " TEXT," +
- Telephony.Carriers.AUTH_TYPE + " INTEGER," +
- Telephony.Carriers.TYPE + " TEXT," +
- Telephony.Carriers.CURRENT + " INTEGER," +
- Telephony.Carriers.PROTOCOL + " TEXT," +
- Telephony.Carriers.ROAMING_PROTOCOL + " TEXT," +
- Telephony.Carriers.CARRIER_ENABLED + " BOOLEAN," +
- Telephony.Carriers.BEARER + " INTEGER," +
- Telephony.Carriers.MVNO_TYPE + " TEXT," +
- Telephony.Carriers.MVNO_MATCH_DATA + " TEXT," +
- Telephony.Carriers.SUBSCRIPTION_ID + " INTEGER DEFAULT " +
- ParticipantData.DEFAULT_SELF_SUB_ID + ");";
-
- public static final String[] APN_PROJECTION = {
- Telephony.Carriers.TYPE, // 0
- Telephony.Carriers.MMSC, // 1
- Telephony.Carriers.MMSPROXY, // 2
- Telephony.Carriers.MMSPORT, // 3
- Telephony.Carriers._ID, // 4
- Telephony.Carriers.CURRENT, // 5
- Telephony.Carriers.NUMERIC, // 6
- Telephony.Carriers.NAME, // 7
- Telephony.Carriers.MCC, // 8
- Telephony.Carriers.MNC, // 9
- Telephony.Carriers.APN, // 10
- Telephony.Carriers.SUBSCRIPTION_ID // 11
- };
-
- public static final int COLUMN_TYPE = 0;
- public static final int COLUMN_MMSC = 1;
- public static final int COLUMN_MMSPROXY = 2;
- public static final int COLUMN_MMSPORT = 3;
- public static final int COLUMN_ID = 4;
- public static final int COLUMN_CURRENT = 5;
- public static final int COLUMN_NUMERIC = 6;
- public static final int COLUMN_NAME = 7;
- public static final int COLUMN_MCC = 8;
- public static final int COLUMN_MNC = 9;
- public static final int COLUMN_APN = 10;
- public static final int COLUMN_SUB_ID = 11;
-
- public static final String[] APN_FULL_PROJECTION = {
- Telephony.Carriers.NAME,
- Telephony.Carriers.MCC,
- Telephony.Carriers.MNC,
- Telephony.Carriers.APN,
- Telephony.Carriers.USER,
- Telephony.Carriers.SERVER,
- Telephony.Carriers.PASSWORD,
- Telephony.Carriers.PROXY,
- Telephony.Carriers.PORT,
- Telephony.Carriers.MMSC,
- Telephony.Carriers.MMSPROXY,
- Telephony.Carriers.MMSPORT,
- Telephony.Carriers.AUTH_TYPE,
- Telephony.Carriers.TYPE,
- Telephony.Carriers.PROTOCOL,
- Telephony.Carriers.ROAMING_PROTOCOL,
- Telephony.Carriers.CARRIER_ENABLED,
- Telephony.Carriers.BEARER,
- Telephony.Carriers.MVNO_TYPE,
- Telephony.Carriers.MVNO_MATCH_DATA,
- Telephony.Carriers.CURRENT,
- Telephony.Carriers.SUBSCRIPTION_ID,
- };
-
- private static final String CURRENT_SELECTION = Telephony.Carriers.CURRENT + " NOT NULL";
-
- /**
- * ApnDatabase is initialized asynchronously from the application.onCreate
- * To ensure that it works in a testing environment it needs to never access the factory context
- */
- public static void initializeAppContext(final Context context) {
- sContext = context;
- }
-
- private ApnDatabase() {
- super(sContext, APN_DATABASE_NAME, null, DB_VERSION);
- if (DEBUG) {
- LogUtil.d(TAG, "ApnDatabase constructor");
- }
- }
-
- public static ApnDatabase getApnDatabase() {
- if (sApnDatabase == null) {
- sApnDatabase = new ApnDatabase();
- }
- return sApnDatabase;
- }
-
- public static boolean doesDatabaseExist() {
- final File dbFile = sContext.getDatabasePath(APN_DATABASE_NAME);
- return dbFile.exists();
- }
-
- @Override
- public void onCreate(final SQLiteDatabase db) {
- if (DEBUG) {
- LogUtil.d(TAG, "ApnDatabase onCreate");
- }
- // Build the table using defaults (apn info bundled with the app)
- rebuildTables(db);
- }
-
- /**
- * Get a copy of user changes in the old table
- *
- * @return The list of user changed apns
- */
- public static List<ContentValues> loadUserDataFromOldTable(final SQLiteDatabase db) {
- Cursor cursor = null;
- try {
- cursor = db.query(APN_TABLE,
- APN_FULL_PROJECTION, CURRENT_SELECTION,
- null/*selectionArgs*/,
- null/*groupBy*/, null/*having*/, null/*orderBy*/);
- if (cursor != null) {
- final List<ContentValues> result = Lists.newArrayList();
- while (cursor.moveToNext()) {
- final ContentValues row = cursorToValues(cursor);
- if (row != null) {
- result.add(row);
- }
- }
- return result;
- }
- } catch (final SQLiteException e) {
- LogUtil.w(TAG, "ApnDatabase.loadUserDataFromOldTable: no old user data: " + e, e);
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- return null;
- }
-
- private static final String[] ID_PROJECTION = new String[]{Telephony.Carriers._ID};
-
- private static final String ID_SELECTION = Telephony.Carriers._ID + "=?";
-
- /**
- * Store use changes of old table into the new apn table
- *
- * @param data The user changes
- */
- public static void saveUserDataFromOldTable(
- final SQLiteDatabase db, final List<ContentValues> data) {
- if (data == null || data.size() < 1) {
- return;
- }
- for (final ContentValues row : data) {
- // Build query from the row data. It is an exact match, column by column,
- // except the CURRENT column
- final StringBuilder selectionBuilder = new StringBuilder();
- final ArrayList<String> selectionArgs = Lists.newArrayList();
- for (final String key : row.keySet()) {
- if (!Telephony.Carriers.CURRENT.equals(key)) {
- if (selectionBuilder.length() > 0) {
- selectionBuilder.append(" AND ");
- }
- final String value = row.getAsString(key);
- if (TextUtils.isEmpty(value)) {
- selectionBuilder.append(key).append(" IS NULL");
- } else {
- selectionBuilder.append(key).append("=?");
- selectionArgs.add(value);
- }
- }
- }
- Cursor cursor = null;
- try {
- cursor = db.query(APN_TABLE,
- ID_PROJECTION,
- selectionBuilder.toString(),
- selectionArgs.toArray(new String[0]),
- null/*groupBy*/, null/*having*/, null/*orderBy*/);
- if (cursor != null && cursor.moveToFirst()) {
- db.update(APN_TABLE, row, ID_SELECTION, new String[]{cursor.getString(0)});
- } else {
- // User APN does not exist, insert into the new table
- row.put(Telephony.Carriers.NUMERIC,
- PhoneUtils.canonicalizeMccMnc(
- row.getAsString(Telephony.Carriers.MCC),
- row.getAsString(Telephony.Carriers.MNC))
- );
- db.insert(APN_TABLE, null/*nullColumnHack*/, row);
- }
- } catch (final SQLiteException e) {
- LogUtil.e(TAG, "ApnDatabase.saveUserDataFromOldTable: query error " + e, e);
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- }
- }
-
- // Convert Cursor to ContentValues
- private static ContentValues cursorToValues(final Cursor cursor) {
- final int columnCount = cursor.getColumnCount();
- if (columnCount > 0) {
- final ContentValues result = new ContentValues();
- for (int i = 0; i < columnCount; i++) {
- final String name = cursor.getColumnName(i);
- final String value = cursor.getString(i);
- result.put(name, value);
- }
- return result;
- }
- return null;
- }
-
- @Override
- public void onOpen(final SQLiteDatabase db) {
- super.onOpen(db);
- if (DEBUG) {
- LogUtil.d(TAG, "ApnDatabase onOpen");
- }
- }
-
- @Override
- public void close() {
- super.close();
- if (DEBUG) {
- LogUtil.d(TAG, "ApnDatabase close");
- }
- }
-
- private void rebuildTables(final SQLiteDatabase db) {
- if (DEBUG) {
- LogUtil.d(TAG, "ApnDatabase rebuildTables");
- }
- db.execSQL("DROP TABLE IF EXISTS " + APN_TABLE + ";");
- db.execSQL(APN_TABLE_SQL);
- loadApnTable(db);
- }
-
- @Override
- public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {
- if (DEBUG) {
- LogUtil.d(TAG, "ApnDatabase onUpgrade");
- }
- rebuildTables(db);
- }
-
- @Override
- public void onDowngrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {
- if (DEBUG) {
- LogUtil.d(TAG, "ApnDatabase onDowngrade");
- }
- rebuildTables(db);
- }
-
- /**
- * Load APN table from app resources
- */
- private static void loadApnTable(final SQLiteDatabase db) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "ApnDatabase loadApnTable");
- }
- final Resources r = sContext.getResources();
- final XmlResourceParser parser = r.getXml(R.xml.apns);
- final ApnsXmlProcessor processor = ApnsXmlProcessor.get(parser);
- processor.setApnHandler(new ApnsXmlProcessor.ApnHandler() {
- @Override
- public void process(final ContentValues apnValues) {
- db.insert(APN_TABLE, null/*nullColumnHack*/, apnValues);
- }
- });
- try {
- processor.process();
- } catch (final Exception e) {
- Log.e(TAG, "Got exception while loading APN database.", e);
- } finally {
- parser.close();
- }
- }
-
- public static void forceBuildAndLoadApnTables() {
- final SQLiteDatabase db = getApnDatabase().getWritableDatabase();
- db.execSQL("DROP TABLE IF EXISTS " + APN_TABLE);
- // Table(s) always need for JB MR1 for APN support for MMS because JB MR1 throws
- // a SecurityException when trying to access the carriers table (which holds the
- // APNs). Some JB MR2 devices also throw the security exception, so we're building
- // the table for JB MR2, too.
- db.execSQL(APN_TABLE_SQL);
-
- loadApnTable(db);
- }
-
- /**
- * Clear all tables
- */
- public static void clearTables() {
- final SQLiteDatabase db = getApnDatabase().getWritableDatabase();
- db.execSQL("DROP TABLE IF EXISTS " + APN_TABLE);
- db.execSQL(APN_TABLE_SQL);
- }
-}
diff --git a/src/com/android/messaging/sms/ApnsXmlProcessor.java b/src/com/android/messaging/sms/ApnsXmlProcessor.java
deleted file mode 100644
index 976896c..0000000
--- a/src/com/android/messaging/sms/ApnsXmlProcessor.java
+++ /dev/null
@@ -1,329 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.sms;
-
-import android.content.ContentValues;
-import android.provider.Telephony;
-
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.PhoneUtils;
-import com.google.common.collect.Maps;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.util.Map;
-
-/*
- * XML processor for the following files:
- * 1. res/xml/apns.xml
- * 2. res/xml/mms_config.xml (or related overlay files)
- */
-class ApnsXmlProcessor {
- public interface ApnHandler {
- public void process(ContentValues apnValues);
- }
-
- public interface MmsConfigHandler {
- public void process(String mccMnc, String key, String value, String type);
- }
-
- private static final String TAG = LogUtil.BUGLE_TAG;
-
- private static final Map<String, String> APN_ATTRIBUTE_MAP = Maps.newHashMap();
- static {
- APN_ATTRIBUTE_MAP.put("mcc", Telephony.Carriers.MCC);
- APN_ATTRIBUTE_MAP.put("mnc", Telephony.Carriers.MNC);
- APN_ATTRIBUTE_MAP.put("carrier", Telephony.Carriers.NAME);
- APN_ATTRIBUTE_MAP.put("apn", Telephony.Carriers.APN);
- APN_ATTRIBUTE_MAP.put("mmsc", Telephony.Carriers.MMSC);
- APN_ATTRIBUTE_MAP.put("mmsproxy", Telephony.Carriers.MMSPROXY);
- APN_ATTRIBUTE_MAP.put("mmsport", Telephony.Carriers.MMSPORT);
- APN_ATTRIBUTE_MAP.put("type", Telephony.Carriers.TYPE);
- APN_ATTRIBUTE_MAP.put("user", Telephony.Carriers.USER);
- APN_ATTRIBUTE_MAP.put("password", Telephony.Carriers.PASSWORD);
- APN_ATTRIBUTE_MAP.put("authtype", Telephony.Carriers.AUTH_TYPE);
- APN_ATTRIBUTE_MAP.put("mvno_match_data", Telephony.Carriers.MVNO_MATCH_DATA);
- APN_ATTRIBUTE_MAP.put("mvno_type", Telephony.Carriers.MVNO_TYPE);
- APN_ATTRIBUTE_MAP.put("protocol", Telephony.Carriers.PROTOCOL);
- APN_ATTRIBUTE_MAP.put("bearer", Telephony.Carriers.BEARER);
- APN_ATTRIBUTE_MAP.put("server", Telephony.Carriers.SERVER);
- APN_ATTRIBUTE_MAP.put("roaming_protocol", Telephony.Carriers.ROAMING_PROTOCOL);
- APN_ATTRIBUTE_MAP.put("proxy", Telephony.Carriers.PROXY);
- APN_ATTRIBUTE_MAP.put("port", Telephony.Carriers.PORT);
- APN_ATTRIBUTE_MAP.put("carrier_enabled", Telephony.Carriers.CARRIER_ENABLED);
- }
-
- private static final String TAG_APNS = "apns";
- private static final String TAG_APN = "apn";
- private static final String TAG_MMS_CONFIG = "mms_config";
-
- // Handler to process one apn
- private ApnHandler mApnHandler;
- // Handler to process one mms_config key/value pair
- private MmsConfigHandler mMmsConfigHandler;
-
- private final StringBuilder mLogStringBuilder = new StringBuilder();
-
- private final XmlPullParser mInputParser;
-
- private ApnsXmlProcessor(XmlPullParser parser) {
- mInputParser = parser;
- mApnHandler = null;
- mMmsConfigHandler = null;
- }
-
- public static ApnsXmlProcessor get(XmlPullParser parser) {
- Assert.notNull(parser);
- return new ApnsXmlProcessor(parser);
- }
-
- public ApnsXmlProcessor setApnHandler(ApnHandler handler) {
- mApnHandler = handler;
- return this;
- }
-
- public ApnsXmlProcessor setMmsConfigHandler(MmsConfigHandler handler) {
- mMmsConfigHandler = handler;
- return this;
- }
-
- /**
- * Move XML parser forward to next event type or the end of doc
- *
- * @param eventType
- * @return The final event type we meet
- * @throws XmlPullParserException
- * @throws IOException
- */
- private int advanceToNextEvent(int eventType) throws XmlPullParserException, IOException {
- for (;;) {
- int nextEvent = mInputParser.next();
- if (nextEvent == eventType
- || nextEvent == XmlPullParser.END_DOCUMENT) {
- return nextEvent;
- }
- }
- }
-
- public void process() {
- try {
- // Find the first element
- if (advanceToNextEvent(XmlPullParser.START_TAG) != XmlPullParser.START_TAG) {
- throw new XmlPullParserException("ApnsXmlProcessor: expecting start tag @"
- + xmlParserDebugContext());
- }
- // A single ContentValues object for holding the parsing result of
- // an apn element
- final ContentValues values = new ContentValues();
- String tagName = mInputParser.getName();
- // Top level tag can be "apns" (apns.xml)
- // or "mms_config" (mms_config.xml)
- if (TAG_APNS.equals(tagName)) {
- // For "apns", there could be "apn" or both "apn" and "mms_config"
- for (;;) {
- if (advanceToNextEvent(XmlPullParser.START_TAG) != XmlPullParser.START_TAG) {
- break;
- }
- tagName = mInputParser.getName();
- if (TAG_APN.equals(tagName)) {
- processApn(values);
- } else if (TAG_MMS_CONFIG.equals(tagName)) {
- processMmsConfig();
- }
- }
- } else if (TAG_MMS_CONFIG.equals(tagName)) {
- // mms_config.xml resource
- processMmsConfig();
- }
- } catch (IOException e) {
- LogUtil.e(TAG, "ApnsXmlProcessor: I/O failure " + e, e);
- } catch (XmlPullParserException e) {
- LogUtil.e(TAG, "ApnsXmlProcessor: parsing failure " + e, e);
- }
- }
-
- private Integer parseInt(String text, Integer defaultValue, String logHint) {
- Integer value = defaultValue;
- try {
- value = Integer.parseInt(text);
- } catch (Exception e) {
- LogUtil.e(TAG,
- "Invalid value " + text + "for" + logHint + " @" + xmlParserDebugContext());
- }
- return value;
- }
-
- private Boolean parseBoolean(String text, Boolean defaultValue, String logHint) {
- Boolean value = defaultValue;
- try {
- value = Boolean.parseBoolean(text);
- } catch (Exception e) {
- LogUtil.e(TAG,
- "Invalid value " + text + "for" + logHint + " @" + xmlParserDebugContext());
- }
- return value;
- }
-
- private static String xmlParserEventString(int event) {
- switch (event) {
- case XmlPullParser.START_DOCUMENT: return "START_DOCUMENT";
- case XmlPullParser.END_DOCUMENT: return "END_DOCUMENT";
- case XmlPullParser.START_TAG: return "START_TAG";
- case XmlPullParser.END_TAG: return "END_TAG";
- case XmlPullParser.TEXT: return "TEXT";
- }
- return Integer.toString(event);
- }
-
- /**
- * @return The debugging information of the parser's current position
- */
- private String xmlParserDebugContext() {
- mLogStringBuilder.setLength(0);
- if (mInputParser != null) {
- try {
- final int eventType = mInputParser.getEventType();
- mLogStringBuilder.append(xmlParserEventString(eventType));
- if (eventType == XmlPullParser.START_TAG
- || eventType == XmlPullParser.END_TAG
- || eventType == XmlPullParser.TEXT) {
- mLogStringBuilder.append('<').append(mInputParser.getName());
- for (int i = 0; i < mInputParser.getAttributeCount(); i++) {
- mLogStringBuilder.append(' ')
- .append(mInputParser.getAttributeName(i))
- .append('=')
- .append(mInputParser.getAttributeValue(i));
- }
- mLogStringBuilder.append("/>");
- }
- return mLogStringBuilder.toString();
- } catch (XmlPullParserException e) {
- LogUtil.e(TAG, "xmlParserDebugContext: " + e, e);
- }
- }
- return "Unknown";
- }
-
- /**
- * Process one apn
- *
- * @param apnValues Where we store the parsed apn
- * @throws IOException
- * @throws XmlPullParserException
- */
- private void processApn(ContentValues apnValues) throws IOException, XmlPullParserException {
- Assert.notNull(apnValues);
- apnValues.clear();
- // Collect all the attributes
- for (int i = 0; i < mInputParser.getAttributeCount(); i++) {
- final String key = APN_ATTRIBUTE_MAP.get(mInputParser.getAttributeName(i));
- if (key != null) {
- apnValues.put(key, mInputParser.getAttributeValue(i));
- }
- }
- // Set numeric to be canonicalized mcc/mnc like "310120", always 6 digits
- final String canonicalMccMnc = PhoneUtils.canonicalizeMccMnc(
- apnValues.getAsString(Telephony.Carriers.MCC),
- apnValues.getAsString(Telephony.Carriers.MNC));
- apnValues.put(Telephony.Carriers.NUMERIC, canonicalMccMnc);
- // Some of the values should not be string type, converting them to desired types
- final String authType = apnValues.getAsString(Telephony.Carriers.AUTH_TYPE);
- if (authType != null) {
- apnValues.put(Telephony.Carriers.AUTH_TYPE, parseInt(authType, -1, "apn authtype"));
- }
- final String carrierEnabled = apnValues.getAsString(Telephony.Carriers.CARRIER_ENABLED);
- if (carrierEnabled != null) {
- apnValues.put(Telephony.Carriers.CARRIER_ENABLED,
- parseBoolean(carrierEnabled, null, "apn carrierEnabled"));
- }
- final String bearer = apnValues.getAsString(Telephony.Carriers.BEARER);
- if (bearer != null) {
- apnValues.put(Telephony.Carriers.BEARER, parseInt(bearer, 0, "apn bearer"));
- }
- // We are at the end tag
- if (mInputParser.next() != XmlPullParser.END_TAG) {
- throw new XmlPullParserException("Apn: expecting end tag @"
- + xmlParserDebugContext());
- }
- // We are done parsing one APN, call the handler
- if (mApnHandler != null) {
- mApnHandler.process(apnValues);
- }
- }
-
- /**
- * Process one mms_config.
- *
- * @throws IOException
- * @throws XmlPullParserException
- */
- private void processMmsConfig()
- throws IOException, XmlPullParserException {
- // Get the mcc and mnc attributes
- final String canonicalMccMnc = PhoneUtils.canonicalizeMccMnc(
- mInputParser.getAttributeValue(null, "mcc"),
- mInputParser.getAttributeValue(null, "mnc"));
- // We are at the start tag
- for (;;) {
- int nextEvent;
- // Skipping spaces
- while ((nextEvent = mInputParser.next()) == XmlPullParser.TEXT) {
- }
- if (nextEvent == XmlPullParser.START_TAG) {
- // Parse one mms config key/value
- processMmsConfigKeyValue(canonicalMccMnc);
- } else if (nextEvent == XmlPullParser.END_TAG) {
- break;
- } else {
- throw new XmlPullParserException("MmsConfig: expecting start or end tag @"
- + xmlParserDebugContext());
- }
- }
- }
-
- /**
- * Process one mms_config key/value pair
- *
- * @param mccMnc The mcc and mnc of this mms_config
- * @throws IOException
- * @throws XmlPullParserException
- */
- private void processMmsConfigKeyValue(String mccMnc)
- throws IOException, XmlPullParserException {
- final String key = mInputParser.getAttributeValue(null, "name");
- // We are at the start tag, the name of the tag is the type
- // e.g. <int name="key">value</int>
- final String type = mInputParser.getName();
- int nextEvent = mInputParser.next();
- String value = null;
- if (nextEvent == XmlPullParser.TEXT) {
- value = mInputParser.getText();
- nextEvent = mInputParser.next();
- }
- if (nextEvent != XmlPullParser.END_TAG) {
- throw new XmlPullParserException("ApnsXmlProcessor: expecting end tag @"
- + xmlParserDebugContext());
- }
- // We are done parsing one mms_config key/value, call the handler
- if (mMmsConfigHandler != null) {
- mMmsConfigHandler.process(mccMnc, key, value, type);
- }
- }
-}
diff --git a/src/com/android/messaging/sms/BugleApnSettingsLoader.java b/src/com/android/messaging/sms/BugleApnSettingsLoader.java
deleted file mode 100644
index 43c95ac..0000000
--- a/src/com/android/messaging/sms/BugleApnSettingsLoader.java
+++ /dev/null
@@ -1,646 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.sms;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteException;
-import android.net.Uri;
-import android.provider.Telephony;
-import android.support.v7.mms.ApnSettingsLoader;
-import android.support.v7.mms.MmsManager;
-import android.text.TextUtils;
-import android.util.SparseArray;
-
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.mmslib.SqliteWrapper;
-import com.android.messaging.util.BugleGservices;
-import com.android.messaging.util.BugleGservicesKeys;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.PhoneUtils;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * APN loader for default SMS SIM
- *
- * This loader tries to load APNs from 3 sources in order:
- * 1. Gservices setting
- * 2. System APN table
- * 3. Local APN table
- */
-public class BugleApnSettingsLoader implements ApnSettingsLoader {
- /**
- * The base implementation of an APN
- */
- private static class BaseApn implements Apn {
- /**
- * Create a base APN from parameters
- *
- * @param typesIn the APN type field
- * @param mmscIn the APN mmsc field
- * @param proxyIn the APN mmsproxy field
- * @param portIn the APN mmsport field
- * @return an instance of base APN, or null if any of the parameter is invalid
- */
- public static BaseApn from(final String typesIn, final String mmscIn, final String proxyIn,
- final String portIn) {
- if (!isValidApnType(trimWithNullCheck(typesIn), APN_TYPE_MMS)) {
- return null;
- }
- String mmsc = trimWithNullCheck(mmscIn);
- if (TextUtils.isEmpty(mmsc)) {
- return null;
- }
- mmsc = trimV4AddrZeros(mmsc);
- try {
- new URI(mmsc);
- } catch (final URISyntaxException e) {
- return null;
- }
- String mmsProxy = trimWithNullCheck(proxyIn);
- int mmsProxyPort = 80;
- if (!TextUtils.isEmpty(mmsProxy)) {
- mmsProxy = trimV4AddrZeros(mmsProxy);
- final String portString = trimWithNullCheck(portIn);
- if (portString != null) {
- try {
- mmsProxyPort = Integer.parseInt(portString);
- } catch (final NumberFormatException e) {
- // Ignore, just use 80 to try
- }
- }
- }
- return new BaseApn(mmsc, mmsProxy, mmsProxyPort);
- }
-
- private final String mMmsc;
- private final String mMmsProxy;
- private final int mMmsProxyPort;
-
- public BaseApn(final String mmsc, final String proxy, final int port) {
- mMmsc = mmsc;
- mMmsProxy = proxy;
- mMmsProxyPort = port;
- }
-
- @Override
- public String getMmsc() {
- return mMmsc;
- }
-
- @Override
- public String getMmsProxy() {
- return mMmsProxy;
- }
-
- @Override
- public int getMmsProxyPort() {
- return mMmsProxyPort;
- }
-
- @Override
- public void setSuccess() {
- // Do nothing
- }
-
- public boolean equals(final BaseApn other) {
- return TextUtils.equals(mMmsc, other.getMmsc()) &&
- TextUtils.equals(mMmsProxy, other.getMmsProxy()) &&
- mMmsProxyPort == other.getMmsProxyPort();
- }
- }
-
- /**
- * The APN represented by the local APN table row
- */
- private static class DatabaseApn implements Apn {
- private static final ContentValues CURRENT_NULL_VALUE;
- private static final ContentValues CURRENT_SET_VALUE;
- static {
- CURRENT_NULL_VALUE = new ContentValues(1);
- CURRENT_NULL_VALUE.putNull(Telephony.Carriers.CURRENT);
- CURRENT_SET_VALUE = new ContentValues(1);
- CURRENT_SET_VALUE.put(Telephony.Carriers.CURRENT, "1"); // 1 for auto selected APN
- }
- private static final String CLEAR_UPDATE_SELECTION = Telephony.Carriers.CURRENT + " =?";
- private static final String[] CLEAR_UPDATE_SELECTION_ARGS = new String[] { "1" };
- private static final String SET_UPDATE_SELECTION = Telephony.Carriers._ID + " =?";
-
- /**
- * Create an APN loaded from local database
- *
- * @param apns the in-memory APN list
- * @param typesIn the APN type field
- * @param mmscIn the APN mmsc field
- * @param proxyIn the APN mmsproxy field
- * @param portIn the APN mmsport field
- * @param rowId the APN's row ID in database
- * @param current the value of CURRENT column in database
- * @return an in-memory APN instance for database APN row, null if parameter invalid
- */
- public static DatabaseApn from(final List<Apn> apns, final String typesIn,
- final String mmscIn, final String proxyIn, final String portIn,
- final long rowId, final int current) {
- if (apns == null) {
- return null;
- }
- final BaseApn base = BaseApn.from(typesIn, mmscIn, proxyIn, portIn);
- if (base == null) {
- return null;
- }
- for (final ApnSettingsLoader.Apn apn : apns) {
- if (apn instanceof DatabaseApn && ((DatabaseApn) apn).equals(base)) {
- return null;
- }
- }
- return new DatabaseApn(apns, base, rowId, current);
- }
-
- private final List<Apn> mApns;
- private final BaseApn mBase;
- private final long mRowId;
- private int mCurrent;
-
- public DatabaseApn(final List<Apn> apns, final BaseApn base, final long rowId,
- final int current) {
- mApns = apns;
- mBase = base;
- mRowId = rowId;
- mCurrent = current;
- }
-
- @Override
- public String getMmsc() {
- return mBase.getMmsc();
- }
-
- @Override
- public String getMmsProxy() {
- return mBase.getMmsProxy();
- }
-
- @Override
- public int getMmsProxyPort() {
- return mBase.getMmsProxyPort();
- }
-
- @Override
- public void setSuccess() {
- moveToListHead();
- setCurrentInDatabase();
- }
-
- /**
- * Try to move this APN to the head of in-memory list
- */
- private void moveToListHead() {
- // If this is being marked as a successful APN, move it to the top of the list so
- // next time it will be tried first
- boolean moved = false;
- synchronized (mApns) {
- if (mApns.get(0) != this) {
- mApns.remove(this);
- mApns.add(0, this);
- moved = true;
- }
- }
- if (moved) {
- LogUtil.d(LogUtil.BUGLE_TAG, "Set APN ["
- + "MMSC=" + getMmsc() + ", "
- + "PROXY=" + getMmsProxy() + ", "
- + "PORT=" + getMmsProxyPort() + "] to be first");
- }
- }
-
- /**
- * Try to set the APN to be CURRENT in its database table
- */
- private void setCurrentInDatabase() {
- synchronized (this) {
- if (mCurrent > 0) {
- // Already current
- return;
- }
- mCurrent = 1;
- }
- LogUtil.d(LogUtil.BUGLE_TAG, "Set APN @" + mRowId + " to be CURRENT in local db");
- final SQLiteDatabase database = ApnDatabase.getApnDatabase().getWritableDatabase();
- database.beginTransaction();
- try {
- // clear the previous current=1 apn
- // we don't clear current=2 apn since it is manually selected by user
- // and we should not override it.
- database.update(ApnDatabase.APN_TABLE, CURRENT_NULL_VALUE,
- CLEAR_UPDATE_SELECTION, CLEAR_UPDATE_SELECTION_ARGS);
- // set this one to be current (1)
- database.update(ApnDatabase.APN_TABLE, CURRENT_SET_VALUE, SET_UPDATE_SELECTION,
- new String[] { Long.toString(mRowId) });
- database.setTransactionSuccessful();
- } finally {
- database.endTransaction();
- }
- }
-
- public boolean equals(final BaseApn other) {
- if (other == null) {
- return false;
- }
- return mBase.equals(other);
- }
- }
-
- /**
- * APN_TYPE_ALL is a special type to indicate that this APN entry can
- * service all data connections.
- */
- public static final String APN_TYPE_ALL = "*";
- /** APN type for MMS traffic */
- public static final String APN_TYPE_MMS = "mms";
-
- private static final String[] APN_PROJECTION_SYSTEM = {
- Telephony.Carriers.TYPE,
- Telephony.Carriers.MMSC,
- Telephony.Carriers.MMSPROXY,
- Telephony.Carriers.MMSPORT,
- };
- private static final String[] APN_PROJECTION_LOCAL = {
- Telephony.Carriers.TYPE,
- Telephony.Carriers.MMSC,
- Telephony.Carriers.MMSPROXY,
- Telephony.Carriers.MMSPORT,
- Telephony.Carriers.CURRENT,
- Telephony.Carriers._ID,
- };
- private static final int COLUMN_TYPE = 0;
- private static final int COLUMN_MMSC = 1;
- private static final int COLUMN_MMSPROXY = 2;
- private static final int COLUMN_MMSPORT = 3;
- private static final int COLUMN_CURRENT = 4;
- private static final int COLUMN_ID = 5;
-
- private static final String SELECTION_APN = Telephony.Carriers.APN + "=?";
- private static final String SELECTION_CURRENT = Telephony.Carriers.CURRENT + " IS NOT NULL";
- private static final String SELECTION_NUMERIC = Telephony.Carriers.NUMERIC + "=?";
- private static final String ORDER_BY = Telephony.Carriers.CURRENT + " DESC";
-
- private final Context mContext;
-
- // Cached APNs for subIds
- private final SparseArray<List<ApnSettingsLoader.Apn>> mApnsCache;
-
- public BugleApnSettingsLoader(final Context context) {
- mContext = context;
- mApnsCache = new SparseArray<>();
- }
-
- @Override
- public List<ApnSettingsLoader.Apn> get(final String apnName) {
- final int subId = PhoneUtils.getDefault().getEffectiveSubId(
- ParticipantData.DEFAULT_SELF_SUB_ID);
- List<ApnSettingsLoader.Apn> apns;
- boolean didLoad = false;
- synchronized (this) {
- apns = mApnsCache.get(subId);
- if (apns == null) {
- apns = new ArrayList<>();
- mApnsCache.put(subId, apns);
- loadLocked(subId, apnName, apns);
- didLoad = true;
- }
- }
- if (didLoad) {
- LogUtil.i(LogUtil.BUGLE_TAG, "Loaded " + apns.size() + " APNs");
- }
- return apns;
- }
-
- private void loadLocked(final int subId, final String apnName, final List<Apn> apns) {
- // Try Gservices first
- loadFromGservices(apns);
- if (apns.size() > 0) {
- return;
- }
- // Try system APN table
- loadFromSystem(subId, apnName, apns);
- if (apns.size() > 0) {
- return;
- }
- // Try local APN table
- loadFromLocalDatabase(apnName, apns);
- if (apns.size() <= 0) {
- LogUtil.w(LogUtil.BUGLE_TAG, "Failed to load any APN");
- }
- }
-
- /**
- * Load from Gservices if APN setting is set in Gservices
- *
- * @param apns the list used to return results
- */
- private void loadFromGservices(final List<Apn> apns) {
- final BugleGservices gservices = BugleGservices.get();
- final String mmsc = gservices.getString(BugleGservicesKeys.MMS_MMSC, null);
- if (TextUtils.isEmpty(mmsc)) {
- return;
- }
- LogUtil.i(LogUtil.BUGLE_TAG, "Loading APNs from gservices");
- final String proxy = gservices.getString(BugleGservicesKeys.MMS_PROXY_ADDRESS, null);
- final int port = gservices.getInt(BugleGservicesKeys.MMS_PROXY_PORT, -1);
- final Apn apn = BaseApn.from("mms", mmsc, proxy, Integer.toString(port));
- if (apn != null) {
- apns.add(apn);
- }
- }
-
- /**
- * Load matching APNs from telephony provider.
- * We try different combinations of the query to work around some platform quirks.
- *
- * @param subId the SIM subId
- * @param apnName the APN name to match
- * @param apns the list used to return results
- */
- private void loadFromSystem(final int subId, final String apnName, final List<Apn> apns) {
- Uri uri;
- if (OsUtil.isAtLeastL_MR1() && subId != MmsManager.DEFAULT_SUB_ID) {
- uri = Uri.withAppendedPath(Telephony.Carriers.CONTENT_URI, "/subId/" + subId);
- } else {
- uri = Telephony.Carriers.CONTENT_URI;
- }
- Cursor cursor = null;
- try {
- for (; ; ) {
- // Try different combinations of queries. Some would work on some platforms.
- // So we query each combination until we find one returns non-empty result.
- cursor = querySystem(uri, true/*checkCurrent*/, apnName);
- if (cursor != null) {
- break;
- }
- cursor = querySystem(uri, false/*checkCurrent*/, apnName);
- if (cursor != null) {
- break;
- }
- cursor = querySystem(uri, true/*checkCurrent*/, null/*apnName*/);
- if (cursor != null) {
- break;
- }
- cursor = querySystem(uri, false/*checkCurrent*/, null/*apnName*/);
- break;
- }
- } catch (final SecurityException e) {
- // Can't access platform APN table, return directly
- return;
- }
- if (cursor == null) {
- return;
- }
- try {
- if (cursor.moveToFirst()) {
- final ApnSettingsLoader.Apn apn = BaseApn.from(
- cursor.getString(COLUMN_TYPE),
- cursor.getString(COLUMN_MMSC),
- cursor.getString(COLUMN_MMSPROXY),
- cursor.getString(COLUMN_MMSPORT));
- if (apn != null) {
- apns.add(apn);
- }
- }
- } finally {
- cursor.close();
- }
- }
-
- /**
- * Query system APN table
- *
- * @param uri The APN query URL to use
- * @param checkCurrent If add "CURRENT IS NOT NULL" condition
- * @param apnName The optional APN name for query condition
- * @return A cursor of the query result. If a cursor is returned as not null, it is
- * guaranteed to contain at least one row.
- */
- private Cursor querySystem(final Uri uri, final boolean checkCurrent, String apnName) {
- LogUtil.i(LogUtil.BUGLE_TAG, "Loading APNs from system, "
- + "checkCurrent=" + checkCurrent + " apnName=" + apnName);
- final StringBuilder selectionBuilder = new StringBuilder();
- String[] selectionArgs = null;
- if (checkCurrent) {
- selectionBuilder.append(SELECTION_CURRENT);
- }
- apnName = trimWithNullCheck(apnName);
- if (!TextUtils.isEmpty(apnName)) {
- if (selectionBuilder.length() > 0) {
- selectionBuilder.append(" AND ");
- }
- selectionBuilder.append(SELECTION_APN);
- selectionArgs = new String[] { apnName };
- }
- try {
- final Cursor cursor = SqliteWrapper.query(
- mContext,
- mContext.getContentResolver(),
- uri,
- APN_PROJECTION_SYSTEM,
- selectionBuilder.toString(),
- selectionArgs,
- null/*sortOrder*/);
- if (cursor == null || cursor.getCount() < 1) {
- if (cursor != null) {
- cursor.close();
- }
- LogUtil.w(LogUtil.BUGLE_TAG, "Query " + uri + " with apn " + apnName + " and "
- + (checkCurrent ? "checking CURRENT" : "not checking CURRENT")
- + " returned empty");
- return null;
- }
- return cursor;
- } catch (final SQLiteException e) {
- LogUtil.w(LogUtil.BUGLE_TAG, "APN table query exception: " + e);
- } catch (final SecurityException e) {
- LogUtil.w(LogUtil.BUGLE_TAG, "Platform restricts APN table access: " + e);
- throw e;
- }
- return null;
- }
-
- /**
- * Load matching APNs from local APN table.
- * We try both using the APN name and not using the APN name.
- *
- * @param apnName the APN name
- * @param apns the list of results to return
- */
- private void loadFromLocalDatabase(final String apnName, final List<Apn> apns) {
- LogUtil.i(LogUtil.BUGLE_TAG, "Loading APNs from local APN table");
- final SQLiteDatabase database = ApnDatabase.getApnDatabase().getWritableDatabase();
- final String mccMnc = PhoneUtils.getMccMncString(PhoneUtils.getDefault().getMccMnc());
- Cursor cursor = null;
- cursor = queryLocalDatabase(database, mccMnc, apnName);
- if (cursor == null) {
- cursor = queryLocalDatabase(database, mccMnc, null/*apnName*/);
- }
- if (cursor == null) {
- LogUtil.w(LogUtil.BUGLE_TAG, "Could not find any APN in local table");
- return;
- }
- try {
- while (cursor.moveToNext()) {
- final Apn apn = DatabaseApn.from(apns,
- cursor.getString(COLUMN_TYPE),
- cursor.getString(COLUMN_MMSC),
- cursor.getString(COLUMN_MMSPROXY),
- cursor.getString(COLUMN_MMSPORT),
- cursor.getLong(COLUMN_ID),
- cursor.getInt(COLUMN_CURRENT));
- if (apn != null) {
- apns.add(apn);
- }
- }
- } finally {
- cursor.close();
- }
- }
-
- /**
- * Make a query of local APN table based on MCC/MNC and APN name, sorted by CURRENT
- * column in descending order
- *
- * @param db the local database
- * @param numeric the MCC/MNC string
- * @param apnName the optional APN name to match
- * @return the cursor of the query, null if no result
- */
- private static Cursor queryLocalDatabase(final SQLiteDatabase db, final String numeric,
- final String apnName) {
- final String selection;
- final String[] selectionArgs;
- if (TextUtils.isEmpty(apnName)) {
- selection = SELECTION_NUMERIC;
- selectionArgs = new String[] { numeric };
- } else {
- selection = SELECTION_NUMERIC + " AND " + SELECTION_APN;
- selectionArgs = new String[] { numeric, apnName };
- }
- Cursor cursor = null;
- try {
- cursor = db.query(ApnDatabase.APN_TABLE, APN_PROJECTION_LOCAL, selection, selectionArgs,
- null/*groupBy*/, null/*having*/, ORDER_BY, null/*limit*/);
- } catch (final SQLiteException e) {
- LogUtil.w(LogUtil.BUGLE_TAG, "Local APN table does not exist. Try rebuilding.", e);
- ApnDatabase.forceBuildAndLoadApnTables();
- cursor = db.query(ApnDatabase.APN_TABLE, APN_PROJECTION_LOCAL, selection, selectionArgs,
- null/*groupBy*/, null/*having*/, ORDER_BY, null/*limit*/);
- }
- if (cursor == null || cursor.getCount() < 1) {
- if (cursor != null) {
- cursor.close();
- }
- LogUtil.w(LogUtil.BUGLE_TAG, "Query local APNs with apn " + apnName
- + " returned empty");
- return null;
- }
- return cursor;
- }
-
- private static String trimWithNullCheck(final String value) {
- return value != null ? value.trim() : null;
- }
-
- /**
- * Trim leading zeros from IPv4 address strings
- * Our base libraries will interpret that as octel..
- * Must leave non v4 addresses and host names alone.
- * For example, 192.168.000.010 -> 192.168.0.10
- *
- * @param addr a string representing an ip addr
- * @return a string propertly trimmed
- */
- private static String trimV4AddrZeros(final String addr) {
- if (addr == null) {
- return null;
- }
- final String[] octets = addr.split("\\.");
- if (octets.length != 4) {
- return addr;
- }
- final StringBuilder builder = new StringBuilder(16);
- String result = null;
- for (int i = 0; i < 4; i++) {
- try {
- if (octets[i].length() > 3) {
- return addr;
- }
- builder.append(Integer.parseInt(octets[i]));
- } catch (final NumberFormatException e) {
- return addr;
- }
- if (i < 3) {
- builder.append('.');
- }
- }
- result = builder.toString();
- return result;
- }
-
- /**
- * Check if the APN contains the APN type we want
- *
- * @param types The string encodes a list of supported types
- * @param requestType The type we want
- * @return true if the input types string contains the requestType
- */
- public static boolean isValidApnType(final String types, final String requestType) {
- // If APN type is unspecified, assume APN_TYPE_ALL.
- if (TextUtils.isEmpty(types)) {
- return true;
- }
- for (final String t : types.split(",")) {
- if (t.equals(requestType) || t.equals(APN_TYPE_ALL)) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Get the ID of first APN to try
- */
- public static String getFirstTryApn(final SQLiteDatabase database, final String mccMnc) {
- String key = null;
- Cursor cursor = null;
- try {
- cursor = queryLocalDatabase(database, mccMnc, null/*apnName*/);
- if (cursor.moveToFirst()) {
- key = cursor.getString(ApnDatabase.COLUMN_ID);
- }
- } catch (final Exception e) {
- // Nothing to do
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- return key;
- }
-}
diff --git a/src/com/android/messaging/sms/BugleCarrierConfigValuesLoader.java b/src/com/android/messaging/sms/BugleCarrierConfigValuesLoader.java
deleted file mode 100644
index ac6c7e4..0000000
--- a/src/com/android/messaging/sms/BugleCarrierConfigValuesLoader.java
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.sms;
-
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.content.res.XmlResourceParser;
-import android.os.Bundle;
-import android.support.v7.mms.CarrierConfigValuesLoader;
-import android.util.SparseArray;
-
-import com.android.messaging.R;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.PhoneUtils;
-
-/**
- * Carrier configuration loader
- *
- * Loader tries to load from resources. If there is MMS API available, also
- * load from system.
- */
-public class BugleCarrierConfigValuesLoader implements CarrierConfigValuesLoader {
- /*
- * Key types
- */
- public static final String KEY_TYPE_INT = "int";
- public static final String KEY_TYPE_BOOL = "bool";
- public static final String KEY_TYPE_STRING = "string";
-
- private final Context mContext;
-
- // Cached values for subIds
- private final SparseArray<Bundle> mValuesCache;
-
- public BugleCarrierConfigValuesLoader(final Context context) {
- mContext = context;
- mValuesCache = new SparseArray<>();
- }
-
- @Override
- public Bundle get(int subId) {
- subId = PhoneUtils.getDefault().getEffectiveSubId(subId);
- Bundle values;
- String loadSource = null;
- synchronized (this) {
- values = mValuesCache.get(subId);
- if (values == null) {
- values = new Bundle();
- mValuesCache.put(subId, values);
- loadSource = loadLocked(subId, values);
- }
- }
- if (loadSource != null) {
- LogUtil.i(LogUtil.BUGLE_TAG, "Carrier configs loaded: " + values
- + " from " + loadSource + " for subId=" + subId);
- }
- return values;
- }
-
- /**
- * Clear the cache for reloading
- */
- public void reset() {
- synchronized (this) {
- mValuesCache.clear();
- }
- }
-
- /**
- * Loading carrier config values
- *
- * @param subId which SIM to load for
- * @param values the result to add to
- * @return the source of the config, could be "resources" or "resources+system"
- */
- private String loadLocked(final int subId, final Bundle values) {
- // Load from resources in earlier platform
- loadFromResources(subId, values);
- if (OsUtil.isAtLeastL()) {
- // Load from system to override if system API exists
- loadFromSystem(subId, values);
- return "resources+system";
- }
- return "resources";
- }
-
- /**
- * Load from system, using MMS API
- *
- * @param subId which SIM to load for
- * @param values the result to add to
- */
- private static void loadFromSystem(final int subId, final Bundle values) {
- try {
- final Bundle systemValues =
- PhoneUtils.get(subId).getSmsManager().getCarrierConfigValues();
- if (systemValues != null) {
- values.putAll(systemValues);
- }
- } catch (final Exception e) {
- LogUtil.w(LogUtil.BUGLE_TAG, "Calling system getCarrierConfigValues exception", e);
- }
- }
-
- /**
- * Load from SIM-dependent resources
- *
- * @param subId which SIM to load for
- * @param values the result to add to
- */
- private void loadFromResources(final int subId, final Bundle values) {
- // Get a subscription-dependent context for loading the mms_config.xml
- final Context subContext = getSubDepContext(mContext, subId);
- // Load and parse the XML
- XmlResourceParser parser = null;
- try {
- parser = subContext.getResources().getXml(R.xml.mms_config);
- final ApnsXmlProcessor processor = ApnsXmlProcessor.get(parser);
- processor.setMmsConfigHandler(new ApnsXmlProcessor.MmsConfigHandler() {
- @Override
- public void process(final String mccMnc, final String key, final String value,
- final String type) {
- update(values, type, key, value);
- }
- });
- processor.process();
- } catch (final Resources.NotFoundException e) {
- LogUtil.w(LogUtil.BUGLE_TAG, "Can not find mms_config.xml");
- } finally {
- if (parser != null) {
- parser.close();
- }
- }
- }
-
- /**
- * Get a subscription's Context so we can load resources from it
- *
- * @param context the sub-independent Context
- * @param subId the SIM's subId
- * @return the sub-dependent Context
- */
- private static Context getSubDepContext(final Context context, final int subId) {
- if (!OsUtil.isAtLeastL_MR1()) {
- return context;
- }
- final int[] mccMnc = PhoneUtils.get(subId).getMccMnc();
- final int mcc = mccMnc[0];
- final int mnc = mccMnc[1];
- final Configuration subConfig = new Configuration();
- if (mcc == 0 && mnc == 0) {
- Configuration config = context.getResources().getConfiguration();
- subConfig.mcc = config.mcc;
- subConfig.mnc = config.mnc;
- } else {
- subConfig.mcc = mcc;
- subConfig.mnc = mnc;
- }
- return context.createConfigurationContext(subConfig);
- }
-
- /**
- * Add or update a carrier config key/value pair to the Bundle
- *
- * @param values the result Bundle to add to
- * @param type the value type
- * @param key the key
- * @param value the value
- */
- public static void update(final Bundle values, final String type, final String key,
- final String value) {
- try {
- if (KEY_TYPE_INT.equals(type)) {
- values.putInt(key, Integer.parseInt(value));
- } else if (KEY_TYPE_BOOL.equals(type)) {
- values.putBoolean(key, Boolean.parseBoolean(value));
- } else if (KEY_TYPE_STRING.equals(type)){
- values.putString(key, value);
- }
- } catch (final NumberFormatException e) {
- LogUtil.w(LogUtil.BUGLE_TAG, "Add carrier values: "
- + "invalid " + key + "," + value + "," + type);
- }
- }
-}
diff --git a/src/com/android/messaging/sms/BugleUserAgentInfoLoader.java b/src/com/android/messaging/sms/BugleUserAgentInfoLoader.java
deleted file mode 100644
index a8dc5c4..0000000
--- a/src/com/android/messaging/sms/BugleUserAgentInfoLoader.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.sms;
-
-import android.content.Context;
-import android.support.v7.mms.UserAgentInfoLoader;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
-
-import com.android.messaging.util.BugleGservices;
-import com.android.messaging.util.BugleGservicesKeys;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.VersionUtil;
-
-/**
- * User agent and UA profile URL loader
- */
-public class BugleUserAgentInfoLoader implements UserAgentInfoLoader {
- private static final String DEFAULT_USER_AGENT_PREFIX = "Bugle/";
-
- private Context mContext;
- private boolean mLoaded;
-
- private String mUserAgent;
- private String mUAProfUrl;
-
- public BugleUserAgentInfoLoader(final Context context) {
- mContext = context;
- }
-
- @Override
- public String getUserAgent() {
- load();
- return mUserAgent;
- }
-
- @Override
- public String getUAProfUrl() {
- load();
- return mUAProfUrl;
- }
-
- private void load() {
- if (mLoaded) {
- return;
- }
- boolean didLoad = false;
- synchronized (this) {
- if (!mLoaded) {
- loadLocked();
- mLoaded = true;
- didLoad = true;
- }
- }
- if (didLoad) {
- LogUtil.i(LogUtil.BUGLE_TAG, "Loaded user agent info: "
- + "UA=" + mUserAgent + ", UAProfUrl=" + mUAProfUrl);
- }
- }
-
- private void loadLocked() {
- if (OsUtil.isAtLeastKLP()) {
- // load the MMS User agent and UaProfUrl from TelephonyManager APIs
- final TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(
- Context.TELEPHONY_SERVICE);
- mUserAgent = telephonyManager.getMmsUserAgent();
- mUAProfUrl = telephonyManager.getMmsUAProfUrl();
- }
- // if user agent string isn't set, use the format "Bugle/<app_version>".
- if (TextUtils.isEmpty(mUserAgent)) {
- final String simpleVersionName = VersionUtil.getInstance(mContext).getSimpleName();
- mUserAgent = DEFAULT_USER_AGENT_PREFIX + simpleVersionName;
- }
- // if the UAProfUrl isn't set, get it from Gservices
- if (TextUtils.isEmpty(mUAProfUrl)) {
- mUAProfUrl = BugleGservices.get().getString(
- BugleGservicesKeys.MMS_UA_PROFILE_URL,
- BugleGservicesKeys.MMS_UA_PROFILE_URL_DEFAULT);
- }
- }
-}
diff --git a/src/com/android/messaging/sms/DatabaseMessages.java b/src/com/android/messaging/sms/DatabaseMessages.java
deleted file mode 100644
index 0f662e5..0000000
--- a/src/com/android/messaging/sms/DatabaseMessages.java
+++ /dev/null
@@ -1,1006 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.sms;
-
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.Context;
-import android.content.res.AssetFileDescriptor;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.media.MediaMetadataRetriever;
-import android.net.Uri;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.provider.Telephony.Mms;
-import android.provider.Telephony.Sms;
-import android.text.TextUtils;
-import android.util.Log;
-import android.webkit.MimeTypeMap;
-
-import com.android.messaging.Factory;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.datamodel.media.VideoThumbnailRequest;
-import com.android.messaging.mmslib.pdu.CharacterSets;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.MediaMetadataRetrieverWrapper;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.PhoneUtils;
-import com.google.common.collect.Lists;
-
-import java.io.ByteArrayOutputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Class contains various SMS/MMS database entities from telephony provider
- */
-public class DatabaseMessages {
- private static final String TAG = LogUtil.BUGLE_TAG;
-
- public abstract static class DatabaseMessage {
- public abstract int getProtocol();
- public abstract String getUri();
- public abstract long getTimestampInMillis();
-
- @Override
- public boolean equals(final Object other) {
- if (other == null || !(other instanceof DatabaseMessage)) {
- return false;
- }
- final DatabaseMessage otherDbMsg = (DatabaseMessage) other;
- // No need to check timestamp since we only need this when we compare
- // messages at the same timestamp
- return TextUtils.equals(getUri(), otherDbMsg.getUri());
- }
-
- @Override
- public int hashCode() {
- // No need to check timestamp since we only need this when we compare
- // messages at the same timestamp
- return getUri().hashCode();
- }
- }
-
- /**
- * SMS message
- */
- public static class SmsMessage extends DatabaseMessage implements Parcelable {
- private static int sIota = 0;
- public static final int INDEX_ID = sIota++;
- public static final int INDEX_TYPE = sIota++;
- public static final int INDEX_ADDRESS = sIota++;
- public static final int INDEX_BODY = sIota++;
- public static final int INDEX_DATE = sIota++;
- public static final int INDEX_THREAD_ID = sIota++;
- public static final int INDEX_STATUS = sIota++;
- public static final int INDEX_READ = sIota++;
- public static final int INDEX_SEEN = sIota++;
- public static final int INDEX_DATE_SENT = sIota++;
- public static final int INDEX_SUB_ID = sIota++;
-
- private static String[] sProjection;
-
- public static String[] getProjection() {
- if (sProjection == null) {
- String[] projection = new String[] {
- Sms._ID,
- Sms.TYPE,
- Sms.ADDRESS,
- Sms.BODY,
- Sms.DATE,
- Sms.THREAD_ID,
- Sms.STATUS,
- Sms.READ,
- Sms.SEEN,
- Sms.DATE_SENT,
- Sms.SUBSCRIPTION_ID,
- };
- if (!MmsUtils.hasSmsDateSentColumn()) {
- projection[INDEX_DATE_SENT] = Sms.DATE;
- }
- if (!OsUtil.isAtLeastL_MR1()) {
- Assert.equals(INDEX_SUB_ID, projection.length - 1);
- String[] withoutSubId = new String[projection.length - 1];
- System.arraycopy(projection, 0, withoutSubId, 0, withoutSubId.length);
- projection = withoutSubId;
- }
-
- sProjection = projection;
- }
-
- return sProjection;
- }
-
- public String mUri;
- public String mAddress;
- public String mBody;
- private long mRowId;
- public long mTimestampInMillis;
- public long mTimestampSentInMillis;
- public int mType;
- public long mThreadId;
- public int mStatus;
- public boolean mRead;
- public boolean mSeen;
- public int mSubId;
-
- private SmsMessage() {
- }
-
- /**
- * Load from a cursor of a query that returns the SMS to import
- *
- * @param cursor
- */
- private void load(final Cursor cursor) {
- mRowId = cursor.getLong(INDEX_ID);
- mAddress = cursor.getString(INDEX_ADDRESS);
- mBody = cursor.getString(INDEX_BODY);
- mTimestampInMillis = cursor.getLong(INDEX_DATE);
- // Before ICS, there is no "date_sent" so use copy of "date" value
- mTimestampSentInMillis = cursor.getLong(INDEX_DATE_SENT);
- mType = cursor.getInt(INDEX_TYPE);
- mThreadId = cursor.getLong(INDEX_THREAD_ID);
- mStatus = cursor.getInt(INDEX_STATUS);
- mRead = cursor.getInt(INDEX_READ) == 0 ? false : true;
- mSeen = cursor.getInt(INDEX_SEEN) == 0 ? false : true;
- mUri = ContentUris.withAppendedId(Sms.CONTENT_URI, mRowId).toString();
- mSubId = PhoneUtils.getDefault().getSubIdFromTelephony(cursor, INDEX_SUB_ID);
- }
-
- /**
- * Get a new SmsMessage by loading from the cursor of a query
- * that returns the SMS to import
- *
- * @param cursor
- * @return
- */
- public static SmsMessage get(final Cursor cursor) {
- final SmsMessage msg = new SmsMessage();
- msg.load(cursor);
- return msg;
- }
-
- @Override
- public String getUri() {
- return mUri;
- }
-
- public int getSubId() {
- return mSubId;
- }
-
- @Override
- public int getProtocol() {
- return MessageData.PROTOCOL_SMS;
- }
-
- @Override
- public long getTimestampInMillis() {
- return mTimestampInMillis;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- private SmsMessage(final Parcel in) {
- mUri = in.readString();
- mRowId = in.readLong();
- mTimestampInMillis = in.readLong();
- mTimestampSentInMillis = in.readLong();
- mType = in.readInt();
- mThreadId = in.readLong();
- mStatus = in.readInt();
- mRead = in.readInt() != 0;
- mSeen = in.readInt() != 0;
- mSubId = in.readInt();
-
- // SMS specific
- mAddress = in.readString();
- mBody = in.readString();
- }
-
- public static final Parcelable.Creator<SmsMessage> CREATOR
- = new Parcelable.Creator<SmsMessage>() {
- @Override
- public SmsMessage createFromParcel(final Parcel in) {
- return new SmsMessage(in);
- }
-
- @Override
- public SmsMessage[] newArray(final int size) {
- return new SmsMessage[size];
- }
- };
-
- @Override
- public void writeToParcel(final Parcel out, final int flags) {
- out.writeString(mUri);
- out.writeLong(mRowId);
- out.writeLong(mTimestampInMillis);
- out.writeLong(mTimestampSentInMillis);
- out.writeInt(mType);
- out.writeLong(mThreadId);
- out.writeInt(mStatus);
- out.writeInt(mRead ? 1 : 0);
- out.writeInt(mSeen ? 1 : 0);
- out.writeInt(mSubId);
-
- // SMS specific
- out.writeString(mAddress);
- out.writeString(mBody);
- }
- }
-
- /**
- * MMS message
- */
- public static class MmsMessage extends DatabaseMessage implements Parcelable {
- private static int sIota = 0;
- public static final int INDEX_ID = sIota++;
- public static final int INDEX_MESSAGE_BOX = sIota++;
- public static final int INDEX_SUBJECT = sIota++;
- public static final int INDEX_SUBJECT_CHARSET = sIota++;
- public static final int INDEX_MESSAGE_SIZE = sIota++;
- public static final int INDEX_DATE = sIota++;
- public static final int INDEX_DATE_SENT = sIota++;
- public static final int INDEX_THREAD_ID = sIota++;
- public static final int INDEX_PRIORITY = sIota++;
- public static final int INDEX_STATUS = sIota++;
- public static final int INDEX_READ = sIota++;
- public static final int INDEX_SEEN = sIota++;
- public static final int INDEX_CONTENT_LOCATION = sIota++;
- public static final int INDEX_TRANSACTION_ID = sIota++;
- public static final int INDEX_MESSAGE_TYPE = sIota++;
- public static final int INDEX_EXPIRY = sIota++;
- public static final int INDEX_RESPONSE_STATUS = sIota++;
- public static final int INDEX_RETRIEVE_STATUS = sIota++;
- public static final int INDEX_SUB_ID = sIota++;
-
- private static String[] sProjection;
-
- public static String[] getProjection() {
- if (sProjection == null) {
- String[] projection = new String[] {
- Mms._ID,
- Mms.MESSAGE_BOX,
- Mms.SUBJECT,
- Mms.SUBJECT_CHARSET,
- Mms.MESSAGE_SIZE,
- Mms.DATE,
- Mms.DATE_SENT,
- Mms.THREAD_ID,
- Mms.PRIORITY,
- Mms.STATUS,
- Mms.READ,
- Mms.SEEN,
- Mms.CONTENT_LOCATION,
- Mms.TRANSACTION_ID,
- Mms.MESSAGE_TYPE,
- Mms.EXPIRY,
- Mms.RESPONSE_STATUS,
- Mms.RETRIEVE_STATUS,
- Mms.SUBSCRIPTION_ID,
- };
-
- if (!OsUtil.isAtLeastL_MR1()) {
- Assert.equals(INDEX_SUB_ID, projection.length - 1);
- String[] withoutSubId = new String[projection.length - 1];
- System.arraycopy(projection, 0, withoutSubId, 0, withoutSubId.length);
- projection = withoutSubId;
- }
-
- sProjection = projection;
- }
-
- return sProjection;
- }
-
- public String mUri;
- private long mRowId;
- public int mType;
- public String mSubject;
- public int mSubjectCharset;
- private long mSize;
- public long mTimestampInMillis;
- public long mSentTimestampInMillis;
- public long mThreadId;
- public int mPriority;
- public int mStatus;
- public boolean mRead;
- public boolean mSeen;
- public String mContentLocation;
- public String mTransactionId;
- public int mMmsMessageType;
- public long mExpiryInMillis;
- public int mSubId;
- public String mSender;
- public int mResponseStatus;
- public int mRetrieveStatus;
-
- public List<MmsPart> mParts = Lists.newArrayList();
- private boolean mPartsProcessed = false;
-
- private MmsMessage() {
- }
-
- /**
- * Load from a cursor of a query that returns the MMS to import
- *
- * @param cursor
- */
- public void load(final Cursor cursor) {
- mRowId = cursor.getLong(INDEX_ID);
- mType = cursor.getInt(INDEX_MESSAGE_BOX);
- mSubject = cursor.getString(INDEX_SUBJECT);
- mSubjectCharset = cursor.getInt(INDEX_SUBJECT_CHARSET);
- if (!TextUtils.isEmpty(mSubject)) {
- // PduPersister stores the subject using ISO_8859_1
- // Let's load it using that encoding and convert it back to its original
- // See PduPersister.persist and PduPersister.toIsoString
- // (Refer to bug b/11162476)
- mSubject = getDecodedString(
- getStringBytes(mSubject, CharacterSets.ISO_8859_1), mSubjectCharset);
- }
- mSize = cursor.getLong(INDEX_MESSAGE_SIZE);
- // MMS db times are in seconds
- mTimestampInMillis = cursor.getLong(INDEX_DATE) * 1000;
- mSentTimestampInMillis = cursor.getLong(INDEX_DATE_SENT) * 1000;
- mThreadId = cursor.getLong(INDEX_THREAD_ID);
- mPriority = cursor.getInt(INDEX_PRIORITY);
- mStatus = cursor.getInt(INDEX_STATUS);
- mRead = cursor.getInt(INDEX_READ) == 0 ? false : true;
- mSeen = cursor.getInt(INDEX_SEEN) == 0 ? false : true;
- mContentLocation = cursor.getString(INDEX_CONTENT_LOCATION);
- mTransactionId = cursor.getString(INDEX_TRANSACTION_ID);
- mMmsMessageType = cursor.getInt(INDEX_MESSAGE_TYPE);
- mExpiryInMillis = cursor.getLong(INDEX_EXPIRY) * 1000;
- mResponseStatus = cursor.getInt(INDEX_RESPONSE_STATUS);
- mRetrieveStatus = cursor.getInt(INDEX_RETRIEVE_STATUS);
- // Clear all parts in case we reuse this object
- mParts.clear();
- mPartsProcessed = false;
- mUri = ContentUris.withAppendedId(Mms.CONTENT_URI, mRowId).toString();
- mSubId = PhoneUtils.getDefault().getSubIdFromTelephony(cursor, INDEX_SUB_ID);
- }
-
- /**
- * Get a new MmsMessage by loading from the cursor of a query
- * that returns the MMS to import
- *
- * @param cursor
- * @return
- */
- public static MmsMessage get(final Cursor cursor) {
- final MmsMessage msg = new MmsMessage();
- msg.load(cursor);
- return msg;
- }
- /**
- * Add a loaded MMS part
- *
- * @param part
- */
- public void addPart(final MmsPart part) {
- mParts.add(part);
- }
-
- public List<MmsPart> getParts() {
- return mParts;
- }
-
- public long getSize() {
- if (!mPartsProcessed) {
- processParts();
- }
- return mSize;
- }
-
- /**
- * Process loaded MMS parts to obtain the combined text, the combined attachment url,
- * the combined content type and the combined size.
- */
- private void processParts() {
- if (mPartsProcessed) {
- return;
- }
- mPartsProcessed = true;
- // Remember the width and height of the first media part
- // These are needed when building attachment list
- long sizeOfParts = 0L;
- for (final MmsPart part : mParts) {
- sizeOfParts += part.mSize;
- }
- if (mSize <= 0) {
- mSize = mSubject != null ? mSubject.getBytes().length : 0L;
- mSize += sizeOfParts;
- }
- }
-
- @Override
- public String getUri() {
- return mUri;
- }
-
- public long getId() {
- return mRowId;
- }
-
- public int getSubId() {
- return mSubId;
- }
-
- @Override
- public int getProtocol() {
- return MessageData.PROTOCOL_MMS;
- }
-
- @Override
- public long getTimestampInMillis() {
- return mTimestampInMillis;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- public void setSender(final String sender) {
- mSender = sender;
- }
-
- private MmsMessage(final Parcel in) {
- mUri = in.readString();
- mRowId = in.readLong();
- mTimestampInMillis = in.readLong();
- mSentTimestampInMillis = in.readLong();
- mType = in.readInt();
- mThreadId = in.readLong();
- mStatus = in.readInt();
- mRead = in.readInt() != 0;
- mSeen = in.readInt() != 0;
- mSubId = in.readInt();
-
- // MMS specific
- mSubject = in.readString();
- mContentLocation = in.readString();
- mTransactionId = in.readString();
- mSender = in.readString();
-
- mSize = in.readLong();
- mExpiryInMillis = in.readLong();
-
- mSubjectCharset = in.readInt();
- mPriority = in.readInt();
- mMmsMessageType = in.readInt();
- mResponseStatus = in.readInt();
- mRetrieveStatus = in.readInt();
-
- final int nParts = in.readInt();
- mParts = new ArrayList<MmsPart>();
- mPartsProcessed = false;
- for (int i = 0; i < nParts; i++) {
- mParts.add((MmsPart) in.readParcelable(getClass().getClassLoader()));
- }
- }
-
- public static final Parcelable.Creator<MmsMessage> CREATOR
- = new Parcelable.Creator<MmsMessage>() {
- @Override
- public MmsMessage createFromParcel(final Parcel in) {
- return new MmsMessage(in);
- }
-
- @Override
- public MmsMessage[] newArray(final int size) {
- return new MmsMessage[size];
- }
- };
-
- @Override
- public void writeToParcel(final Parcel out, final int flags) {
- out.writeString(mUri);
- out.writeLong(mRowId);
- out.writeLong(mTimestampInMillis);
- out.writeLong(mSentTimestampInMillis);
- out.writeInt(mType);
- out.writeLong(mThreadId);
- out.writeInt(mStatus);
- out.writeInt(mRead ? 1 : 0);
- out.writeInt(mSeen ? 1 : 0);
- out.writeInt(mSubId);
-
- out.writeString(mSubject);
- out.writeString(mContentLocation);
- out.writeString(mTransactionId);
- out.writeString(mSender);
-
- out.writeLong(mSize);
- out.writeLong(mExpiryInMillis);
-
- out.writeInt(mSubjectCharset);
- out.writeInt(mPriority);
- out.writeInt(mMmsMessageType);
- out.writeInt(mResponseStatus);
- out.writeInt(mRetrieveStatus);
-
- out.writeInt(mParts.size());
- for (final MmsPart part : mParts) {
- out.writeParcelable(part, 0);
- }
- }
- }
-
- /**
- * Part of an MMS message
- */
- public static class MmsPart implements Parcelable {
- public static final String[] PROJECTION = new String[] {
- Mms.Part._ID,
- Mms.Part.MSG_ID,
- Mms.Part.CHARSET,
- Mms.Part.CONTENT_TYPE,
- Mms.Part.TEXT,
- };
- private static int sIota = 0;
- public static final int INDEX_ID = sIota++;
- public static final int INDEX_MSG_ID = sIota++;
- public static final int INDEX_CHARSET = sIota++;
- public static final int INDEX_CONTENT_TYPE = sIota++;
- public static final int INDEX_TEXT = sIota++;
-
- public String mUri;
- public long mRowId;
- public long mMessageId;
- public String mContentType;
- public String mText;
- public int mCharset;
- private int mWidth;
- private int mHeight;
- public long mSize;
-
- private MmsPart() {
- }
-
- /**
- * Load from a cursor of a query that returns the MMS part to import
- *
- * @param cursor
- */
- public void load(final Cursor cursor, final boolean loadMedia) {
- mRowId = cursor.getLong(INDEX_ID);
- mMessageId = cursor.getLong(INDEX_MSG_ID);
- mContentType = cursor.getString(INDEX_CONTENT_TYPE);
- mText = cursor.getString(INDEX_TEXT);
- mCharset = cursor.getInt(INDEX_CHARSET);
- mWidth = 0;
- mHeight = 0;
- mSize = 0;
- if (isMedia()) {
- // For importing we don't load media since performance is critical
- // For loading when we receive mms, we do load media to get enough
- // information of the media file
- if (loadMedia) {
- if (ContentType.isImageType(mContentType)) {
- loadImage();
- } else if (ContentType.isVideoType(mContentType)) {
- loadVideo();
- } // No need to load audio for parsing
- mSize = MmsUtils.getMediaFileSize(getDataUri());
- }
- } else {
- // Load text if not media type
- loadText();
- }
- mUri = Uri.withAppendedPath(Mms.CONTENT_URI, cursor.getString(INDEX_ID)).toString();
- }
-
- /**
- * Get content type from file extension
- */
- private static String extractContentType(final Context context, final Uri uri) {
- final String path = uri.getPath();
- final MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
- String extension = MimeTypeMap.getFileExtensionFromUrl(path);
- if (TextUtils.isEmpty(extension)) {
- // getMimeTypeFromExtension() doesn't handle spaces in filenames nor can it handle
- // urlEncoded strings. Let's try one last time at finding the extension.
- final int dotPos = path.lastIndexOf('.');
- if (0 <= dotPos) {
- extension = path.substring(dotPos + 1);
- }
- }
- return mimeTypeMap.getMimeTypeFromExtension(extension);
- }
-
- /**
- * Get text of a text part
- */
- private void loadText() {
- byte[] data = null;
- if (isEmbeddedTextType()) {
- // Embedded text, get from the "text" column
- if (!TextUtils.isEmpty(mText)) {
- data = getStringBytes(mText, mCharset);
- }
- } else {
- // Not embedded, load from disk
- final ContentResolver resolver =
- Factory.get().getApplicationContext().getContentResolver();
- final Uri uri = getDataUri();
- InputStream is = null;
- final ByteArrayOutputStream baos = new ByteArrayOutputStream();
- try {
- is = resolver.openInputStream(uri);
- final byte[] buffer = new byte[256];
- int len = is.read(buffer);
- while (len >= 0) {
- baos.write(buffer, 0, len);
- len = is.read(buffer);
- }
- } catch (final IOException e) {
- LogUtil.e(TAG,
- "DatabaseMessages.MmsPart: loading text from file failed: " + e, e);
- } finally {
- if (is != null) {
- try {
- is.close();
- } catch (final IOException e) {
- LogUtil.e(TAG, "DatabaseMessages.MmsPart: close file failed: " + e, e);
- }
- }
- }
- data = baos.toByteArray();
- }
- if (data != null && data.length > 0) {
- mSize = data.length;
- mText = getDecodedString(data, mCharset);
- }
- }
-
- /**
- * Load image file of an image part and parse the dimensions and type
- */
- private void loadImage() {
- final Context context = Factory.get().getApplicationContext();
- final ContentResolver resolver = context.getContentResolver();
- final Uri uri = getDataUri();
- // We have to get the width and height of the image -- they're needed when adding
- // an attachment in bugle.
- InputStream is = null;
- try {
- is = resolver.openInputStream(uri);
- final BitmapFactory.Options opt = new BitmapFactory.Options();
- opt.inJustDecodeBounds = true;
- BitmapFactory.decodeStream(is, null, opt);
- mContentType = opt.outMimeType;
- mWidth = opt.outWidth;
- mHeight = opt.outHeight;
- if (TextUtils.isEmpty(mContentType)) {
- // BitmapFactory couldn't figure out the image type. That's got to be a bad
- // sign, but see if we can figure it out from the file extension.
- mContentType = extractContentType(context, uri);
- }
- } catch (final FileNotFoundException e) {
- LogUtil.e(TAG, "DatabaseMessages.MmsPart.loadImage: file not found", e);
- } finally {
- if (is != null) {
- try {
- is.close();
- } catch (final IOException e) {
- Log.e(TAG, "IOException caught while closing stream", e);
- }
- }
- }
- }
-
- /**
- * Load video file of a video part and parse the dimensions and type
- */
- private void loadVideo() {
- // This is a coarse check, and should not be applied to outgoing messages. However,
- // currently, this does not cause any problems.
- if (!VideoThumbnailRequest.shouldShowIncomingVideoThumbnails()) {
- return;
- }
- final Uri uri = getDataUri();
- final MediaMetadataRetrieverWrapper retriever = new MediaMetadataRetrieverWrapper();
- try {
- retriever.setDataSource(uri);
- // FLAG: This inadvertently fixes a problem with phone receiving audio
- // messages on some carrier. We should handle this in a less accidental way so that
- // we don't break it again. (The carrier changes the content type in the wrapper
- // in-transit from audio/mp4 to video/3gpp without changing the data)
- // Also note: There is a bug in some OEM device where mmr returns
- // video/ffmpeg for image files. That shouldn't happen here but be aware.
- mContentType =
- retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE);
- final Bitmap bitmap = retriever.getFrameAtTime(-1);
- if (bitmap != null) {
- mWidth = bitmap.getWidth();
- mHeight = bitmap.getHeight();
- } else {
- // Get here if it's not actually video (see above)
- LogUtil.i(LogUtil.BUGLE_TAG, "loadVideo: Got null bitmap from " + uri);
- }
- } catch (IOException e) {
- LogUtil.i(LogUtil.BUGLE_TAG, "Error extracting metadata from " + uri, e);
- } finally {
- retriever.release();
- }
- }
-
- /**
- * Get media file size
- */
- private long getMediaFileSize() {
- final Context context = Factory.get().getApplicationContext();
- final Uri uri = getDataUri();
- AssetFileDescriptor fd = null;
- try {
- fd = context.getContentResolver().openAssetFileDescriptor(uri, "r");
- if (fd != null) {
- return fd.getParcelFileDescriptor().getStatSize();
- }
- } catch (final FileNotFoundException e) {
- LogUtil.e(TAG, "DatabaseMessages.MmsPart: cound not find media file: " + e, e);
- } finally {
- if (fd != null) {
- try {
- fd.close();
- } catch (final IOException e) {
- LogUtil.e(TAG, "DatabaseMessages.MmsPart: failed to close " + e, e);
- }
- }
- }
- return 0L;
- }
-
- /**
- * @return If the type is a text type that stores text embedded (i.e. in db table)
- */
- private boolean isEmbeddedTextType() {
- return ContentType.TEXT_PLAIN.equals(mContentType)
- || ContentType.APP_SMIL.equals(mContentType)
- || ContentType.TEXT_HTML.equals(mContentType);
- }
-
- /**
- * Get an instance of the MMS part from the part table cursor
- *
- * @param cursor
- * @param loadMedia Whether to load the media file of the part
- * @return
- */
- public static MmsPart get(final Cursor cursor, final boolean loadMedia) {
- final MmsPart part = new MmsPart();
- part.load(cursor, loadMedia);
- return part;
- }
-
- public boolean isText() {
- return ContentType.TEXT_PLAIN.equals(mContentType)
- || ContentType.TEXT_HTML.equals(mContentType)
- || ContentType.APP_WAP_XHTML.equals(mContentType);
- }
-
- public boolean isMedia() {
- return ContentType.isImageType(mContentType)
- || ContentType.isVideoType(mContentType)
- || ContentType.isAudioType(mContentType)
- || ContentType.isVCardType(mContentType);
- }
-
- public boolean isImage() {
- return ContentType.isImageType(mContentType);
- }
-
- public Uri getDataUri() {
- return Uri.parse("content://mms/part/" + mRowId);
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- private MmsPart(final Parcel in) {
- mUri = in.readString();
- mRowId = in.readLong();
- mMessageId = in.readLong();
- mContentType = in.readString();
- mText = in.readString();
- mCharset = in.readInt();
- mWidth = in.readInt();
- mHeight = in.readInt();
- mSize = in.readLong();
- }
-
- public static final Parcelable.Creator<MmsPart> CREATOR
- = new Parcelable.Creator<MmsPart>() {
- @Override
- public MmsPart createFromParcel(final Parcel in) {
- return new MmsPart(in);
- }
-
- @Override
- public MmsPart[] newArray(final int size) {
- return new MmsPart[size];
- }
- };
-
- @Override
- public void writeToParcel(final Parcel out, final int flags) {
- out.writeString(mUri);
- out.writeLong(mRowId);
- out.writeLong(mMessageId);
- out.writeString(mContentType);
- out.writeString(mText);
- out.writeInt(mCharset);
- out.writeInt(mWidth);
- out.writeInt(mHeight);
- out.writeLong(mSize);
- }
- }
-
- /**
- * This class provides the same DatabaseMessage interface over a local SMS db message
- */
- public static class LocalDatabaseMessage extends DatabaseMessage implements Parcelable {
- private final int mProtocol;
- private final String mUri;
- private final long mTimestamp;
- private final long mLocalId;
- private final String mConversationId;
-
- public LocalDatabaseMessage(final long localId, final int protocol, final String uri,
- final long timestamp, final String conversationId) {
- mLocalId = localId;
- mProtocol = protocol;
- mUri = uri;
- mTimestamp = timestamp;
- mConversationId = conversationId;
- }
-
- @Override
- public int getProtocol() {
- return mProtocol;
- }
-
- @Override
- public long getTimestampInMillis() {
- return mTimestamp;
- }
-
- @Override
- public String getUri() {
- return mUri;
- }
-
- public long getLocalId() {
- return mLocalId;
- }
-
- public String getConversationId() {
- return mConversationId;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- private LocalDatabaseMessage(final Parcel in) {
- mUri = in.readString();
- mConversationId = in.readString();
- mLocalId = in.readLong();
- mTimestamp = in.readLong();
- mProtocol = in.readInt();
- }
-
- public static final Parcelable.Creator<LocalDatabaseMessage> CREATOR
- = new Parcelable.Creator<LocalDatabaseMessage>() {
- @Override
- public LocalDatabaseMessage createFromParcel(final Parcel in) {
- return new LocalDatabaseMessage(in);
- }
-
- @Override
- public LocalDatabaseMessage[] newArray(final int size) {
- return new LocalDatabaseMessage[size];
- }
- };
-
- @Override
- public void writeToParcel(final Parcel out, final int flags) {
- out.writeString(mUri);
- out.writeString(mConversationId);
- out.writeLong(mLocalId);
- out.writeLong(mTimestamp);
- out.writeInt(mProtocol);
- }
- }
-
- /**
- * Address for MMS message
- */
- public static class MmsAddr {
- public static final String[] PROJECTION = new String[] {
- Mms.Addr.ADDRESS,
- Mms.Addr.CHARSET,
- };
- private static int sIota = 0;
- public static final int INDEX_ADDRESS = sIota++;
- public static final int INDEX_CHARSET = sIota++;
-
- public static String get(final Cursor cursor) {
- final int charset = cursor.getInt(INDEX_CHARSET);
- // PduPersister stores the addresses using ISO_8859_1
- // Let's load it using that encoding and convert it back to its original
- // See PduPersister.persistAddress
- return getDecodedString(
- getStringBytes(cursor.getString(INDEX_ADDRESS), CharacterSets.ISO_8859_1),
- charset);
- }
- }
-
- /**
- * Decoded string by character set
- */
- public static String getDecodedString(final byte[] data, final int charset) {
- if (CharacterSets.ANY_CHARSET == charset) {
- return new String(data); // system default encoding.
- } else {
- try {
- final String name = CharacterSets.getMimeName(charset);
- return new String(data, name);
- } catch (final UnsupportedEncodingException e) {
- try {
- return new String(data, CharacterSets.MIMENAME_ISO_8859_1);
- } catch (final UnsupportedEncodingException exception) {
- return new String(data); // system default encoding.
- }
- }
- }
- }
-
- /**
- * Unpack a given String into a byte[].
- */
- public static byte[] getStringBytes(final String data, final int charset) {
- if (CharacterSets.ANY_CHARSET == charset) {
- return data.getBytes();
- } else {
- try {
- final String name = CharacterSets.getMimeName(charset);
- return data.getBytes(name);
- } catch (final UnsupportedEncodingException e) {
- return data.getBytes();
- }
- }
- }
-}
diff --git a/src/com/android/messaging/sms/MmsConfig.java b/src/com/android/messaging/sms/MmsConfig.java
deleted file mode 100755
index f13d785..0000000
--- a/src/com/android/messaging/sms/MmsConfig.java
+++ /dev/null
@@ -1,309 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.sms;
-
-import android.os.Bundle;
-import android.support.v7.mms.CarrierConfigValuesLoader;
-import android.telephony.SubscriptionInfo;
-
-import com.android.messaging.Factory;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.PhoneUtils;
-import com.android.messaging.util.SafeAsyncTask;
-import com.google.common.collect.Maps;
-
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * MMS configuration.
- *
- * This is now a wrapper around the BugleCarrierConfigValuesLoader, which does
- * the actual loading and stores the values in a Bundle. This class provides getter
- * methods for values used in the app, which is easier to use than the raw loader
- * class.
- */
-public class MmsConfig {
- private static final String TAG = LogUtil.BUGLE_TAG;
-
- private static final int DEFAULT_MAX_TEXT_LENGTH = 2000;
-
- /*
- * Key types
- */
- public static final String KEY_TYPE_INT = "int";
- public static final String KEY_TYPE_BOOL = "bool";
- public static final String KEY_TYPE_STRING = "string";
-
- private static final Map<String, String> sKeyTypeMap = Maps.newHashMap();
- static {
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ENABLED_MMS, KEY_TYPE_BOOL);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ENABLED_TRANS_ID, KEY_TYPE_BOOL);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ENABLED_NOTIFY_WAP_MMSC, KEY_TYPE_BOOL);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ALIAS_ENABLED, KEY_TYPE_BOOL);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ALLOW_ATTACH_AUDIO, KEY_TYPE_BOOL);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ENABLE_MULTIPART_SMS, KEY_TYPE_BOOL);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ENABLE_SMS_DELIVERY_REPORTS,
- KEY_TYPE_BOOL);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ENABLE_GROUP_MMS, KEY_TYPE_BOOL);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION,
- KEY_TYPE_BOOL);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_CELL_BROADCAST_APP_LINKS, KEY_TYPE_BOOL);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES,
- KEY_TYPE_BOOL);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ENABLE_MMS_READ_REPORTS, KEY_TYPE_BOOL);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ENABLE_MMS_DELIVERY_REPORTS,
- KEY_TYPE_BOOL);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_SUPPORT_HTTP_CHARSET_HEADER,
- KEY_TYPE_BOOL);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_MAX_MESSAGE_SIZE, KEY_TYPE_INT);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_MAX_IMAGE_HEIGHT, KEY_TYPE_INT);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_MAX_IMAGE_WIDTH, KEY_TYPE_INT);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_RECIPIENT_LIMIT, KEY_TYPE_INT);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_HTTP_SOCKET_TIMEOUT, KEY_TYPE_INT);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ALIAS_MIN_CHARS, KEY_TYPE_INT);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ALIAS_MAX_CHARS, KEY_TYPE_INT);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_SMS_TO_MMS_TEXT_THRESHOLD, KEY_TYPE_INT);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_SMS_TO_MMS_TEXT_LENGTH_THRESHOLD,
- KEY_TYPE_INT);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_MAX_MESSAGE_TEXT_SIZE, KEY_TYPE_INT);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_MAX_SUBJECT_LENGTH, KEY_TYPE_INT);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_UA_PROF_TAG_NAME, KEY_TYPE_STRING);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_HTTP_PARAMS, KEY_TYPE_STRING);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_EMAIL_GATEWAY_NUMBER, KEY_TYPE_STRING);
- sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_NAI_SUFFIX, KEY_TYPE_STRING);
- }
-
- // A map that stores all MmsConfigs, one per active subscription. For pre-LMSim, this will
- // contain just one entry with the default self sub id; for LMSim and above, this will contain
- // all active sub ids but the default subscription id - the default subscription id will be
- // resolved to an active sub id during runtime.
- private static final Map<Integer, MmsConfig> sSubIdToMmsConfigMap = Maps.newHashMap();
- // The fallback values
- private static final MmsConfig sFallback =
- new MmsConfig(ParticipantData.DEFAULT_SELF_SUB_ID, new Bundle());
-
- // Per-subscription configuration values.
- private final Bundle mValues;
- private final int mSubId;
-
- /**
- * Retrieves the MmsConfig instance associated with the given {@code subId}
- */
- public static MmsConfig get(final int subId) {
- final int realSubId = PhoneUtils.getDefault().getEffectiveSubId(subId);
- synchronized (sSubIdToMmsConfigMap) {
- final MmsConfig mmsConfig = sSubIdToMmsConfigMap.get(realSubId);
- if (mmsConfig == null) {
- // The subId is no longer valid. Fall back to the default config.
- LogUtil.e(LogUtil.BUGLE_TAG, "Get mms config failed: invalid subId. subId=" + subId
- + ", real subId=" + realSubId
- + ", map=" + sSubIdToMmsConfigMap.keySet());
- return sFallback;
- }
- return mmsConfig;
- }
- }
-
- private MmsConfig(final int subId, final Bundle values) {
- mSubId = subId;
- mValues = values;
- }
-
- /**
- * Same as load() but doing it using an async thread from SafeAsyncTask thread pool.
- */
- public static void loadAsync() {
- SafeAsyncTask.executeOnThreadPool(new Runnable() {
- @Override
- public void run() {
- load();
- }
- });
- }
-
- /**
- * Reload the device and per-subscription settings.
- */
- public static synchronized void load() {
- final BugleCarrierConfigValuesLoader loader = Factory.get().getCarrierConfigValuesLoader();
- // Rebuild the entire MmsConfig map.
- sSubIdToMmsConfigMap.clear();
- loader.reset();
- if (OsUtil.isAtLeastL_MR1()) {
- final List<SubscriptionInfo> subInfoRecords =
- PhoneUtils.getDefault().toLMr1().getActiveSubscriptionInfoList();
- if (subInfoRecords == null) {
- LogUtil.w(TAG, "Loading mms config failed: no active SIM");
- return;
- }
- for (SubscriptionInfo subInfoRecord : subInfoRecords) {
- final int subId = subInfoRecord.getSubscriptionId();
- final Bundle values = loader.get(subId);
- addMmsConfig(new MmsConfig(subId, values));
- }
- } else {
- final Bundle values = loader.get(ParticipantData.DEFAULT_SELF_SUB_ID);
- addMmsConfig(new MmsConfig(ParticipantData.DEFAULT_SELF_SUB_ID, values));
- }
- }
-
- private static void addMmsConfig(MmsConfig mmsConfig) {
- Assert.isTrue(OsUtil.isAtLeastL_MR1() !=
- (mmsConfig.mSubId == ParticipantData.DEFAULT_SELF_SUB_ID));
- sSubIdToMmsConfigMap.put(mmsConfig.mSubId, mmsConfig);
- }
-
- public int getSmsToMmsTextThreshold() {
- return mValues.getInt(CarrierConfigValuesLoader.CONFIG_SMS_TO_MMS_TEXT_THRESHOLD,
- CarrierConfigValuesLoader.CONFIG_SMS_TO_MMS_TEXT_THRESHOLD_DEFAULT);
- }
-
- public int getSmsToMmsTextLengthThreshold() {
- return mValues.getInt(CarrierConfigValuesLoader.CONFIG_SMS_TO_MMS_TEXT_LENGTH_THRESHOLD,
- CarrierConfigValuesLoader.CONFIG_SMS_TO_MMS_TEXT_LENGTH_THRESHOLD_DEFAULT);
- }
-
- public int getMaxMessageSize() {
- return mValues.getInt(CarrierConfigValuesLoader.CONFIG_MAX_MESSAGE_SIZE,
- CarrierConfigValuesLoader.CONFIG_MAX_MESSAGE_SIZE_DEFAULT);
- }
-
- /**
- * Return the largest MaxMessageSize for any subid
- */
- public static int getMaxMaxMessageSize() {
- int maxMax = 0;
- for (MmsConfig config : sSubIdToMmsConfigMap.values()) {
- maxMax = Math.max(maxMax, config.getMaxMessageSize());
- }
- return maxMax > 0 ? maxMax : sFallback.getMaxMessageSize();
- }
-
- public boolean getTransIdEnabled() {
- return mValues.getBoolean(CarrierConfigValuesLoader.CONFIG_ENABLED_TRANS_ID,
- CarrierConfigValuesLoader.CONFIG_ENABLED_TRANS_ID_DEFAULT);
- }
-
- public String getEmailGateway() {
- return mValues.getString(CarrierConfigValuesLoader.CONFIG_EMAIL_GATEWAY_NUMBER,
- CarrierConfigValuesLoader.CONFIG_EMAIL_GATEWAY_NUMBER_DEFAULT);
- }
-
- public int getMaxImageHeight() {
- return mValues.getInt(CarrierConfigValuesLoader.CONFIG_MAX_IMAGE_HEIGHT,
- CarrierConfigValuesLoader.CONFIG_MAX_IMAGE_HEIGHT_DEFAULT);
- }
-
- public int getMaxImageWidth() {
- return mValues.getInt(CarrierConfigValuesLoader.CONFIG_MAX_IMAGE_WIDTH,
- CarrierConfigValuesLoader.CONFIG_MAX_IMAGE_WIDTH_DEFAULT);
- }
-
- public int getRecipientLimit() {
- final int limit = mValues.getInt(CarrierConfigValuesLoader.CONFIG_RECIPIENT_LIMIT,
- CarrierConfigValuesLoader.CONFIG_RECIPIENT_LIMIT_DEFAULT);
- return limit < 0 ? Integer.MAX_VALUE : limit;
- }
-
- public int getMaxTextLimit() {
- final int max = mValues.getInt(CarrierConfigValuesLoader.CONFIG_MAX_MESSAGE_TEXT_SIZE,
- CarrierConfigValuesLoader.CONFIG_MAX_MESSAGE_TEXT_SIZE_DEFAULT);
- return max > -1 ? max : DEFAULT_MAX_TEXT_LENGTH;
- }
-
- public boolean getMultipartSmsEnabled() {
- return mValues.getBoolean(CarrierConfigValuesLoader.CONFIG_ENABLE_MULTIPART_SMS,
- CarrierConfigValuesLoader.CONFIG_ENABLE_MULTIPART_SMS_DEFAULT);
- }
-
- public boolean getSendMultipartSmsAsSeparateMessages() {
- return mValues.getBoolean(
- CarrierConfigValuesLoader.CONFIG_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES,
- CarrierConfigValuesLoader.CONFIG_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES_DEFAULT);
- }
-
- public boolean getSMSDeliveryReportsEnabled() {
- return mValues.getBoolean(CarrierConfigValuesLoader.CONFIG_ENABLE_SMS_DELIVERY_REPORTS,
- CarrierConfigValuesLoader.CONFIG_ENABLE_SMS_DELIVERY_REPORTS_DEFAULT);
- }
-
- public boolean getNotifyWapMMSC() {
- return mValues.getBoolean(CarrierConfigValuesLoader.CONFIG_ENABLED_NOTIFY_WAP_MMSC,
- CarrierConfigValuesLoader.CONFIG_ENABLED_NOTIFY_WAP_MMSC_DEFAULT);
- }
-
- public boolean isAliasEnabled() {
- return mValues.getBoolean(CarrierConfigValuesLoader.CONFIG_ALIAS_ENABLED,
- CarrierConfigValuesLoader.CONFIG_ALIAS_ENABLED_DEFAULT);
- }
-
- public int getAliasMinChars() {
- return mValues.getInt(CarrierConfigValuesLoader.CONFIG_ALIAS_MIN_CHARS,
- CarrierConfigValuesLoader.CONFIG_ALIAS_MIN_CHARS_DEFAULT);
- }
-
- public int getAliasMaxChars() {
- return mValues.getInt(CarrierConfigValuesLoader.CONFIG_ALIAS_MAX_CHARS,
- CarrierConfigValuesLoader.CONFIG_ALIAS_MAX_CHARS_DEFAULT);
- }
-
- public boolean getAllowAttachAudio() {
- return mValues.getBoolean(CarrierConfigValuesLoader.CONFIG_ALLOW_ATTACH_AUDIO,
- CarrierConfigValuesLoader.CONFIG_ALLOW_ATTACH_AUDIO_DEFAULT);
- }
-
- public int getMaxSubjectLength() {
- return mValues.getInt(CarrierConfigValuesLoader.CONFIG_MAX_SUBJECT_LENGTH,
- CarrierConfigValuesLoader.CONFIG_MAX_SUBJECT_LENGTH_DEFAULT);
- }
-
- public boolean getGroupMmsEnabled() {
- return mValues.getBoolean(CarrierConfigValuesLoader.CONFIG_ENABLE_GROUP_MMS,
- CarrierConfigValuesLoader.CONFIG_ENABLE_GROUP_MMS_DEFAULT);
- }
-
- public boolean getSupportMmsContentDisposition() {
- return mValues.getBoolean(CarrierConfigValuesLoader.CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION,
- CarrierConfigValuesLoader.CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION_DEFAULT);
- }
-
- public boolean getShowCellBroadcast() {
- return mValues.getBoolean(CarrierConfigValuesLoader.CONFIG_CELL_BROADCAST_APP_LINKS,
- CarrierConfigValuesLoader.CONFIG_CELL_BROADCAST_APP_LINKS_DEFAULT);
- }
-
- public Object getValue(final String key) {
- return mValues.get(key);
- }
-
- public Set<String> keySet() {
- return mValues.keySet();
- }
-
- public static String getKeyType(final String key) {
- return sKeyTypeMap.get(key);
- }
-
- public void update(final String type, final String key, final String value) {
- BugleCarrierConfigValuesLoader.update(mValues, type, key, value);
- }
-}
diff --git a/src/com/android/messaging/sms/MmsFailureException.java b/src/com/android/messaging/sms/MmsFailureException.java
deleted file mode 100644
index dd702ee..0000000
--- a/src/com/android/messaging/sms/MmsFailureException.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.sms;
-
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.util.Assert;
-
-/**
- * Exception for MMS failures
- */
-public class MmsFailureException extends Exception {
- private static final long serialVersionUID = 1L;
-
- /**
- * Hint of how we should retry in case of failure. Take values defined in MmsUtils.
- */
- public final int retryHint;
-
- /**
- * If set, provides a more detailed reason for the failure.
- */
- public final int rawStatus;
-
- private void checkRetryHint() {
- Assert.isTrue(retryHint == MmsUtils.MMS_REQUEST_AUTO_RETRY
- || retryHint == MmsUtils.MMS_REQUEST_MANUAL_RETRY
- || retryHint == MmsUtils.MMS_REQUEST_NO_RETRY);
- }
- /**
- * Creates a new MmsFailureException.
- *
- * @param retryHint Hint for how to retry
- */
- public MmsFailureException(final int retryHint) {
- super();
- this.retryHint = retryHint;
- checkRetryHint();
- this.rawStatus = MessageData.RAW_TELEPHONY_STATUS_UNDEFINED;
- }
-
- public MmsFailureException(final int retryHint, final int rawStatus) {
- super();
- this.retryHint = retryHint;
- checkRetryHint();
- this.rawStatus = rawStatus;
- }
-
- /**
- * Creates a new MmsFailureException with the specified detail message.
- *
- * @param retryHint Hint for how to retry
- * @param message the detail message.
- */
- public MmsFailureException(final int retryHint, String message) {
- super(message);
- this.retryHint = retryHint;
- checkRetryHint();
- this.rawStatus = MessageData.RAW_TELEPHONY_STATUS_UNDEFINED;
- }
-
- /**
- * Creates a new MmsFailureException with the specified cause.
- *
- * @param retryHint Hint for how to retry
- * @param cause the cause.
- */
- public MmsFailureException(final int retryHint, Throwable cause) {
- super(cause);
- this.retryHint = retryHint;
- checkRetryHint();
- this.rawStatus = MessageData.RAW_TELEPHONY_STATUS_UNDEFINED;
- }
-
- /**
- * Creates a new MmsFailureException
- * with the specified detail message and cause.
- *
- * @param retryHint Hint for how to retry
- * @param message the detail message.
- * @param cause the cause.
- */
- public MmsFailureException(final int retryHint, String message, Throwable cause) {
- super(message, cause);
- this.retryHint = retryHint;
- checkRetryHint();
- this.rawStatus = MessageData.RAW_TELEPHONY_STATUS_UNDEFINED;
- }
-}
diff --git a/src/com/android/messaging/sms/MmsSender.java b/src/com/android/messaging/sms/MmsSender.java
deleted file mode 100644
index 6dfa81a..0000000
--- a/src/com/android/messaging/sms/MmsSender.java
+++ /dev/null
@@ -1,312 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.sms;
-
-import android.app.Activity;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.support.v7.mms.MmsManager;
-import android.telephony.SmsManager;
-
-import com.android.messaging.datamodel.MmsFileProvider;
-import com.android.messaging.datamodel.action.SendMessageAction;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.mmslib.InvalidHeaderValueException;
-import com.android.messaging.mmslib.pdu.AcknowledgeInd;
-import com.android.messaging.mmslib.pdu.EncodedStringValue;
-import com.android.messaging.mmslib.pdu.GenericPdu;
-import com.android.messaging.mmslib.pdu.NotifyRespInd;
-import com.android.messaging.mmslib.pdu.PduComposer;
-import com.android.messaging.mmslib.pdu.PduHeaders;
-import com.android.messaging.mmslib.pdu.PduParser;
-import com.android.messaging.mmslib.pdu.RetrieveConf;
-import com.android.messaging.mmslib.pdu.SendConf;
-import com.android.messaging.mmslib.pdu.SendReq;
-import com.android.messaging.receiver.SendStatusReceiver;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.PhoneUtils;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-
-/**
- * Class that sends chat message via MMS.
- *
- * The interface emulates a blocking send similar to making an HTTP request.
- */
-public class MmsSender {
- private static final String TAG = LogUtil.BUGLE_TAG;
-
- /**
- * Send an MMS message.
- *
- * @param context Context
- * @param messageUri The unique URI of the message for identifying it during sending
- * @param sendReq The SendReq PDU of the message
- * @throws MmsFailureException
- */
- public static void sendMms(final Context context, final int subId, final Uri messageUri,
- final SendReq sendReq, final Bundle sentIntentExras) throws MmsFailureException {
- sendMms(context,
- subId,
- messageUri,
- null /* locationUrl */,
- sendReq,
- true /* responseImportant */,
- sentIntentExras);
- }
-
- /**
- * Send NotifyRespInd (response to mms auto download).
- *
- * @param context Context
- * @param subId subscription to use to send the response
- * @param transactionId The transaction id of the MMS message
- * @param contentLocation The url of the MMS message
- * @param status The status to send with the NotifyRespInd
- * @throws MmsFailureException
- * @throws InvalidHeaderValueException
- */
- public static void sendNotifyResponseForMmsDownload(final Context context, final int subId,
- final byte[] transactionId, final String contentLocation, final int status)
- throws MmsFailureException, InvalidHeaderValueException {
- // Create the M-NotifyResp.ind
- final NotifyRespInd notifyRespInd = new NotifyRespInd(
- PduHeaders.CURRENT_MMS_VERSION, transactionId, status);
- final Uri messageUri = Uri.parse(contentLocation);
- // Pack M-NotifyResp.ind and send it
- sendMms(context,
- subId,
- messageUri,
- MmsConfig.get(subId).getNotifyWapMMSC() ? contentLocation : null,
- notifyRespInd,
- false /* responseImportant */,
- null /* sentIntentExtras */);
- }
-
- /**
- * Send AcknowledgeInd (response to mms manual download). Ignore failures.
- *
- * @param context Context
- * @param subId The SIM's subId we are currently using
- * @param transactionId The transaction id of the MMS message
- * @param contentLocation The url of the MMS message
- * @throws MmsFailureException
- * @throws InvalidHeaderValueException
- */
- public static void sendAcknowledgeForMmsDownload(final Context context, final int subId,
- final byte[] transactionId, final String contentLocation)
- throws MmsFailureException, InvalidHeaderValueException {
- final String selfNumber = PhoneUtils.get(subId).getCanonicalForSelf(true/*allowOverride*/);
- // Create the M-Acknowledge.ind
- final AcknowledgeInd acknowledgeInd = new AcknowledgeInd(PduHeaders.CURRENT_MMS_VERSION,
- transactionId);
- acknowledgeInd.setFrom(new EncodedStringValue(selfNumber));
- final Uri messageUri = Uri.parse(contentLocation);
- // Sending
- sendMms(context,
- subId,
- messageUri,
- MmsConfig.get(subId).getNotifyWapMMSC() ? contentLocation : null,
- acknowledgeInd,
- false /*responseImportant*/,
- null /* sentIntentExtras */);
- }
-
- /**
- * Send a generic PDU.
- *
- * @param context Context
- * @param messageUri The unique URI of the message for identifying it during sending
- * @param locationUrl The optional URL to send to
- * @param pdu The PDU to send
- * @param responseImportant If the sending response is important. Responses to the
- * Sending of AcknowledgeInd and NotifyRespInd are not important.
- * @throws MmsFailureException
- */
- private static void sendMms(final Context context, final int subId, final Uri messageUri,
- final String locationUrl, final GenericPdu pdu, final boolean responseImportant,
- final Bundle sentIntentExtras) throws MmsFailureException {
- // Write PDU to temporary file to send to platform
- final Uri contentUri = writePduToTempFile(context, pdu, subId);
-
- // Construct PendingIntent that will notify us when message sending is complete
- final Intent sentIntent = new Intent(SendStatusReceiver.MMS_SENT_ACTION,
- messageUri,
- context,
- SendStatusReceiver.class);
- sentIntent.putExtra(SendMessageAction.EXTRA_CONTENT_URI, contentUri);
- sentIntent.putExtra(SendMessageAction.EXTRA_RESPONSE_IMPORTANT, responseImportant);
- if (sentIntentExtras != null) {
- sentIntent.putExtras(sentIntentExtras);
- }
- final PendingIntent sentPendingIntent = PendingIntent.getBroadcast(
- context,
- 0 /*request code*/,
- sentIntent,
- PendingIntent.FLAG_UPDATE_CURRENT);
-
- // Send the message
- MmsManager.sendMultimediaMessage(subId, context, contentUri, locationUrl,
- sentPendingIntent);
- }
-
- private static Uri writePduToTempFile(final Context context, final GenericPdu pdu, int subId)
- throws MmsFailureException {
- final Uri contentUri = MmsFileProvider.buildRawMmsUri();
- final File tempFile = MmsFileProvider.getFile(contentUri);
- FileOutputStream writer = null;
- try {
- // Ensure rawmms directory exists
- tempFile.getParentFile().mkdirs();
- writer = new FileOutputStream(tempFile);
- final byte[] pduBytes = new PduComposer(context, pdu).make();
- if (pduBytes == null) {
- throw new MmsFailureException(
- MmsUtils.MMS_REQUEST_NO_RETRY, "Failed to compose PDU");
- }
- if (pduBytes.length > MmsConfig.get(subId).getMaxMessageSize()) {
- throw new MmsFailureException(
- MmsUtils.MMS_REQUEST_NO_RETRY,
- MessageData.RAW_TELEPHONY_STATUS_MESSAGE_TOO_BIG);
- }
- writer.write(pduBytes);
- } catch (final IOException e) {
- if (tempFile != null) {
- tempFile.delete();
- }
- LogUtil.e(TAG, "Cannot create temporary file " + tempFile.getAbsolutePath(), e);
- throw new MmsFailureException(
- MmsUtils.MMS_REQUEST_AUTO_RETRY, "Cannot create raw mms file");
- } catch (final OutOfMemoryError e) {
- if (tempFile != null) {
- tempFile.delete();
- }
- LogUtil.e(TAG, "Out of memory in composing PDU", e);
- throw new MmsFailureException(
- MmsUtils.MMS_REQUEST_MANUAL_RETRY,
- MessageData.RAW_TELEPHONY_STATUS_MESSAGE_TOO_BIG);
- } finally {
- if (writer != null) {
- try {
- writer.close();
- } catch (final IOException e) {
- // no action we can take here
- }
- }
- }
- return contentUri;
- }
-
- public static SendConf parseSendConf(byte[] response, int subId) {
- if (response != null) {
- final GenericPdu respPdu = new PduParser(
- response, MmsConfig.get(subId).getSupportMmsContentDisposition()).parse();
- if (respPdu != null) {
- if (respPdu instanceof SendConf) {
- return (SendConf) respPdu;
- } else {
- LogUtil.e(TAG, "MmsSender: send response not SendConf");
- }
- } else {
- // Invalid PDU
- LogUtil.e(TAG, "MmsSender: send invalid response");
- }
- }
- // Empty or invalid response
- return null;
- }
-
- /**
- * Download an MMS message.
- *
- * @param context Context
- * @param contentLocation The url of the MMS message
- * @throws MmsFailureException
- * @throws InvalidHeaderValueException
- */
- public static void downloadMms(final Context context, final int subId,
- final String contentLocation, Bundle extras) throws MmsFailureException,
- InvalidHeaderValueException {
- final Uri requestUri = Uri.parse(contentLocation);
- final Uri contentUri = MmsFileProvider.buildRawMmsUri();
-
- final Intent downloadedIntent = new Intent(SendStatusReceiver.MMS_DOWNLOADED_ACTION,
- requestUri,
- context,
- SendStatusReceiver.class);
- downloadedIntent.putExtra(SendMessageAction.EXTRA_CONTENT_URI, contentUri);
- if (extras != null) {
- downloadedIntent.putExtras(extras);
- }
- final PendingIntent downloadedPendingIntent = PendingIntent.getBroadcast(
- context,
- 0 /*request code*/,
- downloadedIntent,
- PendingIntent.FLAG_UPDATE_CURRENT);
-
- MmsManager.downloadMultimediaMessage(subId, context, contentLocation, contentUri,
- downloadedPendingIntent);
- }
-
- public static RetrieveConf parseRetrieveConf(byte[] data, int subId) {
- if (data != null) {
- final GenericPdu pdu = new PduParser(
- data, MmsConfig.get(subId).getSupportMmsContentDisposition()).parse();
- if (pdu != null) {
- if (pdu instanceof RetrieveConf) {
- return (RetrieveConf) pdu;
- } else {
- LogUtil.e(TAG, "MmsSender: downloaded pdu not RetrieveConf: "
- + pdu.getClass().getName());
- }
- } else {
- LogUtil.e(TAG, "MmsSender: downloaded pdu could not be parsed (invalid)");
- }
- }
- LogUtil.e(TAG, "MmsSender: downloaded pdu is empty");
- return null;
- }
-
- // Process different result code from platform MMS service
- public static int getErrorResultStatus(int resultCode, int httpStatusCode) {
- Assert.isFalse(resultCode == Activity.RESULT_OK);
- switch (resultCode) {
- case SmsManager.MMS_ERROR_UNABLE_CONNECT_MMS:
- case SmsManager.MMS_ERROR_IO_ERROR:
- return MmsUtils.MMS_REQUEST_AUTO_RETRY;
- case SmsManager.MMS_ERROR_INVALID_APN:
- case SmsManager.MMS_ERROR_CONFIGURATION_ERROR:
- case SmsManager.MMS_ERROR_NO_DATA_NETWORK:
- case SmsManager.MMS_ERROR_UNSPECIFIED:
- return MmsUtils.MMS_REQUEST_MANUAL_RETRY;
- case SmsManager.MMS_ERROR_HTTP_FAILURE:
- if (httpStatusCode == 404) {
- return MmsUtils.MMS_REQUEST_NO_RETRY;
- } else {
- return MmsUtils.MMS_REQUEST_AUTO_RETRY;
- }
- default:
- return MmsUtils.MMS_REQUEST_MANUAL_RETRY;
- }
- }
-}
diff --git a/src/com/android/messaging/sms/MmsSmsUtils.java b/src/com/android/messaging/sms/MmsSmsUtils.java
deleted file mode 100644
index 1a0ef99..0000000
--- a/src/com/android/messaging/sms/MmsSmsUtils.java
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.sms;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.provider.BaseColumns;
-import android.text.TextUtils;
-import android.util.Patterns;
-
-import com.android.messaging.mmslib.SqliteWrapper;
-import com.android.messaging.util.LogUtil;
-
-import java.util.HashSet;
-import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Utility functions for the Messaging Service
- */
-public class MmsSmsUtils {
- private MmsSmsUtils() {
- // Forbidden being instantiated.
- }
-
- // An alias (or commonly called "nickname") is:
- // Nickname must begin with a letter.
- // Only letters a-z, numbers 0-9, or . are allowed in Nickname field.
- public static boolean isAlias(final String string, final int subId) {
- if (!MmsConfig.get(subId).isAliasEnabled()) {
- return false;
- }
-
- final int len = string == null ? 0 : string.length();
-
- if (len < MmsConfig.get(subId).getAliasMinChars() ||
- len > MmsConfig.get(subId).getAliasMaxChars()) {
- return false;
- }
-
- if (!Character.isLetter(string.charAt(0))) { // Nickname begins with a letter
- return false;
- }
- for (int i = 1; i < len; i++) {
- final char c = string.charAt(i);
- if (!(Character.isLetterOrDigit(c) || c == '.')) {
- return false;
- }
- }
-
- return true;
- }
-
- /**
- * mailbox = name-addr
- * name-addr = [display-name] angle-addr
- * angle-addr = [CFWS] "<" addr-spec ">" [CFWS]
- */
- public static final Pattern NAME_ADDR_EMAIL_PATTERN =
- Pattern.compile("\\s*(\"[^\"]*\"|[^<>\"]+)\\s*<([^<>]+)>\\s*");
-
- public static String extractAddrSpec(final String address) {
- final Matcher match = NAME_ADDR_EMAIL_PATTERN.matcher(address);
-
- if (match.matches()) {
- return match.group(2);
- }
- return address;
- }
-
- /**
- * Returns true if the address is an email address
- *
- * @param address the input address to be tested
- * @return true if address is an email address
- */
- public static boolean isEmailAddress(final String address) {
- if (TextUtils.isEmpty(address)) {
- return false;
- }
-
- final String s = extractAddrSpec(address);
- final Matcher match = Patterns.EMAIL_ADDRESS.matcher(s);
- return match.matches();
- }
-
- /**
- * Returns true if the number is a Phone number
- *
- * @param number the input number to be tested
- * @return true if number is a Phone number
- */
- public static boolean isPhoneNumber(final String number) {
- if (TextUtils.isEmpty(number)) {
- return false;
- }
-
- final Matcher match = Patterns.PHONE.matcher(number);
- return match.matches();
- }
-
- /**
- * Check if MMS is required when sending to email address
- *
- * @param destinationHasEmailAddress destination includes an email address
- * @return true if MMS is required.
- */
- public static boolean getRequireMmsForEmailAddress(final boolean destinationHasEmailAddress,
- final int subId) {
- if (!TextUtils.isEmpty(MmsConfig.get(subId).getEmailGateway())) {
- return false;
- } else {
- return destinationHasEmailAddress;
- }
- }
-
- /**
- * Helper functions for the "threads" table used by MMS and SMS.
- */
- public static final class Threads implements android.provider.Telephony.ThreadsColumns {
- private static final String[] ID_PROJECTION = { BaseColumns._ID };
- private static final Uri THREAD_ID_CONTENT_URI = Uri.parse(
- "content://mms-sms/threadID");
- public static final Uri CONTENT_URI = Uri.withAppendedPath(
- android.provider.Telephony.MmsSms.CONTENT_URI, "conversations");
-
- // No one should construct an instance of this class.
- private Threads() {
- }
-
- /**
- * This is a single-recipient version of
- * getOrCreateThreadId. It's convenient for use with SMS
- * messages.
- */
- public static long getOrCreateThreadId(final Context context, final String recipient) {
- final Set<String> recipients = new HashSet<String>();
-
- recipients.add(recipient);
- return getOrCreateThreadId(context, recipients);
- }
-
- /**
- * Given the recipients list and subject of an unsaved message,
- * return its thread ID. If the message starts a new thread,
- * allocate a new thread ID. Otherwise, use the appropriate
- * existing thread ID.
- *
- * Find the thread ID of the same set of recipients (in
- * any order, without any additions). If one
- * is found, return it. Otherwise, return a unique thread ID.
- */
- public static long getOrCreateThreadId(
- final Context context, final Set<String> recipients) {
- final Uri.Builder uriBuilder = THREAD_ID_CONTENT_URI.buildUpon();
-
- for (String recipient : recipients) {
- if (isEmailAddress(recipient)) {
- recipient = extractAddrSpec(recipient);
- }
-
- uriBuilder.appendQueryParameter("recipient", recipient);
- }
-
- final Uri uri = uriBuilder.build();
- //if (DEBUG) Rlog.v(TAG, "getOrCreateThreadId uri: " + uri);
-
- final Cursor cursor = SqliteWrapper.query(context, context.getContentResolver(),
- uri, ID_PROJECTION, null, null, null);
- if (cursor != null) {
- try {
- if (cursor.moveToFirst()) {
- return cursor.getLong(0);
- } else {
- LogUtil.e(LogUtil.BUGLE_DATAMODEL_TAG,
- "getOrCreateThreadId returned no rows!");
- }
- } finally {
- cursor.close();
- }
- }
-
- LogUtil.e(LogUtil.BUGLE_DATAMODEL_TAG, "getOrCreateThreadId failed with "
- + LogUtil.sanitizePII(recipients.toString()));
- throw new IllegalArgumentException("Unable to find or allocate a thread ID.");
- }
- }
-}
diff --git a/src/com/android/messaging/sms/MmsUtils.java b/src/com/android/messaging/sms/MmsUtils.java
deleted file mode 100644
index 913e9a6..0000000
--- a/src/com/android/messaging/sms/MmsUtils.java
+++ /dev/null
@@ -1,2747 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.sms;
-
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.AssetFileDescriptor;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteException;
-import android.media.MediaMetadataRetriever;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.net.Uri;
-import android.os.Bundle;
-import android.provider.Settings;
-import android.provider.Telephony;
-import android.provider.Telephony.Mms;
-import android.provider.Telephony.Sms;
-import android.provider.Telephony.Threads;
-import android.telephony.SmsManager;
-import android.telephony.SmsMessage;
-import android.text.TextUtils;
-import android.text.util.Rfc822Token;
-import android.text.util.Rfc822Tokenizer;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.datamodel.MediaScratchFileProvider;
-import com.android.messaging.datamodel.action.DownloadMmsAction;
-import com.android.messaging.datamodel.action.SendMessageAction;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.mmslib.InvalidHeaderValueException;
-import com.android.messaging.mmslib.MmsException;
-import com.android.messaging.mmslib.SqliteWrapper;
-import com.android.messaging.mmslib.pdu.CharacterSets;
-import com.android.messaging.mmslib.pdu.EncodedStringValue;
-import com.android.messaging.mmslib.pdu.GenericPdu;
-import com.android.messaging.mmslib.pdu.NotificationInd;
-import com.android.messaging.mmslib.pdu.PduBody;
-import com.android.messaging.mmslib.pdu.PduComposer;
-import com.android.messaging.mmslib.pdu.PduHeaders;
-import com.android.messaging.mmslib.pdu.PduParser;
-import com.android.messaging.mmslib.pdu.PduPart;
-import com.android.messaging.mmslib.pdu.PduPersister;
-import com.android.messaging.mmslib.pdu.RetrieveConf;
-import com.android.messaging.mmslib.pdu.SendConf;
-import com.android.messaging.mmslib.pdu.SendReq;
-import com.android.messaging.sms.SmsSender.SendResult;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.BugleGservices;
-import com.android.messaging.util.BugleGservicesKeys;
-import com.android.messaging.util.BuglePrefs;
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.DebugUtils;
-import com.android.messaging.util.EmailAddress;
-import com.android.messaging.util.ImageUtils;
-import com.android.messaging.util.ImageUtils.ImageResizer;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.MediaMetadataRetrieverWrapper;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.PhoneUtils;
-import com.google.common.base.Joiner;
-
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.GregorianCalendar;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Set;
-import java.util.UUID;
-
-/**
- * Utils for sending sms/mms messages.
- */
-public class MmsUtils {
- private static final String TAG = LogUtil.BUGLE_TAG;
-
- public static final boolean DEFAULT_DELIVERY_REPORT_MODE = false;
- public static final boolean DEFAULT_READ_REPORT_MODE = false;
- public static final long DEFAULT_EXPIRY_TIME_IN_SECONDS = 7 * 24 * 60 * 60;
- public static final int DEFAULT_PRIORITY = PduHeaders.PRIORITY_NORMAL;
-
- public static final int MAX_SMS_RETRY = 3;
-
- /**
- * MMS request succeeded
- */
- public static final int MMS_REQUEST_SUCCEEDED = 0;
- /**
- * MMS request failed with a transient error and can be retried automatically
- */
- public static final int MMS_REQUEST_AUTO_RETRY = 1;
- /**
- * MMS request failed with an error and can be retried manually
- */
- public static final int MMS_REQUEST_MANUAL_RETRY = 2;
- /**
- * MMS request failed with a specific error and should not be retried
- */
- public static final int MMS_REQUEST_NO_RETRY = 3;
-
- public static final String getRequestStatusDescription(final int status) {
- switch (status) {
- case MMS_REQUEST_SUCCEEDED:
- return "SUCCEEDED";
- case MMS_REQUEST_AUTO_RETRY:
- return "AUTO_RETRY";
- case MMS_REQUEST_MANUAL_RETRY:
- return "MANUAL_RETRY";
- case MMS_REQUEST_NO_RETRY:
- return "NO_RETRY";
- default:
- return String.valueOf(status) + " (check MmsUtils)";
- }
- }
-
- public static final int PDU_HEADER_VALUE_UNDEFINED = 0;
-
- private static final int DEFAULT_DURATION = 5000; //ms
-
- // amount of space to leave in a MMS for text and overhead.
- private static final int MMS_MAX_SIZE_SLOP = 1024;
- public static final long INVALID_TIMESTAMP = 0L;
- private static String[] sNoSubjectStrings;
-
- public static class MmsInfo {
- public Uri mUri;
- public int mMessageSize;
- public PduBody mPduBody;
- }
-
- // Sync all remote messages apart from drafts
- private static final String REMOTE_SMS_SELECTION = String.format(
- Locale.US,
- "(%s IN (%d, %d, %d, %d, %d))",
- Sms.TYPE,
- Sms.MESSAGE_TYPE_INBOX,
- Sms.MESSAGE_TYPE_OUTBOX,
- Sms.MESSAGE_TYPE_QUEUED,
- Sms.MESSAGE_TYPE_FAILED,
- Sms.MESSAGE_TYPE_SENT);
-
- private static final String REMOTE_MMS_SELECTION = String.format(
- Locale.US,
- "((%s IN (%d, %d, %d, %d)) AND (%s IN (%d, %d, %d)))",
- Mms.MESSAGE_BOX,
- Mms.MESSAGE_BOX_INBOX,
- Mms.MESSAGE_BOX_OUTBOX,
- Mms.MESSAGE_BOX_SENT,
- Mms.MESSAGE_BOX_FAILED,
- Mms.MESSAGE_TYPE,
- PduHeaders.MESSAGE_TYPE_SEND_REQ,
- PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND,
- PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF);
-
- /**
- * Type selection for importing sms messages.
- *
- * @return The SQL selection for importing sms messages
- */
- public static String getSmsTypeSelectionSql() {
- return REMOTE_SMS_SELECTION;
- }
-
- /**
- * Type selection for importing mms messages.
- *
- * @return The SQL selection for importing mms messages. This selects the message type,
- * not including the selection on timestamp.
- */
- public static String getMmsTypeSelectionSql() {
- return REMOTE_MMS_SELECTION;
- }
-
- // SMIL spec: http://www.w3.org/TR/SMIL3
-
- private static final String sSmilImagePart =
- "<par dur=\"" + DEFAULT_DURATION + "ms\">" +
- "<img src=\"%s\" region=\"Image\" />" +
- "</par>";
-
- private static final String sSmilVideoPart =
- "<par dur=\"%2$dms\">" +
- "<video src=\"%1$s\" dur=\"%2$dms\" region=\"Image\" />" +
- "</par>";
-
- private static final String sSmilAudioPart =
- "<par dur=\"%2$dms\">" +
- "<audio src=\"%1$s\" dur=\"%2$dms\" />" +
- "</par>";
-
- private static final String sSmilTextPart =
- "<par dur=\"" + DEFAULT_DURATION + "ms\">" +
- "<text src=\"%s\" region=\"Text\" />" +
- "</par>";
-
- private static final String sSmilPart =
- "<par dur=\"" + DEFAULT_DURATION + "ms\">" +
- "<ref src=\"%s\" />" +
- "</par>";
-
- private static final String sSmilTextOnly =
- "<smil>" +
- "<head>" +
- "<layout>" +
- "<root-layout/>" +
- "<region id=\"Text\" top=\"0\" left=\"0\" "
- + "height=\"100%%\" width=\"100%%\"/>" +
- "</layout>" +
- "</head>" +
- "<body>" +
- "%s" + // constructed body goes here
- "</body>" +
- "</smil>";
-
- private static final String sSmilVisualAttachmentsOnly =
- "<smil>" +
- "<head>" +
- "<layout>" +
- "<root-layout/>" +
- "<region id=\"Image\" fit=\"meet\" top=\"0\" left=\"0\" "
- + "height=\"100%%\" width=\"100%%\"/>" +
- "</layout>" +
- "</head>" +
- "<body>" +
- "%s" + // constructed body goes here
- "</body>" +
- "</smil>";
-
- private static final String sSmilVisualAttachmentsWithText =
- "<smil>" +
- "<head>" +
- "<layout>" +
- "<root-layout/>" +
- "<region id=\"Image\" fit=\"meet\" top=\"0\" left=\"0\" "
- + "height=\"80%%\" width=\"100%%\"/>" +
- "<region id=\"Text\" top=\"80%%\" left=\"0\" height=\"20%%\" "
- + "width=\"100%%\"/>" +
- "</layout>" +
- "</head>" +
- "<body>" +
- "%s" + // constructed body goes here
- "</body>" +
- "</smil>";
-
- private static final String sSmilNonVisualAttachmentsOnly =
- "<smil>" +
- "<head>" +
- "<layout>" +
- "<root-layout/>" +
- "</layout>" +
- "</head>" +
- "<body>" +
- "%s" + // constructed body goes here
- "</body>" +
- "</smil>";
-
- private static final String sSmilNonVisualAttachmentsWithText = sSmilTextOnly;
-
- public static final String MMS_DUMP_PREFIX = "mmsdump-";
- public static final String SMS_DUMP_PREFIX = "smsdump-";
-
- public static final int MIN_VIDEO_BYTES_PER_SECOND = 4 * 1024;
- public static final int MIN_IMAGE_BYTE_SIZE = 16 * 1024;
- public static final int MAX_VIDEO_ATTACHMENT_COUNT = 1;
-
- public static MmsInfo makePduBody(final Context context, final MessageData message,
- final int subId) {
- final PduBody pb = new PduBody();
-
- // Compute data size requirements for this message: count up images and total size of
- // non-image attachments.
- int totalLength = 0;
- int countImage = 0;
- for (final MessagePartData part : message.getParts()) {
- if (part.isAttachment()) {
- final String contentType = part.getContentType();
- if (ContentType.isImageType(contentType)) {
- countImage++;
- } else if (ContentType.isVCardType(contentType)) {
- totalLength += getDataLength(context, part.getContentUri());
- } else {
- totalLength += getMediaFileSize(part.getContentUri());
- }
- }
- }
- final long minSize = countImage * MIN_IMAGE_BYTE_SIZE;
- final int byteBudget = MmsConfig.get(subId).getMaxMessageSize() - totalLength
- - MMS_MAX_SIZE_SLOP;
- final double budgetFactor =
- minSize > 0 ? Math.max(1.0, byteBudget / ((double) minSize)) : 1;
- final int bytesPerImage = (int) (budgetFactor * MIN_IMAGE_BYTE_SIZE);
- final int widthLimit = MmsConfig.get(subId).getMaxImageWidth();
- final int heightLimit = MmsConfig.get(subId).getMaxImageHeight();
-
- // Actually add the attachments, shrinking images appropriately.
- int index = 0;
- totalLength = 0;
- boolean hasVisualAttachment = false;
- boolean hasNonVisualAttachment = false;
- boolean hasText = false;
- final StringBuilder smilBody = new StringBuilder();
- for (final MessagePartData part : message.getParts()) {
- String srcName;
- if (part.isAttachment()) {
- String contentType = part.getContentType();
- if (ContentType.isImageType(contentType)) {
- // There's a good chance that if we selected the image from our media picker the
- // content type is image/*. Fix the content type here for gifs so that we only
- // need to open the input stream once. All other gif vs static image checks will
- // only have to do a string comparison which is much cheaper.
- final boolean isGif = ImageUtils.isGif(contentType, part.getContentUri());
- contentType = isGif ? ContentType.IMAGE_GIF : contentType;
- srcName = String.format(isGif ? "image%06d.gif" : "image%06d.jpg", index);
- smilBody.append(String.format(sSmilImagePart, srcName));
- totalLength += addPicturePart(context, pb, index, part,
- widthLimit, heightLimit, bytesPerImage, srcName, contentType);
- hasVisualAttachment = true;
- } else if (ContentType.isVideoType(contentType)) {
- srcName = String.format("video%06d.mp4", index);
- final int length = addVideoPart(context, pb, part, srcName);
- totalLength += length;
- smilBody.append(String.format(sSmilVideoPart, srcName,
- getMediaDurationMs(context, part, DEFAULT_DURATION)));
- hasVisualAttachment = true;
- } else if (ContentType.isVCardType(contentType)) {
- srcName = String.format("contact%06d.vcf", index);
- totalLength += addVCardPart(context, pb, part, srcName);
- smilBody.append(String.format(sSmilPart, srcName));
- hasNonVisualAttachment = true;
- } else if (ContentType.isAudioType(contentType)) {
- srcName = String.format("recording%06d.amr", index);
- totalLength += addOtherPart(context, pb, part, srcName);
- final int duration = getMediaDurationMs(context, part, -1);
- Assert.isTrue(duration != -1);
- smilBody.append(String.format(sSmilAudioPart, srcName, duration));
- hasNonVisualAttachment = true;
- } else {
- srcName = String.format("other%06d.dat", index);
- totalLength += addOtherPart(context, pb, part, srcName);
- smilBody.append(String.format(sSmilPart, srcName));
- }
- index++;
- }
- if (!TextUtils.isEmpty(part.getText())) {
- hasText = true;
- }
- }
-
- if (hasText) {
- final String srcName = String.format("text.%06d.txt", index);
- final String text = message.getMessageText();
- totalLength += addTextPart(context, pb, text, srcName);
-
- // Append appropriate SMIL to the body.
- smilBody.append(String.format(sSmilTextPart, srcName));
- }
-
- final String smilTemplate = getSmilTemplate(hasVisualAttachment,
- hasNonVisualAttachment, hasText);
- addSmilPart(pb, smilTemplate, smilBody.toString());
-
- final MmsInfo mmsInfo = new MmsInfo();
- mmsInfo.mPduBody = pb;
- mmsInfo.mMessageSize = totalLength;
-
- return mmsInfo;
- }
-
- private static int getMediaDurationMs(final Context context, final MessagePartData part,
- final int defaultDurationMs) {
- Assert.notNull(context);
- Assert.notNull(part);
- Assert.isTrue(ContentType.isAudioType(part.getContentType()) ||
- ContentType.isVideoType(part.getContentType()));
-
- final MediaMetadataRetrieverWrapper retriever = new MediaMetadataRetrieverWrapper();
- try {
- retriever.setDataSource(part.getContentUri());
- return retriever.extractInteger(
- MediaMetadataRetriever.METADATA_KEY_DURATION, defaultDurationMs);
- } catch (final IOException e) {
- LogUtil.i(LogUtil.BUGLE_TAG, "Error extracting duration from " + part.getContentUri(), e);
- return defaultDurationMs;
- } finally {
- retriever.release();
- }
- }
-
- private static void setPartContentLocationAndId(final PduPart part, final String srcName) {
- // Set Content-Location.
- part.setContentLocation(srcName.getBytes());
-
- // Set Content-Id.
- final int index = srcName.lastIndexOf(".");
- final String contentId = (index == -1) ? srcName : srcName.substring(0, index);
- part.setContentId(contentId.getBytes());
- }
-
- private static int addTextPart(final Context context, final PduBody pb,
- final String text, final String srcName) {
- final PduPart part = new PduPart();
-
- // Set Charset if it's a text media.
- part.setCharset(CharacterSets.UTF_8);
-
- // Set Content-Type.
- part.setContentType(ContentType.TEXT_PLAIN.getBytes());
-
- // Set Content-Location.
- setPartContentLocationAndId(part, srcName);
-
- part.setData(text.getBytes());
-
- pb.addPart(part);
-
- return part.getData().length;
- }
-
- private static int addPicturePart(final Context context, final PduBody pb, final int index,
- final MessagePartData messagePart, int widthLimit, int heightLimit,
- final int maxPartSize, final String srcName, final String contentType) {
- final Uri imageUri = messagePart.getContentUri();
- final int width = messagePart.getWidth();
- final int height = messagePart.getHeight();
-
- // Swap the width and height limits to match the orientation of the image so we scale the
- // picture as little as possible.
- if ((height > width) != (heightLimit > widthLimit)) {
- final int temp = widthLimit;
- widthLimit = heightLimit;
- heightLimit = temp;
- }
-
- final int orientation = ImageUtils.getOrientation(context, imageUri);
- int imageSize = getDataLength(context, imageUri);
- if (imageSize <= 0) {
- LogUtil.e(TAG, "Can't get image", new Exception());
- return 0;
- }
-
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "addPicturePart size: " + imageSize + " width: "
- + width + " widthLimit: " + widthLimit
- + " height: " + height
- + " heightLimit: " + heightLimit);
- }
-
- PduPart part;
- // Check if we're already within the limits - in which case we don't need to resize.
- // The size can be zero here, even when the media has content. See the comment in
- // MediaModel.initMediaSize. Sometimes it'll compute zero and it's costly to read the
- // whole stream to compute the size. When we call getResizedImageAsPart(), we'll correctly
- // set the size.
- if (imageSize <= maxPartSize &&
- width <= widthLimit &&
- height <= heightLimit &&
- (orientation == android.media.ExifInterface.ORIENTATION_UNDEFINED ||
- orientation == android.media.ExifInterface.ORIENTATION_NORMAL)) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "addPicturePart - already sized");
- }
- part = new PduPart();
- part.setDataUri(imageUri);
- part.setContentType(contentType.getBytes());
- } else {
- part = getResizedImageAsPart(widthLimit, heightLimit, maxPartSize,
- width, height, orientation, imageUri, context, contentType);
- if (part == null) {
- final OutOfMemoryError e = new OutOfMemoryError();
- LogUtil.e(TAG, "Can't resize image: not enough memory?", e);
- throw e;
- }
- imageSize = part.getData().length;
- }
-
- setPartContentLocationAndId(part, srcName);
-
- pb.addPart(index, part);
-
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "addPicturePart size: " + imageSize);
- }
-
- return imageSize;
- }
-
- private static void addPartForUri(final Context context, final PduBody pb,
- final String srcName, final Uri uri, final String contentType) {
- final PduPart part = new PduPart();
- part.setDataUri(uri);
- part.setContentType(contentType.getBytes());
-
- setPartContentLocationAndId(part, srcName);
-
- pb.addPart(part);
- }
-
- private static int addVCardPart(final Context context, final PduBody pb,
- final MessagePartData messagePart, final String srcName) {
- final Uri vcardUri = messagePart.getContentUri();
- final String contentType = messagePart.getContentType();
- final int vcardSize = getDataLength(context, vcardUri);
- if (vcardSize <= 0) {
- LogUtil.e(TAG, "Can't get vcard", new Exception());
- return 0;
- }
-
- addPartForUri(context, pb, srcName, vcardUri, contentType);
-
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "addVCardPart size: " + vcardSize);
- }
-
- return vcardSize;
- }
-
- /**
- * Add video part recompressing video if necessary. If recompression fails, part is not
- * added.
- */
- private static int addVideoPart(final Context context, final PduBody pb,
- final MessagePartData messagePart, final String srcName) {
- final Uri attachmentUri = messagePart.getContentUri();
- String contentType = messagePart.getContentType();
-
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "addPart attachmentUrl: " + attachmentUri.toString());
- }
-
- if (TextUtils.isEmpty(contentType)) {
- contentType = ContentType.VIDEO_3G2;
- }
-
- addPartForUri(context, pb, srcName, attachmentUri, contentType);
- return (int) getMediaFileSize(attachmentUri);
- }
-
- private static int addOtherPart(final Context context, final PduBody pb,
- final MessagePartData messagePart, final String srcName) {
- final Uri attachmentUri = messagePart.getContentUri();
- final String contentType = messagePart.getContentType();
-
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "addPart attachmentUrl: " + attachmentUri.toString());
- }
-
- final int dataSize = (int) getMediaFileSize(attachmentUri);
-
- addPartForUri(context, pb, srcName, attachmentUri, contentType);
-
- return dataSize;
- }
-
- private static void addSmilPart(final PduBody pb, final String smilTemplate,
- final String smilBody) {
- final PduPart smilPart = new PduPart();
- smilPart.setContentId("smil".getBytes());
- smilPart.setContentLocation("smil.xml".getBytes());
- smilPart.setContentType(ContentType.APP_SMIL.getBytes());
- final String smil = String.format(smilTemplate, smilBody);
- smilPart.setData(smil.getBytes());
- pb.addPart(0, smilPart);
- }
-
- private static String getSmilTemplate(final boolean hasVisualAttachments,
- final boolean hasNonVisualAttachments, final boolean hasText) {
- if (hasVisualAttachments) {
- return hasText ? sSmilVisualAttachmentsWithText : sSmilVisualAttachmentsOnly;
- }
- if (hasNonVisualAttachments) {
- return hasText ? sSmilNonVisualAttachmentsWithText : sSmilNonVisualAttachmentsOnly;
- }
- return sSmilTextOnly;
- }
-
- private static int getDataLength(final Context context, final Uri uri) {
- InputStream is = null;
- try {
- is = context.getContentResolver().openInputStream(uri);
- try {
- return is == null ? 0 : is.available();
- } catch (final IOException e) {
- LogUtil.e(TAG, "getDataLength couldn't stream: " + uri, e);
- }
- } catch (final FileNotFoundException e) {
- LogUtil.e(TAG, "getDataLength couldn't open: " + uri, e);
- } finally {
- if (is != null) {
- try {
- is.close();
- } catch (final IOException e) {
- LogUtil.e(TAG, "getDataLength couldn't close: " + uri, e);
- }
- }
- }
- return 0;
- }
-
- /**
- * Returns {@code true} if group mms is turned on,
- * {@code false} otherwise.
- *
- * For the group mms feature to be enabled, the following must be true:
- * 1. the feature is enabled in mms_config.xml (currently on by default)
- * 2. the feature is enabled in the SMS settings page
- *
- * @return true if group mms is supported
- */
- public static boolean groupMmsEnabled(final int subId) {
- final Context context = Factory.get().getApplicationContext();
- final Resources resources = context.getResources();
- final BuglePrefs prefs = BuglePrefs.getSubscriptionPrefs(subId);
- final String groupMmsKey = resources.getString(R.string.group_mms_pref_key);
- final boolean groupMmsEnabledDefault = resources.getBoolean(R.bool.group_mms_pref_default);
- final boolean groupMmsPrefOn = prefs.getBoolean(groupMmsKey, groupMmsEnabledDefault);
- return MmsConfig.get(subId).getGroupMmsEnabled() && groupMmsPrefOn;
- }
-
- /**
- * Get a version of this image resized to fit the given dimension and byte-size limits. Note
- * that the content type of the resulting PduPart may not be the same as the content type of
- * this UriImage; always call {@link PduPart#getContentType()} to get the new content type.
- *
- * @param widthLimit The width limit, in pixels
- * @param heightLimit The height limit, in pixels
- * @param byteLimit The binary size limit, in bytes
- * @param width The image width, in pixels
- * @param height The image height, in pixels
- * @param orientation Orientation constant from ExifInterface for rotating or flipping the
- * image
- * @param imageUri Uri to the image data
- * @param context Needed to open the image
- * @return A new PduPart containing the resized image data
- */
- private static PduPart getResizedImageAsPart(final int widthLimit,
- final int heightLimit, final int byteLimit, final int width, final int height,
- final int orientation, final Uri imageUri, final Context context, final String contentType) {
- final PduPart part = new PduPart();
-
- final byte[] data = ImageResizer.getResizedImageData(width, height, orientation,
- widthLimit, heightLimit, byteLimit, imageUri, context, contentType);
- if (data == null) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "Resize image failed.");
- }
- return null;
- }
-
- part.setData(data);
- // Any static images will be compressed into a jpeg
- final String contentTypeOfResizedImage = ImageUtils.isGif(contentType, imageUri)
- ? ContentType.IMAGE_GIF : ContentType.IMAGE_JPEG;
- part.setContentType(contentTypeOfResizedImage.getBytes());
-
- return part;
- }
-
- /**
- * Get media file size
- */
- public static long getMediaFileSize(final Uri uri) {
- final Context context = Factory.get().getApplicationContext();
- AssetFileDescriptor fd = null;
- try {
- fd = context.getContentResolver().openAssetFileDescriptor(uri, "r");
- if (fd != null) {
- return fd.getParcelFileDescriptor().getStatSize();
- }
- } catch (final FileNotFoundException e) {
- LogUtil.e(TAG, "MmsUtils.getMediaFileSize: cound not find media file: " + e, e);
- } finally {
- if (fd != null) {
- try {
- fd.close();
- } catch (final IOException e) {
- LogUtil.e(TAG, "MmsUtils.getMediaFileSize: failed to close " + e, e);
- }
- }
- }
- return 0L;
- }
-
- // Code for extracting the actual phone numbers for the participants in a conversation,
- // given a thread id.
-
- private static final Uri ALL_THREADS_URI =
- Threads.CONTENT_URI.buildUpon().appendQueryParameter("simple", "true").build();
-
- private static final String[] RECIPIENTS_PROJECTION = {
- Threads._ID,
- Threads.RECIPIENT_IDS
- };
-
- private static final int RECIPIENT_IDS = 1;
-
- public static List<String> getRecipientsByThread(final long threadId) {
- final String spaceSepIds = getRawRecipientIdsForThread(threadId);
- if (!TextUtils.isEmpty(spaceSepIds)) {
- final Context context = Factory.get().getApplicationContext();
- return getAddresses(context, spaceSepIds);
- }
- return null;
- }
-
- // NOTE: There are phones on which you can't get the recipients from the thread id for SMS
- // until you have a message in the conversation!
- public static String getRawRecipientIdsForThread(final long threadId) {
- if (threadId <= 0) {
- return null;
- }
- final Context context = Factory.get().getApplicationContext();
- final ContentResolver cr = context.getContentResolver();
- final Cursor thread = cr.query(
- ALL_THREADS_URI,
- RECIPIENTS_PROJECTION, "_id=?", new String[] { String.valueOf(threadId) }, null);
- if (thread != null) {
- try {
- if (thread.moveToFirst()) {
- // recipientIds will be a space-separated list of ids into the
- // canonical addresses table.
- return thread.getString(RECIPIENT_IDS);
- }
- } finally {
- thread.close();
- }
- }
- return null;
- }
-
- private static final Uri SINGLE_CANONICAL_ADDRESS_URI =
- Uri.parse("content://mms-sms/canonical-address");
-
- private static List<String> getAddresses(final Context context, final String spaceSepIds) {
- final List<String> numbers = new ArrayList<String>();
- final String[] ids = spaceSepIds.split(" ");
- for (final String id : ids) {
- long longId;
-
- try {
- longId = Long.parseLong(id);
- if (longId < 0) {
- LogUtil.e(TAG, "MmsUtils.getAddresses: invalid id " + longId);
- continue;
- }
- } catch (final NumberFormatException ex) {
- LogUtil.e(TAG, "MmsUtils.getAddresses: invalid id. " + ex, ex);
- // skip this id
- continue;
- }
-
- // TODO: build a single query where we get all the addresses at once.
- Cursor c = null;
- try {
- c = context.getContentResolver().query(
- ContentUris.withAppendedId(SINGLE_CANONICAL_ADDRESS_URI, longId),
- null, null, null, null);
- } catch (final Exception e) {
- LogUtil.e(TAG, "MmsUtils.getAddresses: query failed for id " + longId, e);
- }
- if (c != null) {
- try {
- if (c.moveToFirst()) {
- final String number = c.getString(0);
- if (!TextUtils.isEmpty(number)) {
- numbers.add(number);
- } else {
- LogUtil.w(TAG, "Canonical MMS/SMS address is empty for id: " + longId);
- }
- }
- } finally {
- c.close();
- }
- }
- }
- if (numbers.isEmpty()) {
- LogUtil.w(TAG, "No MMS addresses found from ids string [" + spaceSepIds + "]");
- }
- return numbers;
- }
-
- // Get telephony SMS thread ID
- public static long getOrCreateSmsThreadId(final Context context, final String dest) {
- // use destinations to determine threadId
- final Set<String> recipients = new HashSet<String>();
- recipients.add(dest);
- try {
- return MmsSmsUtils.Threads.getOrCreateThreadId(context, recipients);
- } catch (final IllegalArgumentException e) {
- LogUtil.e(TAG, "MmsUtils: getting thread id failed: " + e);
- return -1;
- }
- }
-
- // Get telephony SMS thread ID
- public static long getOrCreateThreadId(final Context context, final List<String> dests) {
- if (dests == null || dests.size() == 0) {
- return -1;
- }
- // use destinations to determine threadId
- final Set<String> recipients = new HashSet<String>(dests);
- try {
- return MmsSmsUtils.Threads.getOrCreateThreadId(context, recipients);
- } catch (final IllegalArgumentException e) {
- LogUtil.e(TAG, "MmsUtils: getting thread id failed: " + e);
- return -1;
- }
- }
-
- /**
- * Add an SMS to the given URI with thread_id specified.
- *
- * @param resolver the content resolver to use
- * @param uri the URI to add the message to
- * @param subId subId for the receiving sim
- * @param address the address of the sender
- * @param body the body of the message
- * @param subject the psuedo-subject of the message
- * @param date the timestamp for the message
- * @param read true if the message has been read, false if not
- * @param threadId the thread_id of the message
- * @return the URI for the new message
- */
- private static Uri addMessageToUri(final ContentResolver resolver,
- final Uri uri, final int subId, final String address, final String body,
- final String subject, final Long date, final boolean read, final boolean seen,
- final int status, final int type, final long threadId) {
- final ContentValues values = new ContentValues(7);
-
- values.put(Telephony.Sms.ADDRESS, address);
- if (date != null) {
- values.put(Telephony.Sms.DATE, date);
- }
- values.put(Telephony.Sms.READ, read ? 1 : 0);
- values.put(Telephony.Sms.SEEN, seen ? 1 : 0);
- values.put(Telephony.Sms.SUBJECT, subject);
- values.put(Telephony.Sms.BODY, body);
- if (OsUtil.isAtLeastL_MR1()) {
- values.put(Telephony.Sms.SUBSCRIPTION_ID, subId);
- }
- if (status != Telephony.Sms.STATUS_NONE) {
- values.put(Telephony.Sms.STATUS, status);
- }
- if (type != Telephony.Sms.MESSAGE_TYPE_ALL) {
- values.put(Telephony.Sms.TYPE, type);
- }
- if (threadId != -1L) {
- values.put(Telephony.Sms.THREAD_ID, threadId);
- }
- return resolver.insert(uri, values);
- }
-
- // Insert an SMS message to telephony
- public static Uri insertSmsMessage(final Context context, final Uri uri, final int subId,
- final String dest, final String text, final long timestamp, final int status,
- final int type, final long threadId) {
- Uri response = null;
- try {
- response = addMessageToUri(context.getContentResolver(), uri, subId, dest,
- text, null /* subject */, timestamp, true /* read */,
- true /* seen */, status, type, threadId);
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "Mmsutils: Inserted SMS message into telephony (type = " + type + ")"
- + ", uri: " + response);
- }
- } catch (final SQLiteException e) {
- LogUtil.e(TAG, "MmsUtils: persist sms message failure " + e, e);
- } catch (final IllegalArgumentException e) {
- LogUtil.e(TAG, "MmsUtils: persist sms message failure " + e, e);
- }
- return response;
- }
-
- // Update SMS message type in telephony; returns true if it succeeded.
- public static boolean updateSmsMessageSendingStatus(final Context context, final Uri uri,
- final int type, final long date) {
- try {
- final ContentResolver resolver = context.getContentResolver();
- final ContentValues values = new ContentValues(2);
-
- values.put(Telephony.Sms.TYPE, type);
- values.put(Telephony.Sms.DATE, date);
- final int cnt = resolver.update(uri, values, null, null);
- if (cnt == 1) {
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "Mmsutils: Updated sending SMS " + uri + "; type = " + type
- + ", date = " + date + " (millis since epoch)");
- }
- return true;
- }
- } catch (final SQLiteException e) {
- LogUtil.e(TAG, "MmsUtils: update sms message failure " + e, e);
- } catch (final IllegalArgumentException e) {
- LogUtil.e(TAG, "MmsUtils: update sms message failure " + e, e);
- }
- return false;
- }
-
- // Persist a sent MMS message in telephony
- private static Uri insertSendReq(final Context context, final GenericPdu pdu, final int subId,
- final String subPhoneNumber) {
- final PduPersister persister = PduPersister.getPduPersister(context);
- Uri uri = null;
- try {
- // Persist the PDU
- uri = persister.persist(
- pdu,
- Mms.Sent.CONTENT_URI,
- subId,
- subPhoneNumber,
- null/*preOpenedFiles*/);
- // Update mms table to reflect sent messages are always seen and read
- final ContentValues values = new ContentValues(1);
- values.put(Mms.READ, 1);
- values.put(Mms.SEEN, 1);
- SqliteWrapper.update(context, context.getContentResolver(), uri, values, null, null);
- } catch (final MmsException e) {
- LogUtil.e(TAG, "MmsUtils: persist mms sent message failure " + e, e);
- }
- return uri;
- }
-
- // Persist a received MMS message in telephony
- public static Uri insertReceivedMmsMessage(final Context context,
- final RetrieveConf retrieveConf, final int subId, final String subPhoneNumber,
- final long receivedTimestampInSeconds, final String contentLocation) {
- final PduPersister persister = PduPersister.getPduPersister(context);
- Uri uri = null;
- try {
- uri = persister.persist(
- retrieveConf,
- Mms.Inbox.CONTENT_URI,
- subId,
- subPhoneNumber,
- null/*preOpenedFiles*/);
-
- final ContentValues values = new ContentValues(2);
- // Update mms table with local time instead of PDU time
- values.put(Mms.DATE, receivedTimestampInSeconds);
- // Also update the content location field from NotificationInd so that
- // wap push dedup would work even after the wap push is deleted
- values.put(Mms.CONTENT_LOCATION, contentLocation);
- SqliteWrapper.update(context, context.getContentResolver(), uri, values, null, null);
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "MmsUtils: Inserted MMS message into telephony, uri: " + uri);
- }
- } catch (final MmsException e) {
- LogUtil.e(TAG, "MmsUtils: persist mms received message failure " + e, e);
- // Just returns empty uri to RetrieveMmsRequest, which triggers a permanent failure
- } catch (final SQLiteException e) {
- LogUtil.e(TAG, "MmsUtils: update mms received message failure " + e, e);
- // Time update failure is ignored.
- }
- return uri;
- }
-
- // Update MMS message type in telephony; returns true if it succeeded.
- public static boolean updateMmsMessageSendingStatus(final Context context, final Uri uri,
- final int box, final long timestampInMillis) {
- try {
- final ContentResolver resolver = context.getContentResolver();
- final ContentValues values = new ContentValues();
-
- final long timestampInSeconds = timestampInMillis / 1000L;
- values.put(Telephony.Mms.MESSAGE_BOX, box);
- values.put(Telephony.Mms.DATE, timestampInSeconds);
- final int cnt = resolver.update(uri, values, null, null);
- if (cnt == 1) {
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "Mmsutils: Updated sending MMS " + uri + "; box = " + box
- + ", date = " + timestampInSeconds + " (secs since epoch)");
- }
- return true;
- }
- } catch (final SQLiteException e) {
- LogUtil.e(TAG, "MmsUtils: update mms message failure " + e, e);
- } catch (final IllegalArgumentException e) {
- LogUtil.e(TAG, "MmsUtils: update mms message failure " + e, e);
- }
- return false;
- }
-
- /**
- * Parse values from a received sms message
- *
- * @param context
- * @param msgs The received sms message content
- * @param error The received sms error
- * @return Parsed values from the message
- */
- public static ContentValues parseReceivedSmsMessage(
- final Context context, final SmsMessage[] msgs, final int error) {
- final SmsMessage sms = msgs[0];
- final ContentValues values = new ContentValues();
-
- values.put(Sms.ADDRESS, sms.getDisplayOriginatingAddress());
- values.put(Sms.BODY, buildMessageBodyFromPdus(msgs));
- if (MmsUtils.hasSmsDateSentColumn()) {
- // TODO:: The boxing here seems unnecessary.
- values.put(Sms.DATE_SENT, Long.valueOf(sms.getTimestampMillis()));
- }
- values.put(Sms.PROTOCOL, sms.getProtocolIdentifier());
- if (sms.getPseudoSubject().length() > 0) {
- values.put(Sms.SUBJECT, sms.getPseudoSubject());
- }
- values.put(Sms.REPLY_PATH_PRESENT, sms.isReplyPathPresent() ? 1 : 0);
- values.put(Sms.SERVICE_CENTER, sms.getServiceCenterAddress());
- // Error code
- values.put(Sms.ERROR_CODE, error);
-
- return values;
- }
-
- // Some providers send formfeeds in their messages. Convert those formfeeds to newlines.
- private static String replaceFormFeeds(final String s) {
- return s == null ? "" : s.replace('\f', '\n');
- }
-
- // Parse the message body from message PDUs
- private static String buildMessageBodyFromPdus(final SmsMessage[] msgs) {
- if (msgs.length == 1) {
- // There is only one part, so grab the body directly.
- return replaceFormFeeds(msgs[0].getDisplayMessageBody());
- } else {
- // Build up the body from the parts.
- final StringBuilder body = new StringBuilder();
- for (final SmsMessage msg : msgs) {
- try {
- // getDisplayMessageBody() can NPE if mWrappedMessage inside is null.
- body.append(msg.getDisplayMessageBody());
- } catch (final NullPointerException e) {
- // Nothing to do
- }
- }
- return replaceFormFeeds(body.toString());
- }
- }
-
- // Parse the message date
- public static Long getMessageDate(final SmsMessage sms, long now) {
- // Use now for the timestamp to avoid confusion with clock
- // drift between the handset and the SMSC.
- // Check to make sure the system is giving us a non-bogus time.
- final Calendar buildDate = new GregorianCalendar(2011, 8, 18); // 18 Sep 2011
- final Calendar nowDate = new GregorianCalendar();
- nowDate.setTimeInMillis(now);
- if (nowDate.before(buildDate)) {
- // It looks like our system clock isn't set yet because the current time right now
- // is before an arbitrary time we made this build. Instead of inserting a bogus
- // receive time in this case, use the timestamp of when the message was sent.
- now = sms.getTimestampMillis();
- }
- return now;
- }
-
- /**
- * cleanseMmsSubject will take a subject that's says, "<Subject: no subject>", and return
- * a null string. Otherwise it will return the original subject string.
- * @param resources So the function can grab string resources
- * @param subject the raw subject
- * @return
- */
- public static String cleanseMmsSubject(final Resources resources, final String subject) {
- if (TextUtils.isEmpty(subject)) {
- return null;
- }
- if (sNoSubjectStrings == null) {
- sNoSubjectStrings =
- resources.getStringArray(R.array.empty_subject_strings);
- }
- for (final String noSubjectString : sNoSubjectStrings) {
- if (subject.equalsIgnoreCase(noSubjectString)) {
- return null;
- }
- }
- return subject;
- }
-
- // return a semicolon separated list of phone numbers from a smsto: uri.
- public static String getSmsRecipients(final Uri uri) {
- String recipients = uri.getSchemeSpecificPart();
- final int pos = recipients.indexOf('?');
- if (pos != -1) {
- recipients = recipients.substring(0, pos);
- }
- recipients = replaceUnicodeDigits(recipients).replace(',', ';');
- return recipients;
- }
-
- // This function was lifted from Telephony.PhoneNumberUtils because it was @hide
- /**
- * Replace arabic/unicode digits with decimal digits.
- * @param number
- * the number to be normalized.
- * @return the replaced number.
- */
- private static String replaceUnicodeDigits(final String number) {
- final StringBuilder normalizedDigits = new StringBuilder(number.length());
- for (final char c : number.toCharArray()) {
- final int digit = Character.digit(c, 10);
- if (digit != -1) {
- normalizedDigits.append(digit);
- } else {
- normalizedDigits.append(c);
- }
- }
- return normalizedDigits.toString();
- }
-
- /**
- * @return Whether the data roaming is enabled
- */
- private static boolean isDataRoamingEnabled() {
- boolean dataRoamingEnabled = false;
- final ContentResolver cr = Factory.get().getApplicationContext().getContentResolver();
- if (OsUtil.isAtLeastJB_MR1()) {
- dataRoamingEnabled = (Settings.Global.getInt(cr, Settings.Global.DATA_ROAMING, 0) != 0);
- } else {
- dataRoamingEnabled = (Settings.System.getInt(cr, Settings.System.DATA_ROAMING, 0) != 0);
- }
- return dataRoamingEnabled;
- }
-
- /**
- * @return Whether to auto retrieve MMS
- */
- public static boolean allowMmsAutoRetrieve(final int subId) {
- final Context context = Factory.get().getApplicationContext();
- final Resources resources = context.getResources();
- final BuglePrefs prefs = BuglePrefs.getSubscriptionPrefs(subId);
- final boolean autoRetrieve = prefs.getBoolean(
- resources.getString(R.string.auto_retrieve_mms_pref_key),
- resources.getBoolean(R.bool.auto_retrieve_mms_pref_default));
- if (autoRetrieve) {
- final boolean autoRetrieveInRoaming = prefs.getBoolean(
- resources.getString(R.string.auto_retrieve_mms_when_roaming_pref_key),
- resources.getBoolean(R.bool.auto_retrieve_mms_when_roaming_pref_default));
- final PhoneUtils phoneUtils = PhoneUtils.get(subId);
- if ((autoRetrieveInRoaming && phoneUtils.isDataRoamingEnabled())
- || !phoneUtils.isRoaming()) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Parse the message row id from a message Uri.
- *
- * @param messageUri The input Uri
- * @return The message row id if valid, otherwise -1
- */
- public static long parseRowIdFromMessageUri(final Uri messageUri) {
- try {
- if (messageUri != null) {
- return ContentUris.parseId(messageUri);
- }
- } catch (final UnsupportedOperationException e) {
- // Nothing to do
- } catch (final NumberFormatException e) {
- // Nothing to do
- }
- return -1;
- }
-
- public static SmsMessage getSmsMessageFromDeliveryReport(final Intent intent) {
- final byte[] pdu = intent.getByteArrayExtra("pdu");
- return SmsMessage.createFromPdu(pdu);
- }
-
- /**
- * Update the status and date_sent column of sms message in telephony provider
- *
- * @param smsMessageUri
- * @param status
- * @param timeSentInMillis
- */
- public static void updateSmsStatusAndDateSent(final Uri smsMessageUri, final int status,
- final long timeSentInMillis) {
- if (smsMessageUri == null) {
- return;
- }
- final ContentValues values = new ContentValues();
- values.put(Sms.STATUS, status);
- if (MmsUtils.hasSmsDateSentColumn()) {
- values.put(Sms.DATE_SENT, timeSentInMillis);
- }
- final ContentResolver resolver = Factory.get().getApplicationContext().getContentResolver();
- resolver.update(smsMessageUri, values, null/*where*/, null/*selectionArgs*/);
- }
-
- /**
- * Get the SQL selection statement for matching messages with media.
- *
- * Example for MMS part table:
- * "((ct LIKE 'image/%')
- * OR (ct LIKE 'video/%')
- * OR (ct LIKE 'audio/%')
- * OR (ct='application/ogg'))
- *
- * @param contentTypeColumn The content-type column name
- * @return The SQL selection statement for matching media types: image, video, audio
- */
- public static String getMediaTypeSelectionSql(final String contentTypeColumn) {
- return String.format(
- Locale.US,
- "((%s LIKE '%s') OR (%s LIKE '%s') OR (%s LIKE '%s') OR (%s='%s'))",
- contentTypeColumn,
- "image/%",
- contentTypeColumn,
- "video/%",
- contentTypeColumn,
- "audio/%",
- contentTypeColumn,
- ContentType.AUDIO_OGG);
- }
-
- // Max number of operands per SQL query for deleting SMS messages
- public static final int MAX_IDS_PER_QUERY = 128;
-
- /**
- * Delete MMS messages with media parts.
- *
- * Because the telephony provider constraints, we can't use JOIN and delete messages in one
- * shot. We have to do a query first and then batch delete the messages based on IDs.
- *
- * @return The count of messages deleted.
- */
- public static int deleteMediaMessages() {
- // Do a query first
- //
- // The WHERE clause has two parts:
- // The first part is to select the exact same types of MMS messages as when we import them
- // (so that we don't delete messages that are not in local database)
- // The second part is to select MMS with media parts, including image, video and audio
- final String selection = String.format(
- Locale.US,
- "%s AND (%s IN (SELECT %s FROM part WHERE %s))",
- getMmsTypeSelectionSql(),
- Mms._ID,
- Mms.Part.MSG_ID,
- getMediaTypeSelectionSql(Mms.Part.CONTENT_TYPE));
- final ContentResolver resolver = Factory.get().getApplicationContext().getContentResolver();
- final Cursor cursor = resolver.query(Mms.CONTENT_URI,
- new String[]{ Mms._ID },
- selection,
- null/*selectionArgs*/,
- null/*sortOrder*/);
- int deleted = 0;
- if (cursor != null) {
- final long[] messageIds = new long[cursor.getCount()];
- try {
- int i = 0;
- while (cursor.moveToNext()) {
- messageIds[i++] = cursor.getLong(0);
- }
- } finally {
- cursor.close();
- }
- final int totalIds = messageIds.length;
- if (totalIds > 0) {
- // Batch delete the messages using IDs
- // We don't want to send all IDs at once since there is a limit on SQL statement
- for (int start = 0; start < totalIds; start += MAX_IDS_PER_QUERY) {
- final int end = Math.min(start + MAX_IDS_PER_QUERY, totalIds); // excluding
- final int count = end - start;
- final String batchSelection = String.format(
- Locale.US,
- "%s IN %s",
- Mms._ID,
- getSqlInOperand(count));
- final String[] batchSelectionArgs =
- getSqlInOperandArgs(messageIds, start, count);
- final int deletedForBatch = resolver.delete(
- Mms.CONTENT_URI,
- batchSelection,
- batchSelectionArgs);
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "deleteMediaMessages: deleting IDs = "
- + Joiner.on(',').skipNulls().join(batchSelectionArgs)
- + ", deleted = " + deletedForBatch);
- }
- deleted += deletedForBatch;
- }
- }
- }
- return deleted;
- }
-
- /**
- * Get the (?,?,...) thing for the SQL IN operator by a count
- *
- * @param count
- * @return
- */
- public static String getSqlInOperand(final int count) {
- if (count <= 0) {
- return null;
- }
- final StringBuilder sb = new StringBuilder();
- sb.append("(?");
- for (int i = 0; i < count - 1; i++) {
- sb.append(",?");
- }
- sb.append(")");
- return sb.toString();
- }
-
- /**
- * Get the args for SQL IN operator from a long ID array
- *
- * @param ids The original long id array
- * @param start Start of the ids to fill the args
- * @param count Number of ids to pack
- * @return The long array with the id args
- */
- private static String[] getSqlInOperandArgs(
- final long[] ids, final int start, final int count) {
- if (count <= 0) {
- return null;
- }
- final String[] args = new String[count];
- for (int i = 0; i < count; i++) {
- args[i] = Long.toString(ids[start + i]);
- }
- return args;
- }
-
- /**
- * Delete SMS and MMS messages that are earlier than a specific timestamp
- *
- * @param cutOffTimestampInMillis The cut-off timestamp
- * @return Total number of messages deleted.
- */
- public static int deleteMessagesOlderThan(final long cutOffTimestampInMillis) {
- int deleted = 0;
- final ContentResolver resolver = Factory.get().getApplicationContext().getContentResolver();
- // Delete old SMS
- final String smsSelection = String.format(
- Locale.US,
- "%s AND (%s<=%d)",
- getSmsTypeSelectionSql(),
- Sms.DATE,
- cutOffTimestampInMillis);
- deleted += resolver.delete(Sms.CONTENT_URI, smsSelection, null/*selectionArgs*/);
- // Delete old MMS
- final String mmsSelection = String.format(
- Locale.US,
- "%s AND (%s<=%d)",
- getMmsTypeSelectionSql(),
- Mms.DATE,
- cutOffTimestampInMillis / 1000L);
- deleted += resolver.delete(Mms.CONTENT_URI, mmsSelection, null/*selectionArgs*/);
- return deleted;
- }
-
- /**
- * Update the read status of SMS/MMS messages by thread and timestamp
- *
- * @param threadId The thread of sms/mms to change
- * @param timestampInMillis Change the status before this timestamp
- */
- public static void updateSmsReadStatus(final long threadId, final long timestampInMillis) {
- final ContentResolver resolver = Factory.get().getApplicationContext().getContentResolver();
- final ContentValues values = new ContentValues();
- values.put("read", 1);
- values.put("seen", 1); /* If you read it you saw it */
- final String smsSelection = String.format(
- Locale.US,
- "%s=%d AND %s<=%d AND %s=0",
- Sms.THREAD_ID,
- threadId,
- Sms.DATE,
- timestampInMillis,
- Sms.READ);
- resolver.update(
- Sms.CONTENT_URI,
- values,
- smsSelection,
- null/*selectionArgs*/);
- final String mmsSelection = String.format(
- Locale.US,
- "%s=%d AND %s<=%d AND %s=0",
- Mms.THREAD_ID,
- threadId,
- Mms.DATE,
- timestampInMillis / 1000L,
- Mms.READ);
- resolver.update(
- Mms.CONTENT_URI,
- values,
- mmsSelection,
- null/*selectionArgs*/);
- }
-
- /**
- * Update the read status of a single MMS message by its URI
- *
- * @param mmsUri
- * @param read
- */
- public static void updateReadStatusForMmsMessage(final Uri mmsUri, final boolean read) {
- final ContentResolver resolver = Factory.get().getApplicationContext().getContentResolver();
- final ContentValues values = new ContentValues();
- values.put(Mms.READ, read ? 1 : 0);
- resolver.update(mmsUri, values, null/*where*/, null/*selectionArgs*/);
- }
-
- public static class AttachmentInfo {
- public String mUrl;
- public String mContentType;
- public int mWidth;
- public int mHeight;
- }
-
- /**
- * Convert byte array to Java String using a charset name
- *
- * @param bytes
- * @param charsetName
- * @return
- */
- public static String bytesToString(final byte[] bytes, final String charsetName) {
- if (bytes == null) {
- return null;
- }
- try {
- return new String(bytes, charsetName);
- } catch (final UnsupportedEncodingException e) {
- LogUtil.e(TAG, "MmsUtils.bytesToString: " + e, e);
- return new String(bytes);
- }
- }
-
- /**
- * Convert a Java String to byte array using a charset name
- *
- * @param string
- * @param charsetName
- * @return
- */
- public static byte[] stringToBytes(final String string, final String charsetName) {
- if (string == null) {
- return null;
- }
- try {
- return string.getBytes(charsetName);
- } catch (final UnsupportedEncodingException e) {
- LogUtil.e(TAG, "MmsUtils.stringToBytes: " + e, e);
- return string.getBytes();
- }
- }
-
- private static final String[] TEST_DATE_SENT_PROJECTION = new String[] { Sms.DATE_SENT };
- private static Boolean sHasSmsDateSentColumn = null;
- /**
- * Check if date_sent column exists on ICS and above devices. We need to do a test
- * query to figure that out since on some ICS+ devices, somehow the date_sent column does
- * not exist. http://b/17629135 tracks the associated compliance test.
- *
- * @return Whether "date_sent" column exists in sms table
- */
- public static boolean hasSmsDateSentColumn() {
- if (sHasSmsDateSentColumn == null) {
- Cursor cursor = null;
- try {
- final Context context = Factory.get().getApplicationContext();
- final ContentResolver resolver = context.getContentResolver();
- cursor = SqliteWrapper.query(
- context,
- resolver,
- Sms.CONTENT_URI,
- TEST_DATE_SENT_PROJECTION,
- null/*selection*/,
- null/*selectionArgs*/,
- Sms.DATE_SENT + " ASC LIMIT 1");
- sHasSmsDateSentColumn = true;
- } catch (final SQLiteException e) {
- LogUtil.w(TAG, "date_sent in sms table does not exist", e);
- sHasSmsDateSentColumn = false;
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- }
- return sHasSmsDateSentColumn;
- }
-
- private static final String[] TEST_CARRIERS_PROJECTION =
- new String[] { Telephony.Carriers.MMSC };
- private static Boolean sUseSystemApn = null;
- /**
- * Check if we can access the APN data in the Telephony provider. Access was restricted in
- * JB MR1 (and some JB MR2) devices. If we can't access the APN, we have to fall back and use
- * a private table in our own app.
- *
- * @return Whether we can access the system APN table
- */
- public static boolean useSystemApnTable() {
- if (sUseSystemApn == null) {
- Cursor cursor = null;
- try {
- final Context context = Factory.get().getApplicationContext();
- final ContentResolver resolver = context.getContentResolver();
- cursor = SqliteWrapper.query(
- context,
- resolver,
- Telephony.Carriers.CONTENT_URI,
- TEST_CARRIERS_PROJECTION,
- null/*selection*/,
- null/*selectionArgs*/,
- null);
- sUseSystemApn = true;
- } catch (final SecurityException e) {
- LogUtil.w(TAG, "Can't access system APN, using internal table", e);
- sUseSystemApn = false;
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- }
- return sUseSystemApn;
- }
-
- // For the internal debugger only
- public static void setUseSystemApnTable(final boolean turnOn) {
- if (!turnOn) {
- // We're not turning on to the system table. Instead, we're using our internal table.
- final int osVersion = OsUtil.getApiVersion();
- if (osVersion != android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
- // We're turning on local APNs on a device where we wouldn't normally have the
- // local APN table. Build it here.
-
- final SQLiteDatabase database = ApnDatabase.getApnDatabase().getWritableDatabase();
-
- // Do we already have the table?
- Cursor cursor = null;
- try {
- cursor = database.query(ApnDatabase.APN_TABLE,
- ApnDatabase.APN_PROJECTION,
- null, null, null, null, null, null);
- } catch (final Exception e) {
- // Apparently there's no table, create it now.
- ApnDatabase.forceBuildAndLoadApnTables();
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- }
- }
- sUseSystemApn = turnOn;
- }
-
- /**
- * Checks if we should dump sms, based on both the setting and the global debug
- * flag
- *
- * @return if dump sms is enabled
- */
- public static boolean isDumpSmsEnabled() {
- if (!DebugUtils.isDebugEnabled()) {
- return false;
- }
- return getDumpSmsOrMmsPref(R.string.dump_sms_pref_key, R.bool.dump_sms_pref_default);
- }
-
- /**
- * Checks if we should dump mms, based on both the setting and the global debug
- * flag
- *
- * @return if dump mms is enabled
- */
- public static boolean isDumpMmsEnabled() {
- if (!DebugUtils.isDebugEnabled()) {
- return false;
- }
- return getDumpSmsOrMmsPref(R.string.dump_mms_pref_key, R.bool.dump_mms_pref_default);
- }
-
- /**
- * Load the value of dump sms or mms setting preference
- */
- private static boolean getDumpSmsOrMmsPref(final int prefKeyRes, final int defaultKeyRes) {
- final Context context = Factory.get().getApplicationContext();
- final Resources resources = context.getResources();
- final BuglePrefs prefs = BuglePrefs.getApplicationPrefs();
- final String key = resources.getString(prefKeyRes);
- final boolean defaultValue = resources.getBoolean(defaultKeyRes);
- return prefs.getBoolean(key, defaultValue);
- }
-
- public static final Uri MMS_PART_CONTENT_URI = Uri.parse("content://mms/part");
-
- /**
- * Load MMS from telephony
- *
- * @param mmsUri The MMS pdu Uri
- * @return A memory copy of the MMS pdu including parts (but not addresses)
- */
- public static DatabaseMessages.MmsMessage loadMms(final Uri mmsUri) {
- final Context context = Factory.get().getApplicationContext();
- final ContentResolver resolver = context.getContentResolver();
- DatabaseMessages.MmsMessage mms = null;
- Cursor cursor = null;
- // Load pdu first
- try {
- cursor = SqliteWrapper.query(context, resolver,
- mmsUri,
- DatabaseMessages.MmsMessage.getProjection(),
- null/*selection*/, null/*selectionArgs*/, null/*sortOrder*/);
- if (cursor != null && cursor.moveToFirst()) {
- mms = DatabaseMessages.MmsMessage.get(cursor);
- }
- } catch (final SQLiteException e) {
- LogUtil.e(TAG, "MmsLoader: query pdu failure: " + e, e);
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- if (mms == null) {
- return null;
- }
- // Load parts except SMIL
- // TODO: we may need to load SMIL part in the future.
- final long rowId = MmsUtils.parseRowIdFromMessageUri(mmsUri);
- final String selection = String.format(
- Locale.US,
- "%s != '%s' AND %s = ?",
- Mms.Part.CONTENT_TYPE,
- ContentType.APP_SMIL,
- Mms.Part.MSG_ID);
- cursor = null;
- try {
- cursor = SqliteWrapper.query(context, resolver,
- MMS_PART_CONTENT_URI,
- DatabaseMessages.MmsPart.PROJECTION,
- selection,
- new String[] { Long.toString(rowId) },
- null/*sortOrder*/);
- if (cursor != null) {
- while (cursor.moveToNext()) {
- mms.addPart(DatabaseMessages.MmsPart.get(cursor, true/*loadMedia*/));
- }
- }
- } catch (final SQLiteException e) {
- LogUtil.e(TAG, "MmsLoader: query parts failure: " + e, e);
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- return mms;
- }
-
- /**
- * Get the sender of an MMS message
- *
- * @param recipients The recipient list of the message
- * @param mmsUri The pdu uri of the MMS
- * @return The sender phone number of the MMS
- */
- public static String getMmsSender(final List<String> recipients, final String mmsUri) {
- final Context context = Factory.get().getApplicationContext();
- // We try to avoid the database query.
- // If this is a 1v1 conv., then the other party is the sender
- if (recipients != null && recipients.size() == 1) {
- return recipients.get(0);
- }
- // Otherwise, we have to query the MMS addr table for sender address
- // This should only be done for a received group mms message
- final Cursor cursor = SqliteWrapper.query(
- context,
- context.getContentResolver(),
- Uri.withAppendedPath(Uri.parse(mmsUri), "addr"),
- new String[] { Mms.Addr.ADDRESS, Mms.Addr.CHARSET },
- Mms.Addr.TYPE + "=" + PduHeaders.FROM,
- null/*selectionArgs*/,
- null/*sortOrder*/);
- if (cursor != null) {
- try {
- if (cursor.moveToFirst()) {
- return DatabaseMessages.MmsAddr.get(cursor);
- }
- } finally {
- cursor.close();
- }
- }
- return null;
- }
-
- public static int bugleStatusForMms(final boolean isOutgoing, final boolean isNotification,
- final int messageBox) {
- int bugleStatus = MessageData.BUGLE_STATUS_UNKNOWN;
- // For a message we sync either
- if (isOutgoing) {
- if (messageBox == Mms.MESSAGE_BOX_OUTBOX || messageBox == Mms.MESSAGE_BOX_FAILED) {
- // Not sent counts as failed and available for manual resend
- bugleStatus = MessageData.BUGLE_STATUS_OUTGOING_FAILED;
- } else {
- // Otherwise outgoing message is complete
- bugleStatus = MessageData.BUGLE_STATUS_OUTGOING_COMPLETE;
- }
- } else if (isNotification) {
- // Incoming MMS notifications we sync count as failed and available for manual download
- bugleStatus = MessageData.BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD;
- } else {
- // Other incoming MMS messages are complete
- bugleStatus = MessageData.BUGLE_STATUS_INCOMING_COMPLETE;
- }
- return bugleStatus;
- }
-
- public static MessageData createMmsMessage(final DatabaseMessages.MmsMessage mms,
- final String conversationId, final String participantId, final String selfId,
- final int bugleStatus) {
- Assert.notNull(mms);
- final boolean isNotification = (mms.mMmsMessageType ==
- PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND);
- final int rawMmsStatus = (bugleStatus < MessageData.BUGLE_STATUS_FIRST_INCOMING
- ? mms.mRetrieveStatus : mms.mResponseStatus);
-
- final MessageData message = MessageData.createMmsMessage(mms.getUri(),
- participantId, selfId, conversationId, isNotification, bugleStatus,
- mms.mContentLocation, mms.mTransactionId, mms.mPriority, mms.mSubject,
- mms.mSeen, mms.mRead, mms.getSize(), rawMmsStatus,
- mms.mExpiryInMillis, mms.mSentTimestampInMillis, mms.mTimestampInMillis);
-
- for (final DatabaseMessages.MmsPart part : mms.mParts) {
- final MessagePartData messagePart = MmsUtils.createMmsMessagePart(part);
- // Import media and text parts (skip SMIL and others)
- if (messagePart != null) {
- message.addPart(messagePart);
- }
- }
-
- if (!message.getParts().iterator().hasNext()) {
- message.addPart(MessagePartData.createEmptyMessagePart());
- }
-
- return message;
- }
-
- public static MessagePartData createMmsMessagePart(final DatabaseMessages.MmsPart part) {
- MessagePartData messagePart = null;
- if (part.isText()) {
- final int mmsTextLengthLimit =
- BugleGservices.get().getInt(BugleGservicesKeys.MMS_TEXT_LIMIT,
- BugleGservicesKeys.MMS_TEXT_LIMIT_DEFAULT);
- String text = part.mText;
- if (text != null && text.length() > mmsTextLengthLimit) {
- // Limit the text to a reasonable value. We ran into a situation where a vcard
- // with a photo was sent as plain text. The massive amount of text caused the
- // app to hang, ANR, and eventually crash in native text code.
- text = text.substring(0, mmsTextLengthLimit);
- }
- messagePart = MessagePartData.createTextMessagePart(text);
- } else if (part.isMedia()) {
- messagePart = MessagePartData.createMediaMessagePart(part.mContentType,
- part.getDataUri(), MessagePartData.UNSPECIFIED_SIZE,
- MessagePartData.UNSPECIFIED_SIZE);
- }
- return messagePart;
- }
-
- public static class StatusPlusUri {
- // The request status to be as the result of the operation
- // e.g. MMS_REQUEST_MANUAL_RETRY
- public final int status;
- // The raw telephony status
- public final int rawStatus;
- // The raw telephony URI
- public final Uri uri;
- // The operation result code from system api invocation (sent by system)
- // or mapped from internal exception (sent by app)
- public final int resultCode;
-
- public StatusPlusUri(final int status, final int rawStatus, final Uri uri) {
- this.status = status;
- this.rawStatus = rawStatus;
- this.uri = uri;
- resultCode = MessageData.UNKNOWN_RESULT_CODE;
- }
-
- public StatusPlusUri(final int status, final int rawStatus, final Uri uri,
- final int resultCode) {
- this.status = status;
- this.rawStatus = rawStatus;
- this.uri = uri;
- this.resultCode = resultCode;
- }
- }
-
- public static class SendReqResp {
- public SendReq mSendReq;
- public SendConf mSendConf;
-
- public SendReqResp(final SendReq sendReq, final SendConf sendConf) {
- mSendReq = sendReq;
- mSendConf = sendConf;
- }
- }
-
- /**
- * Returned when sending/downloading MMS via platform APIs. In that case, we have to wait to
- * receive the pending intent to determine status.
- */
- public static final StatusPlusUri STATUS_PENDING = new StatusPlusUri(-1, -1, null);
-
- public static StatusPlusUri downloadMmsMessage(final Context context, final Uri notificationUri,
- final int subId, final String subPhoneNumber, final String transactionId,
- final String contentLocation, final boolean autoDownload,
- final long receivedTimestampInSeconds, Bundle extras) {
- if (TextUtils.isEmpty(contentLocation)) {
- LogUtil.e(TAG, "MmsUtils: Download from empty content location URL");
- return new StatusPlusUri(
- MMS_REQUEST_NO_RETRY, MessageData.RAW_TELEPHONY_STATUS_UNDEFINED, null);
- }
- if (!isMmsDataAvailable(subId)) {
- LogUtil.e(TAG,
- "MmsUtils: failed to download message, no data available");
- return new StatusPlusUri(MMS_REQUEST_MANUAL_RETRY,
- MessageData.RAW_TELEPHONY_STATUS_UNDEFINED,
- null,
- SmsManager.MMS_ERROR_NO_DATA_NETWORK);
- }
- int status = MMS_REQUEST_MANUAL_RETRY;
- try {
- RetrieveConf retrieveConf = null;
- if (DebugUtils.isDebugEnabled() &&
- MediaScratchFileProvider
- .isMediaScratchSpaceUri(Uri.parse(contentLocation))) {
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "MmsUtils: Reading MMS from dump file: " + contentLocation);
- }
- final String fileName = Uri.parse(contentLocation).getPathSegments().get(1);
- final byte[] data = DebugUtils.receiveFromDumpFile(fileName);
- retrieveConf = receiveFromDumpFile(data);
- } else {
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "MmsUtils: Downloading MMS via MMS lib API; notification "
- + "message: " + notificationUri);
- }
- if (OsUtil.isAtLeastL_MR1()) {
- if (subId < 0) {
- LogUtil.e(TAG, "MmsUtils: Incoming MMS came from unknown SIM");
- throw new MmsFailureException(MMS_REQUEST_NO_RETRY,
- "Message from unknown SIM");
- }
- } else {
- Assert.isTrue(subId == ParticipantData.DEFAULT_SELF_SUB_ID);
- }
- if (extras == null) {
- extras = new Bundle();
- }
- extras.putParcelable(DownloadMmsAction.EXTRA_NOTIFICATION_URI, notificationUri);
- extras.putInt(DownloadMmsAction.EXTRA_SUB_ID, subId);
- extras.putString(DownloadMmsAction.EXTRA_SUB_PHONE_NUMBER, subPhoneNumber);
- extras.putString(DownloadMmsAction.EXTRA_TRANSACTION_ID, transactionId);
- extras.putString(DownloadMmsAction.EXTRA_CONTENT_LOCATION, contentLocation);
- extras.putBoolean(DownloadMmsAction.EXTRA_AUTO_DOWNLOAD, autoDownload);
- extras.putLong(DownloadMmsAction.EXTRA_RECEIVED_TIMESTAMP,
- receivedTimestampInSeconds);
-
- MmsSender.downloadMms(context, subId, contentLocation, extras);
- return STATUS_PENDING; // Download happens asynchronously; no status to return
- }
- return insertDownloadedMessageAndSendResponse(context, notificationUri, subId,
- subPhoneNumber, transactionId, contentLocation, autoDownload,
- receivedTimestampInSeconds, retrieveConf);
-
- } catch (final MmsFailureException e) {
- LogUtil.e(TAG, "MmsUtils: failed to download message " + notificationUri, e);
- status = e.retryHint;
- } catch (final InvalidHeaderValueException e) {
- LogUtil.e(TAG, "MmsUtils: failed to download message " + notificationUri, e);
- }
- return new StatusPlusUri(status, PDU_HEADER_VALUE_UNDEFINED, null);
- }
-
- public static StatusPlusUri insertDownloadedMessageAndSendResponse(final Context context,
- final Uri notificationUri, final int subId, final String subPhoneNumber,
- final String transactionId, final String contentLocation,
- final boolean autoDownload, final long receivedTimestampInSeconds,
- final RetrieveConf retrieveConf) {
- final byte[] transactionIdBytes = stringToBytes(transactionId, "UTF-8");
- Uri messageUri = null;
- int status = MMS_REQUEST_MANUAL_RETRY;
- int retrieveStatus = PDU_HEADER_VALUE_UNDEFINED;
-
- retrieveStatus = retrieveConf.getRetrieveStatus();
- if (retrieveStatus == PduHeaders.RETRIEVE_STATUS_OK) {
- status = MMS_REQUEST_SUCCEEDED;
- } else if (retrieveStatus >= PduHeaders.RETRIEVE_STATUS_ERROR_TRANSIENT_FAILURE &&
- retrieveStatus < PduHeaders.RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE) {
- status = MMS_REQUEST_AUTO_RETRY;
- } else {
- // else not meant to retry download
- status = MMS_REQUEST_NO_RETRY;
- LogUtil.e(TAG, "MmsUtils: failed to retrieve message; retrieveStatus: "
- + retrieveStatus);
- }
- final ContentValues values = new ContentValues(1);
- values.put(Mms.RETRIEVE_STATUS, retrieveConf.getRetrieveStatus());
- SqliteWrapper.update(context, context.getContentResolver(),
- notificationUri, values, null, null);
-
- if (status == MMS_REQUEST_SUCCEEDED) {
- // Send response of the notification
- if (autoDownload) {
- sendNotifyResponseForMmsDownload(context, subId, transactionIdBytes,
- contentLocation, PduHeaders.STATUS_RETRIEVED);
- } else {
- sendAcknowledgeForMmsDownload(context, subId, transactionIdBytes, contentLocation);
- }
-
- // Insert downloaded message into telephony
- final Uri inboxUri = MmsUtils.insertReceivedMmsMessage(context, retrieveConf, subId,
- subPhoneNumber, receivedTimestampInSeconds, contentLocation);
- messageUri = ContentUris.withAppendedId(Mms.CONTENT_URI, ContentUris.parseId(inboxUri));
- } else if (status == MMS_REQUEST_AUTO_RETRY) {
- // For a retry do nothing
- } else if (status == MMS_REQUEST_MANUAL_RETRY && autoDownload) {
- // Failure from autodownload - just treat like manual download
- sendNotifyResponseForMmsDownload(context, subId, transactionIdBytes,
- contentLocation, PduHeaders.STATUS_DEFERRED);
- }
- return new StatusPlusUri(status, retrieveStatus, messageUri);
- }
-
- /**
- * Send response for MMS download - catches and ignores errors
- */
- public static void sendNotifyResponseForMmsDownload(final Context context, final int subId,
- final byte[] transactionId, final String contentLocation, final int status) {
- try {
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "MmsUtils: Sending M-NotifyResp.ind for received MMS, status: "
- + String.format("0x%X", status));
- }
- if (contentLocation == null) {
- LogUtil.w(TAG, "MmsUtils: Can't send NotifyResp; contentLocation is null");
- return;
- }
- if (transactionId == null) {
- LogUtil.w(TAG, "MmsUtils: Can't send NotifyResp; transaction id is null");
- return;
- }
- if (!isMmsDataAvailable(subId)) {
- LogUtil.w(TAG, "MmsUtils: Can't send NotifyResp; no data available");
- return;
- }
- MmsSender.sendNotifyResponseForMmsDownload(
- context, subId, transactionId, contentLocation, status);
- } catch (final MmsFailureException e) {
- LogUtil.e(TAG, "sendNotifyResponseForMmsDownload: failed to retrieve message " + e, e);
- } catch (final InvalidHeaderValueException e) {
- LogUtil.e(TAG, "sendNotifyResponseForMmsDownload: failed to retrieve message " + e, e);
- }
- }
-
- /**
- * Send acknowledge for mms download - catched and ignores errors
- */
- public static void sendAcknowledgeForMmsDownload(final Context context, final int subId,
- final byte[] transactionId, final String contentLocation) {
- try {
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "MmsUtils: Sending M-Acknowledge.ind for received MMS");
- }
- if (contentLocation == null) {
- LogUtil.w(TAG, "MmsUtils: Can't send AckInd; contentLocation is null");
- return;
- }
- if (transactionId == null) {
- LogUtil.w(TAG, "MmsUtils: Can't send AckInd; transaction id is null");
- return;
- }
- if (!isMmsDataAvailable(subId)) {
- LogUtil.w(TAG, "MmsUtils: Can't send AckInd; no data available");
- return;
- }
- MmsSender.sendAcknowledgeForMmsDownload(context, subId, transactionId, contentLocation);
- } catch (final MmsFailureException e) {
- LogUtil.e(TAG, "sendAcknowledgeForMmsDownload: failed to retrieve message " + e, e);
- } catch (final InvalidHeaderValueException e) {
- LogUtil.e(TAG, "sendAcknowledgeForMmsDownload: failed to retrieve message " + e, e);
- }
- }
-
- /**
- * Try parsing a PDU without knowing the carrier. This is useful for importing
- * MMS or storing draft when carrier info is not available
- *
- * @param data The PDU data
- * @return Parsed PDU, null if failed to parse
- */
- private static GenericPdu parsePduForAnyCarrier(final byte[] data) {
- GenericPdu pdu = null;
- try {
- pdu = (new PduParser(data, true/*parseContentDisposition*/)).parse();
- } catch (final RuntimeException e) {
- LogUtil.d(TAG, "parsePduForAnyCarrier: Failed to parse PDU with content disposition",
- e);
- }
- if (pdu == null) {
- try {
- pdu = (new PduParser(data, false/*parseContentDisposition*/)).parse();
- } catch (final RuntimeException e) {
- LogUtil.d(TAG,
- "parsePduForAnyCarrier: Failed to parse PDU without content disposition",
- e);
- }
- }
- return pdu;
- }
-
- private static RetrieveConf receiveFromDumpFile(final byte[] data) throws MmsFailureException {
- final GenericPdu pdu = parsePduForAnyCarrier(data);
- if (pdu == null || !(pdu instanceof RetrieveConf)) {
- LogUtil.e(TAG, "receiveFromDumpFile: Parsing retrieved PDU failure");
- throw new MmsFailureException(MMS_REQUEST_MANUAL_RETRY, "Failed reading dump file");
- }
- return (RetrieveConf) pdu;
- }
-
- private static boolean isMmsDataAvailable(final int subId) {
- if (OsUtil.isAtLeastL_MR1()) {
- // L_MR1 above may support sending mms via wifi
- return true;
- }
- final PhoneUtils phoneUtils = PhoneUtils.get(subId);
- return !phoneUtils.isAirplaneModeOn() && phoneUtils.isMobileDataEnabled();
- }
-
- private static boolean isSmsDataAvailable(final int subId) {
- if (OsUtil.isAtLeastL_MR1()) {
- // L_MR1 above may support sending sms via wifi
- return true;
- }
- final PhoneUtils phoneUtils = PhoneUtils.get(subId);
- return !phoneUtils.isAirplaneModeOn();
- }
-
- public static boolean isMobileDataEnabled(final int subId) {
- final PhoneUtils phoneUtils = PhoneUtils.get(subId);
- return phoneUtils.isMobileDataEnabled();
- }
-
- public static boolean isAirplaneModeOn(final int subId) {
- final PhoneUtils phoneUtils = PhoneUtils.get(subId);
- return phoneUtils.isAirplaneModeOn();
- }
-
- public static StatusPlusUri sendMmsMessage(final Context context, final int subId,
- final Uri messageUri, final Bundle extras) {
- int status = MMS_REQUEST_MANUAL_RETRY;
- int rawStatus = MessageData.RAW_TELEPHONY_STATUS_UNDEFINED;
- if (!isMmsDataAvailable(subId)) {
- LogUtil.w(TAG, "MmsUtils: failed to send message, no data available");
- return new StatusPlusUri(MMS_REQUEST_MANUAL_RETRY,
- MessageData.RAW_TELEPHONY_STATUS_UNDEFINED,
- messageUri,
- SmsManager.MMS_ERROR_NO_DATA_NETWORK);
- }
- final PduPersister persister = PduPersister.getPduPersister(context);
- try {
- final SendReq sendReq = (SendReq) persister.load(messageUri);
- if (sendReq == null) {
- LogUtil.w(TAG, "MmsUtils: Sending MMS was deleted; uri = " + messageUri);
- return new StatusPlusUri(MMS_REQUEST_NO_RETRY,
- MessageData.RAW_TELEPHONY_STATUS_UNDEFINED, messageUri);
- }
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, String.format("MmsUtils: Sending MMS, message uri: %s", messageUri));
- }
- extras.putInt(SendMessageAction.KEY_SUB_ID, subId);
- MmsSender.sendMms(context, subId, messageUri, sendReq, extras);
- return STATUS_PENDING;
- } catch (final MmsFailureException e) {
- status = e.retryHint;
- rawStatus = e.rawStatus;
- LogUtil.e(TAG, "MmsUtils: failed to send message " + e, e);
- } catch (final InvalidHeaderValueException e) {
- LogUtil.e(TAG, "MmsUtils: failed to send message " + e, e);
- } catch (final IllegalArgumentException e) {
- LogUtil.e(TAG, "MmsUtils: invalid message to send " + e, e);
- } catch (final MmsException e) {
- LogUtil.e(TAG, "MmsUtils: failed to send message " + e, e);
- }
- // If we get here, some exception occurred
- return new StatusPlusUri(status, rawStatus, messageUri);
- }
-
- public static StatusPlusUri updateSentMmsMessageStatus(final Context context,
- final Uri messageUri, final SendConf sendConf) {
- int status = MMS_REQUEST_MANUAL_RETRY;
- final int respStatus = sendConf.getResponseStatus();
-
- final ContentValues values = new ContentValues(2);
- values.put(Mms.RESPONSE_STATUS, respStatus);
- final byte[] messageId = sendConf.getMessageId();
- if (messageId != null && messageId.length > 0) {
- values.put(Mms.MESSAGE_ID, PduPersister.toIsoString(messageId));
- }
- SqliteWrapper.update(context, context.getContentResolver(),
- messageUri, values, null, null);
- if (respStatus == PduHeaders.RESPONSE_STATUS_OK) {
- status = MMS_REQUEST_SUCCEEDED;
- } else if (respStatus == PduHeaders.RESPONSE_STATUS_ERROR_TRANSIENT_FAILURE ||
- respStatus == PduHeaders.RESPONSE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM ||
- respStatus == PduHeaders.RESPONSE_STATUS_ERROR_TRANSIENT_PARTIAL_SUCCESS) {
- status = MMS_REQUEST_AUTO_RETRY;
- } else {
- // else permanent failure
- LogUtil.e(TAG, "MmsUtils: failed to send message; respStatus = "
- + String.format("0x%X", respStatus));
- }
- return new StatusPlusUri(status, respStatus, messageUri);
- }
-
- public static void clearMmsStatus(final Context context, final Uri uri) {
- // Messaging application can leave invalid values in STATUS field of M-Notification.ind
- // messages. Take this opportunity to clear it.
- // Downloading status just kept in local db and not reflected into telephony.
- final ContentValues values = new ContentValues(1);
- values.putNull(Mms.STATUS);
- SqliteWrapper.update(context, context.getContentResolver(),
- uri, values, null, null);
- }
-
- // Selection for new dedup algorithm:
- // ((m_type<>130) OR (exp>NOW)) AND (date>NOW-7d) AND (date<NOW+7d) AND (ct_l=xxxxxx)
- // i.e. If it is NotificationInd and not expired or not NotificationInd
- // AND message is received with +/- 7 days from now
- // AND content location is the input URL
- private static final String DUP_NOTIFICATION_QUERY_SELECTION =
- "((" + Mms.MESSAGE_TYPE + "<>?) OR (" + Mms.EXPIRY + ">?)) AND ("
- + Mms.DATE + ">?) AND (" + Mms.DATE + "<?) AND (" + Mms.CONTENT_LOCATION +
- "=?)";
- // Selection for old behavior: only checks NotificationInd and its content location
- private static final String DUP_NOTIFICATION_QUERY_SELECTION_OLD =
- "(" + Mms.MESSAGE_TYPE + "=?) AND (" + Mms.CONTENT_LOCATION + "=?)";
-
- private static final int MAX_RETURN = 32;
- private static String[] getDupNotifications(final Context context, final NotificationInd nInd) {
- final byte[] rawLocation = nInd.getContentLocation();
- if (rawLocation != null) {
- final String location = new String(rawLocation);
- // We can not be sure if the content location of an MMS is globally and historically
- // unique. So we limit the dedup time within the last 7 days
- // (or configured by gservices remotely). If the same content location shows up after
- // that, we will download regardless. Duplicated message is better than no message.
- String selection;
- String[] selectionArgs;
- final long timeLimit = BugleGservices.get().getLong(
- BugleGservicesKeys.MMS_WAP_PUSH_DEDUP_TIME_LIMIT_SECS,
- BugleGservicesKeys.MMS_WAP_PUSH_DEDUP_TIME_LIMIT_SECS_DEFAULT);
- if (timeLimit > 0) {
- // New dedup algorithm
- selection = DUP_NOTIFICATION_QUERY_SELECTION;
- final long nowSecs = System.currentTimeMillis() / 1000;
- final long timeLowerBoundSecs = nowSecs - timeLimit;
- // Need upper bound to protect against clock change so that a message has a time
- // stamp in the future
- final long timeUpperBoundSecs = nowSecs + timeLimit;
- selectionArgs = new String[] {
- Integer.toString(PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND),
- Long.toString(nowSecs),
- Long.toString(timeLowerBoundSecs),
- Long.toString(timeUpperBoundSecs),
- location
- };
- } else {
- // If time limit is 0, we revert back to old behavior in case the new
- // dedup algorithm behaves badly
- selection = DUP_NOTIFICATION_QUERY_SELECTION_OLD;
- selectionArgs = new String[] {
- Integer.toString(PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND),
- location
- };
- }
- Cursor cursor = null;
- try {
- cursor = SqliteWrapper.query(
- context, context.getContentResolver(),
- Mms.CONTENT_URI, new String[] { Mms._ID },
- selection, selectionArgs, null);
- final int dupCount = cursor.getCount();
- if (dupCount > 0) {
- // We already received the same notification before.
- // Don't want to return too many dups. It is only for debugging.
- final int returnCount = dupCount < MAX_RETURN ? dupCount : MAX_RETURN;
- final String[] dups = new String[returnCount];
- for (int i = 0; cursor.moveToNext() && i < returnCount; i++) {
- dups[i] = cursor.getString(0);
- }
- return dups;
- }
- } catch (final SQLiteException e) {
- LogUtil.e(TAG, "query failure: " + e, e);
- } finally {
- cursor.close();
- }
- }
- return null;
- }
-
- /**
- * Try parse the address using RFC822 format. If it fails to parse, then return the
- * original address
- *
- * @param address The MMS ind sender address to parse
- * @return The real address. If in RFC822 format, returns the correct email.
- */
- private static String parsePotentialRfc822EmailAddress(final String address) {
- if (address == null || !address.contains("@") || !address.contains("<")) {
- return address;
- }
- final Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(address);
- if (tokens != null && tokens.length > 0) {
- for (final Rfc822Token token : tokens) {
- if (token != null && !TextUtils.isEmpty(token.getAddress())) {
- return token.getAddress();
- }
- }
- }
- return address;
- }
-
- public static DatabaseMessages.MmsMessage processReceivedPdu(final Context context,
- final byte[] pushData, final int subId, final String subPhoneNumber) {
- // Parse data
-
- // Insert placeholder row to telephony and local db
- // Get raw PDU push-data from the message and parse it
- final PduParser parser = new PduParser(pushData,
- MmsConfig.get(subId).getSupportMmsContentDisposition());
- final GenericPdu pdu = parser.parse();
-
- if (null == pdu) {
- LogUtil.e(TAG, "Invalid PUSH data");
- return null;
- }
-
- final PduPersister p = PduPersister.getPduPersister(context);
- final int type = pdu.getMessageType();
-
- Uri messageUri = null;
- switch (type) {
- case PduHeaders.MESSAGE_TYPE_DELIVERY_IND:
- case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND: {
- // TODO: Should this be commented out?
-// threadId = findThreadId(context, pdu, type);
-// if (threadId == -1) {
-// // The associated SendReq isn't found, therefore skip
-// // processing this PDU.
-// break;
-// }
-
-// Uri uri = p.persist(pdu, Inbox.CONTENT_URI, true,
-// MessagingPreferenceActivity.getIsGroupMmsEnabled(mContext), null);
-// // Update thread ID for ReadOrigInd & DeliveryInd.
-// ContentValues values = new ContentValues(1);
-// values.put(Mms.THREAD_ID, threadId);
-// SqliteWrapper.update(mContext, cr, uri, values, null, null);
- LogUtil.w(TAG, "Received unsupported WAP Push, type=" + type);
- break;
- }
- case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND: {
- final NotificationInd nInd = (NotificationInd) pdu;
-
- if (MmsConfig.get(subId).getTransIdEnabled()) {
- final byte [] contentLocationTemp = nInd.getContentLocation();
- if ('=' == contentLocationTemp[contentLocationTemp.length - 1]) {
- final byte [] transactionIdTemp = nInd.getTransactionId();
- final byte [] contentLocationWithId =
- new byte [contentLocationTemp.length
- + transactionIdTemp.length];
- System.arraycopy(contentLocationTemp, 0, contentLocationWithId,
- 0, contentLocationTemp.length);
- System.arraycopy(transactionIdTemp, 0, contentLocationWithId,
- contentLocationTemp.length, transactionIdTemp.length);
- nInd.setContentLocation(contentLocationWithId);
- }
- }
- final String[] dups = getDupNotifications(context, nInd);
- if (dups == null) {
- // TODO: Do we handle Rfc822 Email Addresses?
- //final String contentLocation =
- // MmsUtils.bytesToString(nInd.getContentLocation(), "UTF-8");
- //final byte[] transactionId = nInd.getTransactionId();
- //final long messageSize = nInd.getMessageSize();
- //final long expiry = nInd.getExpiry();
- //final String transactionIdString =
- // MmsUtils.bytesToString(transactionId, "UTF-8");
-
- //final EncodedStringValue fromEncoded = nInd.getFrom();
- // An mms ind received from email address will have from address shown as
- // "John Doe <johndoe@foobar.com>" but the actual received message will only
- // have the email address. So let's try to parse the RFC822 format to get the
- // real email. Otherwise we will create two conversations for the MMS
- // notification and the actual MMS message if auto retrieve is disabled.
- //final String from = parsePotentialRfc822EmailAddress(
- // fromEncoded != null ? fromEncoded.getString() : null);
-
- Uri inboxUri = null;
- try {
- inboxUri = p.persist(pdu, Mms.Inbox.CONTENT_URI, subId, subPhoneNumber,
- null);
- messageUri = ContentUris.withAppendedId(Mms.CONTENT_URI,
- ContentUris.parseId(inboxUri));
- } catch (final MmsException e) {
- LogUtil.e(TAG, "Failed to save the data from PUSH: type=" + type, e);
- }
- } else {
- LogUtil.w(TAG, "Received WAP Push is a dup: " + Joiner.on(',').join(dups));
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.w(TAG, "Dup WAP Push url=" + new String(nInd.getContentLocation()));
- }
- }
- break;
- }
- default:
- LogUtil.e(TAG, "Received unrecognized WAP Push, type=" + type);
- }
-
- DatabaseMessages.MmsMessage mms = null;
- if (messageUri != null) {
- mms = MmsUtils.loadMms(messageUri);
- }
- return mms;
- }
-
- public static Uri insertSendingMmsMessage(final Context context, final List<String> recipients,
- final MessageData content, final int subId, final String subPhoneNumber,
- final long timestamp) {
- final SendReq sendReq = createMmsSendReq(
- context, subId, recipients.toArray(new String[recipients.size()]), content,
- DEFAULT_DELIVERY_REPORT_MODE,
- DEFAULT_READ_REPORT_MODE,
- DEFAULT_EXPIRY_TIME_IN_SECONDS,
- DEFAULT_PRIORITY,
- timestamp);
- Uri messageUri = null;
- if (sendReq != null) {
- final Uri outboxUri = MmsUtils.insertSendReq(context, sendReq, subId, subPhoneNumber);
- if (outboxUri != null) {
- messageUri = ContentUris.withAppendedId(Telephony.Mms.CONTENT_URI,
- ContentUris.parseId(outboxUri));
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "Mmsutils: Inserted sending MMS message into telephony, uri: "
- + outboxUri);
- }
- } else {
- LogUtil.e(TAG, "insertSendingMmsMessage: failed to persist message into telephony");
- }
- }
- return messageUri;
- }
-
- public static MessageData readSendingMmsMessage(final Uri messageUri,
- final String conversationId, final String participantId, final String selfId) {
- MessageData message = null;
- if (messageUri != null) {
- final DatabaseMessages.MmsMessage mms = MmsUtils.loadMms(messageUri);
-
- // Make sure that the message has not been deleted from the Telephony DB
- if (mms != null) {
- // Transform the message
- message = MmsUtils.createMmsMessage(mms, conversationId, participantId, selfId,
- MessageData.BUGLE_STATUS_OUTGOING_RESENDING);
- }
- }
- return message;
- }
-
- /**
- * Create an MMS message with subject, text and image
- *
- * @return Both the M-Send.req and the M-Send.conf for processing in the caller
- * @throws MmsException
- */
- private static SendReq createMmsSendReq(final Context context, final int subId,
- final String[] recipients, final MessageData message,
- final boolean requireDeliveryReport, final boolean requireReadReport,
- final long expiryTime, final int priority, final long timestampMillis) {
- Assert.notNull(context);
- if (recipients == null || recipients.length < 1) {
- throw new IllegalArgumentException("MMS sendReq no recipient");
- }
-
- // Make a copy so we don't propagate changes to recipients to outside of this method
- final String[] recipientsCopy = new String[recipients.length];
- // Don't send phone number as is since some received phone number is malformed
- // for sending. We need to strip the separators.
- for (int i = 0; i < recipients.length; i++) {
- final String recipient = recipients[i];
- if (EmailAddress.isValidEmail(recipients[i])) {
- // Don't do stripping for emails
- recipientsCopy[i] = recipient;
- } else {
- recipientsCopy[i] = stripPhoneNumberSeparators(recipient);
- }
- }
-
- SendReq sendReq = null;
- try {
- sendReq = createSendReq(context, subId, recipientsCopy,
- message, requireDeliveryReport,
- requireReadReport, expiryTime, priority, timestampMillis);
- } catch (final InvalidHeaderValueException e) {
- LogUtil.e(TAG, "InvalidHeaderValue creating sendReq PDU");
- } catch (final OutOfMemoryError e) {
- LogUtil.e(TAG, "Out of memory error creating sendReq PDU");
- }
- return sendReq;
- }
-
- /**
- * Stripping out the invalid characters in a phone number before sending
- * MMS. We only keep alphanumeric and '*', '#', '+'.
- */
- private static String stripPhoneNumberSeparators(final String phoneNumber) {
- if (phoneNumber == null) {
- return null;
- }
- final int len = phoneNumber.length();
- final StringBuilder ret = new StringBuilder(len);
- for (int i = 0; i < len; i++) {
- final char c = phoneNumber.charAt(i);
- if (Character.isLetterOrDigit(c) || c == '+' || c == '*' || c == '#') {
- ret.append(c);
- }
- }
- return ret.toString();
- }
-
- /**
- * Create M-Send.req for the MMS message to be sent.
- *
- * @return the M-Send.req
- * @throws InvalidHeaderValueException if there is any error in parsing the input
- */
- static SendReq createSendReq(final Context context, final int subId,
- final String[] recipients, final MessageData message,
- final boolean requireDeliveryReport,
- final boolean requireReadReport, final long expiryTime, final int priority,
- final long timestampMillis)
- throws InvalidHeaderValueException {
- final SendReq req = new SendReq();
- // From, per spec
- final String lineNumber = PhoneUtils.get(subId).getCanonicalForSelf(true/*allowOverride*/);
- if (!TextUtils.isEmpty(lineNumber)) {
- req.setFrom(new EncodedStringValue(lineNumber));
- }
- // To
- final EncodedStringValue[] encodedNumbers = EncodedStringValue.encodeStrings(recipients);
- if (encodedNumbers != null) {
- req.setTo(encodedNumbers);
- }
- // Subject
- if (!TextUtils.isEmpty(message.getMmsSubject())) {
- req.setSubject(new EncodedStringValue(message.getMmsSubject()));
- }
- // Date
- req.setDate(timestampMillis / 1000L);
- // Body
- final MmsInfo bodyInfo = MmsUtils.makePduBody(context, message, subId);
- req.setBody(bodyInfo.mPduBody);
- // Message size
- req.setMessageSize(bodyInfo.mMessageSize);
- // Message class
- req.setMessageClass(PduHeaders.MESSAGE_CLASS_PERSONAL_STR.getBytes());
- // Expiry
- req.setExpiry(expiryTime);
- // Priority
- req.setPriority(priority);
- // Delivery report
- req.setDeliveryReport(requireDeliveryReport ? PduHeaders.VALUE_YES : PduHeaders.VALUE_NO);
- // Read report
- req.setReadReport(requireReadReport ? PduHeaders.VALUE_YES : PduHeaders.VALUE_NO);
- return req;
- }
-
- public static boolean isDeliveryReportRequired(final int subId) {
- if (!MmsConfig.get(subId).getSMSDeliveryReportsEnabled()) {
- return false;
- }
- final Context context = Factory.get().getApplicationContext();
- final Resources res = context.getResources();
- final BuglePrefs prefs = BuglePrefs.getSubscriptionPrefs(subId);
- final String deliveryReportKey = res.getString(R.string.delivery_reports_pref_key);
- final boolean defaultValue = res.getBoolean(R.bool.delivery_reports_pref_default);
- return prefs.getBoolean(deliveryReportKey, defaultValue);
- }
-
- public static int sendSmsMessage(final String recipient, final String messageText,
- final Uri requestUri, final int subId,
- final String smsServiceCenter, final boolean requireDeliveryReport) {
- if (!isSmsDataAvailable(subId)) {
- LogUtil.w(TAG, "MmsUtils: can't send SMS without radio");
- return MMS_REQUEST_MANUAL_RETRY;
- }
- final Context context = Factory.get().getApplicationContext();
- int status = MMS_REQUEST_MANUAL_RETRY;
- try {
- // Send a single message
- final SendResult result = SmsSender.sendMessage(
- context,
- subId,
- recipient,
- messageText,
- smsServiceCenter,
- requireDeliveryReport,
- requestUri);
- if (!result.hasPending()) {
- // not timed out, check failures
- final int failureLevel = result.getHighestFailureLevel();
- switch (failureLevel) {
- case SendResult.FAILURE_LEVEL_NONE:
- status = MMS_REQUEST_SUCCEEDED;
- break;
- case SendResult.FAILURE_LEVEL_TEMPORARY:
- status = MMS_REQUEST_AUTO_RETRY;
- LogUtil.e(TAG, "MmsUtils: SMS temporary failure");
- break;
- case SendResult.FAILURE_LEVEL_PERMANENT:
- LogUtil.e(TAG, "MmsUtils: SMS permanent failure");
- break;
- }
- } else {
- // Timed out
- LogUtil.e(TAG, "MmsUtils: sending SMS timed out");
- }
- } catch (final Exception e) {
- LogUtil.e(TAG, "MmsUtils: failed to send SMS " + e, e);
- }
- return status;
- }
-
- /**
- * Delete SMS and MMS messages in a particular thread
- *
- * @return the number of messages deleted
- */
- public static int deleteThread(final long threadId, final long cutOffTimestampInMillis) {
- final ContentResolver resolver = Factory.get().getApplicationContext().getContentResolver();
- final Uri threadUri = ContentUris.withAppendedId(Telephony.Threads.CONTENT_URI, threadId);
- if (cutOffTimestampInMillis < Long.MAX_VALUE) {
- return resolver.delete(threadUri, Sms.DATE + "<=?",
- new String[] { Long.toString(cutOffTimestampInMillis) });
- } else {
- return resolver.delete(threadUri, null /* smsSelection */, null /* selectionArgs */);
- }
- }
-
- /**
- * Delete single SMS and MMS message
- *
- * @return number of rows deleted (should be 1 or 0)
- */
- public static int deleteMessage(final Uri messageUri) {
- final ContentResolver resolver = Factory.get().getApplicationContext().getContentResolver();
- return resolver.delete(messageUri, null /* selection */, null /* selectionArgs */);
- }
-
- public static byte[] createDebugNotificationInd(final String fileName) {
- byte[] pduData = null;
- try {
- final Context context = Factory.get().getApplicationContext();
- // Load the message file
- final byte[] data = DebugUtils.receiveFromDumpFile(fileName);
- final RetrieveConf retrieveConf = receiveFromDumpFile(data);
- // Create the notification
- final NotificationInd notification = new NotificationInd();
- final long expiry = System.currentTimeMillis() / 1000 + 600;
- notification.setTransactionId(fileName.getBytes());
- notification.setMmsVersion(retrieveConf.getMmsVersion());
- notification.setFrom(retrieveConf.getFrom());
- notification.setSubject(retrieveConf.getSubject());
- notification.setExpiry(expiry);
- notification.setMessageSize(data.length);
- notification.setMessageClass(retrieveConf.getMessageClass());
-
- final Uri.Builder builder = MediaScratchFileProvider.getUriBuilder();
- builder.appendPath(fileName);
- final Uri contentLocation = builder.build();
- notification.setContentLocation(contentLocation.toString().getBytes());
-
- // Serialize
- pduData = new PduComposer(context, notification).make();
- if (pduData == null || pduData.length < 1) {
- throw new IllegalArgumentException("Empty or zero length PDU data");
- }
- } catch (final MmsFailureException e) {
- // Nothing to do
- } catch (final InvalidHeaderValueException e) {
- // Nothing to do
- }
- return pduData;
- }
-
- public static int mapRawStatusToErrorResourceId(final int bugleStatus, final int rawStatus) {
- int stringResId = R.string.message_status_send_failed;
- switch (rawStatus) {
- case PduHeaders.RESPONSE_STATUS_ERROR_SERVICE_DENIED:
- case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_SERVICE_DENIED:
- //case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_LIMITATIONS_NOT_MET:
- //case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_REQUEST_NOT_ACCEPTED:
- //case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_FORWARDING_DENIED:
- //case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_NOT_SUPPORTED:
- //case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_ADDRESS_HIDING_NOT_SUPPORTED:
- //case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_LACK_OF_PREPAID:
- stringResId = R.string.mms_failure_outgoing_service;
- break;
- case PduHeaders.RESPONSE_STATUS_ERROR_SENDING_ADDRESS_UNRESOLVED:
- case PduHeaders.RESPONSE_STATUS_ERROR_TRANSIENT_SENDNG_ADDRESS_UNRESOLVED:
- case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_SENDING_ADDRESS_UNRESOLVED:
- stringResId = R.string.mms_failure_outgoing_address;
- break;
- case PduHeaders.RESPONSE_STATUS_ERROR_MESSAGE_FORMAT_CORRUPT:
- case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_MESSAGE_FORMAT_CORRUPT:
- stringResId = R.string.mms_failure_outgoing_corrupt;
- break;
- case PduHeaders.RESPONSE_STATUS_ERROR_CONTENT_NOT_ACCEPTED:
- case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_CONTENT_NOT_ACCEPTED:
- stringResId = R.string.mms_failure_outgoing_content;
- break;
- case PduHeaders.RESPONSE_STATUS_ERROR_UNSUPPORTED_MESSAGE:
- //case PduHeaders.RESPONSE_STATUS_ERROR_MESSAGE_NOT_FOUND:
- //case PduHeaders.RESPONSE_STATUS_ERROR_TRANSIENT_MESSAGE_NOT_FOUND:
- stringResId = R.string.mms_failure_outgoing_unsupported;
- break;
- case MessageData.RAW_TELEPHONY_STATUS_MESSAGE_TOO_BIG:
- stringResId = R.string.mms_failure_outgoing_too_large;
- break;
- }
- return stringResId;
- }
-
- /**
- * The absence of a connection type.
- */
- public static final int TYPE_NONE = -1;
-
- public static int getConnectivityEventNetworkType(final Context context, final Intent intent) {
- final ConnectivityManager connMgr = (ConnectivityManager)
- context.getSystemService(Context.CONNECTIVITY_SERVICE);
- if (OsUtil.isAtLeastJB_MR1()) {
- return intent.getIntExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, TYPE_NONE);
- } else {
- final NetworkInfo info = (NetworkInfo) intent.getParcelableExtra(
- ConnectivityManager.EXTRA_NETWORK_INFO);
- if (info != null) {
- return info.getType();
- }
- }
- return TYPE_NONE;
- }
-
- /**
- * Dump the raw MMS data into a file
- *
- * @param rawPdu The raw pdu data
- * @param pdu The parsed pdu, used to construct a dump file name
- */
- public static void dumpPdu(final byte[] rawPdu, final GenericPdu pdu) {
- if (rawPdu == null || rawPdu.length < 1) {
- return;
- }
- final String dumpFileName = MmsUtils.MMS_DUMP_PREFIX + getDumpFileId(pdu);
- final File dumpFile = DebugUtils.getDebugFile(dumpFileName, true);
- if (dumpFile != null) {
- try {
- final FileOutputStream fos = new FileOutputStream(dumpFile);
- final BufferedOutputStream bos = new BufferedOutputStream(fos);
- try {
- bos.write(rawPdu);
- bos.flush();
- } finally {
- bos.close();
- }
- DebugUtils.ensureReadable(dumpFile);
- } catch (final IOException e) {
- LogUtil.e(TAG, "dumpPdu: " + e, e);
- }
- }
- }
-
- /**
- * Get the dump file id based on the parsed PDU
- * 1. Use message id if not empty
- * 2. Use transaction id if message id is empty
- * 3. If all above is empty, use random UUID
- *
- * @param pdu the parsed PDU
- * @return the id of the dump file
- */
- private static String getDumpFileId(final GenericPdu pdu) {
- String fileId = null;
- if (pdu != null && pdu instanceof RetrieveConf) {
- final RetrieveConf retrieveConf = (RetrieveConf) pdu;
- if (retrieveConf.getMessageId() != null) {
- fileId = new String(retrieveConf.getMessageId());
- } else if (retrieveConf.getTransactionId() != null) {
- fileId = new String(retrieveConf.getTransactionId());
- }
- }
- if (TextUtils.isEmpty(fileId)) {
- fileId = UUID.randomUUID().toString();
- }
- return fileId;
- }
-}
diff --git a/src/com/android/messaging/sms/SmsException.java b/src/com/android/messaging/sms/SmsException.java
deleted file mode 100644
index 728db8c..0000000
--- a/src/com/android/messaging/sms/SmsException.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.sms;
-
-/**
- * A generic Exception for errors in sending SMS
- */
-class SmsException extends Exception {
- private static final long serialVersionUID = 1L;
-
- /**
- * Creates a new SmsException.
- */
- public SmsException() {
- super();
- }
-
- /**
- * Creates a new SmsException with the specified detail message.
- *
- * @param message the detail message.
- */
- public SmsException(String message) {
- super(message);
- }
-
- /**
- * Creates a new SmsException with the specified cause.
- *
- * @param cause the cause.
- */
- public SmsException(Throwable cause) {
- super(cause);
- }
-
- /**
- * Creates a new SmsException with the specified detail message and cause.
- *
- * @param message the detail message.
- * @param cause the cause.
- */
- public SmsException(String message, Throwable cause) {
- super(message, cause);
- }
-}
diff --git a/src/com/android/messaging/sms/SmsReleaseStorage.java b/src/com/android/messaging/sms/SmsReleaseStorage.java
deleted file mode 100644
index 13a6284..0000000
--- a/src/com/android/messaging/sms/SmsReleaseStorage.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.sms;
-
-import android.content.res.Resources;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.datamodel.SyncManager;
-import com.android.messaging.util.BugleGservices;
-import com.android.messaging.util.BugleGservicesKeys;
-import com.android.messaging.util.LogUtil;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Class handling message cleanup when storage is low
- */
-public class SmsReleaseStorage {
- /**
- * Class representing a time duration specified by Gservices
- */
- public static class Duration {
- // Time duration unit types
- public static final int UNIT_WEEK = 'w';
- public static final int UNIT_MONTH = 'm';
- public static final int UNIT_YEAR = 'y';
-
- // Number of units
- public final int mCount;
- // Unit type: week, month or year
- public final int mUnit;
-
- public Duration(final int count, final int unit) {
- mCount = count;
- mUnit = unit;
- }
- }
-
- private static final String TAG = LogUtil.BUGLE_TAG;
-
- private static final Duration DEFAULT_DURATION = new Duration(1, Duration.UNIT_MONTH);
-
- private static final Pattern DURATION_PATTERN = Pattern.compile("([1-9]+\\d*)(w|m|y)");
- /**
- * Parse message retaining time duration specified by Gservices
- *
- * @return The parsed time duration from Gservices
- */
- public static Duration parseMessageRetainingDuration() {
- final String smsAutoDeleteMessageRetainingDuration =
- BugleGservices.get().getString(
- BugleGservicesKeys.SMS_STORAGE_PURGING_MESSAGE_RETAINING_DURATION,
- BugleGservicesKeys.SMS_STORAGE_PURGING_MESSAGE_RETAINING_DURATION_DEFAULT);
- final Matcher matcher = DURATION_PATTERN.matcher(smsAutoDeleteMessageRetainingDuration);
- try {
- if (matcher.matches()) {
- return new Duration(
- Integer.parseInt(matcher.group(1)),
- matcher.group(2).charAt(0));
- }
- } catch (final NumberFormatException e) {
- // Nothing to do
- }
- LogUtil.e(TAG, "SmsAutoDelete: invalid duration " +
- smsAutoDeleteMessageRetainingDuration);
- return DEFAULT_DURATION;
- }
-
- /**
- * Get string representation of the time duration
- *
- * @param duration
- * @return
- */
- public static String getMessageRetainingDurationString(final Duration duration) {
- final Resources resources = Factory.get().getApplicationContext().getResources();
- switch (duration.mUnit) {
- case Duration.UNIT_WEEK:
- return resources.getQuantityString(
- R.plurals.week_count, duration.mCount, duration.mCount);
- case Duration.UNIT_MONTH:
- return resources.getQuantityString(
- R.plurals.month_count, duration.mCount, duration.mCount);
- case Duration.UNIT_YEAR:
- return resources.getQuantityString(
- R.plurals.year_count, duration.mCount, duration.mCount);
- }
- throw new IllegalArgumentException(
- "SmsAutoDelete: invalid duration unit " + duration.mUnit);
- }
-
- // Time conversations
- private static final long WEEK_IN_MILLIS = 7 * 24 * 3600 * 1000L;
- private static final long MONTH_IN_MILLIS = 30 * 24 * 3600 * 1000L;
- private static final long YEAR_IN_MILLIS = 365 * 24 * 3600 * 1000L;
-
- /**
- * Convert time duration to time in milliseconds
- *
- * @param duration
- * @return
- */
- public static long durationToTimeInMillis(final Duration duration) {
- switch (duration.mUnit) {
- case Duration.UNIT_WEEK:
- return duration.mCount * WEEK_IN_MILLIS;
- case Duration.UNIT_MONTH:
- return duration.mCount * MONTH_IN_MILLIS;
- case Duration.UNIT_YEAR:
- return duration.mCount * YEAR_IN_MILLIS;
- }
- return -1L;
- }
-
- /**
- * Delete message actions:
- * 0: delete media messages
- * 1: delete old messages
- *
- * @param actionIndex The index of the delete action to perform
- * @param durationInMillis The time duration for retaining messages
- */
- public static void deleteMessages(final int actionIndex, final long durationInMillis) {
- int deleted = 0;
- switch (actionIndex) {
- case 0: {
- // Delete media
- deleted = MmsUtils.deleteMediaMessages();
- break;
- }
- case 1: {
- // Delete old messages
- final long now = System.currentTimeMillis();
- final long cutOffTimestampInMillis = now - durationInMillis;
- // Delete messages from telephony provider
- deleted = MmsUtils.deleteMessagesOlderThan(cutOffTimestampInMillis);
- break;
- }
- default: {
- LogUtil.e(TAG, "SmsStorageStatusManager: invalid action " + actionIndex);
- break;
- }
- }
-
- if (deleted > 0) {
- // Kick off a sync to update local db.
- SyncManager.sync();
- }
- }
-}
diff --git a/src/com/android/messaging/sms/SmsSender.java b/src/com/android/messaging/sms/SmsSender.java
deleted file mode 100644
index 889973f..0000000
--- a/src/com/android/messaging/sms/SmsSender.java
+++ /dev/null
@@ -1,315 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.sms;
-
-import android.app.Activity;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.SystemClock;
-import android.telephony.PhoneNumberUtils;
-import android.telephony.SmsManager;
-import android.text.TextUtils;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.receiver.SendStatusReceiver;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.BugleGservices;
-import com.android.messaging.util.BugleGservicesKeys;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.PhoneUtils;
-import com.android.messaging.util.UiUtils;
-
-import java.util.ArrayList;
-import java.util.Random;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * Class that sends chat message via SMS.
- *
- * The interface emulates a blocking sending similar to making an HTTP request.
- * It calls the SmsManager to send a (potentially multipart) message and waits
- * on the sent status on each part. The waiting has a timeout so it won't wait
- * forever. Once the sent status of all parts received, the call returns.
- * A successful sending requires success status for all parts. Otherwise, we
- * pick the highest level of failure as the error for the whole message, which
- * is used to determine if we need to retry the sending.
- */
-public class SmsSender {
- private static final String TAG = LogUtil.BUGLE_TAG;
-
- public static final String EXTRA_PART_ID = "part_id";
-
- /*
- * A map for pending sms messages. The key is the random request UUID.
- */
- private static ConcurrentHashMap<Uri, SendResult> sPendingMessageMap =
- new ConcurrentHashMap<Uri, SendResult>();
-
- private static final Random RANDOM = new Random();
-
- // Whether we should send multipart SMS as separate messages
- private static Boolean sSendMultipartSmsAsSeparateMessages = null;
-
- /**
- * Class that holds the sent status for all parts of a multipart message sending
- */
- public static class SendResult {
- // Failure levels, used by the caller of the sender.
- // For temporary failures, possibly we could retry the sending
- // For permanent failures, we probably won't retry
- public static final int FAILURE_LEVEL_NONE = 0;
- public static final int FAILURE_LEVEL_TEMPORARY = 1;
- public static final int FAILURE_LEVEL_PERMANENT = 2;
-
- // Tracking the remaining pending parts in sending
- private int mPendingParts;
- // Tracking the highest level of failure among all parts
- private int mHighestFailureLevel;
-
- public SendResult(final int numOfParts) {
- Assert.isTrue(numOfParts > 0);
- mPendingParts = numOfParts;
- mHighestFailureLevel = FAILURE_LEVEL_NONE;
- }
-
- // Update the sent status of one part
- public void setPartResult(final int resultCode) {
- mPendingParts--;
- setHighestFailureLevel(resultCode);
- }
-
- public boolean hasPending() {
- return mPendingParts > 0;
- }
-
- public int getHighestFailureLevel() {
- return mHighestFailureLevel;
- }
-
- private int getFailureLevel(final int resultCode) {
- switch (resultCode) {
- case Activity.RESULT_OK:
- return FAILURE_LEVEL_NONE;
- case SmsManager.RESULT_ERROR_NO_SERVICE:
- return FAILURE_LEVEL_TEMPORARY;
- case SmsManager.RESULT_ERROR_RADIO_OFF:
- return FAILURE_LEVEL_PERMANENT;
- case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
- return FAILURE_LEVEL_PERMANENT;
- default: {
- LogUtil.e(TAG, "SmsSender: Unexpected sent intent resultCode = " + resultCode);
- return FAILURE_LEVEL_PERMANENT;
- }
- }
- }
-
- private void setHighestFailureLevel(final int resultCode) {
- final int level = getFailureLevel(resultCode);
- if (level > mHighestFailureLevel) {
- mHighestFailureLevel = level;
- }
- }
-
- @Override
- public String toString() {
- final StringBuilder sb = new StringBuilder();
- sb.append("SendResult:");
- sb.append("Pending=").append(mPendingParts).append(",");
- sb.append("HighestFailureLevel=").append(mHighestFailureLevel);
- return sb.toString();
- }
- }
-
- public static void setResult(final Uri requestId, final int resultCode,
- final int errorCode, final int partId, int subId) {
- if (resultCode != Activity.RESULT_OK) {
- LogUtil.e(TAG, "SmsSender: failure in sending message part. "
- + " requestId=" + requestId + " partId=" + partId
- + " resultCode=" + resultCode + " errorCode=" + errorCode);
- if (errorCode != SendStatusReceiver.NO_ERROR_CODE) {
- final Context context = Factory.get().getApplicationContext();
- UiUtils.showToastAtBottom(getSendErrorToastMessage(context, subId, errorCode));
- }
- } else {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "SmsSender: received sent result. " + " requestId=" + requestId
- + " partId=" + partId + " resultCode=" + resultCode);
- }
- }
- if (requestId != null) {
- final SendResult result = sPendingMessageMap.get(requestId);
- if (result != null) {
- synchronized (result) {
- result.setPartResult(resultCode);
- if (!result.hasPending()) {
- result.notifyAll();
- }
- }
- } else {
- LogUtil.e(TAG, "SmsSender: ignoring sent result. " + " requestId=" + requestId
- + " partId=" + partId + " resultCode=" + resultCode);
- }
- }
- }
-
- private static String getSendErrorToastMessage(final Context context, final int subId,
- final int errorCode) {
- final String carrierName = PhoneUtils.get(subId).getCarrierName();
- if (TextUtils.isEmpty(carrierName)) {
- return context.getString(R.string.carrier_send_error_unknown_carrier, errorCode);
- } else {
- return context.getString(R.string.carrier_send_error, carrierName, errorCode);
- }
- }
-
- // This should be called from a RequestWriter queue thread
- public static SendResult sendMessage(final Context context, final int subId, String dest,
- String message, final String serviceCenter, final boolean requireDeliveryReport,
- final Uri messageUri) throws SmsException {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "SmsSender: sending message. " +
- "dest=" + dest + " message=" + message +
- " serviceCenter=" + serviceCenter +
- " requireDeliveryReport=" + requireDeliveryReport +
- " requestId=" + messageUri);
- }
- if (TextUtils.isEmpty(message)) {
- throw new SmsException("SmsSender: empty text message");
- }
- // Get the real dest and message for email or alias if dest is email or alias
- // Or sanitize the dest if dest is a number
- if (!TextUtils.isEmpty(MmsConfig.get(subId).getEmailGateway()) &&
- (MmsSmsUtils.isEmailAddress(dest) || MmsSmsUtils.isAlias(dest, subId))) {
- // The original destination (email address) goes with the message
- message = dest + " " + message;
- // the new address is the email gateway #
- dest = MmsConfig.get(subId).getEmailGateway();
- } else {
- // remove spaces and dashes from destination number
- // (e.g. "801 555 1212" -> "8015551212")
- // (e.g. "+8211-123-4567" -> "+82111234567")
- dest = PhoneNumberUtils.stripSeparators(dest);
- }
- if (TextUtils.isEmpty(dest)) {
- throw new SmsException("SmsSender: empty destination address");
- }
- // Divide the input message by SMS length limit
- final SmsManager smsManager = PhoneUtils.get(subId).getSmsManager();
- final ArrayList<String> messages = smsManager.divideMessage(message);
- if (messages == null || messages.size() < 1) {
- throw new SmsException("SmsSender: fails to divide message");
- }
- // Prepare the send result, which collects the send status for each part
- final SendResult pendingResult = new SendResult(messages.size());
- sPendingMessageMap.put(messageUri, pendingResult);
- // Actually send the sms
- sendInternal(
- context, subId, dest, messages, serviceCenter, requireDeliveryReport, messageUri);
- // Wait for pending intent to come back
- synchronized (pendingResult) {
- final long smsSendTimeoutInMillis = BugleGservices.get().getLong(
- BugleGservicesKeys.SMS_SEND_TIMEOUT_IN_MILLIS,
- BugleGservicesKeys.SMS_SEND_TIMEOUT_IN_MILLIS_DEFAULT);
- final long beginTime = SystemClock.elapsedRealtime();
- long waitTime = smsSendTimeoutInMillis;
- // We could possibly be woken up while still pending
- // so make sure we wait the full timeout period unless
- // we have the send results of all parts.
- while (pendingResult.hasPending() && waitTime > 0) {
- try {
- pendingResult.wait(waitTime);
- } catch (final InterruptedException e) {
- LogUtil.e(TAG, "SmsSender: sending wait interrupted");
- }
- waitTime = smsSendTimeoutInMillis - (SystemClock.elapsedRealtime() - beginTime);
- }
- }
- // Either we timed out or have all the results (success or failure)
- sPendingMessageMap.remove(messageUri);
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "SmsSender: sending completed. " +
- "dest=" + dest + " message=" + message + " result=" + pendingResult);
- }
- return pendingResult;
- }
-
- // Actually sending the message using SmsManager
- private static void sendInternal(final Context context, final int subId, String dest,
- final ArrayList<String> messages, final String serviceCenter,
- final boolean requireDeliveryReport, final Uri messageUri) throws SmsException {
- Assert.notNull(context);
- final SmsManager smsManager = PhoneUtils.get(subId).getSmsManager();
- final int messageCount = messages.size();
- final ArrayList<PendingIntent> deliveryIntents = new ArrayList<PendingIntent>(messageCount);
- final ArrayList<PendingIntent> sentIntents = new ArrayList<PendingIntent>(messageCount);
- for (int i = 0; i < messageCount; i++) {
- // Make pending intents different for each message part
- final int partId = (messageCount <= 1 ? 0 : i + 1);
- if (requireDeliveryReport && (i == (messageCount - 1))) {
- // TODO we only care about the delivery status of the last part
- // Shall we have better tracking of delivery status of all parts?
- deliveryIntents.add(PendingIntent.getBroadcast(
- context,
- partId,
- getSendStatusIntent(context, SendStatusReceiver.MESSAGE_DELIVERED_ACTION,
- messageUri, partId, subId),
- 0/*flag*/));
- } else {
- deliveryIntents.add(null);
- }
- sentIntents.add(PendingIntent.getBroadcast(
- context,
- partId,
- getSendStatusIntent(context, SendStatusReceiver.MESSAGE_SENT_ACTION,
- messageUri, partId, subId),
- 0/*flag*/));
- }
- if (sSendMultipartSmsAsSeparateMessages == null) {
- sSendMultipartSmsAsSeparateMessages = MmsConfig.get(subId)
- .getSendMultipartSmsAsSeparateMessages();
- }
- try {
- if (sSendMultipartSmsAsSeparateMessages) {
- // If multipart sms is not supported, send them as separate messages
- for (int i = 0; i < messageCount; i++) {
- smsManager.sendTextMessage(dest,
- serviceCenter,
- messages.get(i),
- sentIntents.get(i),
- deliveryIntents.get(i));
- }
- } else {
- smsManager.sendMultipartTextMessage(
- dest, serviceCenter, messages, sentIntents, deliveryIntents);
- }
- } catch (final Exception e) {
- throw new SmsException("SmsSender: caught exception in sending " + e);
- }
- }
-
- private static Intent getSendStatusIntent(final Context context, final String action,
- final Uri requestUri, final int partId, final int subId) {
- // Encode requestId in intent data
- final Intent intent = new Intent(action, requestUri, context, SendStatusReceiver.class);
- intent.putExtra(SendStatusReceiver.EXTRA_PART_ID, partId);
- intent.putExtra(SendStatusReceiver.EXTRA_SUB_ID, subId);
- return intent;
- }
-}
diff --git a/src/com/android/messaging/sms/SmsStorageStatusManager.java b/src/com/android/messaging/sms/SmsStorageStatusManager.java
deleted file mode 100644
index ff7b79d..0000000
--- a/src/com/android/messaging/sms/SmsStorageStatusManager.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.sms;
-
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.res.Resources;
-import android.support.v4.app.NotificationCompat;
-import android.support.v4.app.NotificationManagerCompat;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.util.PendingIntentConstants;
-import com.android.messaging.util.PhoneUtils;
-
-/**
- * Class that handles SMS auto delete and notification when storage is low
- */
-public class SmsStorageStatusManager {
- /**
- * Handles storage low signal for SMS
- */
- public static void handleStorageLow() {
- if (!PhoneUtils.getDefault().isSmsEnabled()) {
- return;
- }
-
- // TODO: Auto-delete messages, when that setting exists and is enabled
-
- // Notify low storage for SMS
- postStorageLowNotification();
- }
-
- /**
- * Handles storage OK signal for SMS
- */
- public static void handleStorageOk() {
- if (!PhoneUtils.getDefault().isSmsEnabled()) {
- return;
- }
- cancelStorageLowNotification();
- }
-
- /**
- * Post sms storage low notification
- */
- private static void postStorageLowNotification() {
- final Context context = Factory.get().getApplicationContext();
- final Resources resources = context.getResources();
- final PendingIntent pendingIntent = UIIntents.get()
- .getPendingIntentForLowStorageNotifications(context);
-
- final NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
- builder.setContentTitle(resources.getString(R.string.sms_storage_low_title))
- .setTicker(resources.getString(R.string.sms_storage_low_notification_ticker))
- .setSmallIcon(R.drawable.ic_failed_light)
- .setPriority(Notification.PRIORITY_DEFAULT)
- .setOngoing(true) // Can't be swiped off
- .setAutoCancel(false) // Don't auto cancel
- .setContentIntent(pendingIntent);
-
- final NotificationCompat.BigTextStyle bigTextStyle =
- new NotificationCompat.BigTextStyle(builder);
- bigTextStyle.bigText(resources.getString(R.string.sms_storage_low_text));
- final Notification notification = bigTextStyle.build();
-
- final NotificationManagerCompat notificationManager =
- NotificationManagerCompat.from(Factory.get().getApplicationContext());
-
- notificationManager.notify(getNotificationTag(),
- PendingIntentConstants.SMS_STORAGE_LOW_NOTIFICATION_ID, notification);
- }
-
- /**
- * Cancel the notification
- */
- public static void cancelStorageLowNotification() {
- final NotificationManagerCompat notificationManager =
- NotificationManagerCompat.from(Factory.get().getApplicationContext());
- notificationManager.cancel(getNotificationTag(),
- PendingIntentConstants.SMS_STORAGE_LOW_NOTIFICATION_ID);
- }
-
- private static String getNotificationTag() {
- return Factory.get().getApplicationContext().getPackageName() + ":smsstoragelow";
- }
-}
diff --git a/src/com/android/messaging/sms/SystemProperties.java b/src/com/android/messaging/sms/SystemProperties.java
deleted file mode 100644
index 669e448..0000000
--- a/src/com/android/messaging/sms/SystemProperties.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.sms;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-/**
- * Hacky way to call the hidden SystemProperties class API
- */
-class SystemProperties {
- private static Method sSystemPropertiesGetMethod = null;
-
- public static String get(final String name) {
- if (sSystemPropertiesGetMethod == null) {
- try {
- final Class systemPropertiesClass = Class.forName("android.os.SystemProperties");
- if (systemPropertiesClass != null) {
- sSystemPropertiesGetMethod =
- systemPropertiesClass.getMethod("get", String.class);
- }
- } catch (final ClassNotFoundException e) {
- // Nothing to do
- } catch (final NoSuchMethodException e) {
- // Nothing to do
- }
- }
- if (sSystemPropertiesGetMethod != null) {
- try {
- return (String) sSystemPropertiesGetMethod.invoke(null, name);
- } catch (final IllegalArgumentException e) {
- // Nothing to do
- } catch (final IllegalAccessException e) {
- // Nothing to do
- } catch (final InvocationTargetException e) {
- // Nothing to do
- }
- }
- return null;
- }
-}