Revert "Initial checkin of AOSP Messaging app."
This reverts commit 461a34b466.
Change-Id: Iac4ca77eeaa94989e91dead49a7959c905bd3078
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user