blob: 361eaa1e6f992baf86bfe4f5138d38928198a3ac [file] [log] [blame]
/*
* Copyright 2020 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.google.android.iwlan;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.telephony.AccessNetworkConstants;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.NetworkService;
import android.telephony.NetworkServiceCallback;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class IwlanNetworkService extends NetworkService {
private static final String TAG = IwlanNetworkService.class.getSimpleName();
private Context mContext;
private IwlanNetworkMonitorCallback mNetworkMonitorCallback;
private IwlanOnSubscriptionsChangedListener mSubsChangeListener;
private Handler mIwlanNetworkServiceHandler;
private HandlerThread mIwlanNetworkServiceHandlerThread;
private static boolean sNetworkConnected;
private static final Map<Integer, IwlanNetworkServiceProvider> sIwlanNetworkServiceProviders =
new ConcurrentHashMap<>();
private static final int EVENT_BASE = IwlanEventListener.NETWORK_SERVICE_INTERNAL_EVENT_BASE;
private static final int EVENT_NETWORK_REGISTRATION_INFO_REQUEST = EVENT_BASE;
private static final int EVENT_CREATE_NETWORK_SERVICE_PROVIDER = EVENT_BASE + 1;
private static final int EVENT_REMOVE_NETWORK_SERVICE_PROVIDER = EVENT_BASE + 2;
@VisibleForTesting
enum Transport {
UNSPECIFIED_NETWORK,
MOBILE,
WIFI;
}
private static Transport sDefaultDataTransport = Transport.UNSPECIFIED_NETWORK;
// This callback runs in the same thread as IwlanNetworkServiceHandler
final class IwlanNetworkMonitorCallback extends ConnectivityManager.NetworkCallback {
/** Called when the framework connects and has declared a new network ready for use. */
@Override
public void onAvailable(Network network) {
Log.d(TAG, "onAvailable: " + network);
}
/**
* Called when the network is about to be lost, typically because there are no outstanding
* requests left for it. This may be paired with a {@link NetworkCallback#onAvailable} call
* with the new replacement network for graceful handover. This method is not guaranteed to
* be called before {@link NetworkCallback#onLost} is called, for example in case a network
* is suddenly disconnected.
*/
@Override
public void onLosing(Network network, int maxMsToLive) {
Log.d(TAG, "onLosing: maxMsToLive: " + maxMsToLive + " network: " + network);
}
/**
* Called when a network disconnects or otherwise no longer satisfies this request or
* callback.
*/
@Override
public void onLost(Network network) {
Log.d(TAG, "onLost: " + network);
IwlanNetworkService.setNetworkConnected(false, Transport.UNSPECIFIED_NETWORK);
}
/** Called when the network corresponding to this request changes {@link LinkProperties}. */
@Override
public void onLinkPropertiesChanged(Network network, LinkProperties linkProperties) {
Log.d(TAG, "onLinkPropertiesChanged: " + linkProperties);
}
/** Called when access to the specified network is blocked or unblocked. */
@Override
public void onBlockedStatusChanged(Network network, boolean blocked) {
// TODO: check if we need to handle this
Log.d(TAG, "onBlockedStatusChanged: " + " BLOCKED:" + blocked);
}
@Override
public void onCapabilitiesChanged(
Network network, NetworkCapabilities networkCapabilities) {
// onCapabilitiesChanged is guaranteed to be called immediately after onAvailable per
// API
Log.d(TAG, "onCapabilitiesChanged: " + network);
if (networkCapabilities != null) {
if (networkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
IwlanNetworkService.setNetworkConnected(
true, IwlanNetworkService.Transport.MOBILE);
} else if (networkCapabilities.hasTransport(TRANSPORT_WIFI)) {
IwlanNetworkService.setNetworkConnected(
true, IwlanNetworkService.Transport.WIFI);
} else {
Log.w(TAG, "Network does not have cellular or wifi capability");
}
}
}
}
final class IwlanOnSubscriptionsChangedListener
extends SubscriptionManager.OnSubscriptionsChangedListener {
/**
* Callback invoked when there is any change to any SubscriptionInfo. Typically this method
* invokes {@link SubscriptionManager#getActiveSubscriptionInfoList}
*/
@Override
public void onSubscriptionsChanged() {
for (IwlanNetworkServiceProvider np : sIwlanNetworkServiceProviders.values()) {
np.subscriptionChanged();
}
}
}
@VisibleForTesting
class IwlanNetworkServiceProvider extends NetworkServiceProvider {
private final IwlanNetworkService mIwlanNetworkService;
private final String SUB_TAG;
private boolean mIsSubActive = false;
/**
* Constructor
*
* @param slotIndex SIM slot id the data service provider associated with.
*/
public IwlanNetworkServiceProvider(int slotIndex, IwlanNetworkService iwlanNetworkService) {
super(slotIndex);
SUB_TAG = TAG + "[" + slotIndex + "]";
mIwlanNetworkService = iwlanNetworkService;
// Register IwlanEventListener
List<Integer> events = new ArrayList<Integer>();
events.add(IwlanEventListener.CROSS_SIM_CALLING_ENABLE_EVENT);
events.add(IwlanEventListener.CROSS_SIM_CALLING_DISABLE_EVENT);
IwlanEventListener.getInstance(mContext, slotIndex)
.addEventListener(events, mIwlanNetworkServiceHandler);
}
@Override
public void requestNetworkRegistrationInfo(int domain, NetworkServiceCallback callback) {
mIwlanNetworkServiceHandler.sendMessage(
mIwlanNetworkServiceHandler.obtainMessage(
EVENT_NETWORK_REGISTRATION_INFO_REQUEST,
new NetworkRegistrationInfoRequestData(domain, callback, this)));
}
/**
* Called when the instance of network service is destroyed (e.g. got unbind or binder died)
* or when the network service provider is removed. The extended class should implement this
* method to perform cleanup works.
*/
@Override
public void close() {
mIwlanNetworkService.removeNetworkServiceProvider(this);
IwlanEventListener.getInstance(mContext, getSlotIndex())
.removeEventListener(mIwlanNetworkServiceHandler);
}
@VisibleForTesting
void subscriptionChanged() {
boolean subActive = false;
SubscriptionManager sm = SubscriptionManager.from(mContext);
if (sm.getActiveSubscriptionInfoForSimSlotIndex(getSlotIndex()) != null) {
subActive = true;
}
if (subActive == mIsSubActive) {
return;
}
mIsSubActive = subActive;
if (subActive) {
Log.d(SUB_TAG, "sub changed from not_ready --> ready");
} else {
Log.d(SUB_TAG, "sub changed from ready --> not_ready");
}
notifyNetworkRegistrationInfoChanged();
}
}
private final class IwlanNetworkServiceHandler extends Handler {
private final String TAG = IwlanNetworkServiceHandler.class.getSimpleName();
@Override
public void handleMessage(Message msg) {
Log.d(TAG, "msg.what = " + eventToString(msg.what));
IwlanNetworkServiceProvider iwlanNetworkServiceProvider;
switch (msg.what) {
case IwlanEventListener.CROSS_SIM_CALLING_ENABLE_EVENT:
iwlanNetworkServiceProvider = getNetworkServiceProvider(msg.arg1);
iwlanNetworkServiceProvider.notifyNetworkRegistrationInfoChanged();
break;
case IwlanEventListener.CROSS_SIM_CALLING_DISABLE_EVENT:
iwlanNetworkServiceProvider = getNetworkServiceProvider(msg.arg1);
iwlanNetworkServiceProvider.notifyNetworkRegistrationInfoChanged();
break;
case EVENT_NETWORK_REGISTRATION_INFO_REQUEST:
NetworkRegistrationInfoRequestData networkRegistrationInfoRequestData =
(NetworkRegistrationInfoRequestData) msg.obj;
int domain = networkRegistrationInfoRequestData.mDomain;
NetworkServiceCallback callback = networkRegistrationInfoRequestData.mCallback;
iwlanNetworkServiceProvider =
networkRegistrationInfoRequestData.mIwlanNetworkServiceProvider;
if (callback == null) {
Log.d(TAG, "Error: callback is null. returning");
return;
}
if (domain != NetworkRegistrationInfo.DOMAIN_PS) {
callback.onRequestNetworkRegistrationInfoComplete(
NetworkServiceCallback.RESULT_ERROR_UNSUPPORTED, null);
return;
}
NetworkRegistrationInfo.Builder nriBuilder =
new NetworkRegistrationInfo.Builder();
nriBuilder
.setAvailableServices(
Arrays.asList(NetworkRegistrationInfo.SERVICE_TYPE_DATA))
.setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WLAN)
.setEmergencyOnly(!iwlanNetworkServiceProvider.mIsSubActive)
.setDomain(NetworkRegistrationInfo.DOMAIN_PS);
if (!IwlanNetworkService.isNetworkConnected(
IwlanHelper.isDefaultDataSlot(
mContext, iwlanNetworkServiceProvider.getSlotIndex()),
IwlanHelper.isCrossSimCallingEnabled(
mContext, iwlanNetworkServiceProvider.getSlotIndex()))) {
nriBuilder
.setRegistrationState(
NetworkRegistrationInfo
.REGISTRATION_STATE_NOT_REGISTERED_SEARCHING)
.setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_UNKNOWN);
Log.d(TAG, "reg state REGISTRATION_STATE_NOT_REGISTERED_SEARCHING");
} else {
nriBuilder
.setRegistrationState(
NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
.setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_IWLAN);
Log.d(TAG, "reg state REGISTRATION_STATE_HOME");
}
callback.onRequestNetworkRegistrationInfoComplete(
NetworkServiceCallback.RESULT_SUCCESS, nriBuilder.build());
break;
case EVENT_CREATE_NETWORK_SERVICE_PROVIDER:
iwlanNetworkServiceProvider = (IwlanNetworkServiceProvider) msg.obj;
if (sIwlanNetworkServiceProviders.isEmpty()) {
initCallback();
}
addIwlanNetworkServiceProvider(iwlanNetworkServiceProvider);
break;
case EVENT_REMOVE_NETWORK_SERVICE_PROVIDER:
iwlanNetworkServiceProvider = (IwlanNetworkServiceProvider) msg.obj;
IwlanNetworkServiceProvider nsp =
sIwlanNetworkServiceProviders.remove(
iwlanNetworkServiceProvider.getSlotIndex());
if (nsp == null) {
Log.w(
TAG,
"No NetworkServiceProvider exists for slot "
+ iwlanNetworkServiceProvider.getSlotIndex());
return;
}
if (sIwlanNetworkServiceProviders.isEmpty()) {
deinitCallback();
}
break;
default:
throw new IllegalStateException("Unexpected value: " + msg.what);
}
}
IwlanNetworkServiceHandler(Looper looper) {
super(looper);
}
}
private static final class NetworkRegistrationInfoRequestData {
final int mDomain;
final NetworkServiceCallback mCallback;
final IwlanNetworkServiceProvider mIwlanNetworkServiceProvider;
private NetworkRegistrationInfoRequestData(
int domain, NetworkServiceCallback callback, IwlanNetworkServiceProvider nsp) {
mDomain = domain;
mCallback = callback;
mIwlanNetworkServiceProvider = nsp;
}
}
/**
* Create the instance of {@link NetworkServiceProvider}. Network service provider must override
* this method to facilitate the creation of {@link NetworkServiceProvider} instances. The
* system will call this method after binding the network service for each active SIM slot id.
*
* @param slotIndex SIM slot id the network service associated with.
* @return Network service object. Null if failed to create the provider (e.g. invalid slot
* index)
*/
@Override
public NetworkServiceProvider onCreateNetworkServiceProvider(int slotIndex) {
Log.d(TAG, "onCreateNetworkServiceProvider: slotidx:" + slotIndex);
// TODO: validity check slot index
if (mIwlanNetworkServiceHandler == null) {
initHandler();
}
IwlanNetworkServiceProvider np = new IwlanNetworkServiceProvider(slotIndex, this);
mIwlanNetworkServiceHandler.sendMessage(
mIwlanNetworkServiceHandler.obtainMessage(
EVENT_CREATE_NETWORK_SERVICE_PROVIDER, np));
return np;
}
public static boolean isNetworkConnected(boolean isDds, boolean isCstEnabled) {
if (!isDds && isCstEnabled) {
// Only Non-DDS sub with CST enabled, can use any transport.
return sNetworkConnected;
} else {
// For all other cases, only wifi transport can be used.
return ((sDefaultDataTransport == Transport.WIFI) && sNetworkConnected);
}
}
public static void setNetworkConnected(boolean connected, Transport transport) {
if (connected == sNetworkConnected && transport == sDefaultDataTransport) {
return;
}
if (connected && (transport == IwlanNetworkService.Transport.UNSPECIFIED_NETWORK)) {
return;
}
sNetworkConnected = connected;
sDefaultDataTransport = transport;
for (IwlanNetworkServiceProvider np : sIwlanNetworkServiceProviders.values()) {
np.notifyNetworkRegistrationInfoChanged();
}
}
void addIwlanNetworkServiceProvider(IwlanNetworkServiceProvider np) {
int slotIndex = np.getSlotIndex();
if (sIwlanNetworkServiceProviders.containsKey(slotIndex)) {
throw new IllegalStateException(
"NetworkServiceProvider already exists for slot " + slotIndex);
}
sIwlanNetworkServiceProviders.put(slotIndex, np);
}
public void removeNetworkServiceProvider(IwlanNetworkServiceProvider np) {
mIwlanNetworkServiceHandler.sendMessage(
mIwlanNetworkServiceHandler.obtainMessage(
EVENT_REMOVE_NETWORK_SERVICE_PROVIDER, np));
}
void initCallback() {
// register for default network callback
ConnectivityManager connectivityManager =
mContext.getSystemService(ConnectivityManager.class);
mNetworkMonitorCallback = new IwlanNetworkMonitorCallback();
connectivityManager.registerSystemDefaultNetworkCallback(
mNetworkMonitorCallback, mIwlanNetworkServiceHandler);
Log.d(TAG, "Registered with Connectivity Service");
/* register with subscription manager */
SubscriptionManager subscriptionManager =
mContext.getSystemService(SubscriptionManager.class);
mSubsChangeListener = new IwlanOnSubscriptionsChangedListener();
subscriptionManager.addOnSubscriptionsChangedListener(
new HandlerExecutor(mIwlanNetworkServiceHandler), mSubsChangeListener);
Log.d(TAG, "Registered with Subscription Service");
}
void deinitCallback() {
// deinit network related stuff
ConnectivityManager connectivityManager =
mContext.getSystemService(ConnectivityManager.class);
connectivityManager.unregisterNetworkCallback(mNetworkMonitorCallback);
mNetworkMonitorCallback = null;
// deinit subscription manager related stuff
SubscriptionManager subscriptionManager =
mContext.getSystemService(SubscriptionManager.class);
subscriptionManager.removeOnSubscriptionsChangedListener(mSubsChangeListener);
mSubsChangeListener = null;
if (mIwlanNetworkServiceHandlerThread != null) {
mIwlanNetworkServiceHandlerThread.quit();
mIwlanNetworkServiceHandlerThread = null;
}
mIwlanNetworkServiceHandler = null;
}
Context getContext() {
return getApplicationContext();
}
@VisibleForTesting
void setAppContext(Context appContext) {
mContext = appContext;
}
@VisibleForTesting
IwlanNetworkServiceProvider getNetworkServiceProvider(int slotIndex) {
return sIwlanNetworkServiceProviders.get(slotIndex);
}
@VisibleForTesting
void initHandler() {
mIwlanNetworkServiceHandler = new IwlanNetworkServiceHandler(getLooper());
}
@VisibleForTesting
Looper getLooper() {
mIwlanNetworkServiceHandlerThread = new HandlerThread("IwlanNetworkServiceThread");
mIwlanNetworkServiceHandlerThread.start();
return mIwlanNetworkServiceHandlerThread.getLooper();
}
private static String eventToString(int event) {
switch (event) {
case IwlanEventListener.CROSS_SIM_CALLING_ENABLE_EVENT:
return "CROSS_SIM_CALLING_ENABLE_EVENT";
case IwlanEventListener.CROSS_SIM_CALLING_DISABLE_EVENT:
return "CROSS_SIM_CALLING_DISABLE_EVENT";
case EVENT_NETWORK_REGISTRATION_INFO_REQUEST:
return "EVENT_NETWORK_REGISTRATION_INFO_REQUEST";
case EVENT_CREATE_NETWORK_SERVICE_PROVIDER:
return "EVENT_CREATE_NETWORK_SERVICE_PROVIDER";
case EVENT_REMOVE_NETWORK_SERVICE_PROVIDER:
return "EVENT_REMOVE_NETWORK_SERVICE_PROVIDER";
default:
return "Unknown(" + event + ")";
}
}
@Override
public void onCreate() {
mContext = getApplicationContext();
}
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "IwlanNetworkService onBind");
return super.onBind(intent);
}
}