From 077a029813d1af7ab0073da897ae001cf1b50f0a Mon Sep 17 00:00:00 2001 From: Brad Ebinger Date: Mon, 19 Oct 2020 13:55:20 -0700 Subject: [PATCH] AIDL Implementation of SipTransport API Creates underlying AIDL implementation of the SipTransportImplBase and SipDelegate API to be used by the ImsService. Bug: 154763999 Test: atest FrameworksTelephonyTests Change-Id: Ic2ed943a45c69e8503fa96995b932b65a73140d2 --- .../ims/aidl/ISipDelegateStateCallback.aidl | 33 +++ .../telephony/ims/aidl/ISipTransport.aidl | 10 +- .../ims/aidl/SipDelegateAidlWrapper.java | 192 ++++++++++++++++++ .../ims/stub/SipTransportImplBase.java | 107 ++++++++-- 4 files changed, 329 insertions(+), 13 deletions(-) create mode 100644 telephony/java/android/telephony/ims/aidl/ISipDelegateStateCallback.aidl create mode 100644 telephony/java/android/telephony/ims/aidl/SipDelegateAidlWrapper.java diff --git a/telephony/java/android/telephony/ims/aidl/ISipDelegateStateCallback.aidl b/telephony/java/android/telephony/ims/aidl/ISipDelegateStateCallback.aidl new file mode 100644 index 0000000000000..609ee260cbab8 --- /dev/null +++ b/telephony/java/android/telephony/ims/aidl/ISipDelegateStateCallback.aidl @@ -0,0 +1,33 @@ +/* + * Copyright (c) 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 android.telephony.ims.aidl; + +import android.telephony.ims.DelegateRegistrationState; +import android.telephony.ims.FeatureTagState; +import android.telephony.ims.SipDelegateImsConfiguration; +import android.telephony.ims.aidl.ISipDelegate; + +/** + * See {@link SipDelegateStateCallback} for docs regarding this callback. + * {@hide} + */ +oneway interface ISipDelegateStateCallback { + void onCreated(ISipDelegate c, in List deniedFeatureTags); + void onFeatureTagRegistrationChanged(in DelegateRegistrationState registrationState); + void onImsConfigurationChanged(in SipDelegateImsConfiguration registeredSipConfig); + void onDestroyed(int reason); +} diff --git a/telephony/java/android/telephony/ims/aidl/ISipTransport.aidl b/telephony/java/android/telephony/ims/aidl/ISipTransport.aidl index fe233430ffad1..cd888391c584f 100644 --- a/telephony/java/android/telephony/ims/aidl/ISipTransport.aidl +++ b/telephony/java/android/telephony/ims/aidl/ISipTransport.aidl @@ -16,9 +16,17 @@ package android.telephony.ims.aidl; +import android.telephony.ims.DelegateRequest; +import android.telephony.ims.aidl.ISipDelegate; +import android.telephony.ims.aidl.ISipDelegateMessageCallback; +import android.telephony.ims.aidl.ISipDelegateStateCallback; + /** * Interface for commands to the SIP Transport implementation. * {@hide} */ -interface ISipTransport { +oneway interface ISipTransport { + void createSipDelegate(in DelegateRequest request, ISipDelegateStateCallback dc, + ISipDelegateMessageCallback mc); + void destroySipDelegate(ISipDelegate delegate, int reason); } diff --git a/telephony/java/android/telephony/ims/aidl/SipDelegateAidlWrapper.java b/telephony/java/android/telephony/ims/aidl/SipDelegateAidlWrapper.java new file mode 100644 index 0000000000000..a7f62cc32be1e --- /dev/null +++ b/telephony/java/android/telephony/ims/aidl/SipDelegateAidlWrapper.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) 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 android.telephony.ims.aidl; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Binder; +import android.os.RemoteException; +import android.telephony.ims.DelegateMessageCallback; +import android.telephony.ims.DelegateRegistrationState; +import android.telephony.ims.DelegateStateCallback; +import android.telephony.ims.FeatureTagState; +import android.telephony.ims.SipDelegateImsConfiguration; +import android.telephony.ims.SipDelegateManager; +import android.telephony.ims.SipMessage; +import android.telephony.ims.stub.SipDelegate; + +import java.util.List; +import java.util.concurrent.Executor; + +/** + * Implementation of callbacks by wrapping the internal AIDL from telephony. Also implements + * ISipDelegate internally when {@link DelegateStateCallback#onCreated(SipDelegate, List)} is called + * in order to trampoline events back to telephony. + * @hide + */ +public class SipDelegateAidlWrapper implements DelegateStateCallback, DelegateMessageCallback { + + private final ISipDelegate.Stub mDelegateBinder = new ISipDelegate.Stub() { + @Override + public void sendMessage(SipMessage sipMessage, int configVersion) { + SipDelegate d = mDelegate; + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> d.sendMessage(sipMessage, configVersion)); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void notifyMessageReceived(String viaTransactionId) { + SipDelegate d = mDelegate; + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> d.notifyMessageReceived(viaTransactionId)); + } finally { + Binder.restoreCallingIdentity(token); + } + + } + + @Override + public void notifyMessageReceiveError(String viaTransactionId, int reason) { + SipDelegate d = mDelegate; + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> d.notifyMessageReceiveError(viaTransactionId, reason)); + } finally { + Binder.restoreCallingIdentity(token); + } + + } + + @Override + public void closeDialog(String callId) { + SipDelegate d = mDelegate; + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> d.closeDialog(callId)); + } finally { + Binder.restoreCallingIdentity(token); + } + } + }; + + private final ISipDelegateMessageCallback mMessageBinder; + private final ISipDelegateStateCallback mStateBinder; + private final Executor mExecutor; + + private volatile SipDelegate mDelegate; + + public SipDelegateAidlWrapper(Executor executor, ISipDelegateStateCallback stateBinder, + ISipDelegateMessageCallback messageBinder) { + mExecutor = executor; + mStateBinder = stateBinder; + mMessageBinder = messageBinder; + } + + @Override + public void onMessageReceived(SipMessage message) { + try { + mMessageBinder.onMessageReceived(message); + } catch (RemoteException e) { + // BinderDied will be called on SipTransport instance to trigger destruction. Notify + // failure message failure locally for now. + SipDelegate d = mDelegate; + if (d != null) { + notifyLocalMessageFailedToBeReceived(message, + SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_DEAD); + } + } + } + + @Override + public void onMessageSent(String viaTransactionId) { + try { + mMessageBinder.onMessageSent(viaTransactionId); + } catch (RemoteException e) { + // BinderDied will trigger destroySipDelegate, so just ignore this locally. + } + } + + @Override + public void onMessageSendFailure(String viaTransactionId, int reason) { + try { + mMessageBinder.onMessageSendFailure(viaTransactionId, reason); + } catch (RemoteException e) { + // BinderDied will trigger destroySipDelegate, so just ignore this locally. + } + } + + @Override + public void onCreated(@NonNull SipDelegate delegate, + @Nullable List deniedTags) { + mDelegate = delegate; + try { + mStateBinder.onCreated(mDelegateBinder, deniedTags); + } catch (RemoteException e) { + // BinderDied will trigger destroySipDelegate, so just ignore this locally. + } + } + + @Override + public void onFeatureTagRegistrationChanged(DelegateRegistrationState registrationState) { + try { + mStateBinder.onFeatureTagRegistrationChanged(registrationState); + } catch (RemoteException e) { + // BinderDied will trigger destroySipDelegate, so just ignore this locally. + } + } + + @Override + public void onImsConfigurationChanged(@NonNull SipDelegateImsConfiguration config) { + try { + mStateBinder.onImsConfigurationChanged(config); + } catch (RemoteException e) { + // BinderDied will trigger destroySipDelegate, so just ignore this locally. + } + } + + @Override + public void onDestroyed(int reasonCode) { + mDelegate = null; + try { + mStateBinder.onDestroyed(reasonCode); + } catch (RemoteException e) { + // Do not worry about this if the remote side is already dead. + } + } + + public SipDelegate getDelegate() { + return mDelegate; + } + + public ISipDelegate getDelegateBinder() { + return mDelegateBinder; + } + + private void notifyLocalMessageFailedToBeReceived(SipMessage m, int reason) { + //TODO: parse transaction ID or throw IllegalArgumentException if the SipMessage + // transaction ID can not be parsed. + SipDelegate d = mDelegate; + if (d != null) { + mExecutor.execute(() -> d.notifyMessageReceiveError(null, reason)); + } + } +} diff --git a/telephony/java/android/telephony/ims/stub/SipTransportImplBase.java b/telephony/java/android/telephony/ims/stub/SipTransportImplBase.java index 8b0fde6b6bf2c..b48f6317e4135 100644 --- a/telephony/java/android/telephony/ims/stub/SipTransportImplBase.java +++ b/telephony/java/android/telephony/ims/stub/SipTransportImplBase.java @@ -18,12 +18,22 @@ package android.telephony.ims.stub; import android.annotation.NonNull; import android.annotation.SystemApi; +import android.os.Binder; +import android.os.IBinder; import android.telephony.ims.DelegateMessageCallback; import android.telephony.ims.DelegateRequest; import android.telephony.ims.DelegateStateCallback; import android.telephony.ims.SipDelegateManager; +import android.telephony.ims.aidl.ISipDelegate; +import android.telephony.ims.aidl.ISipDelegateMessageCallback; +import android.telephony.ims.aidl.ISipDelegateStateCallback; import android.telephony.ims.aidl.ISipTransport; +import android.telephony.ims.aidl.SipDelegateAidlWrapper; +import android.util.Log; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; import java.util.concurrent.Executor; /** @@ -37,16 +47,46 @@ import java.util.concurrent.Executor; */ @SystemApi public class SipTransportImplBase { + private static final String LOG_TAG = "SipTransportIB"; + + private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { + @Override + public void binderDied() { + mBinderExecutor.execute(() -> binderDiedInternal()); + } + }; + + private final ISipTransport.Stub mSipTransportImpl = new ISipTransport.Stub() { + @Override + public void createSipDelegate(DelegateRequest request, ISipDelegateStateCallback dc, + ISipDelegateMessageCallback mc) { + final long token = Binder.clearCallingIdentity(); + try { + mBinderExecutor.execute(() -> createSipDelegateInternal(request, dc, mc)); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void destroySipDelegate(ISipDelegate delegate, int reason) { + final long token = Binder.clearCallingIdentity(); + try { + mBinderExecutor.execute(() -> destroySipDelegateInternal(delegate, reason)); + } finally { + Binder.restoreCallingIdentity(token); + } + } + }; private final Executor mBinderExecutor; - private final ISipTransport mSipTransportImpl = new ISipTransport.Stub() { - - }; + private final ArrayList mDelegates = new ArrayList<>(); /** * Create an implementation of SipTransportImplBase. * - * @param executor The executor that remote calls from the framework should be called on. + * @param executor The executor that remote calls from the framework will be called on. This + * includes the methods here as well as the methods in {@link SipDelegate}. */ public SipTransportImplBase(@NonNull Executor executor) { if (executor == null) { @@ -57,8 +97,13 @@ public class SipTransportImplBase { } /** - * The ImsService implements this method to handle requests to create a new {@link SipDelegate} - * for subscription associated with it. + * Called by the Telephony framework to request the creation of a new {@link SipDelegate}. + *

+ * The implementation must call {@link DelegateStateCallback#onCreated(SipDelegate, List)} with + * the {@link SipDelegate} that is associated with the {@link DelegateRequest}. + *

+ * This method will be called on the Executor specified in + * {@link SipTransportImplBase#SipTransportImplBase(Executor)}. * * @param request A SIP delegate request containing the parameters that the remote RCS * application wishes to use. @@ -70,20 +115,58 @@ public class SipTransportImplBase { */ public void createSipDelegate(@NonNull DelegateRequest request, @NonNull DelegateStateCallback dc, @NonNull DelegateMessageCallback mc) { - + throw new UnsupportedOperationException("destroySipDelegate not implemented!"); } /** - * Destroys the SipDelegate associated with a remote IMS application. After the delegate is - * destroyed, SipDelegate#onDestroy should be called to notify listeners of its destruction to - * release resources. - * @param delegate The delegate to be modified. - * @param reason The reason the remote connection to this SipDelegate is being destroyed. + * Destroys the SipDelegate associated with a remote IMS application. + *

+ * After the delegate is destroyed, {@link DelegateStateCallback#onDestroyed(int)} must be + * called to notify listeners of its destruction to release associated resources. + *

+ * This method will be called on the Executor specified in + * {@link SipTransportImplBase#SipTransportImplBase(Executor)}. + * @param delegate The delegate to be destroyed. + * @param reason The reason the remote connection to this {@link SipDelegate} is being + * destroyed. * @hide */ public void destroySipDelegate(@NonNull SipDelegate delegate, @SipDelegateManager.SipDelegateDestroyReason int reason) { + throw new UnsupportedOperationException("destroySipDelegate not implemented!"); + } + private void createSipDelegateInternal(DelegateRequest r, ISipDelegateStateCallback cb, + ISipDelegateMessageCallback mc) { + SipDelegateAidlWrapper wrapper = new SipDelegateAidlWrapper(mBinderExecutor, cb, mc); + mDelegates.add(wrapper); + createSipDelegate(r, wrapper, wrapper); + } + + private void destroySipDelegateInternal(ISipDelegate d, int reason) { + SipDelegateAidlWrapper result = null; + for (SipDelegateAidlWrapper w : mDelegates) { + if (Objects.equals(d, w.getDelegateBinder())) { + result = w; + break; + } + } + + if (result != null) { + mDelegates.remove(result); + destroySipDelegate(result.getDelegate(), reason); + } else { + Log.w(LOG_TAG, "destroySipDelegateInternal, could not findSipDelegate corresponding to " + + d); + } + } + + private void binderDiedInternal() { + for (SipDelegateAidlWrapper w : mDelegates) { + destroySipDelegate(w.getDelegate(), + SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_SERVICE_DEAD); + } + mDelegates.clear(); } /**