Allow EuiccService IPCs to happen in parallel.

The (currently) undocumented behavior of oneway AIDLs is to serialize
IPCs made from the same client IBinder object. This is undesirable in
EuiccService as we want to respond to short-lived calls (like listing
subscriptions or getting the EID) while long-lived calls (like
download) are in progress. Introduce our own thread pool to work
around this behavior.

This also fixes a bug where uncaught exceptions thrown in the
implementation of an EuiccService method would be logged but never
trigger a callback on the client. Such exceptions now correctly crash
the app and trigger error callbacks in any active callers.

Bug: 62535655
Test: TreeHugger + verified subscription list during a download
Change-Id: I8b75af7fdc72117a163d81f7bf1447f6cc12ec6d
This commit is contained in:
Jeff Davidson
2017-06-12 11:40:52 -07:00
parent ae448fbf5f
commit 707c372f09

View File

@@ -25,6 +25,12 @@ import android.telephony.euicc.DownloadableSubscription;
import android.telephony.euicc.EuiccInfo;
import android.util.ArraySet;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Service interface linking the system with an eUICC local profile assistant (LPA) application.
*
@@ -116,10 +122,45 @@ public abstract class EuiccService extends Service {
private final IEuiccService.Stub mStubWrapper;
private ThreadPoolExecutor mExecutor;
public EuiccService() {
mStubWrapper = new IEuiccServiceWrapper();
}
@Override
@CallSuper
public void onCreate() {
super.onCreate();
// We use a oneway AIDL interface to avoid blocking phone process binder threads on IPCs to
// an external process, but doing so means the requests are serialized by binder, which is
// not desired. Spin up a background thread pool to allow requests to be parallelized.
// TODO(b/38206971): Consider removing this if basic card-level functions like listing
// profiles are moved to the platform.
mExecutor = new ThreadPoolExecutor(
4 /* corePoolSize */,
4 /* maxPoolSize */,
30, TimeUnit.SECONDS, /* keepAliveTime */
new LinkedBlockingQueue<>(), /* workQueue */
new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "EuiccService #" + mCount.getAndIncrement());
}
}
);
mExecutor.allowCoreThreadTimeOut(true);
}
@Override
@CallSuper
public void onDestroy() {
mExecutor.shutdownNow();
super.onDestroy();
}
/**
* If overriding this method, call through to the super method for any unknown actions.
* {@inheritDoc}
@@ -279,23 +320,33 @@ public abstract class EuiccService extends Service {
public void downloadSubscription(int slotId, DownloadableSubscription subscription,
boolean switchAfterDownload, boolean forceDeactivateSim,
IDownloadSubscriptionCallback callback) {
int result = EuiccService.this.onDownloadSubscription(
slotId, subscription, switchAfterDownload, forceDeactivateSim);
try {
callback.onComplete(result);
} catch (RemoteException e) {
// Can't communicate with the phone process; ignore.
}
mExecutor.execute(new Runnable() {
@Override
public void run() {
int result = EuiccService.this.onDownloadSubscription(
slotId, subscription, switchAfterDownload, forceDeactivateSim);
try {
callback.onComplete(result);
} catch (RemoteException e) {
// Can't communicate with the phone process; ignore.
}
}
});
}
@Override
public void getEid(int slotId, IGetEidCallback callback) {
String eid = EuiccService.this.onGetEid(slotId);
try {
callback.onSuccess(eid);
} catch (RemoteException e) {
// Can't communicate with the phone process; ignore.
}
mExecutor.execute(new Runnable() {
@Override
public void run() {
String eid = EuiccService.this.onGetEid(slotId);
try {
callback.onSuccess(eid);
} catch (RemoteException e) {
// Can't communicate with the phone process; ignore.
}
}
});
}
@Override
@@ -303,92 +354,135 @@ public abstract class EuiccService extends Service {
DownloadableSubscription subscription,
boolean forceDeactivateSim,
IGetDownloadableSubscriptionMetadataCallback callback) {
GetDownloadableSubscriptionMetadataResult result =
EuiccService.this.onGetDownloadableSubscriptionMetadata(
slotId, subscription, forceDeactivateSim);
try {
callback.onComplete(result);
} catch (RemoteException e) {
// Can't communicate with the phone process; ignore.
}
mExecutor.execute(new Runnable() {
@Override
public void run() {
GetDownloadableSubscriptionMetadataResult result =
EuiccService.this.onGetDownloadableSubscriptionMetadata(
slotId, subscription, forceDeactivateSim);
try {
callback.onComplete(result);
} catch (RemoteException e) {
// Can't communicate with the phone process; ignore.
}
}
});
}
@Override
public void getDefaultDownloadableSubscriptionList(int slotId, boolean forceDeactivateSim,
IGetDefaultDownloadableSubscriptionListCallback callback) {
GetDefaultDownloadableSubscriptionListResult result =
EuiccService.this.onGetDefaultDownloadableSubscriptionList(
slotId, forceDeactivateSim);
try {
callback.onComplete(result);
} catch (RemoteException e) {
// Can't communicate with the phone process; ignore.
}
mExecutor.execute(new Runnable() {
@Override
public void run() {
GetDefaultDownloadableSubscriptionListResult result =
EuiccService.this.onGetDefaultDownloadableSubscriptionList(
slotId, forceDeactivateSim);
try {
callback.onComplete(result);
} catch (RemoteException e) {
// Can't communicate with the phone process; ignore.
}
}
});
}
@Override
public void getEuiccProfileInfoList(int slotId, IGetEuiccProfileInfoListCallback callback) {
GetEuiccProfileInfoListResult result =
EuiccService.this.onGetEuiccProfileInfoList(slotId);
try {
callback.onComplete(result);
} catch (RemoteException e) {
// Can't communicate with the phone process; ignore.
}
mExecutor.execute(new Runnable() {
@Override
public void run() {
GetEuiccProfileInfoListResult result =
EuiccService.this.onGetEuiccProfileInfoList(slotId);
try {
callback.onComplete(result);
} catch (RemoteException e) {
// Can't communicate with the phone process; ignore.
}
}
});
}
@Override
public void getEuiccInfo(int slotId, IGetEuiccInfoCallback callback) {
EuiccInfo euiccInfo = EuiccService.this.onGetEuiccInfo(slotId);
try {
callback.onSuccess(euiccInfo);
} catch (RemoteException e) {
// Can't communicate with the phone process; ignore.
}
mExecutor.execute(new Runnable() {
@Override
public void run() {
EuiccInfo euiccInfo = EuiccService.this.onGetEuiccInfo(slotId);
try {
callback.onSuccess(euiccInfo);
} catch (RemoteException e) {
// Can't communicate with the phone process; ignore.
}
}
});
}
@Override
public void deleteSubscription(int slotId, String iccid,
IDeleteSubscriptionCallback callback) {
int result = EuiccService.this.onDeleteSubscription(slotId, iccid);
try {
callback.onComplete(result);
} catch (RemoteException e) {
// Can't communicate with the phone process; ignore.
}
mExecutor.execute(new Runnable() {
@Override
public void run() {
int result = EuiccService.this.onDeleteSubscription(slotId, iccid);
try {
callback.onComplete(result);
} catch (RemoteException e) {
// Can't communicate with the phone process; ignore.
}
}
});
}
@Override
public void switchToSubscription(int slotId, String iccid, boolean forceDeactivateSim,
ISwitchToSubscriptionCallback callback) {
int result =
EuiccService.this.onSwitchToSubscription(slotId, iccid, forceDeactivateSim);
try {
callback.onComplete(result);
} catch (RemoteException e) {
// Can't communicate with the phone process; ignore.
}
mExecutor.execute(new Runnable() {
@Override
public void run() {
int result =
EuiccService.this.onSwitchToSubscription(
slotId, iccid, forceDeactivateSim);
try {
callback.onComplete(result);
} catch (RemoteException e) {
// Can't communicate with the phone process; ignore.
}
}
});
}
@Override
public void updateSubscriptionNickname(int slotId, String iccid, String nickname,
IUpdateSubscriptionNicknameCallback callback) {
int result = EuiccService.this.onUpdateSubscriptionNickname(slotId, iccid, nickname);
try {
callback.onComplete(result);
} catch (RemoteException e) {
// Can't communicate with the phone process; ignore.
}
mExecutor.execute(new Runnable() {
@Override
public void run() {
int result =
EuiccService.this.onUpdateSubscriptionNickname(slotId, iccid, nickname);
try {
callback.onComplete(result);
} catch (RemoteException e) {
// Can't communicate with the phone process; ignore.
}
}
});
}
@Override
public void eraseSubscriptions(int slotId, IEraseSubscriptionsCallback callback) {
int result = EuiccService.this.onEraseSubscriptions(slotId);
try {
callback.onComplete(result);
} catch (RemoteException e) {
// Can't communicate with the phone process; ignore.
}
mExecutor.execute(new Runnable() {
@Override
public void run() {
int result = EuiccService.this.onEraseSubscriptions(slotId);
try {
callback.onComplete(result);
} catch (RemoteException e) {
// Can't communicate with the phone process; ignore.
}
}
});
}
}
}