891 lines
34 KiB
Java
891 lines
34 KiB
Java
![]() |
/*
|
|||
|
* Copyright (C) 2022 The Android Open Source Project
|
|||
|
*
|
|||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|||
|
* you may not use this file except in compliance with the License.
|
|||
|
* You may obtain a copy of the License at
|
|||
|
*
|
|||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
|
*
|
|||
|
* Unless required by applicable law or agreed to in writing, software
|
|||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
|
* See the License for the specific language governing permissions and
|
|||
|
* limitations under the License.
|
|||
|
*/
|
|||
|
|
|||
|
package android.telephony;
|
|||
|
|
|||
|
import android.annotation.FlaggedApi;
|
|||
|
import android.annotation.IntDef;
|
|||
|
import android.annotation.NonNull;
|
|||
|
import android.annotation.Nullable;
|
|||
|
import android.annotation.SuppressLint;
|
|||
|
import android.annotation.SystemApi;
|
|||
|
import android.app.Service;
|
|||
|
import android.content.Intent;
|
|||
|
import android.net.Uri;
|
|||
|
import android.os.Build;
|
|||
|
import android.os.CancellationSignal;
|
|||
|
import android.os.IBinder;
|
|||
|
import android.os.Parcel;
|
|||
|
import android.os.Parcelable;
|
|||
|
import android.os.RemoteException;
|
|||
|
import android.telephony.Annotation.DisconnectCauses;
|
|||
|
import android.telephony.Annotation.PreciseDisconnectCauses;
|
|||
|
import android.telephony.ims.ImsReasonInfo;
|
|||
|
import android.text.TextUtils;
|
|||
|
import android.util.Log;
|
|||
|
|
|||
|
import com.android.internal.telephony.IDomainSelectionServiceController;
|
|||
|
import com.android.internal.telephony.IDomainSelector;
|
|||
|
import com.android.internal.telephony.ITransportSelectorCallback;
|
|||
|
import com.android.internal.telephony.ITransportSelectorResultCallback;
|
|||
|
import com.android.internal.telephony.IWwanSelectorCallback;
|
|||
|
import com.android.internal.telephony.IWwanSelectorResultCallback;
|
|||
|
import com.android.internal.telephony.flags.Flags;
|
|||
|
import com.android.internal.telephony.util.TelephonyUtils;
|
|||
|
import com.android.telephony.Rlog;
|
|||
|
|
|||
|
import java.lang.annotation.Retention;
|
|||
|
import java.lang.annotation.RetentionPolicy;
|
|||
|
import java.lang.ref.WeakReference;
|
|||
|
import java.util.List;
|
|||
|
import java.util.Objects;
|
|||
|
import java.util.concurrent.CancellationException;
|
|||
|
import java.util.concurrent.CompletableFuture;
|
|||
|
import java.util.concurrent.CompletionException;
|
|||
|
import java.util.concurrent.Executor;
|
|||
|
import java.util.function.Consumer;
|
|||
|
|
|||
|
/**
|
|||
|
* Base domain selection implementation.
|
|||
|
* <p>
|
|||
|
* Services that extend {@link DomainSelectionService} must register the service in their
|
|||
|
* AndroidManifest.xml to be detected by the framework.
|
|||
|
* <p>
|
|||
|
* 1) The application must declare that they use the
|
|||
|
* android.permission.BIND_DOMAIN_SELECTION_SERVICE permission.
|
|||
|
* <p>
|
|||
|
* 2) The DomainSelectionService definition in the manifest must follow this format:
|
|||
|
* <pre>
|
|||
|
* {@code
|
|||
|
* ...
|
|||
|
* <service android:name=".EgDomainSelectionService"
|
|||
|
* android:permission="android.permission.BIND_DOMAIN_SELECTION_SERVICE" >
|
|||
|
* <intent-filter>
|
|||
|
* <action android:name="android.telephony.DomainSelectionService" />
|
|||
|
* </intent-filter>
|
|||
|
* </service>
|
|||
|
* ...
|
|||
|
* }
|
|||
|
* </pre>
|
|||
|
* <p>
|
|||
|
* The ComponentName corresponding to this DomainSelectionService component MUST also be set
|
|||
|
* as the system domain selection implementation in order to be bound.
|
|||
|
* The system domain selection implementation is set in the device overlay for
|
|||
|
* {@code config_domain_selection_service_component_name}
|
|||
|
* in {@code packages/services/Telephony/res/values/config.xml}.
|
|||
|
*
|
|||
|
* @hide
|
|||
|
*/
|
|||
|
@SystemApi
|
|||
|
@FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
|
|||
|
public abstract class DomainSelectionService extends Service {
|
|||
|
|
|||
|
private static final String LOG_TAG = "DomainSelectionService";
|
|||
|
|
|||
|
/**
|
|||
|
* The intent that must be defined as an intent-filter in the AndroidManifest of the
|
|||
|
* {@link DomainSelectionService}.
|
|||
|
*
|
|||
|
* @hide
|
|||
|
*/
|
|||
|
public static final String SERVICE_INTERFACE = "android.telephony.DomainSelectionService";
|
|||
|
|
|||
|
/** @hide */
|
|||
|
@Retention(RetentionPolicy.SOURCE)
|
|||
|
@IntDef(prefix = "SELECTOR_TYPE_",
|
|||
|
value = {
|
|||
|
SELECTOR_TYPE_CALLING,
|
|||
|
SELECTOR_TYPE_SMS})
|
|||
|
public @interface SelectorType {}
|
|||
|
|
|||
|
/** Indicates the domain selector type for calling. */
|
|||
|
public static final int SELECTOR_TYPE_CALLING = 1;
|
|||
|
/** Indicates the domain selector type for sms. */
|
|||
|
public static final int SELECTOR_TYPE_SMS = 2;
|
|||
|
|
|||
|
/** Indicates that the modem can scan for emergency service as per modem’s implementation. */
|
|||
|
public static final int SCAN_TYPE_NO_PREFERENCE = 0;
|
|||
|
|
|||
|
/** Indicates that the modem will scan for emergency service in limited service mode. */
|
|||
|
public static final int SCAN_TYPE_LIMITED_SERVICE = 1;
|
|||
|
|
|||
|
/** Indicates that the modem will scan for emergency service in full service mode. */
|
|||
|
public static final int SCAN_TYPE_FULL_SERVICE = 2;
|
|||
|
|
|||
|
/** @hide */
|
|||
|
@Retention(RetentionPolicy.SOURCE)
|
|||
|
@IntDef(prefix = "SCAN_TYPE_",
|
|||
|
value = {
|
|||
|
SCAN_TYPE_NO_PREFERENCE,
|
|||
|
SCAN_TYPE_LIMITED_SERVICE,
|
|||
|
SCAN_TYPE_FULL_SERVICE})
|
|||
|
public @interface EmergencyScanType {}
|
|||
|
|
|||
|
/**
|
|||
|
* Contains attributes required to determine the domain for a telephony service.
|
|||
|
*/
|
|||
|
@FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
|
|||
|
public static final class SelectionAttributes implements Parcelable {
|
|||
|
|
|||
|
private static final String TAG = "SelectionAttributes";
|
|||
|
|
|||
|
private int mSlotIndex;
|
|||
|
private int mSubId;
|
|||
|
private @Nullable String mCallId;
|
|||
|
private @Nullable Uri mAddress;
|
|||
|
private @SelectorType int mSelectorType;
|
|||
|
private boolean mIsVideoCall;
|
|||
|
private boolean mIsEmergency;
|
|||
|
private boolean mIsTestEmergencyNumber;
|
|||
|
private boolean mIsExitedFromAirplaneMode;
|
|||
|
private @Nullable ImsReasonInfo mImsReasonInfo;
|
|||
|
private @PreciseDisconnectCauses int mCause;
|
|||
|
private @Nullable EmergencyRegistrationResult mEmergencyRegistrationResult;
|
|||
|
|
|||
|
/**
|
|||
|
* @param slotIndex The logical slot index.
|
|||
|
* @param subscriptionId The subscription identifier.
|
|||
|
* @param callId The call identifier.
|
|||
|
* @param address The dialed address.
|
|||
|
* @param selectorType Indicates the requested domain selector type.
|
|||
|
* @param video Indicates it's a video call.
|
|||
|
* @param emergency Indicates it's emergency service.
|
|||
|
* @param isTest Indicates it's a test emergency number.
|
|||
|
* @param exited {@code true} if the request caused the device to move out of airplane mode.
|
|||
|
* @param imsReasonInfo The reason why the last PS attempt failed.
|
|||
|
* @param cause The reason why the last CS attempt failed.
|
|||
|
* @param regResult The current registration result for emergency services.
|
|||
|
*/
|
|||
|
private SelectionAttributes(int slotIndex, int subscriptionId, @Nullable String callId,
|
|||
|
@Nullable Uri address, @SelectorType int selectorType,
|
|||
|
boolean video, boolean emergency, boolean isTest, boolean exited,
|
|||
|
@Nullable ImsReasonInfo imsReasonInfo, @PreciseDisconnectCauses int cause,
|
|||
|
@Nullable EmergencyRegistrationResult regResult) {
|
|||
|
mSlotIndex = slotIndex;
|
|||
|
mSubId = subscriptionId;
|
|||
|
mCallId = callId;
|
|||
|
mAddress = address;
|
|||
|
mSelectorType = selectorType;
|
|||
|
mIsVideoCall = video;
|
|||
|
mIsEmergency = emergency;
|
|||
|
mIsTestEmergencyNumber = isTest;
|
|||
|
mIsExitedFromAirplaneMode = exited;
|
|||
|
mImsReasonInfo = imsReasonInfo;
|
|||
|
mCause = cause;
|
|||
|
mEmergencyRegistrationResult = regResult;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Copy constructor.
|
|||
|
*
|
|||
|
* @param s Source selection attributes.
|
|||
|
* @hide
|
|||
|
*/
|
|||
|
public SelectionAttributes(@NonNull SelectionAttributes s) {
|
|||
|
mSlotIndex = s.mSlotIndex;
|
|||
|
mSubId = s.mSubId;
|
|||
|
mCallId = s.mCallId;
|
|||
|
mAddress = s.mAddress;
|
|||
|
mSelectorType = s.mSelectorType;
|
|||
|
mIsEmergency = s.mIsEmergency;
|
|||
|
mIsTestEmergencyNumber = s.mIsTestEmergencyNumber;
|
|||
|
mIsExitedFromAirplaneMode = s.mIsExitedFromAirplaneMode;
|
|||
|
mImsReasonInfo = s.mImsReasonInfo;
|
|||
|
mCause = s.mCause;
|
|||
|
mEmergencyRegistrationResult = s.mEmergencyRegistrationResult;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Constructs a SelectionAttributes object from the given parcel.
|
|||
|
*/
|
|||
|
private SelectionAttributes(@NonNull Parcel in) {
|
|||
|
readFromParcel(in);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* @return The logical slot index.
|
|||
|
*/
|
|||
|
public int getSlotIndex() {
|
|||
|
return mSlotIndex;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* @return The subscription identifier.
|
|||
|
*/
|
|||
|
public int getSubscriptionId() {
|
|||
|
return mSubId;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* @return The call identifier.
|
|||
|
*/
|
|||
|
public @Nullable String getCallId() {
|
|||
|
return mCallId;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* @return The dialed address.
|
|||
|
*/
|
|||
|
public @Nullable Uri getAddress() {
|
|||
|
return mAddress;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* @return The domain selector type.
|
|||
|
*/
|
|||
|
public @SelectorType int getSelectorType() {
|
|||
|
return mSelectorType;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* @return {@code true} if the request is for a video call.
|
|||
|
*/
|
|||
|
public boolean isVideoCall() {
|
|||
|
return mIsVideoCall;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* @return {@code true} if the request is for emergency services.
|
|||
|
*/
|
|||
|
public boolean isEmergency() {
|
|||
|
return mIsEmergency;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* @return {@code true} if the dialed number is a test emergency number.
|
|||
|
*/
|
|||
|
public boolean isTestEmergencyNumber() {
|
|||
|
return mIsTestEmergencyNumber;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* @return {@code true} if the request caused the device to move out of airplane mode.
|
|||
|
*/
|
|||
|
public boolean isExitedFromAirplaneMode() {
|
|||
|
return mIsExitedFromAirplaneMode;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* @return The PS disconnect cause if trying over PS resulted in a failure and
|
|||
|
* reselection is required.
|
|||
|
*/
|
|||
|
public @Nullable ImsReasonInfo getPsDisconnectCause() {
|
|||
|
return mImsReasonInfo;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* @return The CS disconnect cause if trying over CS resulted in a failure and
|
|||
|
* reselection is required.
|
|||
|
*/
|
|||
|
public @PreciseDisconnectCauses int getCsDisconnectCause() {
|
|||
|
return mCause;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* @return The current registration state of cellular network.
|
|||
|
*/
|
|||
|
public @Nullable EmergencyRegistrationResult getEmergencyRegistrationResult() {
|
|||
|
return mEmergencyRegistrationResult;
|
|||
|
}
|
|||
|
|
|||
|
@Override
|
|||
|
public @NonNull String toString() {
|
|||
|
return "{ slotIndex=" + mSlotIndex
|
|||
|
+ ", subId=" + mSubId
|
|||
|
+ ", callId=" + mCallId
|
|||
|
+ ", address=" + (Build.IS_DEBUGGABLE ? mAddress : "***")
|
|||
|
+ ", type=" + mSelectorType
|
|||
|
+ ", videoCall=" + mIsVideoCall
|
|||
|
+ ", emergency=" + mIsEmergency
|
|||
|
+ ", isTest=" + mIsTestEmergencyNumber
|
|||
|
+ ", airplaneMode=" + mIsExitedFromAirplaneMode
|
|||
|
+ ", reasonInfo=" + mImsReasonInfo
|
|||
|
+ ", cause=" + mCause
|
|||
|
+ ", regResult=" + mEmergencyRegistrationResult
|
|||
|
+ " }";
|
|||
|
}
|
|||
|
|
|||
|
@Override
|
|||
|
public boolean equals(Object o) {
|
|||
|
if (this == o) return true;
|
|||
|
if (o == null || getClass() != o.getClass()) return false;
|
|||
|
SelectionAttributes that = (SelectionAttributes) o;
|
|||
|
return mSlotIndex == that.mSlotIndex && mSubId == that.mSubId
|
|||
|
&& TextUtils.equals(mCallId, that.mCallId)
|
|||
|
&& equalsHandlesNulls(mAddress, that.mAddress)
|
|||
|
&& mSelectorType == that.mSelectorType && mIsVideoCall == that.mIsVideoCall
|
|||
|
&& mIsEmergency == that.mIsEmergency
|
|||
|
&& mIsTestEmergencyNumber == that.mIsTestEmergencyNumber
|
|||
|
&& mIsExitedFromAirplaneMode == that.mIsExitedFromAirplaneMode
|
|||
|
&& equalsHandlesNulls(mImsReasonInfo, that.mImsReasonInfo)
|
|||
|
&& mCause == that.mCause
|
|||
|
&& equalsHandlesNulls(mEmergencyRegistrationResult,
|
|||
|
that.mEmergencyRegistrationResult);
|
|||
|
}
|
|||
|
|
|||
|
@Override
|
|||
|
public int hashCode() {
|
|||
|
return Objects.hash(mCallId, mAddress, mImsReasonInfo,
|
|||
|
mIsVideoCall, mIsEmergency, mIsTestEmergencyNumber, mIsExitedFromAirplaneMode,
|
|||
|
mEmergencyRegistrationResult, mSlotIndex, mSubId, mSelectorType, mCause);
|
|||
|
}
|
|||
|
|
|||
|
@Override
|
|||
|
public int describeContents() {
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
@Override
|
|||
|
public void writeToParcel(@NonNull Parcel out, int flags) {
|
|||
|
out.writeInt(mSlotIndex);
|
|||
|
out.writeInt(mSubId);
|
|||
|
out.writeString8(mCallId);
|
|||
|
out.writeParcelable(mAddress, 0);
|
|||
|
out.writeInt(mSelectorType);
|
|||
|
out.writeBoolean(mIsVideoCall);
|
|||
|
out.writeBoolean(mIsEmergency);
|
|||
|
out.writeBoolean(mIsTestEmergencyNumber);
|
|||
|
out.writeBoolean(mIsExitedFromAirplaneMode);
|
|||
|
out.writeParcelable(mImsReasonInfo, 0);
|
|||
|
out.writeInt(mCause);
|
|||
|
out.writeParcelable(mEmergencyRegistrationResult, 0);
|
|||
|
}
|
|||
|
|
|||
|
private void readFromParcel(@NonNull Parcel in) {
|
|||
|
mSlotIndex = in.readInt();
|
|||
|
mSubId = in.readInt();
|
|||
|
mCallId = in.readString8();
|
|||
|
mAddress = in.readParcelable(Uri.class.getClassLoader(),
|
|||
|
android.net.Uri.class);
|
|||
|
mSelectorType = in.readInt();
|
|||
|
mIsVideoCall = in.readBoolean();
|
|||
|
mIsEmergency = in.readBoolean();
|
|||
|
mIsTestEmergencyNumber = in.readBoolean();
|
|||
|
mIsExitedFromAirplaneMode = in.readBoolean();
|
|||
|
mImsReasonInfo = in.readParcelable(ImsReasonInfo.class.getClassLoader(),
|
|||
|
android.telephony.ims.ImsReasonInfo.class);
|
|||
|
mCause = in.readInt();
|
|||
|
mEmergencyRegistrationResult = in.readParcelable(
|
|||
|
EmergencyRegistrationResult.class.getClassLoader(),
|
|||
|
EmergencyRegistrationResult.class);
|
|||
|
}
|
|||
|
|
|||
|
public static final @NonNull Creator<SelectionAttributes> CREATOR =
|
|||
|
new Creator<SelectionAttributes>() {
|
|||
|
@Override
|
|||
|
public SelectionAttributes createFromParcel(@NonNull Parcel in) {
|
|||
|
return new SelectionAttributes(in);
|
|||
|
}
|
|||
|
|
|||
|
@Override
|
|||
|
public SelectionAttributes[] newArray(int size) {
|
|||
|
return new SelectionAttributes[size];
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
private static boolean equalsHandlesNulls(Object a, Object b) {
|
|||
|
return (a == null) ? (b == null) : a.equals(b);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Builder class creating a new instance.
|
|||
|
*/
|
|||
|
@FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
|
|||
|
public static final class Builder {
|
|||
|
private final int mSlotIndex;
|
|||
|
private final int mSubId;
|
|||
|
private @Nullable String mCallId;
|
|||
|
private @Nullable Uri mAddress;
|
|||
|
private final @SelectorType int mSelectorType;
|
|||
|
private boolean mIsVideoCall;
|
|||
|
private boolean mIsEmergency;
|
|||
|
private boolean mIsTestEmergencyNumber;
|
|||
|
private boolean mIsExitedFromAirplaneMode;
|
|||
|
private @Nullable ImsReasonInfo mImsReasonInfo;
|
|||
|
private @PreciseDisconnectCauses int mCause;
|
|||
|
private @Nullable EmergencyRegistrationResult mEmergencyRegistrationResult;
|
|||
|
|
|||
|
/**
|
|||
|
* Default constructor for Builder.
|
|||
|
*/
|
|||
|
public Builder(int slotIndex, int subscriptionId, @SelectorType int selectorType) {
|
|||
|
mSlotIndex = slotIndex;
|
|||
|
mSubId = subscriptionId;
|
|||
|
mSelectorType = selectorType;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Sets the call identifier.
|
|||
|
*
|
|||
|
* @param callId The call identifier.
|
|||
|
* @return The same instance of the builder.
|
|||
|
*/
|
|||
|
public @NonNull Builder setCallId(@Nullable String callId) {
|
|||
|
mCallId = callId;
|
|||
|
return this;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Sets the dialed address.
|
|||
|
*
|
|||
|
* @param address The dialed address.
|
|||
|
* @return The same instance of the builder.
|
|||
|
*/
|
|||
|
public @NonNull Builder setAddress(@Nullable Uri address) {
|
|||
|
mAddress = address;
|
|||
|
return this;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Sets whether it's a video call or not.
|
|||
|
*
|
|||
|
* @param isVideo Indicates it's a video call.
|
|||
|
* @return The same instance of the builder.
|
|||
|
*/
|
|||
|
public @NonNull Builder setVideoCall(boolean isVideo) {
|
|||
|
mIsVideoCall = isVideo;
|
|||
|
return this;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Sets whether it's an emergency service or not.
|
|||
|
*
|
|||
|
* @param isEmergency Indicates it's emergency service.
|
|||
|
* @return The same instance of the builder.
|
|||
|
*/
|
|||
|
public @NonNull Builder setEmergency(boolean isEmergency) {
|
|||
|
mIsEmergency = isEmergency;
|
|||
|
return this;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Sets whether it's a test emergency number or not.
|
|||
|
*
|
|||
|
* @param isTest Indicates it's a test emergency number.
|
|||
|
* @return The same instance of the builder.
|
|||
|
*/
|
|||
|
public @NonNull Builder setTestEmergencyNumber(boolean isTest) {
|
|||
|
mIsTestEmergencyNumber = isTest;
|
|||
|
return this;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Sets whether the request caused the device to move out of airplane mode.
|
|||
|
*
|
|||
|
* @param exited {@code true} if the request caused the device to move out of
|
|||
|
* airplane mode.
|
|||
|
* @return The same instance of the builder.
|
|||
|
*/
|
|||
|
public @NonNull Builder setExitedFromAirplaneMode(boolean exited) {
|
|||
|
mIsExitedFromAirplaneMode = exited;
|
|||
|
return this;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Sets an optional reason why the last PS attempt failed.
|
|||
|
*
|
|||
|
* @param info The reason why the last PS attempt failed.
|
|||
|
* @return The same instance of the builder.
|
|||
|
*/
|
|||
|
public @NonNull Builder setPsDisconnectCause(@Nullable ImsReasonInfo info) {
|
|||
|
mImsReasonInfo = info;
|
|||
|
return this;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Sets an optional reason why the last CS attempt failed.
|
|||
|
*
|
|||
|
* @param cause The reason why the last CS attempt failed.
|
|||
|
* @return The same instance of the builder.
|
|||
|
*/
|
|||
|
public @NonNull Builder setCsDisconnectCause(@PreciseDisconnectCauses int cause) {
|
|||
|
mCause = cause;
|
|||
|
return this;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Sets the current registration result for emergency services.
|
|||
|
*
|
|||
|
* @param regResult The current registration result for emergency services.
|
|||
|
* @return The same instance of the builder.
|
|||
|
*/
|
|||
|
public @NonNull Builder setEmergencyRegistrationResult(
|
|||
|
@Nullable EmergencyRegistrationResult regResult) {
|
|||
|
mEmergencyRegistrationResult = regResult;
|
|||
|
return this;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Build the SelectionAttributes.
|
|||
|
* @return The SelectionAttributes object.
|
|||
|
*/
|
|||
|
public @NonNull SelectionAttributes build() {
|
|||
|
return new SelectionAttributes(mSlotIndex, mSubId, mCallId, mAddress,
|
|||
|
mSelectorType, mIsVideoCall, mIsEmergency, mIsTestEmergencyNumber,
|
|||
|
mIsExitedFromAirplaneMode, mImsReasonInfo,
|
|||
|
mCause, mEmergencyRegistrationResult);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* A wrapper class for ITransportSelectorCallback interface.
|
|||
|
*/
|
|||
|
private final class TransportSelectorCallbackWrapper implements TransportSelectorCallback {
|
|||
|
private static final String TAG = "TransportSelectorCallbackWrapper";
|
|||
|
|
|||
|
private final @NonNull ITransportSelectorCallback mCallback;
|
|||
|
private final @NonNull Executor mExecutor;
|
|||
|
|
|||
|
private @Nullable ITransportSelectorResultCallbackAdapter mResultCallback;
|
|||
|
private @Nullable DomainSelectorWrapper mSelectorWrapper;
|
|||
|
|
|||
|
TransportSelectorCallbackWrapper(@NonNull ITransportSelectorCallback cb,
|
|||
|
@NonNull Executor executor) {
|
|||
|
mCallback = cb;
|
|||
|
mExecutor = executor;
|
|||
|
}
|
|||
|
|
|||
|
@Override
|
|||
|
public void onCreated(@NonNull DomainSelector selector) {
|
|||
|
try {
|
|||
|
mSelectorWrapper = new DomainSelectorWrapper(selector, mExecutor);
|
|||
|
mCallback.onCreated(mSelectorWrapper.getCallbackBinder());
|
|||
|
} catch (Exception e) {
|
|||
|
Rlog.e(TAG, "onCreated e=" + e);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
@Override
|
|||
|
public void onWlanSelected(boolean useEmergencyPdn) {
|
|||
|
try {
|
|||
|
mCallback.onWlanSelected(useEmergencyPdn);
|
|||
|
} catch (Exception e) {
|
|||
|
Rlog.e(TAG, "onWlanSelected e=" + e);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
@Override
|
|||
|
public void onWwanSelected(Consumer<WwanSelectorCallback> consumer) {
|
|||
|
try {
|
|||
|
mResultCallback = new ITransportSelectorResultCallbackAdapter(consumer, mExecutor);
|
|||
|
mCallback.onWwanSelectedAsync(mResultCallback);
|
|||
|
} catch (Exception e) {
|
|||
|
Rlog.e(TAG, "onWwanSelected e=" + e);
|
|||
|
executeMethodAsyncNoException(mExecutor,
|
|||
|
() -> consumer.accept(null), TAG, "onWwanSelectedAsync-Exception");
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
@Override
|
|||
|
public void onSelectionTerminated(@DisconnectCauses int cause) {
|
|||
|
try {
|
|||
|
mCallback.onSelectionTerminated(cause);
|
|||
|
mSelectorWrapper = null;
|
|||
|
} catch (Exception e) {
|
|||
|
Rlog.e(TAG, "onSelectionTerminated e=" + e);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private class ITransportSelectorResultCallbackAdapter
|
|||
|
extends ITransportSelectorResultCallback.Stub {
|
|||
|
private final @NonNull Consumer<WwanSelectorCallback> mConsumer;
|
|||
|
private final @NonNull Executor mExecutor;
|
|||
|
|
|||
|
ITransportSelectorResultCallbackAdapter(
|
|||
|
@NonNull Consumer<WwanSelectorCallback> consumer,
|
|||
|
@NonNull Executor executor) {
|
|||
|
mConsumer = consumer;
|
|||
|
mExecutor = executor;
|
|||
|
}
|
|||
|
|
|||
|
@Override
|
|||
|
public void onCompleted(@NonNull IWwanSelectorCallback cb) {
|
|||
|
if (mConsumer == null) return;
|
|||
|
|
|||
|
WwanSelectorCallback callback = new WwanSelectorCallbackWrapper(cb, mExecutor);
|
|||
|
executeMethodAsyncNoException(mExecutor,
|
|||
|
() -> mConsumer.accept(callback), TAG, "onWwanSelectedAsync-Completed");
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* A wrapper class for IDomainSelector interface.
|
|||
|
*/
|
|||
|
private final class DomainSelectorWrapper {
|
|||
|
private static final String TAG = "DomainSelectorWrapper";
|
|||
|
|
|||
|
private @NonNull IDomainSelector mCallbackBinder;
|
|||
|
|
|||
|
DomainSelectorWrapper(@NonNull DomainSelector cb, @NonNull Executor executor) {
|
|||
|
mCallbackBinder = new IDomainSelectorAdapter(cb, executor);
|
|||
|
}
|
|||
|
|
|||
|
private class IDomainSelectorAdapter extends IDomainSelector.Stub {
|
|||
|
private final @NonNull WeakReference<DomainSelector> mDomainSelectorWeakRef;
|
|||
|
private final @NonNull Executor mExecutor;
|
|||
|
|
|||
|
IDomainSelectorAdapter(@NonNull DomainSelector domainSelector,
|
|||
|
@NonNull Executor executor) {
|
|||
|
mDomainSelectorWeakRef =
|
|||
|
new WeakReference<DomainSelector>(domainSelector);
|
|||
|
mExecutor = executor;
|
|||
|
}
|
|||
|
|
|||
|
@Override
|
|||
|
public void reselectDomain(@NonNull SelectionAttributes attr) {
|
|||
|
final DomainSelector domainSelector = mDomainSelectorWeakRef.get();
|
|||
|
if (domainSelector == null) return;
|
|||
|
|
|||
|
executeMethodAsyncNoException(mExecutor,
|
|||
|
() -> domainSelector.reselectDomain(attr), TAG, "reselectDomain");
|
|||
|
}
|
|||
|
|
|||
|
@Override
|
|||
|
public void finishSelection() {
|
|||
|
final DomainSelector domainSelector = mDomainSelectorWeakRef.get();
|
|||
|
if (domainSelector == null) return;
|
|||
|
|
|||
|
executeMethodAsyncNoException(mExecutor,
|
|||
|
() -> domainSelector.finishSelection(), TAG, "finishSelection");
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public @NonNull IDomainSelector getCallbackBinder() {
|
|||
|
return mCallbackBinder;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* A wrapper class for IWwanSelectorCallback and IWwanSelectorResultCallback.
|
|||
|
*/
|
|||
|
private final class WwanSelectorCallbackWrapper
|
|||
|
implements WwanSelectorCallback, CancellationSignal.OnCancelListener {
|
|||
|
private static final String TAG = "WwanSelectorCallbackWrapper";
|
|||
|
|
|||
|
private final @NonNull IWwanSelectorCallback mCallback;
|
|||
|
private final @NonNull Executor mExecutor;
|
|||
|
|
|||
|
private @Nullable IWwanSelectorResultCallbackAdapter mResultCallback;
|
|||
|
|
|||
|
WwanSelectorCallbackWrapper(@NonNull IWwanSelectorCallback cb,
|
|||
|
@NonNull Executor executor) {
|
|||
|
mCallback = cb;
|
|||
|
mExecutor = executor;
|
|||
|
}
|
|||
|
|
|||
|
@Override
|
|||
|
public void onCancel() {
|
|||
|
try {
|
|||
|
mCallback.onCancel();
|
|||
|
} catch (Exception e) {
|
|||
|
Rlog.e(TAG, "onCancel e=" + e);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
@Override
|
|||
|
public void onRequestEmergencyNetworkScan(@NonNull List<Integer> preferredNetworks,
|
|||
|
@EmergencyScanType int scanType, boolean resetScan,
|
|||
|
@NonNull CancellationSignal signal,
|
|||
|
@NonNull Consumer<EmergencyRegistrationResult> consumer) {
|
|||
|
try {
|
|||
|
if (signal != null) signal.setOnCancelListener(this);
|
|||
|
mResultCallback = new IWwanSelectorResultCallbackAdapter(consumer, mExecutor);
|
|||
|
mCallback.onRequestEmergencyNetworkScan(
|
|||
|
preferredNetworks.stream().mapToInt(Integer::intValue).toArray(),
|
|||
|
scanType, resetScan, mResultCallback);
|
|||
|
} catch (Exception e) {
|
|||
|
Rlog.e(TAG, "onRequestEmergencyNetworkScan e=" + e);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
@Override
|
|||
|
public void onDomainSelected(@NetworkRegistrationInfo.Domain int domain,
|
|||
|
boolean useEmergencyPdn) {
|
|||
|
try {
|
|||
|
mCallback.onDomainSelected(domain, useEmergencyPdn);
|
|||
|
} catch (Exception e) {
|
|||
|
Rlog.e(TAG, "onDomainSelected e=" + e);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private class IWwanSelectorResultCallbackAdapter
|
|||
|
extends IWwanSelectorResultCallback.Stub {
|
|||
|
private final @NonNull Consumer<EmergencyRegistrationResult> mConsumer;
|
|||
|
private final @NonNull Executor mExecutor;
|
|||
|
|
|||
|
IWwanSelectorResultCallbackAdapter(
|
|||
|
@NonNull Consumer<EmergencyRegistrationResult> consumer,
|
|||
|
@NonNull Executor executor) {
|
|||
|
mConsumer = consumer;
|
|||
|
mExecutor = executor;
|
|||
|
}
|
|||
|
|
|||
|
@Override
|
|||
|
public void onComplete(@NonNull EmergencyRegistrationResult result) {
|
|||
|
if (mConsumer == null) return;
|
|||
|
|
|||
|
executeMethodAsyncNoException(mExecutor,
|
|||
|
() -> mConsumer.accept(result), TAG, "onScanComplete");
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private final Object mExecutorLock = new Object();
|
|||
|
|
|||
|
/** Executor used to execute methods called remotely by the framework. */
|
|||
|
private @NonNull Executor mExecutor;
|
|||
|
|
|||
|
/**
|
|||
|
* Selects a calling domain given the SelectionAttributes of the call request.
|
|||
|
* <p>
|
|||
|
* When the framework generates a request to place a call, {@link #onDomainSelection}
|
|||
|
* will be called in order to determine the domain (CS or PS). For PS calls, the transport
|
|||
|
* (WWAN or WLAN) will also need to be determined.
|
|||
|
* <p>
|
|||
|
* Once the domain/transport has been selected or an error has occurred,
|
|||
|
* {@link TransportSelectorCallback} must be used to communicate the result back
|
|||
|
* to the framework.
|
|||
|
*
|
|||
|
* @param attr Required to determine the domain.
|
|||
|
* @param callback The callback instance being registered.
|
|||
|
*/
|
|||
|
public abstract void onDomainSelection(@NonNull SelectionAttributes attr,
|
|||
|
@NonNull TransportSelectorCallback callback);
|
|||
|
|
|||
|
/**
|
|||
|
* Notifies the change in {@link ServiceState} for a specific logical slot index.
|
|||
|
*
|
|||
|
* @param slotIndex For which the state changed.
|
|||
|
* @param subscriptionId For which the state changed.
|
|||
|
* @param serviceState Updated {@link ServiceState}.
|
|||
|
*/
|
|||
|
public void onServiceStateUpdated(int slotIndex, int subscriptionId,
|
|||
|
@NonNull ServiceState serviceState) {
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Notifies the change in {@link BarringInfo} for a specific logical slot index.
|
|||
|
*
|
|||
|
* @param slotIndex For which the state changed.
|
|||
|
* @param subscriptionId For which the state changed.
|
|||
|
* @param info Updated {@link BarringInfo}.
|
|||
|
*/
|
|||
|
public void onBarringInfoUpdated(int slotIndex, int subscriptionId, @NonNull BarringInfo info) {
|
|||
|
}
|
|||
|
|
|||
|
private final IBinder mDomainSelectionServiceController =
|
|||
|
new IDomainSelectionServiceController.Stub() {
|
|||
|
@Override
|
|||
|
public void selectDomain(@NonNull SelectionAttributes attr,
|
|||
|
@NonNull ITransportSelectorCallback callback) throws RemoteException {
|
|||
|
executeMethodAsync(getCachedExecutor(),
|
|||
|
() -> DomainSelectionService.this.onDomainSelection(attr,
|
|||
|
new TransportSelectorCallbackWrapper(callback, getCachedExecutor())),
|
|||
|
LOG_TAG, "onDomainSelection");
|
|||
|
}
|
|||
|
|
|||
|
@Override
|
|||
|
public void updateServiceState(int slotIndex, int subscriptionId,
|
|||
|
@NonNull ServiceState serviceState) {
|
|||
|
executeMethodAsyncNoException(getCachedExecutor(),
|
|||
|
() -> DomainSelectionService.this.onServiceStateUpdated(slotIndex,
|
|||
|
subscriptionId, serviceState), LOG_TAG, "onServiceStateUpdated");
|
|||
|
}
|
|||
|
|
|||
|
@Override
|
|||
|
public void updateBarringInfo(int slotIndex, int subscriptionId,
|
|||
|
@NonNull BarringInfo info) {
|
|||
|
executeMethodAsyncNoException(getCachedExecutor(),
|
|||
|
() -> DomainSelectionService.this.onBarringInfoUpdated(slotIndex,
|
|||
|
subscriptionId, info),
|
|||
|
LOG_TAG, "onBarringInfoUpdated");
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
private static void executeMethodAsync(@NonNull Executor executor, @NonNull Runnable r,
|
|||
|
@NonNull String tag, @NonNull String errorLogName) throws RemoteException {
|
|||
|
try {
|
|||
|
CompletableFuture.runAsync(
|
|||
|
() -> TelephonyUtils.runWithCleanCallingIdentity(r), executor).join();
|
|||
|
} catch (CancellationException | CompletionException e) {
|
|||
|
Rlog.w(tag, "Binder - " + errorLogName + " exception: " + e.getMessage());
|
|||
|
throw new RemoteException(e.getMessage());
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void executeMethodAsyncNoException(@NonNull Executor executor, @NonNull Runnable r,
|
|||
|
@NonNull String tag, @NonNull String errorLogName) {
|
|||
|
try {
|
|||
|
CompletableFuture.runAsync(
|
|||
|
() -> TelephonyUtils.runWithCleanCallingIdentity(r), executor);
|
|||
|
} catch (CancellationException | CompletionException e) {
|
|||
|
Rlog.w(tag, "Binder - " + errorLogName + " exception: " + e.getMessage());
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/** @hide */
|
|||
|
@Override
|
|||
|
public final @Nullable IBinder onBind(@Nullable Intent intent) {
|
|||
|
if (intent == null) return null;
|
|||
|
if (SERVICE_INTERFACE.equals(intent.getAction())) {
|
|||
|
Log.i(LOG_TAG, "DomainSelectionService Bound.");
|
|||
|
return mDomainSelectionServiceController;
|
|||
|
}
|
|||
|
return null;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* The Executor to use when calling callback methods from the framework.
|
|||
|
* <p>
|
|||
|
* By default, calls from the framework will use Binder threads to call these methods.
|
|||
|
*
|
|||
|
* @return an {@link Executor} used to execute methods called remotely by the framework.
|
|||
|
*/
|
|||
|
@SuppressLint("OnNameExpected")
|
|||
|
public @NonNull Executor getCreateExecutor() {
|
|||
|
return Runnable::run;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Gets the {@link Executor} which executes methods of this service.
|
|||
|
* This method should be private when this service is implemented in a separated process
|
|||
|
* other than telephony framework.
|
|||
|
* @return {@link Executor} instance.
|
|||
|
* @hide
|
|||
|
*/
|
|||
|
public final @NonNull Executor getCachedExecutor() {
|
|||
|
synchronized (mExecutorLock) {
|
|||
|
if (mExecutor == null) {
|
|||
|
Executor e = getCreateExecutor();
|
|||
|
mExecutor = (e != null) ? e : Runnable::run;
|
|||
|
}
|
|||
|
return mExecutor;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Returns a string representation of the domain.
|
|||
|
* @param domain The domain.
|
|||
|
* @return The name of the domain.
|
|||
|
* @hide
|
|||
|
*/
|
|||
|
public static @NonNull String getDomainName(@NetworkRegistrationInfo.Domain int domain) {
|
|||
|
return NetworkRegistrationInfo.domainToString(domain);
|
|||
|
}
|
|||
|
}
|