1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.mms.service; 18 19import com.android.mms.service.exception.MmsNetworkException; 20import com.android.mms.service.http.NameResolver; 21 22import android.content.Context; 23import android.net.ConnectivityManager; 24import android.net.Network; 25import android.net.NetworkCapabilities; 26import android.net.NetworkRequest; 27import android.os.SystemClock; 28import android.provider.Settings; 29import android.util.Log; 30 31import java.net.InetAddress; 32import java.net.UnknownHostException; 33 34/** 35 * Manages the MMS network connectivity 36 */ 37public class MmsNetworkManager implements NameResolver { 38 // Timeout used to call ConnectivityManager.requestNetwork 39 private static final int NETWORK_REQUEST_TIMEOUT_MILLIS = 60 * 1000; 40 // Wait timeout for this class, a little bit longer than the above timeout 41 // to make sure we don't bail prematurely 42 private static final int NETWORK_ACQUIRE_TIMEOUT_MILLIS = 43 NETWORK_REQUEST_TIMEOUT_MILLIS + (5 * 1000); 44 45 private Context mContext; 46 // The requested MMS {@link android.net.Network} we are holding 47 // We need this when we unbind from it. This is also used to indicate if the 48 // MMS network is available. 49 private Network mNetwork; 50 // The current count of MMS requests that require the MMS network 51 // If mMmsRequestCount is 0, we should release the MMS network. 52 private int mMmsRequestCount; 53 54 // This is really just for using the capability 55 private NetworkRequest mNetworkRequest = new NetworkRequest.Builder() 56 .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) 57 .addCapability(NetworkCapabilities.NET_CAPABILITY_MMS) 58 .build(); 59 60 // The callback to register when we request MMS network 61 private ConnectivityManager.NetworkCallback mNetworkCallback; 62 63 private ConnectivityManager mConnectivityManager; 64 65 // TODO: we need to re-architect this when we support MSIM, like maybe one manager for each SIM? 66 public MmsNetworkManager(Context context) { 67 mContext = context; 68 mNetworkCallback = null; 69 mNetwork = null; 70 mMmsRequestCount = 0; 71 mConnectivityManager = null; 72 } 73 74 public Network getNetwork() { 75 synchronized (this) { 76 return mNetwork; 77 } 78 } 79 80 /** 81 * Acquire the MMS network 82 * 83 * @throws com.android.mms.service.exception.MmsNetworkException if we fail to acquire it 84 */ 85 public void acquireNetwork() throws MmsNetworkException { 86 if (inAirplaneMode()) { 87 // Fast fail airplane mode 88 throw new MmsNetworkException("In airplane mode"); 89 } 90 synchronized (this) { 91 mMmsRequestCount += 1; 92 if (mNetwork != null) { 93 // Already available 94 Log.d(MmsService.TAG, "MmsNetworkManager: already available"); 95 return; 96 } 97 Log.d(MmsService.TAG, "MmsNetworkManager: start new network request"); 98 // Not available, so start a new request 99 newRequest(); 100 final long shouldEnd = SystemClock.elapsedRealtime() + NETWORK_ACQUIRE_TIMEOUT_MILLIS; 101 long waitTime = NETWORK_ACQUIRE_TIMEOUT_MILLIS; 102 while (waitTime > 0) { 103 try { 104 this.wait(waitTime); 105 } catch (InterruptedException e) { 106 Log.w(MmsService.TAG, "MmsNetworkManager: acquire network wait interrupted"); 107 } 108 if (mNetwork != null) { 109 // Success 110 return; 111 } 112 // Calculate remaining waiting time to make sure we wait the full timeout period 113 waitTime = shouldEnd - SystemClock.elapsedRealtime(); 114 } 115 // Timed out, so release the request and fail 116 Log.d(MmsService.TAG, "MmsNetworkManager: timed out"); 117 releaseRequest(mNetworkCallback); 118 resetLocked(); 119 throw new MmsNetworkException("Acquiring network timed out"); 120 } 121 } 122 123 /** 124 * Release the MMS network when nobody is holding on to it. 125 */ 126 public void releaseNetwork() { 127 synchronized (this) { 128 if (mMmsRequestCount > 0) { 129 mMmsRequestCount -= 1; 130 Log.d(MmsService.TAG, "MmsNetworkManager: release, count=" + mMmsRequestCount); 131 if (mMmsRequestCount < 1) { 132 releaseRequest(mNetworkCallback); 133 resetLocked(); 134 } 135 } 136 } 137 } 138 139 /** 140 * Start a new {@link android.net.NetworkRequest} for MMS 141 */ 142 private void newRequest() { 143 final ConnectivityManager connectivityManager = getConnectivityManager(); 144 mNetworkCallback = new ConnectivityManager.NetworkCallback() { 145 @Override 146 public void onAvailable(Network network) { 147 super.onAvailable(network); 148 Log.d(MmsService.TAG, "NetworkCallbackListener.onAvailable: network=" + network); 149 synchronized (MmsNetworkManager.this) { 150 mNetwork = network; 151 MmsNetworkManager.this.notifyAll(); 152 } 153 } 154 155 @Override 156 public void onLost(Network network) { 157 super.onLost(network); 158 Log.d(MmsService.TAG, "NetworkCallbackListener.onLost: network=" + network); 159 synchronized (MmsNetworkManager.this) { 160 releaseRequest(this); 161 if (mNetworkCallback == this) { 162 resetLocked(); 163 } 164 MmsNetworkManager.this.notifyAll(); 165 } 166 } 167 168 @Override 169 public void onUnavailable() { 170 super.onUnavailable(); 171 Log.d(MmsService.TAG, "NetworkCallbackListener.onUnavailable"); 172 synchronized (MmsNetworkManager.this) { 173 releaseRequest(this); 174 if (mNetworkCallback == this) { 175 resetLocked(); 176 } 177 MmsNetworkManager.this.notifyAll(); 178 } 179 } 180 }; 181 connectivityManager.requestNetwork( 182 mNetworkRequest, mNetworkCallback, NETWORK_REQUEST_TIMEOUT_MILLIS); 183 } 184 185 /** 186 * Release the current {@link android.net.NetworkRequest} for MMS 187 * 188 * @param callback the {@link android.net.ConnectivityManager.NetworkCallback} to unregister 189 */ 190 private void releaseRequest(ConnectivityManager.NetworkCallback callback) { 191 if (callback != null) { 192 final ConnectivityManager connectivityManager = getConnectivityManager(); 193 connectivityManager.unregisterNetworkCallback(callback); 194 } 195 } 196 197 /** 198 * Reset the state 199 */ 200 private void resetLocked() { 201 mNetworkCallback = null; 202 mNetwork = null; 203 mMmsRequestCount = 0; 204 } 205 206 @Override 207 public InetAddress[] getAllByName(String host) throws UnknownHostException { 208 synchronized (this) { 209 if (mNetwork != null) { 210 return mNetwork.getAllByName(host); 211 } 212 return new InetAddress[0]; 213 } 214 } 215 216 private ConnectivityManager getConnectivityManager() { 217 if (mConnectivityManager == null) { 218 mConnectivityManager = (ConnectivityManager) mContext.getSystemService( 219 Context.CONNECTIVITY_SERVICE); 220 } 221 return mConnectivityManager; 222 } 223 224 private boolean inAirplaneMode() { 225 return Settings.System.getInt(mContext.getContentResolver(), 226 Settings.Global.AIRPLANE_MODE_ON, 0) != 0; 227 } 228} 229