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