blob: 2f61232846d6664195c29eea50cc1f20028947de [file] [log] [blame]
/*
* Copyright (C) 2014 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.mms.service;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.net.NetworkInfo;
import android.os.SystemClock;
import android.provider.Settings;
import android.util.Log;
import com.android.mms.service.exception.MmsNetworkException;
import com.android.okhttp.ConnectionPool;
import com.android.okhttp.HostResolver;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* Manages the MMS network connectivity
*/
public class MmsNetworkManager implements HostResolver {
// Timeout used to call ConnectivityManager.requestNetwork
private static final int NETWORK_REQUEST_TIMEOUT_MILLIS = 60 * 1000;
// Wait timeout for this class, a little bit longer than the above timeout
// to make sure we don't bail prematurely
private static final int NETWORK_ACQUIRE_TIMEOUT_MILLIS =
NETWORK_REQUEST_TIMEOUT_MILLIS + (5 * 1000);
// Borrowed from {@link android.net.Network}
private static final boolean httpKeepAlive =
Boolean.parseBoolean(System.getProperty("http.keepAlive", "true"));
private static final int httpMaxConnections =
httpKeepAlive ? Integer.parseInt(System.getProperty("http.maxConnections", "5")) : 0;
private static final long httpKeepAliveDurationMs =
Long.parseLong(System.getProperty("http.keepAliveDuration", "300000")); // 5 minutes.
private final Context mContext;
// The requested MMS {@link android.net.Network} we are holding
// We need this when we unbind from it. This is also used to indicate if the
// MMS network is available.
private Network mNetwork;
// The current count of MMS requests that require the MMS network
// If mMmsRequestCount is 0, we should release the MMS network.
private int mMmsRequestCount;
// This is really just for using the capability
private final NetworkRequest mNetworkRequest;
// The callback to register when we request MMS network
private ConnectivityManager.NetworkCallback mNetworkCallback;
private volatile ConnectivityManager mConnectivityManager;
// The OkHttp's ConnectionPool used by the HTTP client associated with this network manager
private ConnectionPool mConnectionPool;
// The MMS HTTP client for this network
private MmsHttpClient mMmsHttpClient;
// The SIM ID which we use to connect
private final int mSubId;
public MmsNetworkManager(Context context, int subId) {
mContext = context;
mNetworkCallback = null;
mNetwork = null;
mMmsRequestCount = 0;
mConnectivityManager = null;
mConnectionPool = null;
mMmsHttpClient = null;
mSubId = subId;
mNetworkRequest = new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.addCapability(NetworkCapabilities.NET_CAPABILITY_MMS)
.setNetworkSpecifier(Integer.toString(mSubId))
.build();
}
/**
* Acquire the MMS network
*
* @throws com.android.mms.service.exception.MmsNetworkException if we fail to acquire it
*/
public void acquireNetwork() throws MmsNetworkException {
synchronized (this) {
mMmsRequestCount += 1;
if (mNetwork != null) {
// Already available
Log.d(MmsService.TAG, "MmsNetworkManager: already available");
return;
}
Log.d(MmsService.TAG, "MmsNetworkManager: start new network request");
// Not available, so start a new request
newRequest();
final long shouldEnd = SystemClock.elapsedRealtime() + NETWORK_ACQUIRE_TIMEOUT_MILLIS;
long waitTime = NETWORK_ACQUIRE_TIMEOUT_MILLIS;
while (waitTime > 0) {
try {
this.wait(waitTime);
} catch (InterruptedException e) {
Log.w(MmsService.TAG, "MmsNetworkManager: acquire network wait interrupted");
}
if (mNetwork != null) {
// Success
return;
}
// Calculate remaining waiting time to make sure we wait the full timeout period
waitTime = shouldEnd - SystemClock.elapsedRealtime();
}
// Timed out, so release the request and fail
Log.d(MmsService.TAG, "MmsNetworkManager: timed out");
releaseRequestLocked(mNetworkCallback);
throw new MmsNetworkException("Acquiring network timed out");
}
}
/**
* Release the MMS network when nobody is holding on to it.
*/
public void releaseNetwork() {
synchronized (this) {
if (mMmsRequestCount > 0) {
mMmsRequestCount -= 1;
Log.d(MmsService.TAG, "MmsNetworkManager: release, count=" + mMmsRequestCount);
if (mMmsRequestCount < 1) {
releaseRequestLocked(mNetworkCallback);
}
}
}
}
/**
* Start a new {@link android.net.NetworkRequest} for MMS
*/
private void newRequest() {
final ConnectivityManager connectivityManager = getConnectivityManager();
mNetworkCallback = new ConnectivityManager.NetworkCallback() {
@Override
public void onAvailable(Network network) {
super.onAvailable(network);
Log.d(MmsService.TAG, "NetworkCallbackListener.onAvailable: network=" + network);
synchronized (MmsNetworkManager.this) {
mNetwork = network;
MmsNetworkManager.this.notifyAll();
}
}
@Override
public void onLost(Network network) {
super.onLost(network);
Log.d(MmsService.TAG, "NetworkCallbackListener.onLost: network=" + network);
synchronized (MmsNetworkManager.this) {
releaseRequestLocked(this);
MmsNetworkManager.this.notifyAll();
}
}
@Override
public void onUnavailable() {
super.onUnavailable();
Log.d(MmsService.TAG, "NetworkCallbackListener.onUnavailable");
synchronized (MmsNetworkManager.this) {
releaseRequestLocked(this);
MmsNetworkManager.this.notifyAll();
}
}
};
connectivityManager.requestNetwork(
mNetworkRequest, mNetworkCallback, NETWORK_REQUEST_TIMEOUT_MILLIS);
}
/**
* Release the current {@link android.net.NetworkRequest} for MMS
*
* @param callback the {@link android.net.ConnectivityManager.NetworkCallback} to unregister
*/
private void releaseRequestLocked(ConnectivityManager.NetworkCallback callback) {
if (callback != null) {
final ConnectivityManager connectivityManager = getConnectivityManager();
connectivityManager.unregisterNetworkCallback(callback);
}
resetLocked();
}
/**
* Reset the state
*/
private void resetLocked() {
mNetworkCallback = null;
mNetwork = null;
mMmsRequestCount = 0;
// Currently we follow what android.net.Network does with ConnectionPool,
// which is per Network object. So if Network changes, we should clear
// out the ConnectionPool and thus the MmsHttpClient (since it is linked
// to a specific ConnectionPool).
mConnectionPool = null;
mMmsHttpClient = null;
}
private static final InetAddress[] EMPTY_ADDRESS_ARRAY = new InetAddress[0];
@Override
public InetAddress[] getAllByName(String host) throws UnknownHostException {
Network network = null;
synchronized (this) {
if (mNetwork == null) {
return EMPTY_ADDRESS_ARRAY;
}
network = mNetwork;
}
return network.getAllByName(host);
}
private ConnectivityManager getConnectivityManager() {
if (mConnectivityManager == null) {
mConnectivityManager = (ConnectivityManager) mContext.getSystemService(
Context.CONNECTIVITY_SERVICE);
}
return mConnectivityManager;
}
private ConnectionPool getOrCreateConnectionPoolLocked() {
if (mConnectionPool == null) {
mConnectionPool = new ConnectionPool(httpMaxConnections, httpKeepAliveDurationMs);
}
return mConnectionPool;
}
/**
* Get an MmsHttpClient for the current network
*
* @return The MmsHttpClient instance
*/
public MmsHttpClient getOrCreateHttpClient() {
synchronized (this) {
if (mMmsHttpClient == null) {
if (mNetwork != null) {
// Create new MmsHttpClient for the current Network
mMmsHttpClient = new MmsHttpClient(
mContext,
mNetwork.getSocketFactory(),
MmsNetworkManager.this,
getOrCreateConnectionPoolLocked());
}
}
return mMmsHttpClient;
}
}
/**
* Get the APN name for the active network
*
* @return The APN name if available, otherwise null
*/
public String getApnName() {
Network network = null;
synchronized (this) {
if (mNetwork == null) {
Log.d(MmsService.TAG, "MmsNetworkManager: getApnName: network not available");
return null;
}
network = mNetwork;
}
String apnName = null;
final ConnectivityManager connectivityManager = getConnectivityManager();
NetworkInfo mmsNetworkInfo = connectivityManager.getNetworkInfo(network);
if (mmsNetworkInfo != null) {
apnName = mmsNetworkInfo.getExtraInfo();
}
Log.d(MmsService.TAG, "MmsNetworkManager: getApnName: " + apnName);
return apnName;
}
}