blob: 603bf8cc68121ad3956ec67750b0fd979739d21c [file] [log] [blame]
/*
* Copyright (C) 2013 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 java.util.ArrayList;
import java.util.Random;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncResult;
import android.os.Handler;
import android.os.Message;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.telephony.RadioAccessFamily;
import android.telephony.Rlog;
import android.telephony.TelephonyManager;
import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneBase;
import com.android.internal.telephony.PhoneProxy;
import com.android.internal.telephony.dataconnection.DctController;
import com.android.internal.telephony.RadioCapability;
import com.android.internal.telephony.uicc.UiccController;
import com.android.internal.telephony.TelephonyIntents;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.concurrent.atomic.AtomicInteger;
public class ProxyController {
static final String LOG_TAG = "ProxyController";
private static final int EVENT_NOTIFICATION_RC_CHANGED = 1;
private static final int EVENT_START_RC_RESPONSE = 2;
private static final int EVENT_APPLY_RC_RESPONSE = 3;
private static final int EVENT_FINISH_RC_RESPONSE = 4;
private static final int SET_RC_STATUS_IDLE = 0;
private static final int SET_RC_STATUS_STARTING = 1;
private static final int SET_RC_STATUS_STARTED = 2;
private static final int SET_RC_STATUS_APPLYING = 3;
private static final int SET_RC_STATUS_SUCCESS = 4;
private static final int SET_RC_STATUS_FAIL = 5;
// The entire transaction must complete within this amount of time
// or a FINISH will be issued to each Logical Modem with the old
// Radio Access Family.
private static final int SET_RC_TIMEOUT_WAITING_MSEC = (45 * 1000);
//***** Class Variables
private static ProxyController sProxyController;
private PhoneProxy[] mProxyPhones;
private UiccController mUiccController;
private CommandsInterface[] mCi;
private Context mContext;
private DctController mDctController;
//UiccPhoneBookController to use proper IccPhoneBookInterfaceManagerProxy object
private UiccPhoneBookController mUiccPhoneBookController;
//PhoneSubInfoController to use proper PhoneSubInfoProxy object
private PhoneSubInfoController mPhoneSubInfoController;
//UiccSmsController to use proper IccSmsInterfaceManager object
private UiccSmsController mUiccSmsController;
WakeLock mWakeLock;
// record each phone's set radio capability status
private int[] mSetRadioAccessFamilyStatus;
private int mRadioAccessFamilyStatusCounter;
private String[] mLogicalModemIds;
// Allows the generation of unique Id's for radio capability request session id
private AtomicInteger mUniqueIdGenerator = new AtomicInteger(new Random().nextInt());
// on-going radio capability request session id
private int mRadioCapabilitySessionId;
// Record new and old Radio Access Family (raf) configuration.
// The old raf configuration is used to restore each logical modem raf when FINISH is
// issued if any requests fail.
private int[] mNewRadioAccessFamily;
private int[] mOldRadioAccessFamily;
// runnable for radio capability request timeout handling
RadioCapabilityRunnable mSetRadioCapabilityRunnable;
//***** Class Methods
public static ProxyController getInstance(Context context, PhoneProxy[] phoneProxy,
UiccController uiccController, CommandsInterface[] ci) {
if (sProxyController == null) {
sProxyController = new ProxyController(context, phoneProxy, uiccController, ci);
}
return sProxyController;
}
public static ProxyController getInstance() {
return sProxyController;
}
private ProxyController(Context context, PhoneProxy[] phoneProxy, UiccController uiccController,
CommandsInterface[] ci) {
logd("Constructor - Enter");
mContext = context;
mProxyPhones = phoneProxy;
mUiccController = uiccController;
mCi = ci;
mDctController = DctController.makeDctController(phoneProxy);
mUiccPhoneBookController = new UiccPhoneBookController(mProxyPhones);
mPhoneSubInfoController = new PhoneSubInfoController(mProxyPhones);
mUiccSmsController = new UiccSmsController(mProxyPhones);
mSetRadioAccessFamilyStatus = new int[mProxyPhones.length];
mNewRadioAccessFamily = new int[mProxyPhones.length];
mOldRadioAccessFamily = new int[mProxyPhones.length];
mLogicalModemIds = new String[mProxyPhones.length];
// TODO Get logical modem ids assume its just the phoneId as a string for now
for (int i = 0; i < mProxyPhones.length; i++) {
mLogicalModemIds[i] = Integer.toString(i);
}
mSetRadioCapabilityRunnable = new RadioCapabilityRunnable();
// wake lock for set radio capability
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG);
mWakeLock.setReferenceCounted(false);
// Clear to be sure we're in the initial state
clearTransaction();
for (int i = 0; i < mProxyPhones.length; i++) {
mProxyPhones[i].registerForRadioCapabilityChanged(
mHandler, EVENT_NOTIFICATION_RC_CHANGED, null);
}
logd("Constructor - Exit");
}
public void updateDataConnectionTracker(int sub) {
mProxyPhones[sub].updateDataConnectionTracker();
}
public void enableDataConnectivity(int sub) {
mProxyPhones[sub].setInternalDataEnabled(true);
}
public void disableDataConnectivity(int sub,
Message dataCleanedUpMsg) {
mProxyPhones[sub].setInternalDataEnabled(false, dataCleanedUpMsg);
}
public void updateCurrentCarrierInProvider(int sub) {
mProxyPhones[sub].updateCurrentCarrierInProvider();
}
public void registerForAllDataDisconnected(int subId, Handler h, int what, Object obj) {
int phoneId = SubscriptionController.getInstance().getPhoneId(subId);
if (phoneId >= 0 && phoneId < TelephonyManager.getDefault().getPhoneCount()) {
mProxyPhones[phoneId].registerForAllDataDisconnected(h, what, obj);
}
}
public void unregisterForAllDataDisconnected(int subId, Handler h) {
int phoneId = SubscriptionController.getInstance().getPhoneId(subId);
if (phoneId >= 0 && phoneId < TelephonyManager.getDefault().getPhoneCount()) {
mProxyPhones[phoneId].unregisterForAllDataDisconnected(h);
}
}
public boolean isDataDisconnected(int subId) {
int phoneId = SubscriptionController.getInstance().getPhoneId(subId);
if (phoneId >= 0 && phoneId < TelephonyManager.getDefault().getPhoneCount()) {
Phone activePhone = mProxyPhones[phoneId].getActivePhone();
return ((PhoneBase) activePhone).mDcTracker.isDisconnected();
} else {
return false;
}
}
/**
* Get phone radio type and access technology.
*
* @param phoneId which phone you want to get
* @return phone radio type and access technology for input phone ID
*/
public int getRadioAccessFamily(int phoneId) {
if (phoneId >= mProxyPhones.length) {
return RadioAccessFamily.RAF_UNKNOWN;
} else {
return mProxyPhones[phoneId].getRadioAccessFamily();
}
}
/**
* Set phone radio type and access technology for each phone.
*
* @param rafs an RadioAccessFamily array to indicate all phone's
* new radio access family. The length of RadioAccessFamily
* must equal to phone count.
*/
public void setRadioCapability(RadioAccessFamily[] rafs) {
if (rafs.length != mProxyPhones.length) {
throw new RuntimeException("Length of input rafs must equal to total phone count");
}
// Check if there is any ongoing transaction and throw an exception if there
// is one as this is a programming error.
synchronized (mSetRadioAccessFamilyStatus) {
for (int i = 0; i < mProxyPhones.length; i++) {
logd("setRadioCapability: mSetRadioAccessFamilyStatus[" + i + "]="
+ mSetRadioAccessFamilyStatus[i]);
if (mSetRadioAccessFamilyStatus[i] != SET_RC_STATUS_IDLE) {
throw new RuntimeException("setRadioCapability: Phone" + i + " is not idle");
}
}
}
// Clear to be sure we're in the initial state
clearTransaction();
// A new sessionId for this transaction
mRadioCapabilitySessionId = mUniqueIdGenerator.getAndIncrement();
// Keep a wake lock until we finish radio capability changed
mWakeLock.acquire();
// Start timer to make sure all phones respond within a specific time interval.
// Will send FINISH if a timeout occurs.
mSetRadioCapabilityRunnable.setTimeoutState(mRadioCapabilitySessionId);
mHandler.postDelayed(mSetRadioCapabilityRunnable, SET_RC_TIMEOUT_WAITING_MSEC);
synchronized (mSetRadioAccessFamilyStatus) {
logd("setRadioCapability: new request session id=" + mRadioCapabilitySessionId);
mRadioAccessFamilyStatusCounter = rafs.length;
for (int i = 0; i < rafs.length; i++) {
int phoneId = rafs[i].getPhoneId();
logd("setRadioCapability: phoneId=" + phoneId + " status=STARTING");
mSetRadioAccessFamilyStatus[phoneId] = SET_RC_STATUS_STARTING;
mOldRadioAccessFamily[phoneId] = mProxyPhones[phoneId].getRadioAccessFamily();
mNewRadioAccessFamily[phoneId] = rafs[i].getRadioAccessFamily();
logd("setRadioCapability: mOldRadioAccessFamily[" + phoneId + "]="
+ mOldRadioAccessFamily[phoneId]);
logd("setRadioCapability: mNewRadioAccessFamily[" + phoneId + "]="
+ mNewRadioAccessFamily[phoneId]);
sendRadioCapabilityRequest(
phoneId,
mRadioCapabilitySessionId,
RadioCapability.RC_PHASE_START,
mOldRadioAccessFamily[phoneId],
mLogicalModemIds[phoneId],
RadioCapability.RC_STATUS_NONE,
EVENT_START_RC_RESPONSE);
}
}
}
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
logd("handleMessage msg.what=" + msg.what);
switch (msg.what) {
case EVENT_START_RC_RESPONSE:
onStartRadioCapabilityResponse(msg);
break;
case EVENT_APPLY_RC_RESPONSE:
onApplyRadioCapabilityResponse(msg);
break;
case EVENT_NOTIFICATION_RC_CHANGED:
onNotificationRadioCapabilityChanged(msg);
break;
case EVENT_FINISH_RC_RESPONSE:
onFinishRadioCapabilityResponse(msg);
break;
default:
break;
}
}
};
/**
* Handle START response
* @param msg obj field isa RadioCapability
*/
private void onStartRadioCapabilityResponse(Message msg) {
synchronized (mSetRadioAccessFamilyStatus) {
RadioCapability rc = (RadioCapability) ((AsyncResult) msg.obj).result;
if ((rc == null) || (rc.getSession() != mRadioCapabilitySessionId)) {
logd("onStartRadioCapabilityResponse: Ignore session=" + mRadioCapabilitySessionId
+ " rc=" + rc);
return;
}
mRadioAccessFamilyStatusCounter--;
int id = rc.getPhoneId();
if (((AsyncResult) msg.obj).exception != null) {
logd("onStartRadioCapabilityResponse: Error response session=" + rc.getSession());
logd("onStartRadioCapabilityResponse: phoneId=" + id + " status=FAIL");
mSetRadioAccessFamilyStatus[id] = SET_RC_STATUS_FAIL;
} else {
logd("onStartRadioCapabilityResponse: phoneId=" + id + " status=STARTED");
mSetRadioAccessFamilyStatus[id] = SET_RC_STATUS_STARTED;
}
if (mRadioAccessFamilyStatusCounter == 0) {
resetRadioAccessFamilyStatusCounter();
boolean success = checkAllRadioCapabilitySuccess();
logd("onStartRadioCapabilityResponse: success=" + success);
if (!success) {
issueFinish(RadioCapability.RC_STATUS_FAIL,
mRadioCapabilitySessionId);
} else {
// All logical modem accepted the new radio access family, issue the APPLY
for (int i = 0; i < mProxyPhones.length; i++) {
sendRadioCapabilityRequest(
i,
mRadioCapabilitySessionId,
RadioCapability.RC_PHASE_APPLY,
mNewRadioAccessFamily[i],
mLogicalModemIds[i],
RadioCapability.RC_STATUS_NONE,
EVENT_APPLY_RC_RESPONSE);
logd("onStartRadioCapabilityResponse: phoneId=" + i + " status=APPLYING");
mSetRadioAccessFamilyStatus[i] = SET_RC_STATUS_APPLYING;
}
}
}
}
}
/**
* Handle APPLY response
* @param msg obj field isa RadioCapability
*/
private void onApplyRadioCapabilityResponse(Message msg) {
RadioCapability rc = (RadioCapability) ((AsyncResult) msg.obj).result;
if ((rc == null) || (rc.getSession() != mRadioCapabilitySessionId)) {
logd("onApplyRadioCapabilityResponse: Ignore session=" + mRadioCapabilitySessionId
+ " rc=" + rc);
return;
}
logd("onApplyRadioCapabilityResponse: rc=" + rc);
if (((AsyncResult) msg.obj).exception != null) {
synchronized (mSetRadioAccessFamilyStatus) {
logd("onApplyRadioCapabilityResponse: Error response session=" + rc.getSession());
int id = rc.getPhoneId();
logd("onApplyRadioCapabilityResponse: phoneId=" + id + " status=FAIL");
mSetRadioAccessFamilyStatus[id] = SET_RC_STATUS_FAIL;
}
} else {
logd("onApplyRadioCapabilityResponse: Valid start expecting notification rc=" + rc);
}
}
/**
* Handle the notification unsolicited response associated with the APPLY
* @param msg obj field isa RadioCapability
*/
private void onNotificationRadioCapabilityChanged(Message msg) {
RadioCapability rc = (RadioCapability) ((AsyncResult) msg.obj).result;
if ((rc == null) || (rc.getSession() != mRadioCapabilitySessionId)) {
logd("onNotificationRadioCapabilityChanged: Ignore session=" + mRadioCapabilitySessionId
+ " rc=" + rc);
return;
}
synchronized (mSetRadioAccessFamilyStatus) {
logd("onNotificationRadioCapabilityChanged: rc=" + rc);
// skip the overdue response by checking sessionId
if (rc.getSession() != mRadioCapabilitySessionId) {
logd("onNotificationRadioCapabilityChanged: Ignore session="
+ mRadioCapabilitySessionId + " rc=" + rc);
return;
}
int id = rc.getPhoneId();
if ((((AsyncResult) msg.obj).exception != null) ||
(rc.getStatus() == RadioCapability.RC_STATUS_FAIL)) {
logd("onNotificationRadioCapabilityChanged: phoneId=" + id + " status=FAIL");
mSetRadioAccessFamilyStatus[id] = SET_RC_STATUS_FAIL;
} else {
logd("onNotificationRadioCapabilityChanged: phoneId=" + id + " status=SUCCESS");
mSetRadioAccessFamilyStatus[id] = SET_RC_STATUS_SUCCESS;
}
mRadioAccessFamilyStatusCounter--;
if (mRadioAccessFamilyStatusCounter == 0) {
logd("onNotificationRadioCapabilityChanged: removing callback from handler");
mHandler.removeCallbacks(mSetRadioCapabilityRunnable);
resetRadioAccessFamilyStatusCounter();
boolean success = checkAllRadioCapabilitySuccess();
logd("onNotificationRadioCapabilityChanged: APPLY URC success=" + success);
int status;
if (success) {
status = RadioCapability.RC_STATUS_SUCCESS;
} else {
status = RadioCapability.RC_STATUS_FAIL;
}
issueFinish(status, mRadioCapabilitySessionId);
}
}
}
/**
* Handle the FINISH Phase response
* @param msg obj field isa RadioCapability
*/
void onFinishRadioCapabilityResponse(Message msg) {
RadioCapability rc = (RadioCapability) ((AsyncResult) msg.obj).result;
if ((rc == null) || (rc.getSession() != mRadioCapabilitySessionId)) {
logd("onFinishRadioCapabilityResponse: Ignore session=" + mRadioCapabilitySessionId
+ " rc=" + rc);
return;
}
synchronized (mSetRadioAccessFamilyStatus) {
logd(" onFinishRadioCapabilityResponse mRadioAccessFamilyStatusCounter="
+ mRadioAccessFamilyStatusCounter);
mRadioAccessFamilyStatusCounter--;
if (mRadioAccessFamilyStatusCounter == 0) {
completeRadioCapabilityTransaction();
}
}
}
private void issueFinish(int status, int sessionId) {
// Issue FINISH
synchronized(mSetRadioAccessFamilyStatus) {
for (int i = 0; i < mProxyPhones.length; i++) {
if (mSetRadioAccessFamilyStatus[i] != SET_RC_STATUS_FAIL) {
logd("issueFinish: phoneId=" + i + " sessionId=" + sessionId
+ " status=" + status);
sendRadioCapabilityRequest(
i,
sessionId,
RadioCapability.RC_PHASE_FINISH,
mOldRadioAccessFamily[i],
mLogicalModemIds[i],
status,
EVENT_FINISH_RC_RESPONSE);
if (status == RadioCapability.RC_STATUS_FAIL) {
logd("issueFinish: phoneId: " + i + " status: FAIL");
// At least one failed, mark them all failed.
mSetRadioAccessFamilyStatus[i] = SET_RC_STATUS_FAIL;
}
} else {
logd("issueFinish: Ignore already FAIL, Phone" + i + " sessionId=" + sessionId
+ " status=" + status);
}
}
}
}
private void completeRadioCapabilityTransaction() {
// Create the intent to broadcast
Intent intent;
boolean success = checkAllRadioCapabilitySuccess();
logd("onFinishRadioCapabilityResponse: success=" + success);
if (success) {
ArrayList<RadioAccessFamily> phoneRAFList = new ArrayList<RadioAccessFamily>();
for (int i = 0; i < mProxyPhones.length; i++) {
int raf = mProxyPhones[i].getRadioAccessFamily();
logd("radioAccessFamily[" + i + "]=" + raf);
RadioAccessFamily phoneRC = new RadioAccessFamily(i, raf);
phoneRAFList.add(phoneRC);
}
intent = new Intent(TelephonyIntents.ACTION_SET_RADIO_CAPABILITY_DONE);
intent.putParcelableArrayListExtra(TelephonyIntents.EXTRA_RADIO_ACCESS_FAMILY,
phoneRAFList);
} else {
intent = new Intent(TelephonyIntents.ACTION_SET_RADIO_CAPABILITY_FAILED);
}
// Reinitialize
clearTransaction();
// Broadcast that we're done
mContext.sendBroadcast(intent);
}
// Clear this transaction
private void clearTransaction() {
logd("clearTransaction");
synchronized(mSetRadioAccessFamilyStatus) {
for (int i = 0; i < mProxyPhones.length; i++) {
logd("clearTransaction: phoneId=" + i + " status=IDLE");
mSetRadioAccessFamilyStatus[i] = SET_RC_STATUS_IDLE;
mOldRadioAccessFamily[i] = 0;
mNewRadioAccessFamily[i] = 0;
}
if (mWakeLock.isHeld()) {
mWakeLock.release();
}
}
}
private boolean checkAllRadioCapabilitySuccess() {
synchronized(mSetRadioAccessFamilyStatus) {
for (int i = 0; i < mProxyPhones.length; i++) {
if (mSetRadioAccessFamilyStatus[i] == SET_RC_STATUS_FAIL) {
return false;
}
}
return true;
}
}
private void resetRadioAccessFamilyStatusCounter() {
mRadioAccessFamilyStatusCounter = mProxyPhones.length;
}
private void sendRadioCapabilityRequest(int phoneId, int sessionId, int rcPhase,
int radioFamily, String logicalModemId, int status, int eventId) {
RadioCapability requestRC = new RadioCapability(
phoneId, sessionId, rcPhase, radioFamily, logicalModemId, status);
mProxyPhones[phoneId].setRadioCapability(
requestRC, mHandler.obtainMessage(eventId));
}
/**
* RadioCapabilityRunnable is used to check
* if radio capability request's response is out of date.
* <p>
* Note that the setRadioCapability will be stopped directly and send FINISH
* with fail status to all logical modems. and send out fail intent
*
*/
private class RadioCapabilityRunnable implements Runnable {
private int mSessionId;
public RadioCapabilityRunnable() {
}
public void setTimeoutState(int sessionId) {
mSessionId = sessionId;
}
@Override
public void run() {
if (mSessionId != mRadioCapabilitySessionId) {
logd("RadioCapability timeout: Ignore mSessionId=" + mSessionId
+ "!= mRadioCapabilitySessionId=" + mRadioCapabilitySessionId);
return;
}
synchronized(mSetRadioAccessFamilyStatus) {
for (int i = 0; i < mProxyPhones.length; i++) {
logd("RadioCapability timeout: mSetRadioAccessFamilyStatus[" + i + "]=" +
mSetRadioAccessFamilyStatus[i]);
}
// Increment the sessionId as we are completing the transaction below
// so we don't want it completed when the FINISH phase is done.
int uniqueDifferentId = mUniqueIdGenerator.getAndIncrement();
// send FINISH request with fail status and then uniqueDifferentId
issueFinish(RadioCapability.RC_STATUS_FAIL,
uniqueDifferentId);
completeRadioCapabilityTransaction();
}
}
}
private void logd(String string) {
Rlog.d(LOG_TAG, string);
}
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
try {
mDctController.dump(fd, pw, args);
} catch (Exception e) {
e.printStackTrace();
}
}
}