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