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 android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.os.AsyncResult; 22 import android.os.CancellationSignal; 23 import android.os.Handler; 24 import android.os.HandlerThread; 25 import android.os.Looper; 26 import android.os.Message; 27 import android.telephony.AccessNetworkConstants.RadioAccessNetworkType; 28 import android.telephony.Annotation.DisconnectCauses; 29 import android.telephony.DomainSelectionService; 30 import android.telephony.DomainSelectionService.EmergencyScanType; 31 import android.telephony.DomainSelector; 32 import android.telephony.EmergencyRegResult; 33 import android.telephony.NetworkRegistrationInfo; 34 import android.telephony.TransportSelectorCallback; 35 import android.telephony.WwanSelectorCallback; 36 import android.util.LocalLog; 37 import android.util.Log; 38 39 import com.android.internal.annotations.VisibleForTesting; 40 import com.android.internal.infra.AndroidFuture; 41 import com.android.internal.telephony.Phone; 42 import com.android.internal.telephony.util.TelephonyUtils; 43 44 import java.io.PrintWriter; 45 import java.util.List; 46 import java.util.concurrent.CompletableFuture; 47 import java.util.concurrent.Executor; 48 import java.util.concurrent.Executors; 49 import java.util.function.Consumer; 50 51 52 /** 53 * Manages the information of request and the callback binder. 54 */ 55 public class DomainSelectionConnection { 56 57 private static final boolean DBG = TelephonyUtils.IS_DEBUGGABLE; 58 59 protected static final int EVENT_EMERGENCY_NETWORK_SCAN_RESULT = 1; 60 protected static final int EVENT_QUALIFIED_NETWORKS_CHANGED = 2; 61 62 /** Callback to receive responses from DomainSelectionConnection. */ 63 public interface DomainSelectionConnectionCallback { 64 /** 65 * Notifies that selection has terminated because there is no decision that can be made 66 * or a timeout has occurred. The call should be terminated when this method is called. 67 * 68 * @param cause Indicates the reason. 69 */ onSelectionTerminated(@isconnectCauses int cause)70 void onSelectionTerminated(@DisconnectCauses int cause); 71 } 72 73 /** An internal class implementing {@link TransportSelectorCallback} interface. */ 74 private final class TransportSelectorCallbackWrapper implements TransportSelectorCallback { 75 @Override onCreated(@onNull DomainSelector selector)76 public void onCreated(@NonNull DomainSelector selector) { 77 mDomainSelector = selector; 78 DomainSelectionConnection.this.onCreated(); 79 } 80 81 @Override onWlanSelected(boolean useEmergencyPdn)82 public void onWlanSelected(boolean useEmergencyPdn) { 83 DomainSelectionConnection.this.onWlanSelected(useEmergencyPdn); 84 } 85 86 @Override onWwanSelected()87 public @NonNull WwanSelectorCallback onWwanSelected() { 88 if (mWwanSelectorCallback == null) { 89 mWwanSelectorCallback = new WwanSelectorCallbackWrapper(); 90 } 91 DomainSelectionConnection.this.onWwanSelected(); 92 return mWwanSelectorCallback; 93 } 94 95 @Override onWwanSelected(final Consumer<WwanSelectorCallback> consumer)96 public void onWwanSelected(final Consumer<WwanSelectorCallback> consumer) { 97 if (mWwanSelectorCallback == null) { 98 mWwanSelectorCallback = new WwanSelectorCallbackWrapper(); 99 } 100 if (mWwanSelectedExecutor == null) { 101 mWwanSelectedExecutor = Executors.newSingleThreadExecutor(); 102 } 103 mWwanSelectedExecutor.execute(() -> { 104 DomainSelectionConnection.this.onWwanSelected(); 105 consumer.accept(mWwanSelectorCallback); 106 }); 107 } 108 109 @Override onSelectionTerminated(int cause)110 public void onSelectionTerminated(int cause) { 111 DomainSelectionConnection.this.onSelectionTerminated(cause); 112 dispose(); 113 } 114 } 115 116 /** An internal class implementing {@link WwanSelectorCallback} interface. */ 117 private final class WwanSelectorCallbackWrapper 118 implements WwanSelectorCallback, CancellationSignal.OnCancelListener { 119 @Override onRequestEmergencyNetworkScan(@onNull List<Integer> preferredNetworks, @EmergencyScanType int scanType, @NonNull CancellationSignal signal, @NonNull Consumer<EmergencyRegResult> consumer)120 public void onRequestEmergencyNetworkScan(@NonNull List<Integer> preferredNetworks, 121 @EmergencyScanType int scanType, @NonNull CancellationSignal signal, 122 @NonNull Consumer<EmergencyRegResult> consumer) { 123 if (signal != null) signal.setOnCancelListener(this); 124 mResultCallback = consumer; 125 initHandler(); 126 DomainSelectionConnection.this.onRequestEmergencyNetworkScan( 127 preferredNetworks.stream().mapToInt(Integer::intValue).toArray(), scanType); 128 } 129 130 @Override onDomainSelected(@etworkRegistrationInfo.Domain int domain, boolean useEmergencyPdn)131 public void onDomainSelected(@NetworkRegistrationInfo.Domain int domain, 132 boolean useEmergencyPdn) { 133 DomainSelectionConnection.this.onDomainSelected(domain, useEmergencyPdn); 134 } 135 136 @Override onCancel()137 public void onCancel() { 138 DomainSelectionConnection.this.onCancel(); 139 } 140 } 141 142 protected final class DomainSelectionConnectionHandler extends Handler { DomainSelectionConnectionHandler(Looper looper)143 DomainSelectionConnectionHandler(Looper looper) { 144 super(looper); 145 } 146 147 @Override handleMessage(Message msg)148 public void handleMessage(Message msg) { 149 AsyncResult ar; 150 switch (msg.what) { 151 case EVENT_EMERGENCY_NETWORK_SCAN_RESULT: 152 mIsWaitingForScanResult = false; 153 if (mResultCallback == null) break; 154 ar = (AsyncResult) msg.obj; 155 EmergencyRegResult regResult = (EmergencyRegResult) ar.result; 156 if (DBG) logd("EVENT_EMERGENCY_NETWORK_SCAN_RESULT result=" + regResult); 157 CompletableFuture.runAsync( 158 () -> mResultCallback.accept(regResult), 159 mController.getDomainSelectionServiceExecutor()).join(); 160 break; 161 case EVENT_QUALIFIED_NETWORKS_CHANGED: 162 onQualifiedNetworksChanged(); 163 break; 164 default: 165 loge("handleMessage unexpected msg=" + msg.what); 166 break; 167 } 168 } 169 } 170 171 protected String mTag = "DomainSelectionConnection"; 172 173 private final LocalLog mLocalLog = new LocalLog(30); 174 private final @NonNull TransportSelectorCallback mTransportSelectorCallback; 175 176 /** 177 * Controls the communication between {@link DomainSelectionConnection} and 178 * {@link DomainSelectionService}. 179 */ 180 private final @NonNull DomainSelectionController mController; 181 /** Indicates whether the requested service is for emergency services. */ 182 private final boolean mIsEmergency; 183 184 /** Interface to receive the request to trigger emergency network scan and selected domain. */ 185 private @Nullable WwanSelectorCallback mWwanSelectorCallback; 186 /** Interface to return the result of emergency network scan. */ 187 private @Nullable Consumer<EmergencyRegResult> mResultCallback; 188 /** Interface to the {@link DomainSelector} created for this service. */ 189 private @Nullable DomainSelector mDomainSelector; 190 191 /** The slot requested this connection. */ 192 protected @NonNull Phone mPhone; 193 /** The requested domain selector type. */ 194 private @DomainSelectionService.SelectorType int mSelectorType; 195 196 /** The attributes required to determine the domain. */ 197 private @Nullable DomainSelectionService.SelectionAttributes mSelectionAttributes; 198 199 private @Nullable Looper mLooper; 200 protected @Nullable DomainSelectionConnectionHandler mHandler; 201 private boolean mRegisteredRegistrant; 202 private boolean mIsWaitingForScanResult; 203 204 private @NonNull AndroidFuture<Integer> mOnComplete; 205 206 private @Nullable Executor mWwanSelectedExecutor; 207 208 /** 209 * Creates an instance. 210 * 211 * @param phone For which this service is requested. 212 * @param selectorType Indicates the type of the requested service. 213 * @param isEmergency Indicates whether this request is for emergency service. 214 * @param controller The controller to communicate with the domain selection service. 215 */ DomainSelectionConnection(@onNull Phone phone, @DomainSelectionService.SelectorType int selectorType, boolean isEmergency, @NonNull DomainSelectionController controller)216 public DomainSelectionConnection(@NonNull Phone phone, 217 @DomainSelectionService.SelectorType int selectorType, boolean isEmergency, 218 @NonNull DomainSelectionController controller) { 219 mController = controller; 220 mPhone = phone; 221 mSelectorType = selectorType; 222 mIsEmergency = isEmergency; 223 224 mTransportSelectorCallback = new TransportSelectorCallbackWrapper(); 225 mOnComplete = new AndroidFuture<>(); 226 } 227 228 /** 229 * Returns the attributes required to determine the domain for a telephony service. 230 * 231 * @return The attributes required to determine the domain. 232 */ getSelectionAttributes()233 public @Nullable DomainSelectionService.SelectionAttributes getSelectionAttributes() { 234 return mSelectionAttributes; 235 } 236 237 /** 238 * Returns the interface for the callbacks. 239 * 240 * @return The {@link TransportSelectorCallback} interface. 241 */ 242 @VisibleForTesting getTransportSelectorCallback()243 public @NonNull TransportSelectorCallback getTransportSelectorCallback() { 244 return mTransportSelectorCallback; 245 } 246 247 /** 248 * Returns the {@link CompletableFuture} to receive the selected domain. 249 * 250 * @return The callback to receive response. 251 */ getCompletableFuture()252 public @NonNull CompletableFuture<Integer> getCompletableFuture() { 253 return mOnComplete; 254 } 255 256 /** 257 * Returs the {@link Phone} which requested this connection. 258 * 259 * @return The {@link Phone} instance. 260 */ getPhone()261 public @NonNull Phone getPhone() { 262 return mPhone; 263 } 264 265 /** 266 * Requests the domain selection servic to select a domain. 267 * 268 * @param attr The attributes required to determine the domain. 269 */ 270 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED) selectDomain(@onNull DomainSelectionService.SelectionAttributes attr)271 public void selectDomain(@NonNull DomainSelectionService.SelectionAttributes attr) { 272 mSelectionAttributes = attr; 273 mController.selectDomain(attr, getTransportSelectorCallback()); 274 } 275 276 /** 277 * Notifies that {@link DomainSelector} instance has been created for the selection request. 278 */ onCreated()279 public void onCreated() { 280 // Can be overridden if required 281 } 282 283 /** 284 * Notifies that WLAN transport has been selected. 285 */ onWlanSelected()286 public void onWlanSelected() { 287 // Can be overridden. 288 } 289 290 /** 291 * Notifies that WLAN transport has been selected. 292 * 293 * @param useEmergencyPdn Indicates whether Wi-Fi emergency services use emergency PDN or not. 294 */ onWlanSelected(boolean useEmergencyPdn)295 public void onWlanSelected(boolean useEmergencyPdn) { 296 // Can be overridden. 297 onWlanSelected(); 298 } 299 300 /** 301 * Notifies that WWAN transport has been selected. 302 */ onWwanSelected()303 public void onWwanSelected() { 304 // Can be overridden. 305 } 306 307 /** 308 * Notifies that selection has terminated because there is no decision that can be made 309 * or a timeout has occurred. The call should be terminated when this method is called. 310 * 311 * @param cause Indicates the reason. 312 */ onSelectionTerminated(@isconnectCauses int cause)313 public void onSelectionTerminated(@DisconnectCauses int cause) { 314 // Can be overridden. 315 } 316 317 /** 318 * Requests the emergency network scan. 319 * 320 * @param preferredNetworks The ordered list of preferred networks to scan. 321 * @param scanType Indicates the scan preference, such as full service or limited service. 322 */ onRequestEmergencyNetworkScan( @onNull @adioAccessNetworkType int[] preferredNetworks, @EmergencyScanType int scanType)323 public void onRequestEmergencyNetworkScan( 324 @NonNull @RadioAccessNetworkType int[] preferredNetworks, 325 @EmergencyScanType int scanType) { 326 // Can be overridden if required 327 if (!mRegisteredRegistrant) { 328 mPhone.registerForEmergencyNetworkScan(mHandler, 329 EVENT_EMERGENCY_NETWORK_SCAN_RESULT, null); 330 mRegisteredRegistrant = true; 331 } 332 mIsWaitingForScanResult = true; 333 mPhone.triggerEmergencyNetworkScan(preferredNetworks, scanType, null); 334 } 335 336 /** 337 * Notifies the domain selected. 338 * 339 * @param domain The selected domain. 340 */ onDomainSelected(@etworkRegistrationInfo.Domain int domain)341 public void onDomainSelected(@NetworkRegistrationInfo.Domain int domain) { 342 // Can be overridden if required 343 CompletableFuture<Integer> future = getCompletableFuture(); 344 future.complete(domain); 345 } 346 347 /** 348 * Notifies the domain selected. 349 * 350 * @param domain The selected domain. 351 * @param useEmergencyPdn Indicates whether emergency services use emergency PDN or not. 352 */ onDomainSelected(@etworkRegistrationInfo.Domain int domain, boolean useEmergencyPdn)353 public void onDomainSelected(@NetworkRegistrationInfo.Domain int domain, 354 boolean useEmergencyPdn) { 355 // Can be overridden if required 356 onDomainSelected(domain); 357 } 358 359 /** 360 * Notifies that the emergency network scan is canceled. 361 */ onCancel()362 public void onCancel() { 363 // Can be overridden if required 364 onCancel(false); 365 } 366 onCancel(boolean resetScan)367 private void onCancel(boolean resetScan) { 368 if (mIsWaitingForScanResult) { 369 mIsWaitingForScanResult = false; 370 mPhone.cancelEmergencyNetworkScan(resetScan, null); 371 } 372 } 373 374 /** 375 * Cancels an ongoing selection operation. It is up to the {@link DomainSelectionService} 376 * to clean up all ongoing operations with the framework. 377 */ cancelSelection()378 public void cancelSelection() { 379 if (mDomainSelector == null) return; 380 mDomainSelector.cancelSelection(); 381 dispose(); 382 } 383 384 /** 385 * Requests the domain selection service to reselect a domain. 386 * 387 * @param attr The attributes required to determine the domain. 388 * @return The callback to receive the response. 389 */ reselectDomain( @onNull DomainSelectionService.SelectionAttributes attr)390 public @NonNull CompletableFuture<Integer> reselectDomain( 391 @NonNull DomainSelectionService.SelectionAttributes attr) { 392 mSelectionAttributes = attr; 393 if (mDomainSelector == null) return null; 394 mOnComplete = new AndroidFuture<>(); 395 mDomainSelector.reselectDomain(attr); 396 return mOnComplete; 397 } 398 399 /** 400 * Finishes the selection procedure and cleans everything up. 401 */ finishSelection()402 public void finishSelection() { 403 if (mDomainSelector == null) return; 404 mDomainSelector.finishSelection(); 405 dispose(); 406 } 407 408 /** Indicates that the service connection has been removed. */ onServiceDisconnected()409 public void onServiceDisconnected() { 410 // Can be overridden. 411 dispose(); 412 } 413 dispose()414 private void dispose() { 415 if (mRegisteredRegistrant) { 416 mPhone.unregisterForEmergencyNetworkScan(mHandler); 417 mRegisteredRegistrant = false; 418 } 419 onCancel(true); 420 mController.removeConnection(this); 421 if (mLooper != null) mLooper.quitSafely(); 422 mLooper = null; 423 mHandler = null; 424 } 425 initHandler()426 protected void initHandler() { 427 if (mLooper == null) { 428 HandlerThread handlerThread = new HandlerThread(mTag); 429 handlerThread.start(); 430 mLooper = handlerThread.getLooper(); 431 } 432 if (mHandler == null) mHandler = new DomainSelectionConnectionHandler(mLooper); 433 } 434 435 /** 436 * Notifies the change of qualified networks. 437 */ onQualifiedNetworksChanged()438 protected void onQualifiedNetworksChanged() { 439 if (mIsEmergency 440 && (mSelectorType == DomainSelectionService.SELECTOR_TYPE_CALLING)) { 441 // DomainSelectionConnection for emergency calls shall override this. 442 throw new IllegalStateException("DomainSelectionConnection for emergency calls" 443 + " should override onQualifiedNetworksChanged()"); 444 } 445 } 446 447 /** 448 * Dumps local log. 449 */ dump(@onNull PrintWriter printWriter)450 public void dump(@NonNull PrintWriter printWriter) { 451 mLocalLog.dump(printWriter); 452 } 453 logd(String msg)454 protected void logd(String msg) { 455 Log.d(mTag, msg); 456 } 457 logi(String msg)458 protected void logi(String msg) { 459 Log.i(mTag, msg); 460 mLocalLog.log(msg); 461 } 462 loge(String msg)463 protected void loge(String msg) { 464 Log.e(mTag, msg); 465 mLocalLog.log(msg); 466 } 467 } 468