| /* |
| * 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.ims; |
| |
| import android.annotation.Nullable; |
| import android.content.Context; |
| import android.os.IBinder; |
| import android.os.Looper; |
| import android.os.RemoteException; |
| import android.telephony.TelephonyManager; |
| import android.telephony.ims.ImsService; |
| import android.telephony.ims.aidl.IImsConfig; |
| import android.telephony.ims.aidl.IImsRegistration; |
| import android.telephony.ims.aidl.ISipTransport; |
| import android.telephony.ims.feature.ImsFeature; |
| import android.telephony.ims.stub.ImsRegistrationImplBase; |
| import android.util.Log; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| |
| import java.util.NoSuchElementException; |
| |
| /** |
| * Base class of MmTelFeatureConnection and RcsFeatureConnection. |
| */ |
| public abstract class FeatureConnection { |
| protected static final String TAG = "FeatureConnection"; |
| |
| protected static boolean sImsSupportedOnDevice = true; |
| |
| protected final int mSlotId; |
| protected final int mSubId; |
| protected Context mContext; |
| protected IBinder mBinder; |
| |
| // We are assuming the feature is available when started. |
| protected volatile boolean mIsAvailable = true; |
| // ImsFeature Status from the ImsService. Cached. |
| protected Integer mFeatureStateCached = null; |
| protected long mFeatureCapabilities; |
| private final IImsRegistration mRegistrationBinder; |
| private final IImsConfig mConfigBinder; |
| private final ISipTransport mSipTransportBinder; |
| protected final Object mLock = new Object(); |
| |
| public FeatureConnection(Context context, int slotId, int subId, IImsConfig c, |
| IImsRegistration r, ISipTransport s) { |
| mSlotId = slotId; |
| mSubId = subId; |
| mContext = context; |
| mRegistrationBinder = r; |
| mConfigBinder = c; |
| mSipTransportBinder = s; |
| } |
| |
| protected TelephonyManager getTelephonyManager() { |
| return (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); |
| } |
| |
| /** |
| * Set the binder which type is IImsMmTelFeature or IImsRcsFeature to connect to MmTelFeature |
| * or RcsFeature. |
| */ |
| public void setBinder(IBinder binder) { |
| synchronized (mLock) { |
| mBinder = binder; |
| try { |
| if (mBinder != null) { |
| mBinder.linkToDeath(mDeathRecipient, 0); |
| } |
| } catch (RemoteException e) { |
| Log.w(TAG, "setBinder: linkToDeath on already dead Binder, setting null"); |
| mBinder = null; |
| } |
| } |
| } |
| |
| protected final IBinder.DeathRecipient mDeathRecipient = () -> { |
| Log.w(TAG, "DeathRecipient triggered, binder died."); |
| if (mContext != null && Looper.getMainLooper() != null) { |
| // Move this signal to the main thread, notifying ImsManager of the Binder |
| // death on another thread may lead to deadlocks. |
| mContext.getMainExecutor().execute(this::onRemovedOrDied); |
| return; |
| } |
| // No choice - execute on the current Binder thread. |
| onRemovedOrDied(); |
| }; |
| |
| /** |
| * Called when the MmTelFeature/RcsFeature has either been removed by Telephony or crashed. |
| */ |
| protected void onRemovedOrDied() { |
| synchronized (mLock) { |
| if (mIsAvailable) { |
| mIsAvailable = false; |
| try { |
| if (mBinder != null) { |
| mBinder.unlinkToDeath(mDeathRecipient, 0); |
| } |
| } catch (NoSuchElementException e) { |
| Log.w(TAG, "onRemovedOrDied: unlinkToDeath called on unlinked Binder."); |
| } |
| } |
| } |
| } |
| |
| public @ImsRegistrationImplBase.ImsRegistrationTech int getRegistrationTech() |
| throws RemoteException { |
| IImsRegistration registration = getRegistration(); |
| if (registration != null) { |
| return registration.getRegistrationTechnology(); |
| } else { |
| Log.w(TAG, "getRegistrationTech: ImsRegistration is null"); |
| return ImsRegistrationImplBase.REGISTRATION_TECH_NONE; |
| } |
| } |
| |
| public @Nullable IImsRegistration getRegistration() { |
| return mRegistrationBinder; |
| } |
| |
| public @Nullable IImsConfig getConfig() { |
| return mConfigBinder; |
| } |
| |
| public @Nullable ISipTransport getSipTransport() { |
| return mSipTransportBinder; |
| } |
| |
| @VisibleForTesting |
| public void checkServiceIsReady() throws RemoteException { |
| if (!sImsSupportedOnDevice) { |
| throw new RemoteException("IMS is not supported on this device."); |
| } |
| if (!isBinderReady()) { |
| throw new RemoteException("ImsServiceProxy is not ready to accept commands."); |
| } |
| } |
| |
| /** |
| * @return Returns true if the ImsService is ready to take commands, false otherwise. If this |
| * method returns false, it doesn't mean that the Binder connection is not available (use |
| * {@link #isBinderReady()} to check that), but that the ImsService is not accepting commands |
| * at this time. |
| * |
| * For example, for DSDS devices, only one slot can be {@link ImsFeature#STATE_READY} to take |
| * commands at a time, so the other slot must stay at {@link ImsFeature#STATE_UNAVAILABLE}. |
| */ |
| public boolean isBinderReady() { |
| return isBinderAlive() && getFeatureState() == ImsFeature.STATE_READY; |
| } |
| |
| /** |
| * @return false if the binder connection is no longer alive. |
| */ |
| public boolean isBinderAlive() { |
| return mIsAvailable && mBinder != null && mBinder.isBinderAlive(); |
| } |
| |
| public void updateFeatureState(int state) { |
| synchronized (mLock) { |
| mFeatureStateCached = state; |
| } |
| } |
| |
| public long getFeatureCapabilties() { |
| synchronized (mLock) { |
| return mFeatureCapabilities; |
| } |
| } |
| |
| public void updateFeatureCapabilities(long caps) { |
| synchronized (mLock) { |
| if (mFeatureCapabilities != caps) { |
| mFeatureCapabilities = caps; |
| onFeatureCapabilitiesUpdated(caps); |
| } |
| } |
| } |
| |
| public boolean isCapable(@ImsService.ImsServiceCapability long capabilities) |
| throws RemoteException { |
| if (!isBinderAlive()) { |
| throw new RemoteException("isCapable: ImsService is not alive"); |
| } |
| return (getFeatureCapabilties() & capabilities) > 0; |
| } |
| |
| /** |
| * @return an integer describing the current Feature Status, defined in |
| * {@link ImsFeature.ImsState}. |
| */ |
| public int getFeatureState() { |
| synchronized (mLock) { |
| if (isBinderAlive() && mFeatureStateCached != null) { |
| return mFeatureStateCached; |
| } |
| } |
| // Don't synchronize on Binder call. |
| Integer state = retrieveFeatureState(); |
| synchronized (mLock) { |
| if (state == null) { |
| return ImsFeature.STATE_UNAVAILABLE; |
| } |
| // Cache only non-null value for feature status. |
| mFeatureStateCached = state; |
| } |
| Log.i(TAG + " [" + mSlotId + "]", "getFeatureState - returning " |
| + ImsFeature.STATE_LOG_MAP.get(state)); |
| return state; |
| } |
| |
| public int getSubId() { |
| return mSubId; |
| } |
| |
| /** |
| * Internal method used to retrieve the feature status from the corresponding ImsService. |
| */ |
| protected abstract Integer retrieveFeatureState(); |
| |
| protected abstract void onFeatureCapabilitiesUpdated(long capabilities); |
| } |