1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.ims; 18 19 import android.annotation.Nullable; 20 import android.content.Context; 21 import android.os.Handler; 22 import android.os.IBinder; 23 import android.os.Looper; 24 import android.os.RemoteException; 25 import android.telephony.TelephonyManager; 26 import android.telephony.ims.aidl.IImsRegistration; 27 import android.telephony.ims.feature.ImsFeature; 28 import android.telephony.ims.stub.ImsRegistrationImplBase; 29 import android.util.Log; 30 31 import com.android.ims.internal.IImsServiceFeatureCallback; 32 import com.android.internal.annotations.VisibleForTesting; 33 import com.android.internal.telephony.util.HandlerExecutor; 34 35 import java.util.concurrent.Executor; 36 37 /** 38 * Base class of MmTelFeatureConnection and RcsFeatureConnection. 39 */ 40 public abstract class FeatureConnection { 41 protected static final String TAG = "FeatureConnection"; 42 43 public interface IFeatureUpdate { 44 /** 45 * Called when the ImsFeature has changed its state. Use 46 * {@link ImsFeature#getFeatureState()} to get the new state. 47 */ notifyStateChanged()48 void notifyStateChanged(); 49 50 /** 51 * Called when the ImsFeature has become unavailable due to the binder switching or app 52 * crashing. A new ImsServiceProxy should be requested for that feature. 53 */ notifyUnavailable()54 void notifyUnavailable(); 55 } 56 57 protected static boolean sImsSupportedOnDevice = true; 58 59 protected final int mSlotId; 60 protected Context mContext; 61 protected IBinder mBinder; 62 @VisibleForTesting 63 public Executor mExecutor; 64 65 // We are assuming the feature is available when started. 66 protected volatile boolean mIsAvailable = true; 67 // ImsFeature Status from the ImsService. Cached. 68 protected Integer mFeatureStateCached = null; 69 protected IFeatureUpdate mStatusCallback; 70 protected IImsRegistration mRegistrationBinder; 71 protected final Object mLock = new Object(); 72 FeatureConnection(Context context, int slotId)73 public FeatureConnection(Context context, int slotId) { 74 mSlotId = slotId; 75 mContext = context; 76 77 // Callbacks should be scheduled on the main thread. 78 if (context.getMainLooper() != null) { 79 mExecutor = context.getMainExecutor(); 80 } else { 81 // Fallback to the current thread. 82 if (Looper.myLooper() == null) { 83 Looper.prepare(); 84 } 85 mExecutor = new HandlerExecutor(new Handler(Looper.myLooper())); 86 } 87 } 88 getTelephonyManager()89 protected TelephonyManager getTelephonyManager() { 90 return (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); 91 } 92 93 /** 94 * Set the binder which type is IImsMmTelFeature or IImsRcsFeature to connect to MmTelFeature 95 * or RcsFeature. 96 */ setBinder(IBinder binder)97 public void setBinder(IBinder binder) { 98 synchronized (mLock) { 99 mBinder = binder; 100 try { 101 if (mBinder != null) { 102 mBinder.linkToDeath(mDeathRecipient, 0); 103 } 104 } catch (RemoteException e) { 105 // No need to do anything if the binder is already dead. 106 } 107 } 108 } 109 110 protected final IBinder.DeathRecipient mDeathRecipient = () -> { 111 Log.w(TAG, "DeathRecipient triggered, binder died."); 112 if (mContext != null && Looper.getMainLooper() != null) { 113 // Move this signal to the main thread, notifying ImsManager of the Binder 114 // death on another thread may lead to deadlocks. 115 mContext.getMainExecutor().execute(this::onRemovedOrDied); 116 return; 117 } 118 // No choice - execute on the current Binder thread. 119 onRemovedOrDied(); 120 }; 121 122 /** 123 * Called when the MmTelFeature/RcsFeature has either been removed by Telephony or crashed. 124 */ onRemovedOrDied()125 protected void onRemovedOrDied() { 126 synchronized (mLock) { 127 if (mIsAvailable) { 128 mIsAvailable = false; 129 mRegistrationBinder = null; 130 if (mBinder != null) { 131 mBinder.unlinkToDeath(mDeathRecipient, 0); 132 } 133 if (mStatusCallback != null) { 134 Log.d(TAG, "onRemovedOrDied: notifyUnavailable"); 135 mStatusCallback.notifyUnavailable(); 136 // Unlink because this FeatureConnection should no longer send callbacks. 137 mStatusCallback = null; 138 } 139 } 140 } 141 } 142 143 /** 144 * The listener for ImsManger and RcsFeatureManager to receive IMS feature status changed. 145 * @param callback Callback that will fire when the feature status has changed. 146 */ setStatusCallback(IFeatureUpdate callback)147 public void setStatusCallback(IFeatureUpdate callback) { 148 mStatusCallback = callback; 149 } 150 151 @VisibleForTesting getListener()152 public IImsServiceFeatureCallback getListener() { 153 return mListenerBinder; 154 } 155 156 /** 157 * The callback to receive ImsFeature status changed. 158 */ 159 private final IImsServiceFeatureCallback mListenerBinder = 160 new IImsServiceFeatureCallback.Stub() { 161 @Override 162 public void imsFeatureCreated(int slotId, int feature) { 163 mExecutor.execute(() -> { 164 handleImsFeatureCreatedCallback(slotId, feature); 165 }); 166 } 167 @Override 168 public void imsFeatureRemoved(int slotId, int feature) { 169 mExecutor.execute(() -> { 170 handleImsFeatureRemovedCallback(slotId, feature); 171 }); 172 } 173 @Override 174 public void imsStatusChanged(int slotId, int feature, int status) { 175 mExecutor.execute(() -> { 176 handleImsStatusChangedCallback(slotId, feature, status); 177 }); 178 } 179 }; 180 getRegistrationTech()181 public @ImsRegistrationImplBase.ImsRegistrationTech int getRegistrationTech() 182 throws RemoteException { 183 IImsRegistration registration = getRegistration(); 184 if (registration != null) { 185 return registration.getRegistrationTechnology(); 186 } else { 187 Log.w(TAG, "getRegistrationTech: ImsRegistration is null"); 188 return ImsRegistrationImplBase.REGISTRATION_TECH_NONE; 189 } 190 } 191 getRegistration()192 public @Nullable IImsRegistration getRegistration() { 193 synchronized (mLock) { 194 // null if cache is invalid; 195 if (mRegistrationBinder != null) { 196 return mRegistrationBinder; 197 } 198 } 199 // We don't want to synchronize on a binder call to another process. 200 IImsRegistration regBinder = getRegistrationBinder(); 201 synchronized (mLock) { 202 // mRegistrationBinder may have changed while we tried to get the registration 203 // interface. 204 if (mRegistrationBinder == null) { 205 mRegistrationBinder = regBinder; 206 } 207 } 208 return mRegistrationBinder; 209 } 210 211 @VisibleForTesting checkServiceIsReady()212 public void checkServiceIsReady() throws RemoteException { 213 if (!sImsSupportedOnDevice) { 214 throw new RemoteException("IMS is not supported on this device."); 215 } 216 if (!isBinderReady()) { 217 throw new RemoteException("ImsServiceProxy is not ready to accept commands."); 218 } 219 } 220 221 /** 222 * @return Returns true if the ImsService is ready to take commands, false otherwise. If this 223 * method returns false, it doesn't mean that the Binder connection is not available (use 224 * {@link #isBinderReady()} to check that), but that the ImsService is not accepting commands 225 * at this time. 226 * 227 * For example, for DSDS devices, only one slot can be {@link ImsFeature#STATE_READY} to take 228 * commands at a time, so the other slot must stay at {@link ImsFeature#STATE_UNAVAILABLE}. 229 */ isBinderReady()230 public boolean isBinderReady() { 231 return isBinderAlive() && getFeatureState() == ImsFeature.STATE_READY; 232 } 233 234 /** 235 * @return false if the binder connection is no longer alive. 236 */ isBinderAlive()237 public boolean isBinderAlive() { 238 return mIsAvailable && mBinder != null && mBinder.isBinderAlive(); 239 } 240 241 /** 242 * @return an integer describing the current Feature Status, defined in 243 * {@link ImsFeature.ImsState}. 244 */ getFeatureState()245 public int getFeatureState() { 246 synchronized (mLock) { 247 if (isBinderAlive() && mFeatureStateCached != null) { 248 return mFeatureStateCached; 249 } 250 } 251 // Don't synchronize on Binder call. 252 Integer state = retrieveFeatureState(); 253 synchronized (mLock) { 254 if (state == null) { 255 return ImsFeature.STATE_UNAVAILABLE; 256 } 257 // Cache only non-null value for feature status. 258 mFeatureStateCached = state; 259 } 260 Log.i(TAG + " [" + mSlotId + "]", "getFeatureState - returning " 261 + ImsFeature.STATE_LOG_MAP.get(state)); 262 return state; 263 } 264 265 /** 266 * An ImsFeature has been created for this FeatureConnection for the associated 267 * {@link ImsFeature.FeatureType}. 268 * @param slotId The slot ID associated with the event. 269 * @param feature The {@link ImsFeature.FeatureType} associated with the event. 270 */ handleImsFeatureCreatedCallback(int slotId, int feature)271 protected abstract void handleImsFeatureCreatedCallback(int slotId, int feature); 272 273 /** 274 * An ImsFeature has been removed for this FeatureConnection for the associated 275 * {@link ImsFeature.FeatureType}. 276 * @param slotId The slot ID associated with the event. 277 * @param feature The {@link ImsFeature.FeatureType} associated with the event. 278 */ handleImsFeatureRemovedCallback(int slotId, int feature)279 protected abstract void handleImsFeatureRemovedCallback(int slotId, int feature); 280 281 /** 282 * The status of an ImsFeature has changed for the associated {@link ImsFeature.FeatureType}. 283 * @param slotId The slot ID associated with the event. 284 * @param feature The {@link ImsFeature.FeatureType} associated with the event. 285 * @param status The new {@link ImsFeature.ImsState} associated with the ImsFeature 286 */ handleImsStatusChangedCallback(int slotId, int feature, int status)287 protected abstract void handleImsStatusChangedCallback(int slotId, int feature, int status); 288 289 /** 290 * Internal method used to retrieve the feature status from the corresponding ImsService. 291 */ retrieveFeatureState()292 protected abstract Integer retrieveFeatureState(); 293 294 /** 295 * @return The ImsRegistration instance associated with the FeatureConnection. 296 */ getRegistrationBinder()297 protected abstract IImsRegistration getRegistrationBinder(); 298 } 299