1 /* 2 * Copyright (C) 2022 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.internal.telephony.domainselection; 18 19 import static android.telephony.TelephonyManager.HAL_SERVICE_NETWORK; 20 21 import static com.android.internal.telephony.RIL.RADIO_HAL_VERSION_2_1; 22 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.content.ComponentName; 26 import android.content.Context; 27 import android.os.SystemProperties; 28 import android.telephony.DomainSelectionService; 29 import android.text.TextUtils; 30 import android.util.IndentingPrintWriter; 31 import android.util.LocalLog; 32 import android.util.Log; 33 34 import com.android.internal.annotations.VisibleForTesting; 35 import com.android.internal.telephony.Phone; 36 import com.android.internal.telephony.PhoneFactory; 37 import com.android.internal.telephony.flags.Flags; 38 import com.android.internal.telephony.util.TelephonyUtils; 39 40 import java.io.FileDescriptor; 41 import java.io.PrintWriter; 42 43 /** 44 * This class is an entry point to provide whether the AOSP domain selection is supported or not, 45 * and bind the {@link DomainSelectionController} with the given {@link DomainSelectionService} to 46 * provide a specific {@link DomainSelectionConnection} object for communicating with each domain 47 * selector. 48 */ 49 public class DomainSelectionResolver { 50 @VisibleForTesting 51 protected static final String PACKAGE_NAME_NONE = "none"; 52 private static final String TAG = DomainSelectionResolver.class.getSimpleName(); 53 private static final boolean DBG = TelephonyUtils.IS_DEBUGGABLE; 54 /** For test purpose only with userdebug release */ 55 private static final String PROP_DISABLE_DOMAIN_SELECTION = 56 "telephony.test.disable_domain_selection"; 57 private static DomainSelectionResolver sInstance = null; 58 59 /** 60 * Creates the DomainSelectionResolver singleton instance. 61 * 62 * @param context The context of the application. 63 * @param flattenedComponentName A flattened component name for the domain selection service 64 * to be bound to the domain selection controller. 65 */ make(Context context, String flattenedComponentName)66 public static void make(Context context, String flattenedComponentName) { 67 Log.i(TAG, "make flag=" + Flags.apDomainSelectionEnabled() 68 + ", useOem=" + Flags.useOemDomainSelectionService()); 69 if (sInstance == null) { 70 sInstance = new DomainSelectionResolver(context, flattenedComponentName); 71 } 72 } 73 74 /** 75 * Returns the singleton instance of DomainSelectionResolver. 76 * 77 * @return A {@link DomainSelectionResolver} instance. 78 */ getInstance()79 public static DomainSelectionResolver getInstance() { 80 if (sInstance == null) { 81 throw new IllegalStateException("DomainSelectionResolver is not ready!"); 82 } 83 return sInstance; 84 } 85 86 /** 87 * Sets a {@link DomainSelectionResolver} for injecting mock DomainSelectionResolver. 88 * 89 * @param resolver A {@link DomainSelectionResolver} instance to test. 90 */ 91 @VisibleForTesting setDomainSelectionResolver(DomainSelectionResolver resolver)92 public static void setDomainSelectionResolver(DomainSelectionResolver resolver) { 93 sInstance = resolver; 94 } 95 96 /** 97 * Testing interface for injecting mock DomainSelectionController. 98 */ 99 @VisibleForTesting 100 public interface DomainSelectionControllerFactory { 101 /** 102 * Returns a {@link DomainSelectionController} created using the specified context. 103 */ create(@onNull Context context)104 DomainSelectionController create(@NonNull Context context); 105 } 106 107 private DomainSelectionControllerFactory mDomainSelectionControllerFactory = 108 new DomainSelectionControllerFactory() { 109 @Override 110 public DomainSelectionController create(@NonNull Context context) { 111 return new DomainSelectionController(context); 112 } 113 }; 114 115 // Persistent Logging 116 private final LocalLog mEventLog = new LocalLog(10); 117 private final Context mContext; 118 // Stores the default component name to bind the domain selection service so that 119 // the test can override this component name with their own domain selection service. 120 private final ComponentName mDefaultComponentName; 121 // DomainSelectionController, which are bound to DomainSelectionService. 122 private DomainSelectionController mController; 123 DomainSelectionResolver(Context context, String flattenedComponentName)124 public DomainSelectionResolver(Context context, String flattenedComponentName) { 125 mContext = context; 126 flattenedComponentName = (flattenedComponentName == null) ? "" : flattenedComponentName; 127 mDefaultComponentName = ComponentName.unflattenFromString(flattenedComponentName); 128 logi("DomainSelectionResolver created: componentName=[" + flattenedComponentName + "]"); 129 } 130 131 /** 132 * Checks if the device supports the domain selection service to route the call / SMS / 133 * supplementary services to the appropriate domain. 134 * This checks the device-config and Radio HAL version for supporting the domain selection. 135 * The domain selection requires the Radio HAL version greater than or equal to 2.1. 136 * 137 * @return {@code true} if the domain selection is supported on the device, 138 * {@code false} otherwise. 139 */ isDomainSelectionSupported()140 public boolean isDomainSelectionSupported() { 141 if (DBG && SystemProperties.getBoolean(PROP_DISABLE_DOMAIN_SELECTION, false)) { 142 logi("Disabled for test"); 143 return false; 144 } 145 return mDefaultComponentName != null && PhoneFactory.getDefaultPhone() 146 .getHalVersion(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_2_1); 147 } 148 149 /** 150 * Returns a {@link DomainSelectionConnection} instance. 151 * 152 * @param phone The Phone instance for witch this request is. 153 * @param selectorType Indicates the selector type requested. 154 * @param isEmergency Indicates whether this is for emergency service. 155 * @throws IllegalStateException If the {@link DomainSelectionController} is not created 156 * because {@link #initialize} method is not called even if the domain selection is 157 * supported. 158 * @return A {@link DomainSelectionConnection} instance if the device supports 159 * AOSP domain selection and IMS is available or {@code null} otherwise. 160 */ getDomainSelectionConnection(Phone phone, @DomainSelectionService.SelectorType int selectorType, boolean isEmergency)161 public @Nullable DomainSelectionConnection getDomainSelectionConnection(Phone phone, 162 @DomainSelectionService.SelectorType int selectorType, boolean isEmergency) { 163 if (mController == null) { 164 // If the caller calls this method without checking whether the domain selection 165 // is supported or not, this exception will be thrown. 166 throw new IllegalStateException("DomainSelection is not supported!"); 167 } 168 169 if (phone == null || phone.getImsPhone() == null 170 || (!(isEmergency && selectorType == DomainSelectionService.SELECTOR_TYPE_CALLING) 171 && !phone.isImsAvailable())) { 172 // In case of emergency calls, to recover the temporary failure in IMS service 173 // connection, DomainSelection shall be started even when IMS isn't available. 174 // DomainSelector will keep finding next available transport. 175 // For other telephony services, if the binder of ImsService is not available, 176 // CS domain will be used. 177 return null; 178 } 179 180 return mController.getDomainSelectionConnection(phone, selectorType, isEmergency); 181 } 182 183 /** Sets a factory interface for creating {@link DomainSelectionController} instance. */ 184 @VisibleForTesting setDomainSelectionControllerFactory(DomainSelectionControllerFactory factory)185 public void setDomainSelectionControllerFactory(DomainSelectionControllerFactory factory) { 186 mDomainSelectionControllerFactory = factory; 187 } 188 189 /** 190 * Creates the {@link DomainSelectionController} and requests the domain selection controller 191 * to bind to the {@link DomainSelectionService} with the component name. 192 */ initialize()193 public void initialize() { 194 logi("Initialize"); 195 mController = mDomainSelectionControllerFactory.create(mContext); 196 if (mDefaultComponentName != null) { 197 mController.bind(mDefaultComponentName); 198 } else { 199 logi("No component name specified for domain selection service."); 200 } 201 } 202 203 /** 204 * Sets the component name of domain selection service to be bound. 205 * 206 * NOTE: This should only be used for testing. 207 * 208 * @return {@code true} if the requested operation is successfully done, 209 * {@code false} otherwise. 210 */ setDomainSelectionServiceOverride(@onNull ComponentName componentName)211 public boolean setDomainSelectionServiceOverride(@NonNull ComponentName componentName) { 212 if (mController == null) { 213 logd("Controller is not initialized."); 214 return false; 215 } 216 logi("setDomainSelectionServiceOverride: " + componentName); 217 if (TextUtils.isEmpty(componentName.getPackageName()) 218 || TextUtils.equals(PACKAGE_NAME_NONE, componentName.getPackageName())) { 219 // Unbind the active service connection to the domain selection service. 220 mController.unbind(); 221 return true; 222 } 223 // Override the domain selection service with the given component name. 224 return mController.bind(componentName); 225 } 226 227 /** 228 * Clears the overridden domain selection service and restores the domain selection service 229 * with the default component. 230 * 231 * NOTE: This should only be used for testing. 232 * 233 * @return {@code true} if the requested operation is successfully done, 234 * {@code false} otherwise. 235 */ clearDomainSelectionServiceOverride()236 public boolean clearDomainSelectionServiceOverride() { 237 if (mController == null) { 238 logd("Controller is not initialized."); 239 return false; 240 } 241 logi("clearDomainSelectionServiceOverride"); 242 mController.unbind(); 243 return mController.bind(mDefaultComponentName); 244 } 245 246 /** 247 * Dumps this instance into a readable format for dumpsys usage. 248 */ dump(FileDescriptor fd, PrintWriter pw, String[] args)249 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 250 IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); 251 ipw.println("Resolver:"); 252 ipw.increaseIndent(); 253 ipw.println("Event Log:"); 254 ipw.increaseIndent(); 255 mEventLog.dump(ipw); 256 ipw.decreaseIndent(); 257 ipw.decreaseIndent(); 258 259 ipw.println("Controller:"); 260 ipw.increaseIndent(); 261 DomainSelectionController controller = mController; 262 if (controller == null) { 263 ipw.println("no active controller"); 264 } else { 265 controller.dump(ipw); 266 } 267 ipw.decreaseIndent(); 268 } 269 logd(String s)270 private void logd(String s) { 271 Log.d(TAG, s); 272 } 273 logi(String s)274 private void logi(String s) { 275 Log.i(TAG, s); 276 mEventLog.log(s); 277 } 278 } 279