Files
message_q/src/android/support/v7/mms/DefaultApnSettingsLoader.java
T
Michael W 6161d0fca1 Messaging: Get rid of everything around the local apn database
Change-Id: Ie1916e93b274009494e76d6d99f36a025139127d
2025-05-18 09:18:48 +00:00

336 lines
11 KiB
Java

/*
* Copyright (C) 2015 The Android Open Source Project
* Copyright (C) 2024-2025 The LineageOS 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 android.support.v7.mms;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.net.Uri;
import android.provider.Telephony;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
/**
* Default implementation of APN settings loader
*/
class DefaultApnSettingsLoader 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
}
}
/**
* 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 = {
Telephony.Carriers.TYPE,
Telephony.Carriers.MMSC,
Telephony.Carriers.MMSPROXY,
Telephony.Carriers.MMSPORT,
};
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 final Context mContext;
// Cached APNs for subIds
private final SparseArray<List<Apn>> mApnsCache;
DefaultApnSettingsLoader(final Context context) {
mContext = context;
mApnsCache = new SparseArray<>();
}
@Override
public List<Apn> get(final String apnName) {
final int subId = Utils.getEffectiveSubscriptionId(MmsManager.DEFAULT_SUB_ID);
List<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) {
Log.i(MmsService.TAG, "Loaded " + apns.size() + " APNs");
}
return apns;
}
private void loadLocked(final int subId, final String apnName, final List<Apn> apns) {
// Try system APN table first
loadFromSystem(subId, apnName, apns);
}
/**
* 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 (subId != MmsManager.DEFAULT_SUB_ID) {
uri = Uri.withAppendedPath(Telephony.Carriers.CONTENT_URI, "/subId/" + subId);
} else {
uri = Telephony.Carriers.CONTENT_URI;
}
Cursor cursor;
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 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) {
Log.i(MmsService.TAG, "Loading APNs from system, "
+ "checkCurrent=" + checkCurrent + " apnName=" + apnName);
final StringBuilder selectionBuilder = new StringBuilder();
String[] selectionArgs = null;
if (checkCurrent) {
selectionBuilder.append(Telephony.Carriers.CURRENT).append(" IS NOT NULL");
}
apnName = trimWithNullCheck(apnName);
if (!TextUtils.isEmpty(apnName)) {
if (selectionBuilder.length() > 0) {
selectionBuilder.append(" AND ");
}
selectionBuilder.append(Telephony.Carriers.APN).append("=?");
selectionArgs = new String[] { apnName };
}
try {
final Cursor cursor = mContext.getContentResolver().query(
uri,
APN_PROJECTION,
selectionBuilder.toString(),
selectionArgs,
null/*sortOrder*/);
if (cursor == null || cursor.getCount() < 1) {
if (cursor != null) {
cursor.close();
}
Log.w(MmsService.TAG, "Query " + uri + " with apn " + apnName + " and "
+ (checkCurrent ? "checking CURRENT" : "not checking CURRENT")
+ " returned empty");
return null;
}
return cursor;
} catch (final SQLiteException e) {
Log.w(MmsService.TAG, "APN table query exception: " + e);
} catch (final SecurityException e) {
Log.w(MmsService.TAG, "Platform restricts APN table access: " + e);
throw e;
}
return null;
}
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;
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;
}
}