/*
* Copyright (C) 2017 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.ims;
import android.annotation.FlaggedApi;
import android.annotation.LongDef;
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.os.IBinder;
import android.os.RemoteException;
import android.telephony.CarrierConfigManager;
import android.telephony.ims.aidl.IImsConfig;
import android.telephony.ims.aidl.IImsMmTelFeature;
import android.telephony.ims.aidl.IImsRcsFeature;
import android.telephony.ims.aidl.IImsRegistration;
import android.telephony.ims.aidl.IImsServiceController;
import android.telephony.ims.aidl.IImsServiceControllerListener;
import android.telephony.ims.aidl.ISipTransport;
import android.telephony.ims.feature.ImsFeature;
import android.telephony.ims.feature.MmTelFeature;
import android.telephony.ims.feature.RcsFeature;
import android.telephony.ims.stub.ImsConfigImplBase;
import android.telephony.ims.stub.ImsFeatureConfiguration;
import android.telephony.ims.stub.ImsRegistrationImplBase;
import android.telephony.ims.stub.SipTransportImplBase;
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import com.android.ims.internal.IImsFeatureStatusCallback;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.flags.Flags;
import com.android.internal.telephony.util.TelephonyUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.function.Supplier;
/**
* Main ImsService implementation, which binds via the Telephony ImsResolver. Services that extend
* ImsService must register the service in their AndroidManifest to be detected by the framework.
* First, the application must declare that they use the "android.permission.BIND_IMS_SERVICE"
* permission. Then, the ImsService definition in the manifest must follow the following format:
*
* ...
*
* Note: This should never be set by {@link #getImsServiceCapabilities()}, as whether it is * there or not depends on whether or not {@link ImsFeature#FEATURE_EMERGENCY_MMTEL} is defined * for this ImsService. If it is set, it will be removed during sanitization before the final * capabilities bitfield is sent back to the framework. * @hide This is encoded into the {@link ImsFeature#FEATURE_EMERGENCY_MMTEL}, but we will be * adding other capabilities in a central location, so track this capability here as well. */ public static final long CAPABILITY_EMERGENCY_OVER_MMTEL = 1 << 0; /** * This ImsService supports the capability to create SIP delegates for other IMS applications * to use to proxy SIP messaging traffic through it. *
* In order for the framework to report SipDelegate creation as being available for this * ImsService implementation, this ImsService must report this capability flag in * {@link #getImsServiceCapabilities()}, {@link #getSipTransport(int)} must not return null, and * this ImsService MUST report the ability to create both {@link ImsFeature#FEATURE_MMTEL} and * {@link ImsFeature#FEATURE_RCS} features. */ public static final long CAPABILITY_SIP_DELEGATE_CREATION = 1 << 1; /** * This ImsService supports the terminal based call waiting service. *
* In order for the IMS service to support the service, IMS service shall * override {@link MmTelFeature#setTerminalBasedCallWaitingStatus}. * If ImsService has this capability, Android platform will handle the synchronization * between the network based call waiting service over circuit-switched networks and the * terminal based call waiting service of IMS service, and will handle the received * circuit-switched waiting calls. Otherwise, this functionality of Android platform shall * be disabled. */ public static final long CAPABILITY_TERMINAL_BASED_CALL_WAITING = 1 << 2; /** * This ImsService supports the capability to manage calls on multiple subscriptions at the same * time. *
* When set, this ImsService supports managing calls on multiple subscriptions at the same time * for all WLAN network configurations. Telephony will allow new outgoing/incoming IMS calls to * be set up on other subscriptions while there is an ongoing call. The ImsService must also * support managing calls on WWAN + WWAN configurations whenever the modem also reports * simultaneous calling availability, which can be listened to using the * {@link android.telephony.TelephonyCallback.SimultaneousCellularCallingSupportListener} API. * Telephony will only allow additional ongoing/incoming IMS calls on another subscription to be * set up on WWAN + WWAN configurations when the modem reports that simultaneous cellular * calling is allowed at the current time on both subscriptions where there are ongoing calls. *
* When unset (default), this ImsService can not support calls on multiple subscriptions at the
* same time for any WLAN or WWAN configurations, so pending outgoing call placed on another
* cellular subscription while there is an ongoing call will be cancelled by Telephony.
* Similarly, any incoming call notification on another cellular subscription while there is an
* ongoing call will be rejected.
* @hide TODO: move this to system API when we have a backing implementation + CTS testing
*/
@FlaggedApi(Flags.FLAG_SIMULTANEOUS_CALLING_INDICATIONS)
public static final long CAPABILITY_SUPPORTS_SIMULTANEOUS_CALLING = 1 << 3;
/**
* Used for internal correctness checks of capabilities set by the ImsService implementation and
* tracks the index of the largest defined flag in the capabilities long.
* @hide
*/
public static final long CAPABILITY_MAX_INDEX =
Long.numberOfTrailingZeros(CAPABILITY_SUPPORTS_SIMULTANEOUS_CALLING);
/**
* @hide
*/
@LongDef(flag = true,
prefix = "CAPABILITY_",
value = {
// CAPABILITY_EMERGENCY_OVER_MMTEL is not included here because it is managed by
// whether or not ImsFeature.FEATURE_EMERGENCY_MMTEL feature is set and should
// not be set by users of ImsService.
CAPABILITY_SIP_DELEGATE_CREATION,
CAPABILITY_TERMINAL_BASED_CALL_WAITING,
CAPABILITY_SUPPORTS_SIMULTANEOUS_CALLING
})
@Retention(RetentionPolicy.SOURCE)
public @interface ImsServiceCapability {}
/**
* Used for logging purposes, see {@link #getCapabilitiesString(long)}
* @hide
*/
private static final Map
* Method stubs called from the framework will be called asynchronously. Vendor specifies the
* {@link Executor} that the methods stubs will be called. If mExecutor is set to null by
* vendor use Runnable::run.
*/
public ImsService() {
}
/**
* Listener that notifies the framework of ImsService changes.
* @hide
*/
public static class Listener extends IImsServiceControllerListener.Stub {
/**
* The IMS features that this ImsService supports has changed.
* @param c a new {@link ImsFeatureConfiguration} containing {@link ImsFeature.FeatureType}s
* that this ImsService supports. This may trigger the addition/removal of feature
* in this service.
*/
public void onUpdateSupportedImsFeatures(ImsFeatureConfiguration c) {
}
}
/**
* @hide
*/
protected final IBinder mImsServiceController = new IImsServiceController.Stub() {
@Override
public void setListener(IImsServiceControllerListener l) {
synchronized (mListenerLock) {
if (mListener != null && mListener.asBinder().isBinderAlive()) {
try {
mListener.asBinder().unlinkToDeath(mDeathRecipient, 0);
} catch (NoSuchElementException e) {
Log.w(LOG_TAG, "IImsServiceControllerListener does not exist");
}
}
mListener = l;
if (mListener == null) {
executeMethodAsync(() -> releaseResource(), "releaseResource");
return;
}
try {
mListener.asBinder().linkToDeath(mDeathRecipient, 0);
Log.i(LOG_TAG, "setListener: register linkToDeath");
} catch (RemoteException e) {
// RemoteException means target binder process was crashed
// release resource
executeMethodAsync(() -> releaseResource(), "releaseResource");
}
}
}
@Override
public IImsMmTelFeature createMmTelFeature(int slotId, int subId) {
MmTelFeature f = (MmTelFeature) getImsFeature(slotId, ImsFeature.FEATURE_MMTEL);
if (f == null) {
return executeMethodAsyncForResult(() -> createMmTelFeatureInternal(slotId, subId),
"createMmTelFeature");
} else {
return f.getBinder();
}
}
@Override
public IImsMmTelFeature createEmergencyOnlyMmTelFeature(int slotId) {
MmTelFeature f = (MmTelFeature) getImsFeature(slotId, ImsFeature.FEATURE_MMTEL);
if (f == null) {
return executeMethodAsyncForResult(() -> createEmergencyOnlyMmTelFeatureInternal(
slotId), "createEmergencyOnlyMmTelFeature");
} else {
return f.getBinder();
}
}
@Override
public IImsRcsFeature createRcsFeature(int slotId, int subId) {
RcsFeature f = (RcsFeature) getImsFeature(slotId, ImsFeature.FEATURE_RCS);
if (f == null) {
return executeMethodAsyncForResult(() ->
createRcsFeatureInternal(slotId, subId), "createRcsFeature");
} else {
return f.getBinder();
}
}
@Override
public void addFeatureStatusCallback(int slotId, int featureType,
IImsFeatureStatusCallback c) {
executeMethodAsync(() -> ImsService.this.addImsFeatureStatusCallback(
slotId, featureType, c), "addFeatureStatusCallback");
}
@Override
public void removeFeatureStatusCallback(int slotId, int featureType,
IImsFeatureStatusCallback c) {
executeMethodAsync(() -> ImsService.this.removeImsFeatureStatusCallback(
slotId, featureType, c), "removeFeatureStatusCallback");
}
@Override
public void removeImsFeature(int slotId, int featureType, boolean changeSubId) {
if (changeSubId && isImsFeatureCreatedForSlot(slotId, featureType)) {
Log.w(LOG_TAG, "Do not remove Ims feature for compatibility");
return;
}
executeMethodAsync(() -> ImsService.this.removeImsFeature(slotId, featureType),
"removeImsFeature");
setImsFeatureCreatedForSlot(slotId, featureType, false);
}
@Override
public ImsFeatureConfiguration querySupportedImsFeatures() {
return executeMethodAsyncForResult(() -> ImsService.this.querySupportedImsFeatures(),
"ImsFeatureConfiguration");
}
@Override
public long getImsServiceCapabilities() {
return executeMethodAsyncForResult(() -> {
long caps = ImsService.this.getImsServiceCapabilities();
long sanitizedCaps = sanitizeCapabilities(caps);
if (caps != sanitizedCaps) {
Log.w(LOG_TAG, "removing invalid bits from field: 0x"
+ Long.toHexString(caps ^ sanitizedCaps));
}
return sanitizedCaps;
}, "getImsServiceCapabilities");
}
@Override
public void notifyImsServiceReadyForFeatureCreation() {
executeMethodAsync(() -> ImsService.this.readyForFeatureCreation(),
"notifyImsServiceReadyForFeatureCreation");
}
@Override
public IImsConfig getConfig(int slotId, int subId) {
return executeMethodAsyncForResult(() -> {
ImsConfigImplBase c =
ImsService.this.getConfigForSubscription(slotId, subId);
if (c != null) {
c.setDefaultExecutor(getCachedExecutor());
return c.getIImsConfig();
} else {
return null;
}
}, "getConfig");
}
@Override
public IImsRegistration getRegistration(int slotId, int subId) {
return executeMethodAsyncForResult(() -> {
ImsRegistrationImplBase r =
ImsService.this.getRegistrationForSubscription(slotId, subId);
if (r != null) {
r.setDefaultExecutor(getCachedExecutor());
return r.getBinder();
} else {
return null;
}
}, "getRegistration");
}
@Override
public ISipTransport getSipTransport(int slotId) {
return executeMethodAsyncForResult(() -> {
SipTransportImplBase s = ImsService.this.getSipTransport(slotId);
if (s != null) {
s.setDefaultExecutor(getCachedExecutor());
return s.getBinder();
} else {
return null;
}
}, "getSipTransport");
}
@Override
public void enableIms(int slotId, int subId) {
executeMethodAsync(() ->
ImsService.this.enableImsForSubscription(slotId, subId), "enableIms");
}
@Override
public void disableIms(int slotId, int subId) {
executeMethodAsync(() ->
ImsService.this.disableImsForSubscription(slotId, subId), "disableIms");
}
@Override
public void resetIms(int slotId, int subId) {
executeMethodAsync(() ->
ImsService.this.resetImsInternal(slotId, subId), "resetIms");
}
};
private final IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
Log.w(LOG_TAG,
"IImsServiceControllerListener binder to framework has died. Cleaning up");
executeMethodAsync(() -> releaseResource(), "releaseResource");
}
};
/**
* @hide
*/
@Override
public IBinder onBind(Intent intent) {
if(SERVICE_INTERFACE.equals(intent.getAction())) {
Log.i(LOG_TAG, "ImsService Bound.");
return mImsServiceController;
}
return null;
}
private Executor getCachedExecutor() {
synchronized (mExecutorLock) {
if (mExecutor == null) {
Executor e = ImsService.this.getExecutor();
mExecutor = (e != null) ? e : Runnable::run;
}
return mExecutor;
}
}
private IImsMmTelFeature createMmTelFeatureInternal(int slotId, int subscriptionId) {
MmTelFeature f = createMmTelFeatureForSubscription(slotId, subscriptionId);
if (f != null) {
setupFeature(f, slotId, ImsFeature.FEATURE_MMTEL);
f.setDefaultExecutor(getCachedExecutor());
return f.getBinder();
} else {
Log.e(LOG_TAG, "createMmTelFeatureInternal: null feature returned.");
return null;
}
}
private IImsMmTelFeature createEmergencyOnlyMmTelFeatureInternal(int slotId) {
MmTelFeature f = createEmergencyOnlyMmTelFeature(slotId);
if (f != null) {
setupFeature(f, slotId, ImsFeature.FEATURE_MMTEL);
f.setDefaultExecutor(getCachedExecutor());
return f.getBinder();
} else {
Log.e(LOG_TAG, "createEmergencyOnlyMmTelFeatureInternal: null feature returned.");
return null;
}
}
private IImsRcsFeature createRcsFeatureInternal(int slotId, int subId) {
RcsFeature f = createRcsFeatureForSubscription(slotId, subId);
if (f != null) {
f.setDefaultExecutor(getCachedExecutor());
setupFeature(f, slotId, ImsFeature.FEATURE_RCS);
return f.getBinder();
} else {
Log.e(LOG_TAG, "createRcsFeatureInternal: null feature returned.");
return null;
}
}
private void setupFeature(ImsFeature f, int slotId, int featureType) {
f.initialize(this, slotId);
addImsFeature(slotId, featureType, f);
}
private void addImsFeatureStatusCallback(int slotId, int featureType,
IImsFeatureStatusCallback c) {
synchronized (mFeaturesBySlot) {
// get ImsFeature associated with the slot/feature
SparseArray
* This should be a static configuration and should not change at runtime.
* @return The optional static capabilities of this ImsService implementation.
*/
// ImsService follows a different convention, since it is a stub class. The on* methods are
// final and call back into the framework with a state update.
@SuppressLint("OnNameExpected")
public @ImsServiceCapability long getImsServiceCapabilities() {
// Stub implementation to be implemented by ImsService.
return 0L;
}
/**
* The ImsService has been bound and is ready for ImsFeature creation based on the Features that
* the ImsService has registered for with the framework, either in the manifest or via
* {@link #querySupportedImsFeatures()}.
*
* The ImsService should use this signal instead of onCreate/onBind or similar to perform
* feature initialization because the framework may bind to this service multiple times to
* query the ImsService's {@link ImsFeatureConfiguration} via
* {@link #querySupportedImsFeatures()}before creating features.
*/
public void readyForFeatureCreation() {
}
/**
* The framework has enabled IMS for the subscription specified, the ImsService should register
* for IMS and perform all appropriate initialization to bring up all ImsFeatures.
*
* @param slotId The slot ID that IMS will be enabled for.
* @param subscriptionId The subscription ID that IMS will be enabled for.
*/
public void enableImsForSubscription(int slotId, int subscriptionId) {
enableIms(slotId);
}
/**
* The framework has disabled IMS for the subscription specified. The ImsService must deregister
* for IMS and set capability status to false for all ImsFeatures.
* @param slotId The slot ID that IMS will be disabled for.
* @param subscriptionId The subscription ID that IMS will be disabled for.
*/
public void disableImsForSubscription(int slotId, int subscriptionId) {
disableIms(slotId);
}
/**
* The subscription has removed. The ImsService should notify ImsRegistrationImplBase and
* ImsConfigImplBase the SIM state was changed.
* @param slotId The slot ID which has removed.
*/
private void notifySubscriptionRemoved(int slotId) {
ImsRegistrationImplBase registrationImplBase =
getRegistration(slotId);
if (registrationImplBase != null) {
registrationImplBase.clearRegistrationCache();
}
ImsConfigImplBase imsConfigImplBase = getConfig(slotId);
if (imsConfigImplBase != null) {
imsConfigImplBase.clearConfigurationCache();
}
}
/**
* The framework has enabled IMS for the slot specified, the ImsService should register for IMS
* and perform all appropriate initialization to bring up all ImsFeatures.
* @deprecated Use {@link #enableImsForSubscription} instead.
*/
@Deprecated
public void enableIms(int slotId) {
}
/**
* The framework has disabled IMS for the slot specified. The ImsService must deregister for IMS
* and set capability status to false for all ImsFeatures.
* @deprecated Use {@link #disableImsForSubscription} instead.
*/
@Deprecated
public void disableIms(int slotId) {
}
/**
* The framework has reset IMS for the slot specified. The ImsService must deregister
* and release all resources for IMS. After resetIms is called, either
* {@link #enableImsForSubscription(int, int)} or {@link #disableImsForSubscription(int, int)}
* will be called for the same slotId.
*
* @param slotId The slot ID that IMS will be reset for.
* @hide
*/
public void resetIms(int slotId) {
throw new UnsupportedOperationException();
}
/**
* When called, the framework is requesting that a new {@link MmTelFeature} is created for the
* specified subscription.
*
* @param subscriptionId The subscription ID that the MMTEL Feature is being created for.
* @return The newly created {@link MmTelFeature} associated with the subscription or null if
* the feature is not supported.
*/
public @Nullable MmTelFeature createMmTelFeatureForSubscription(int slotId,
int subscriptionId) {
setImsFeatureCreatedForSlot(slotId, ImsFeature.FEATURE_MMTEL, true);
return createMmTelFeature(slotId);
}
/**
* When called, the framework is requesting that a new {@link RcsFeature} is created for the
* specified subscription.
*
* @param subscriptionId The subscription ID that the RCS Feature is being created for.
* @return The newly created {@link RcsFeature} associated with the subscription or null if the
* feature is not supported.
*/
public @Nullable RcsFeature createRcsFeatureForSubscription(int slotId, int subscriptionId) {
setImsFeatureCreatedForSlot(slotId, ImsFeature.FEATURE_RCS, true);
return createRcsFeature(slotId);
}
/**
* When called, the framework is requesting that a new emergency-only {@link MmTelFeature} is
* created for the specified slot. For emergency calls, there is no known Subscription Id.
*
* @param slotId The slot ID that the MMTEL Feature is being created for.
* @return An MmTelFeature instance to be used for the slot ID when there is not
* subscription inserted. Only requested when there is no subscription active on
* the specified slot.
*/
public @Nullable MmTelFeature createEmergencyOnlyMmTelFeature(int slotId) {
setImsFeatureCreatedForSlot(slotId, ImsFeature.FEATURE_MMTEL, true);
return createMmTelFeature(slotId);
}
/**
* When called, the framework is requesting that a new {@link MmTelFeature} is created for the
* specified slot.
* @deprecated Use {@link #createMmTelFeatureForSubscription} instead
*
* @param slotId The slot ID that the MMTEL Feature is being created for.
* @return The newly created {@link MmTelFeature} associated with the slot or null if the
* feature is not supported.
*/
@Deprecated
public MmTelFeature createMmTelFeature(int slotId) {
return null;
}
/**
* When called, the framework is requesting that a new {@link RcsFeature} is created for the
* specified slot.
* @deprecated Use {@link #createRcsFeatureForSubscription} instead
*
* @param slotId The slot ID that the RCS Feature is being created for.
* @return The newly created {@link RcsFeature} associated with the slot or null if the feature
* is not supported.
*/
@Deprecated
public RcsFeature createRcsFeature(int slotId) {
return null;
}
/**
* Return the {@link ImsConfigImplBase} implementation associated with the provided
* subscription. This will be used by the platform to get/set specific IMS related
* configurations.
*
* @param subscriptionId The subscription ID that the IMS configuration is associated with.
* @return ImsConfig implementation that is associated with the specified subscription.
*/
public @NonNull ImsConfigImplBase getConfigForSubscription(int slotId, int subscriptionId) {
return getConfig(slotId);
}
/**
* Return the {@link ImsRegistrationImplBase} implementation associated with the provided
* subscription.
*
* @param subscriptionId The subscription ID that is associated with the IMS Registration.
* @return the ImsRegistration implementation associated with the subscription.
*/
public @NonNull ImsRegistrationImplBase getRegistrationForSubscription(int slotId,
int subscriptionId) {
return getRegistration(slotId);
}
/**
* Return the {@link ImsConfigImplBase} implementation associated with the provided slot. This
* will be used by the platform to get/set specific IMS related configurations.
* @deprecated use {@link #getConfigForSubscription} instead.
*
* @param slotId The slot that the IMS configuration is associated with.
* @return ImsConfig implementation that is associated with the specified slot.
*/
@Deprecated
public ImsConfigImplBase getConfig(int slotId) {
return new ImsConfigImplBase();
}
/**
* Return the {@link ImsRegistrationImplBase} implementation associated with the provided slot.
* @deprecated use {@link #getRegistrationForSubscription} instead.
*
* @param slotId The slot that is associated with the IMS Registration.
* @return the ImsRegistration implementation associated with the slot.
*/
@Deprecated
public ImsRegistrationImplBase getRegistration(int slotId) {
return new ImsRegistrationImplBase();
}
/**
* Return the {@link SipTransportImplBase} implementation associated with the provided slot.
*
* This is an optional interface used for devices that must support IMS single registration and
* proxy SIP traffic to remote IMS applications. If this is not supported for this IMS service,
* this method should return {@code null}. If this feature is supported, then this method must
* never be {@code null} and the optional ImsService capability flag
* {@link #CAPABILITY_SIP_DELEGATE_CREATION} must be set in
* {@link #getImsServiceCapabilities()}. Otherwise the framework will assume this feature is not
* supported for this ImsService.
* @param slotId The slot that is associated with the SipTransport implementation.
* @return the SipTransport implementation for the specified slot.
*/
// ImsService follows a different convention, since it is a stub class. The on* methods are
// final and call back into the framework with a state update.
@SuppressLint("OnNameExpected")
public @Nullable SipTransportImplBase getSipTransport(int slotId) {
// Stub implementation for ImsServices that do not support SipTransport.
return null;
}
private static long sanitizeCapabilities(@ImsServiceCapability long caps) {
long filter = 0xFFFFFFFFFFFFFFFFL;
// pad the "allowed" set with zeros
filter <<= CAPABILITY_MAX_INDEX + 1;
// remove values above the allowed set.
caps &= ~filter;
// CAPABILITY_EMERGENCY_OVER_MMTEL should also not be set here, will be set by telephony
// internally.
caps &= ~CAPABILITY_EMERGENCY_OVER_MMTEL;
return caps;
}
/**
* @return A string representation of the ImsService capabilities for logging.
* @hide
*/
public static String getCapabilitiesString(@ImsServiceCapability long caps) {
StringBuffer result = new StringBuffer();
result.append("capabilities={ ");
// filter incrementally fills 0s from left to right. This is used to keep filtering out
// more bits in the long until the remaining leftmost bits are all zero.
long filter = 0xFFFFFFFFFFFFFFFFL;
// position of iterator to potentially print capability.
long i = 0;
while ((caps & filter) != 0 && i <= 63) {
long bitToCheck = (1L << i);
if ((caps & bitToCheck) != 0) {
result.append(CAPABILITIES_LOG_MAP.getOrDefault(bitToCheck, bitToCheck + "?"));
result.append(" ");
}
// shift left by one and fill in another 1 on the leftmost bit.
filter <<= 1;
i++;
}
result.append("}");
return result.toString();
}
/**
* The ImsService will now be able to define an Executor that the ImsService can be used to
* execute the methods. By default all ImsService level method calls will use this Executor.
* The ImsService has set the default executor as Runnable::run,
* Should be override or default executor will be used.
* @return an Executor used to execute methods called remotely by the framework.
*/
public @NonNull Executor getExecutor() {
return Runnable::run;
}
}