blob: 48a90ea127f666b8f009fa4df7d052c64f29c6bd [file] [log] [blame]
/*
* Copyright (C) 2019 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.internal.telephony;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.os.Handler;
import android.telephony.SubscriptionManager;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.metrics.TelephonyMetrics;
import com.android.internal.telephony.nano.TelephonyProto.TelephonyEvent;
/**
* This class will validate whether cellular network verified by Connectivity's
* validation process. It listens request on a specific subId, sends a network request
* to Connectivity and listens to its callback or timeout.
*/
public class CellularNetworkValidator {
private static final String LOG_TAG = "NetworkValidator";
// States of validator. Only one validation can happen at once.
// IDLE: no validation going on.
private static final int STATE_IDLE = 0;
// VALIDATING: validation going on.
private static final int STATE_VALIDATING = 1;
// VALIDATED: validation is done and successful.
// Waiting for stopValidation() to release
// validationg NetworkRequest.
private static final int STATE_VALIDATED = 2;
// Singleton instance.
private static CellularNetworkValidator sInstance;
private int mState = STATE_IDLE;
private int mSubId;
private long mTimeoutInMs;
private boolean mReleaseAfterValidation;
private NetworkRequest mNetworkRequest;
private ValidationCallback mValidationCallback;
private Context mContext;
private ConnectivityManager mConnectivityManager;
@VisibleForTesting
public Handler mHandler = new Handler();
@VisibleForTesting
public ConnectivityNetworkCallback mNetworkCallback;
@VisibleForTesting
public Runnable mTimeoutCallback;
/**
* Callback to pass in when starting validation.
*/
public interface ValidationCallback {
/**
* Validation failed, passed or timed out.
*/
void onValidationResult(boolean validated, int subId);
}
/**
* Create instance.
*/
public static CellularNetworkValidator make(Context context) {
if (sInstance != null) {
logd("createCellularNetworkValidator failed. Instance already exists.");
} else {
sInstance = new CellularNetworkValidator(context);
}
return sInstance;
}
/**
* Get instance.
*/
public static CellularNetworkValidator getInstance() {
return sInstance;
}
/**
* Check whether this feature is supported or not.
*/
public boolean isValidationFeatureSupported() {
return PhoneConfigurationManager.getInstance().getCurrentPhoneCapability()
.validationBeforeSwitchSupported;
}
@VisibleForTesting
public CellularNetworkValidator(Context context) {
mContext = context;
mConnectivityManager = (ConnectivityManager)
mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
}
/**
* API to start a validation
*/
public synchronized void validate(int subId, long timeoutInMs,
boolean releaseAfterValidation, ValidationCallback callback) {
// If it's already validating the same subscription, do nothing.
if (subId == mSubId) return;
Phone phone = PhoneFactory.getPhone(SubscriptionManager.getPhoneId(subId));
if (phone == null) {
logd("Failed to start validation. Inactive subId " + subId);
callback.onValidationResult(false, subId);
return;
}
if (isValidating()) {
stopValidation();
}
mState = STATE_VALIDATING;
mSubId = subId;
mTimeoutInMs = timeoutInMs;
mValidationCallback = callback;
mReleaseAfterValidation = releaseAfterValidation;
mNetworkRequest = createNetworkRequest();
logd("Start validating subId " + mSubId + " mTimeoutInMs " + mTimeoutInMs
+ " mReleaseAfterValidation " + mReleaseAfterValidation);
mNetworkCallback = new ConnectivityNetworkCallback(subId);
mConnectivityManager.requestNetwork(mNetworkRequest, mNetworkCallback, mHandler);
mTimeoutCallback = () -> {
logd("timeout on subId " + subId + " validation.");
reportValidationResult(false, subId);
};
mHandler.postDelayed(mTimeoutCallback, mTimeoutInMs);
}
private void removeTimeoutCallback() {
// Remove timeout callback.
if (mTimeoutCallback != null) {
mHandler.removeCallbacks(mTimeoutCallback);
mTimeoutCallback = null;
}
}
/**
* API to stop the current validation.
*/
public synchronized void stopValidation() {
if (!isValidating()) {
logd("No need to stop validation.");
} else {
mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
mState = STATE_IDLE;
}
removeTimeoutCallback();
mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
}
/**
* Return which subscription is under validating.
*/
public synchronized int getSubIdInValidation() {
return mSubId;
}
/**
* Return whether there's an ongoing validation.
*/
public synchronized boolean isValidating() {
return mState != STATE_IDLE;
}
private NetworkRequest createNetworkRequest() {
return new NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.setNetworkSpecifier(String.valueOf(mSubId))
.build();
}
private synchronized void reportValidationResult(boolean passed, int subId) {
// If the validation result is not for current subId, do nothing.
if (mSubId != subId) return;
removeTimeoutCallback();
// Deal with the result only when state is still VALIDATING. This is to avoid
// receiving multiple callbacks in queue.
if (mState == STATE_VALIDATING) {
mValidationCallback.onValidationResult(passed, mSubId);
if (!mReleaseAfterValidation && passed) {
mState = STATE_VALIDATED;
} else {
mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
mState = STATE_IDLE;
}
TelephonyMetrics.getInstance().writeNetworkValidate(passed
? TelephonyEvent.NetworkValidationState.NETWORK_VALIDATION_STATE_PASSED
: TelephonyEvent.NetworkValidationState.NETWORK_VALIDATION_STATE_FAILED);
}
mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
}
@VisibleForTesting
public class ConnectivityNetworkCallback extends ConnectivityManager.NetworkCallback {
private final int mSubId;
ConnectivityNetworkCallback(int subId) {
mSubId = subId;
}
/**
* ConnectivityManager.NetworkCallback implementation
*/
@Override
public void onAvailable(Network network) {
logd("network onAvailable " + network);
if (ConnectivityNetworkCallback.this.mSubId == CellularNetworkValidator.this.mSubId) {
TelephonyMetrics.getInstance().writeNetworkValidate(
TelephonyEvent.NetworkValidationState.NETWORK_VALIDATION_STATE_AVAILABLE);
}
}
@Override
public void onLosing(Network network, int maxMsToLive) {
logd("network onLosing " + network + " maxMsToLive " + maxMsToLive);
reportValidationResult(false, ConnectivityNetworkCallback.this.mSubId);
}
@Override
public void onLost(Network network) {
logd("network onLost " + network);
reportValidationResult(false, ConnectivityNetworkCallback.this.mSubId);
}
@Override
public void onUnavailable() {
logd("onUnavailable");
reportValidationResult(false, ConnectivityNetworkCallback.this.mSubId);
}
@Override
public void onCapabilitiesChanged(Network network,
NetworkCapabilities networkCapabilities) {
if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) {
logd("onValidated");
reportValidationResult(true, ConnectivityNetworkCallback.this.mSubId);
}
}
}
private static void logd(String log) {
Log.d(LOG_TAG, log);
}
}