blob: a2d4e5bc8e47d1ba6d699940da4190b9213e815c [file] [log] [blame]
/*
* Copyright (C) 2018 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 static com.android.internal.telephony.RILConstants.RADIO_NOT_AVAILABLE;
import static com.android.internal.telephony.RILConstants.REQUEST_NOT_SUPPORTED;
import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_PHONE_CAPABILITY;
import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_SLOT_STATUS;
import static com.android.internal.telephony.RILConstants
.RIL_REQUEST_SET_LOGICAL_TO_PHYSICAL_SLOT_MAPPING;
import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_PREFERRED_DATA_MODEM;
import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SWITCH_DUAL_SIM_CONFIG;
import android.content.Context;
import android.hardware.radio.V1_0.RadioResponseInfo;
import android.hardware.radio.V1_0.RadioResponseType;
import android.hardware.radio.config.V1_0.IRadioConfig;
import android.hardware.radio.config.V1_1.ModemsConfig;
import android.net.ConnectivityManager;
import android.os.AsyncResult;
import android.os.Handler;
import android.os.HwBinder;
import android.os.Message;
import android.os.Registrant;
import android.os.RemoteException;
import android.os.WorkSource;
import android.telephony.Rlog;
import android.util.SparseArray;
import com.android.internal.telephony.uicc.IccSlotStatus;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.NoSuchElementException;
import java.util.concurrent.atomic.AtomicLong;
/**
* This class provides wrapper APIs for IRadioConfig interface.
*/
public class RadioConfig extends Handler {
private static final String TAG = "RadioConfig";
private static final boolean DBG = true;
private static final boolean VDBG = false; //STOPSHIP if true
private static final int EVENT_SERVICE_DEAD = 1;
private static final HalVersion RADIO_CONFIG_HAL_VERSION_UNKNOWN = new HalVersion(-1, -1);
private static final HalVersion RADIO_CONFIG_HAL_VERSION_1_0 = new HalVersion(1, 0);
private static final HalVersion RADIO_CONFIG_HAL_VERSION_1_1 = new HalVersion(1, 1);
private final boolean mIsMobileNetworkSupported;
private volatile IRadioConfig mRadioConfigProxy = null;
// IRadioConfig version
private HalVersion mRadioConfigVersion = RADIO_CONFIG_HAL_VERSION_UNKNOWN;
private final ServiceDeathRecipient mServiceDeathRecipient;
private final AtomicLong mRadioConfigProxyCookie = new AtomicLong(0);
private final RadioConfigResponse mRadioConfigResponse;
private final RadioConfigIndication mRadioConfigIndication;
private final SparseArray<RILRequest> mRequestList = new SparseArray<RILRequest>();
/* default work source which will blame phone process */
private final WorkSource mDefaultWorkSource;
private static RadioConfig sRadioConfig;
protected Registrant mSimSlotStatusRegistrant;
final class ServiceDeathRecipient implements HwBinder.DeathRecipient {
@Override
public void serviceDied(long cookie) {
// Deal with service going away
logd("serviceDied");
sendMessage(obtainMessage(EVENT_SERVICE_DEAD, cookie));
}
}
private RadioConfig(Context context) {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(
Context.CONNECTIVITY_SERVICE);
mIsMobileNetworkSupported = cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
mRadioConfigResponse = new RadioConfigResponse(this);
mRadioConfigIndication = new RadioConfigIndication(this);
mServiceDeathRecipient = new ServiceDeathRecipient();
mDefaultWorkSource = new WorkSource(context.getApplicationInfo().uid,
context.getPackageName());
}
/**
* Returns the singleton static instance of RadioConfig
*/
public static RadioConfig getInstance(Context context) {
if (sRadioConfig == null) {
sRadioConfig = new RadioConfig(context);
}
return sRadioConfig;
}
@Override
public void handleMessage(Message message) {
switch (message.what) {
case EVENT_SERVICE_DEAD:
logd("handleMessage: EVENT_SERVICE_DEAD cookie = " + message.obj
+ " mRadioConfigProxyCookie = " + mRadioConfigProxyCookie.get());
if ((long) message.obj == mRadioConfigProxyCookie.get()) {
resetProxyAndRequestList("EVENT_SERVICE_DEAD", null);
}
break;
}
}
/**
* Release each request in mRequestList then clear the list
* @param error is the RIL_Errno sent back
* @param loggable true means to print all requests in mRequestList
*/
private void clearRequestList(int error, boolean loggable) {
RILRequest rr;
synchronized (mRequestList) {
int count = mRequestList.size();
if (DBG && loggable) {
logd("clearRequestList: mRequestList=" + count);
}
for (int i = 0; i < count; i++) {
rr = mRequestList.valueAt(i);
if (DBG && loggable) {
logd(i + ": [" + rr.mSerial + "] " + requestToString(rr.mRequest));
}
rr.onError(error, null);
rr.release();
}
mRequestList.clear();
}
}
private void resetProxyAndRequestList(String caller, Exception e) {
loge(caller + ": " + e);
mRadioConfigProxy = null;
// increment the cookie so that death notification can be ignored
mRadioConfigProxyCookie.incrementAndGet();
RILRequest.resetSerial();
// Clear request list on close
clearRequestList(RADIO_NOT_AVAILABLE, false);
getRadioConfigProxy(null);
}
/** Returns a {@link IRadioConfig} instance or null if the service is not available. */
public IRadioConfig getRadioConfigProxy(Message result) {
if (!mIsMobileNetworkSupported) {
if (VDBG) logd("getRadioConfigProxy: Not calling getService(): wifi-only");
if (result != null) {
AsyncResult.forMessage(result, null,
CommandException.fromRilErrno(RADIO_NOT_AVAILABLE));
result.sendToTarget();
}
return null;
}
if (mRadioConfigProxy != null) {
return mRadioConfigProxy;
}
updateRadioConfigProxy();
if (mRadioConfigProxy == null) {
if (result != null) {
AsyncResult.forMessage(result, null,
CommandException.fromRilErrno(RADIO_NOT_AVAILABLE));
result.sendToTarget();
}
}
return mRadioConfigProxy;
}
private void updateRadioConfigProxy() {
try {
// Try to get service from different versions.
try {
mRadioConfigProxy = android.hardware.radio.config.V1_1.IRadioConfig.getService(
true);
mRadioConfigVersion = RADIO_CONFIG_HAL_VERSION_1_1;
} catch (NoSuchElementException e) {
}
if (mRadioConfigProxy == null) {
try {
mRadioConfigProxy = android.hardware.radio.config.V1_0
.IRadioConfig.getService(true);
mRadioConfigVersion = RADIO_CONFIG_HAL_VERSION_1_0;
} catch (NoSuchElementException e) {
}
}
if (mRadioConfigProxy == null) {
loge("getRadioConfigProxy: mRadioConfigProxy == null");
return;
}
// Link to death recipient and set response. If fails, set proxy to null and return.
mRadioConfigProxy.linkToDeath(mServiceDeathRecipient,
mRadioConfigProxyCookie.incrementAndGet());
mRadioConfigProxy.setResponseFunctions(mRadioConfigResponse,
mRadioConfigIndication);
} catch (RemoteException | RuntimeException e) {
mRadioConfigProxy = null;
loge("getRadioConfigProxy: RadioConfigProxy setResponseFunctions: " + e);
return;
}
}
private RILRequest obtainRequest(int request, Message result, WorkSource workSource) {
RILRequest rr = RILRequest.obtain(request, result, workSource);
synchronized (mRequestList) {
mRequestList.append(rr.mSerial, rr);
}
return rr;
}
private RILRequest findAndRemoveRequestFromList(int serial) {
RILRequest rr;
synchronized (mRequestList) {
rr = mRequestList.get(serial);
if (rr != null) {
mRequestList.remove(serial);
}
}
return rr;
}
/**
* This is a helper function to be called when a RadioConfigResponse callback is called.
* It finds and returns RILRequest corresponding to the response if one is found.
* @param responseInfo RadioResponseInfo received in response callback
* @return RILRequest corresponding to the response
*/
public RILRequest processResponse(RadioResponseInfo responseInfo) {
int serial = responseInfo.serial;
int error = responseInfo.error;
int type = responseInfo.type;
if (type != RadioResponseType.SOLICITED) {
loge("processResponse: Unexpected response type " + type);
}
RILRequest rr = findAndRemoveRequestFromList(serial);
if (rr == null) {
loge("processResponse: Unexpected response! serial: " + serial + " error: " + error);
return null;
}
return rr;
}
/**
* Wrapper function for IRadioConfig.getSimSlotsStatus().
*/
public void getSimSlotsStatus(Message result) {
IRadioConfig radioConfigProxy = getRadioConfigProxy(result);
if (radioConfigProxy != null) {
RILRequest rr = obtainRequest(RIL_REQUEST_GET_SLOT_STATUS, result, mDefaultWorkSource);
if (DBG) {
logd(rr.serialString() + "> " + requestToString(rr.mRequest));
}
try {
radioConfigProxy.getSimSlotsStatus(rr.mSerial);
} catch (RemoteException | RuntimeException e) {
resetProxyAndRequestList("getSimSlotsStatus", e);
}
}
}
/**
* Wrapper function for IRadioConfig.setPreferredDataModem(int modemId).
*/
public void setPreferredDataModem(int modemId, Message result) {
if (!isSetPreferredDataCommandSupported()) {
if (result != null) {
AsyncResult.forMessage(result, null,
CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
result.sendToTarget();
}
return;
}
RILRequest rr = obtainRequest(RIL_REQUEST_SET_PREFERRED_DATA_MODEM,
result, mDefaultWorkSource);
if (DBG) {
logd(rr.serialString() + "> " + requestToString(rr.mRequest));
}
try {
((android.hardware.radio.config.V1_1.IRadioConfig) mRadioConfigProxy)
.setPreferredDataModem(rr.mSerial, (byte) modemId);
} catch (RemoteException | RuntimeException e) {
resetProxyAndRequestList("setPreferredDataModem", e);
}
}
/**
* Wrapper function for IRadioConfig.getPhoneCapability().
*/
public void getPhoneCapability(Message result) {
IRadioConfig radioConfigProxy = getRadioConfigProxy(null);
if (radioConfigProxy == null || mRadioConfigVersion.less(RADIO_CONFIG_HAL_VERSION_1_1)) {
if (result != null) {
AsyncResult.forMessage(result, null,
CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
result.sendToTarget();
}
return;
}
RILRequest rr = obtainRequest(RIL_REQUEST_GET_PHONE_CAPABILITY, result, mDefaultWorkSource);
if (DBG) {
logd(rr.serialString() + "> " + requestToString(rr.mRequest));
}
try {
((android.hardware.radio.config.V1_1.IRadioConfig) mRadioConfigProxy)
.getPhoneCapability(rr.mSerial);
} catch (RemoteException | RuntimeException e) {
resetProxyAndRequestList("getPhoneCapability", e);
}
}
/**
* @return whether current radio config version supports SET_PREFERRED_DATA_MODEM command.
* If yes, we'll use RIL_REQUEST_SET_PREFERRED_DATA_MODEM to indicate which modem is preferred.
* If not, we shall use RIL_REQUEST_ALLOW_DATA for on-demand PS attach / detach.
* See PhoneSwitcher for more details.
*/
public boolean isSetPreferredDataCommandSupported() {
IRadioConfig radioConfigProxy = getRadioConfigProxy(null);
return radioConfigProxy != null && mRadioConfigVersion
.greaterOrEqual(RADIO_CONFIG_HAL_VERSION_1_1);
}
/**
* Wrapper function for IRadioConfig.setSimSlotsMapping(int32_t serial, vec<uint32_t> slotMap).
*/
public void setSimSlotsMapping(int[] physicalSlots, Message result) {
IRadioConfig radioConfigProxy = getRadioConfigProxy(result);
if (radioConfigProxy != null) {
RILRequest rr = obtainRequest(RIL_REQUEST_SET_LOGICAL_TO_PHYSICAL_SLOT_MAPPING, result,
mDefaultWorkSource);
if (DBG) {
logd(rr.serialString() + "> " + requestToString(rr.mRequest)
+ " " + Arrays.toString(physicalSlots));
}
try {
radioConfigProxy.setSimSlotsMapping(rr.mSerial,
primitiveArrayToArrayList(physicalSlots));
} catch (RemoteException | RuntimeException e) {
resetProxyAndRequestList("setSimSlotsMapping", e);
}
}
}
private static ArrayList<Integer> primitiveArrayToArrayList(int[] arr) {
ArrayList<Integer> arrayList = new ArrayList<>(arr.length);
for (int i : arr) {
arrayList.add(i);
}
return arrayList;
}
static String requestToString(int request) {
switch (request) {
case RIL_REQUEST_GET_PHONE_CAPABILITY:
return "GET_PHONE_CAPABILITY";
case RIL_REQUEST_GET_SLOT_STATUS:
return "GET_SLOT_STATUS";
case RIL_REQUEST_SET_LOGICAL_TO_PHYSICAL_SLOT_MAPPING:
return "SET_LOGICAL_TO_PHYSICAL_SLOT_MAPPING";
case RIL_REQUEST_SET_PREFERRED_DATA_MODEM:
return "SET_PREFERRED_DATA_MODEM";
case RIL_REQUEST_SWITCH_DUAL_SIM_CONFIG:
return "SWITCH_DUAL_SIM_CONFIG";
default:
return "<unknown request " + request + ">";
}
}
/**
* Wrapper function for using IRadioConfig.setModemsConfig(int32_t serial,
* ModemsConfig modemsConfig) to switch between single-sim and multi-sim.
*/
public void setModemsConfig(int numOfLiveModems, Message result) {
IRadioConfig radioConfigProxy = getRadioConfigProxy(result);
if (radioConfigProxy != null
&& mRadioConfigVersion.greaterOrEqual(RADIO_CONFIG_HAL_VERSION_1_1)) {
android.hardware.radio.config.V1_1.IRadioConfig radioConfigProxy11 =
(android.hardware.radio.config.V1_1.IRadioConfig) radioConfigProxy;
RILRequest rr = obtainRequest(RIL_REQUEST_SWITCH_DUAL_SIM_CONFIG,
result, mDefaultWorkSource);
if (DBG) {
logd(rr.serialString() + "> " + requestToString(rr.mRequest)
+ ", numOfLiveModems = " + numOfLiveModems);
}
try {
ModemsConfig modemsConfig = new ModemsConfig();
modemsConfig.numOfLiveModems = (byte) numOfLiveModems;
radioConfigProxy11.setModemsConfig(rr.mSerial, modemsConfig);
} catch (RemoteException | RuntimeException e) {
resetProxyAndRequestList("setModemsConfig", e);
}
}
}
// TODO: not needed for now, but if we don't want to use System Properties any more,
// we need to implement a wrapper function for getModemsConfig as well
/**
* Register a handler to get SIM slot status changed notifications.
*/
public void registerForSimSlotStatusChanged(Handler h, int what, Object obj) {
mSimSlotStatusRegistrant = new Registrant(h, what, obj);
}
/**
* Unregister corresponding to registerForSimSlotStatusChanged().
*/
public void unregisterForSimSlotStatusChanged(Handler h) {
if (mSimSlotStatusRegistrant != null && mSimSlotStatusRegistrant.getHandler() == h) {
mSimSlotStatusRegistrant.clear();
mSimSlotStatusRegistrant = null;
}
}
static ArrayList<IccSlotStatus> convertHalSlotStatus(
ArrayList<android.hardware.radio.config.V1_0.SimSlotStatus> halSlotStatusList) {
ArrayList<IccSlotStatus> response = new ArrayList<IccSlotStatus>(halSlotStatusList.size());
for (android.hardware.radio.config.V1_0.SimSlotStatus slotStatus : halSlotStatusList) {
IccSlotStatus iccSlotStatus = new IccSlotStatus();
iccSlotStatus.setCardState(slotStatus.cardState);
iccSlotStatus.setSlotState(slotStatus.slotState);
iccSlotStatus.logicalSlotIndex = slotStatus.logicalSlotId;
iccSlotStatus.atr = slotStatus.atr;
iccSlotStatus.iccid = slotStatus.iccid;
response.add(iccSlotStatus);
}
return response;
}
static ArrayList<IccSlotStatus> convertHalSlotStatus_1_2(
ArrayList<android.hardware.radio.config.V1_2.SimSlotStatus> halSlotStatusList) {
ArrayList<IccSlotStatus> response = new ArrayList<IccSlotStatus>(halSlotStatusList.size());
for (android.hardware.radio.config.V1_2.SimSlotStatus slotStatus : halSlotStatusList) {
IccSlotStatus iccSlotStatus = new IccSlotStatus();
iccSlotStatus.setCardState(slotStatus.base.cardState);
iccSlotStatus.setSlotState(slotStatus.base.slotState);
iccSlotStatus.logicalSlotIndex = slotStatus.base.logicalSlotId;
iccSlotStatus.atr = slotStatus.base.atr;
iccSlotStatus.iccid = slotStatus.base.iccid;
iccSlotStatus.eid = slotStatus.eid;
response.add(iccSlotStatus);
}
return response;
}
private static void logd(String log) {
Rlog.d(TAG, log);
}
private static void loge(String log) {
Rlog.e(TAG, log);
}
}