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.IntDef; 20 import android.content.Context; 21 import android.content.pm.PackageManager; 22 import android.os.RemoteException; 23 import android.telephony.ims.ImsReasonInfo; 24 import android.telephony.ims.ImsService; 25 import android.telephony.ims.feature.ImsFeature; 26 27 import com.android.ims.internal.IImsServiceFeatureCallback; 28 import com.android.internal.annotations.VisibleForTesting; 29 import com.android.telephony.Rlog; 30 31 import java.lang.annotation.Retention; 32 import java.lang.annotation.RetentionPolicy; 33 import java.util.ArrayList; 34 import java.util.List; 35 import java.util.concurrent.Executor; 36 37 /** 38 * Helper class for managing a connection to the ImsFeature manager. 39 */ 40 public class FeatureConnector<U extends FeatureUpdates> { 41 private static final String TAG = "FeatureConnector"; 42 private static final boolean DBG = false; 43 44 /** 45 * This Connection has become unavailable due to the ImsService being disconnected due to 46 * an event such as SIM Swap, carrier configuration change, etc... 47 * 48 * {@link Listener#connectionReady} will be called when a new Manager is available. 49 */ 50 public static final int UNAVAILABLE_REASON_DISCONNECTED = 0; 51 52 /** 53 * This Connection has become unavailable due to the ImsService moving to the NOT_READY state. 54 * 55 * {@link Listener#connectionReady} will be called when the manager moves back to ready. 56 */ 57 public static final int UNAVAILABLE_REASON_NOT_READY = 1; 58 59 /** 60 * IMS is not supported on this device. This should be considered a permanent error and 61 * a Manager will never become available. 62 */ 63 public static final int UNAVAILABLE_REASON_IMS_UNSUPPORTED = 2; 64 65 /** 66 * The server of this information has crashed or otherwise generated an error that will require 67 * a retry to connect. This is rare, however in this case, {@link #disconnect()} and 68 * {@link #connect()} will need to be called again to recreate the connection with the server. 69 * <p> 70 * Only applicable if this is used outside of the server's own process. 71 */ 72 public static final int UNAVAILABLE_REASON_SERVER_UNAVAILABLE = 3; 73 74 /** 75 * @hide 76 */ 77 @Retention(RetentionPolicy.SOURCE) 78 @IntDef(prefix = "UNAVAILABLE_REASON_", value = { 79 UNAVAILABLE_REASON_DISCONNECTED, 80 UNAVAILABLE_REASON_NOT_READY, 81 UNAVAILABLE_REASON_IMS_UNSUPPORTED, 82 UNAVAILABLE_REASON_SERVER_UNAVAILABLE 83 }) 84 public @interface UnavailableReason {} 85 86 /** 87 * Factory used to create a new instance of the manager that this FeatureConnector is waiting 88 * to connect the FeatureConnection to. 89 * @param <U> The Manager that this FeatureConnector has been created for. 90 */ 91 public interface ManagerFactory<U extends FeatureUpdates> { 92 /** 93 * Create a manager instance, which will connect to the FeatureConnection. 94 */ createManager(Context context, int phoneId)95 U createManager(Context context, int phoneId); 96 } 97 98 /** 99 * Listener interface used by Listeners of FeatureConnector that are waiting for a Manager 100 * interface for a specific ImsFeature. 101 * @param <U> The Manager that the listener is listening for. 102 */ 103 public interface Listener<U extends FeatureUpdates> { 104 /** 105 * ImsFeature manager is connected to the underlying IMS implementation. 106 */ connectionReady(U manager, int subId)107 void connectionReady(U manager, int subId) throws ImsException; 108 109 /** 110 * The underlying IMS implementation is unavailable and can not be used to communicate. 111 */ connectionUnavailable(@navailableReason int reason)112 void connectionUnavailable(@UnavailableReason int reason); 113 } 114 115 private final IImsServiceFeatureCallback mCallback = new IImsServiceFeatureCallback.Stub() { 116 117 @Override 118 public void imsFeatureCreated(ImsFeatureContainer c, int subId) { 119 log("imsFeatureCreated: " + c + ", subId: " + subId); 120 synchronized (mLock) { 121 mManager.associate(c, subId); 122 mManager.updateFeatureCapabilities(c.getCapabilities()); 123 mDisconnectedReason = null; 124 } 125 // Notifies executor, so notify outside of lock 126 imsStatusChanged(c.getState(), subId); 127 } 128 129 @Override 130 public void imsFeatureRemoved(@UnavailableReason int reason) { 131 log("imsFeatureRemoved: reason=" + reason); 132 synchronized (mLock) { 133 // only generate new events if the disconnect event isn't the same as before, except 134 // for UNAVAILABLE_REASON_SERVER_UNAVAILABLE, which indicates a local issue and 135 // each event is actionable. 136 if (mDisconnectedReason != null 137 && (mDisconnectedReason == reason 138 && mDisconnectedReason != UNAVAILABLE_REASON_SERVER_UNAVAILABLE)) { 139 log("imsFeatureRemoved: ignore"); 140 return; 141 } 142 mDisconnectedReason = reason; 143 // Ensure that we set ready state back to false so that we do not miss setting ready 144 // later if the initial state when recreated is READY. 145 mLastReadyState = false; 146 } 147 // Allow the listener to do cleanup while the connection still potentially valid (unless 148 // the process crashed). 149 mExecutor.execute(() -> mListener.connectionUnavailable(reason)); 150 mManager.invalidate(); 151 } 152 153 @Override 154 public void imsStatusChanged(int status, int subId) { 155 log("imsStatusChanged: status=" + ImsFeature.STATE_LOG_MAP.get(status)); 156 final U manager; 157 final boolean isReady; 158 synchronized (mLock) { 159 if (mDisconnectedReason != null) { 160 log("imsStatusChanged: ignore"); 161 return; 162 } 163 mManager.updateFeatureState(status); 164 manager = mManager; 165 isReady = mReadyFilter.contains(status); 166 boolean didReadyChange = isReady ^ mLastReadyState; 167 mLastReadyState = isReady; 168 if (!didReadyChange) { 169 log("imsStatusChanged: ready didn't change, ignore"); 170 return; 171 } 172 } 173 mExecutor.execute(() -> { 174 try { 175 if (isReady) { 176 notifyReady(manager, subId); 177 } else { 178 notifyNotReady(); 179 } 180 } catch (ImsException e) { 181 if (e.getCode() 182 == ImsReasonInfo.CODE_LOCAL_IMS_NOT_SUPPORTED_ON_DEVICE) { 183 mListener.connectionUnavailable(UNAVAILABLE_REASON_IMS_UNSUPPORTED); 184 } else { 185 notifyNotReady(); 186 } 187 } 188 }); 189 } 190 191 @Override 192 public void updateCapabilities(long caps) { 193 log("updateCapabilities: capabilities=" + ImsService.getCapabilitiesString(caps)); 194 synchronized (mLock) { 195 if (mDisconnectedReason != null) { 196 log("updateCapabilities: ignore"); 197 return; 198 } 199 mManager.updateFeatureCapabilities(caps); 200 } 201 } 202 }; 203 204 private final int mPhoneId; 205 private final Context mContext; 206 private final ManagerFactory<U> mFactory; 207 private final Listener<U> mListener; 208 private final Executor mExecutor; 209 private final Object mLock = new Object(); 210 private final String mLogPrefix; 211 // A List of integers, each corresponding to an ImsFeature.ImsState, that the FeatureConnector 212 // will use to call Listener#connectionReady when the ImsFeature that this connector is waiting 213 // for changes into one of the states in this list. 214 private final List<Integer> mReadyFilter = new ArrayList<>(); 215 216 private U mManager; 217 // Start in disconnected state; 218 private Integer mDisconnectedReason = UNAVAILABLE_REASON_DISCONNECTED; 219 // Stop redundant connectionAvailable if the ready filter contains multiple states. 220 // Also, do not send the first unavailable until after we have moved to available once. 221 private boolean mLastReadyState = false; 222 223 224 225 @VisibleForTesting FeatureConnector(Context context, int phoneId, ManagerFactory<U> factory, String logPrefix, List<Integer> readyFilter, Listener<U> listener, Executor executor)226 public FeatureConnector(Context context, int phoneId, ManagerFactory<U> factory, 227 String logPrefix, List<Integer> readyFilter, Listener<U> listener, Executor executor) { 228 mContext = context; 229 mPhoneId = phoneId; 230 mFactory = factory; 231 mLogPrefix = logPrefix; 232 mReadyFilter.addAll(readyFilter); 233 mListener = listener; 234 mExecutor = executor; 235 } 236 237 /** 238 * Start the creation of a connection to the underlying ImsService implementation. When the 239 * service is connected, {@link FeatureConnector.Listener#connectionReady} will be 240 * called with an active instance. 241 * 242 * If this device does not support an ImsStack (i.e. doesn't support 243 * {@link PackageManager#FEATURE_TELEPHONY_IMS} feature), this method will do nothing. 244 */ connect()245 public void connect() { 246 if (DBG) log("connect"); 247 if (!isSupported()) { 248 mExecutor.execute(() -> mListener.connectionUnavailable( 249 UNAVAILABLE_REASON_IMS_UNSUPPORTED)); 250 logw("connect: not supported."); 251 return; 252 } 253 synchronized (mLock) { 254 if (mManager == null) { 255 mManager = mFactory.createManager(mContext, mPhoneId); 256 } 257 } 258 mManager.registerFeatureCallback(mPhoneId, mCallback); 259 } 260 261 // Check if this ImsFeature is supported or not. isSupported()262 private boolean isSupported() { 263 return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY_IMS); 264 } 265 266 /** 267 * Disconnect from the ImsService Implementation and clean up. When this is complete, 268 * {@link FeatureConnector.Listener#connectionUnavailable(int)} will be called one last time. 269 */ disconnect()270 public void disconnect() { 271 if (DBG) log("disconnect"); 272 final U manager; 273 synchronized (mLock) { 274 manager = mManager; 275 } 276 if (manager == null) return; 277 278 manager.unregisterFeatureCallback(mCallback); 279 try { 280 mCallback.imsFeatureRemoved(UNAVAILABLE_REASON_DISCONNECTED); 281 } catch (RemoteException ignore) {} // local call 282 } 283 284 // Should be called on executor notifyReady(U manager, int subId)285 private void notifyReady(U manager, int subId) throws ImsException { 286 try { 287 if (DBG) log("notifyReady"); 288 mListener.connectionReady(manager, subId); 289 } 290 catch (ImsException e) { 291 if(DBG) log("notifyReady exception: " + e.getMessage()); 292 throw e; 293 } 294 } 295 296 // Should be called on executor. notifyNotReady()297 private void notifyNotReady() { 298 if (DBG) log("notifyNotReady"); 299 mListener.connectionUnavailable(UNAVAILABLE_REASON_NOT_READY); 300 } 301 log(String message)302 private void log(String message) { 303 Rlog.d(TAG, "[" + mLogPrefix + ", " + mPhoneId + "] " + message); 304 } 305 logw(String message)306 private void logw(String message) { 307 Rlog.w(TAG, "[" + mLogPrefix + ", " + mPhoneId + "] " + message); 308 } 309 } 310