blob: 97fa8f2308781414bc757c388ca677b7436d2f2b [file] [log] [blame]
/*
* Copyright (C) 2019 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.cts;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.telephony.ims.ImsService;
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 androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.HashSet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
/**
* A Test ImsService that will verify ImsService functionality.
*/
public class TestImsService extends Service {
private static final String TAG = "CtsImsTestImsService";
private static MessageExecutor sMessageExecutor = null;
private TestImsRegistration mImsRegistrationImplBase;
private TestRcsFeature mTestRcsFeature;
private TestMmTelFeature mTestMmTelFeature;
private TestImsConfig mTestImsConfig;
private TestSipTransport mTestSipTransport;
private ImsService mTestImsService;
private ImsService mTestImsServiceCompat;
private Executor mExecutor = Runnable::run;
private boolean mIsEnabled = false;
private boolean mSetNullRcsBinding = false;
private boolean mIsSipTransportImplemented = false;
private boolean mIsTestTypeExecutor = false;
private boolean mIsImsServiceCompat = false;
private long mCapabilities = 0;
private ImsFeatureConfiguration mFeatureConfig;
protected boolean mIsTelephonyBound = false;
private HashSet<Integer> mSubIDs = new HashSet<Integer>();
protected final Object mLock = new Object();
public static final int LATCH_FEATURES_READY = 0;
public static final int LATCH_ENABLE_IMS = 1;
public static final int LATCH_DISABLE_IMS = 2;
public static final int LATCH_CREATE_MMTEL = 3;
public static final int LATCH_CREATE_RCS = 4;
public static final int LATCH_REMOVE_MMTEL = 5;
public static final int LATCH_REMOVE_RCS = 6;
public static final int LATCH_MMTEL_READY = 7;
public static final int LATCH_RCS_READY = 8;
public static final int LATCH_MMTEL_CAP_SET = 9;
public static final int LATCH_RCS_CAP_SET = 10;
public static final int LATCH_UCE_LISTENER_SET = 11;
public static final int LATCH_UCE_REQUEST_PUBLISH = 12;
public static final int LATCH_ON_UNBIND = 13;
private static final int LATCH_MAX = 14;
protected static final CountDownLatch[] sLatches = new CountDownLatch[LATCH_MAX];
static {
for (int i = 0; i < LATCH_MAX; i++) {
sLatches[i] = new CountDownLatch(1);
}
}
interface RemovedListener {
void onRemoved();
}
interface ReadyListener {
void onReady();
}
interface CapabilitiesSetListener {
void onSet();
}
interface RcsCapabilityExchangeEventListener {
void onSet();
}
interface DeviceCapPublishListener {
void onPublish();
}
// This is defined here instead TestImsService extending ImsService directly because the GTS
// tests were failing to run on pre-P devices. Not sure why, but TestImsService is loaded
// even if it isn't used.
private class ImsServiceUT extends ImsService {
ImsServiceUT(Context context) {
// As explained above, ImsServiceUT is created in order to get around classloader
// restrictions. Attach the base context from the wrapper ImsService.
if (getBaseContext() == null) {
attachBaseContext(context);
}
if (mIsTestTypeExecutor) {
mImsRegistrationImplBase = new TestImsRegistration(mExecutor);
mTestSipTransport = new TestSipTransport(mExecutor);
mTestImsConfig = new TestImsConfig(mExecutor);
} else {
mImsRegistrationImplBase = new TestImsRegistration();
mTestImsConfig = new TestImsConfig();
mTestSipTransport = new TestSipTransport();
}
}
@Override
public ImsFeatureConfiguration querySupportedImsFeatures() {
return getFeatureConfig();
}
@Override
public long getImsServiceCapabilities() {
return mCapabilities;
}
@Override
public void readyForFeatureCreation() {
synchronized (mLock) {
countDownLatch(LATCH_FEATURES_READY);
}
}
@Override
public void enableImsForSubscription(int slotId, int subId) {
synchronized (mLock) {
countDownLatch(LATCH_ENABLE_IMS);
mSubIDs.add(subId);
setIsEnabled(true);
}
}
@Override
public void disableImsForSubscription(int slotId, int subId) {
synchronized (mLock) {
countDownLatch(LATCH_DISABLE_IMS);
mSubIDs.add(subId);
setIsEnabled(false);
}
}
@Override
public RcsFeature createRcsFeatureForSubscription(int slotId, int subId) {
TestImsService.ReadyListener readyListener = () -> {
synchronized (mLock) {
countDownLatch(LATCH_RCS_READY);
}
};
TestImsService.RemovedListener removedListener = () -> {
synchronized (mLock) {
countDownLatch(LATCH_REMOVE_RCS);
mTestRcsFeature = null;
}
};
TestImsService.CapabilitiesSetListener setListener = () -> {
synchronized (mLock) {
countDownLatch(LATCH_RCS_CAP_SET);
}
};
TestImsService.RcsCapabilityExchangeEventListener capExchangeEventListener = () -> {
synchronized (mLock) {
countDownLatch(LATCH_UCE_LISTENER_SET);
}
};
synchronized (mLock) {
countDownLatch(LATCH_CREATE_RCS);
mSubIDs.add(subId);
if (mIsTestTypeExecutor) {
mTestRcsFeature = new TestRcsFeature(readyListener, removedListener,
setListener, capExchangeEventListener, mExecutor);
} else {
mTestRcsFeature = new TestRcsFeature(readyListener, removedListener,
setListener, capExchangeEventListener);
}
// Setup UCE request listener
mTestRcsFeature.setDeviceCapPublishListener(() -> {
synchronized (mLock) {
countDownLatch(LATCH_UCE_REQUEST_PUBLISH);
}
});
if (mSetNullRcsBinding) {
return null;
}
return mTestRcsFeature;
}
}
@Override
public ImsConfigImplBase getConfigForSubscription(int slotId, int subId) {
mSubIDs.add(subId);
return mTestImsConfig;
}
@Override
public MmTelFeature createMmTelFeatureForSubscription(int slotId, int subId) {
TestImsService.ReadyListener readyListener = () -> {
synchronized (mLock) {
countDownLatch(LATCH_MMTEL_READY);
}
};
TestImsService.RemovedListener removedListener = () -> {
synchronized (mLock) {
countDownLatch(LATCH_REMOVE_MMTEL);
mTestMmTelFeature = null;
}
};
TestImsService.CapabilitiesSetListener capSetListener = () -> {
synchronized (mLock) {
countDownLatch(LATCH_MMTEL_CAP_SET);
}
};
synchronized (mLock) {
countDownLatch(LATCH_CREATE_MMTEL);
mSubIDs.add(subId);
if (mIsTestTypeExecutor) {
mTestMmTelFeature = new TestMmTelFeature(readyListener, removedListener,
capSetListener, mExecutor);
} else {
mTestMmTelFeature = new TestMmTelFeature(readyListener, removedListener,
capSetListener);
}
return mTestMmTelFeature;
}
}
@Override
public ImsRegistrationImplBase getRegistrationForSubscription(int slotId, int subId) {
mSubIDs.add(subId);
return mImsRegistrationImplBase;
}
@Nullable
@Override
public SipTransportImplBase getSipTransport(int slotId) {
if (mIsSipTransportImplemented) {
return mTestSipTransport;
} else {
return null;
}
}
@Override
public @NonNull Executor getExecutor() {
if (mIsTestTypeExecutor) {
return mExecutor;
} else {
mExecutor = Runnable::run;
return mExecutor;
}
}
}
private class ImsServiceUT_compat extends ImsService {
ImsServiceUT_compat(Context context) {
// As explained above, ImsServiceUT is created in order to get around classloader
// restrictions. Attach the base context from the wrapper ImsService.
if (getBaseContext() == null) {
attachBaseContext(context);
}
if (mIsTestTypeExecutor) {
mImsRegistrationImplBase = new TestImsRegistration(mExecutor);
mTestSipTransport = new TestSipTransport(mExecutor);
mTestImsConfig = new TestImsConfig(mExecutor);
} else {
mImsRegistrationImplBase = new TestImsRegistration();
mTestImsConfig = new TestImsConfig();
mTestSipTransport = new TestSipTransport();
}
}
@Override
public ImsFeatureConfiguration querySupportedImsFeatures() {
return getFeatureConfig();
}
@Override
public long getImsServiceCapabilities() {
return mCapabilities;
}
@Override
public void readyForFeatureCreation() {
synchronized (mLock) {
countDownLatch(LATCH_FEATURES_READY);
}
}
@Override
public void enableIms(int slotId) {
synchronized (mLock) {
countDownLatch(LATCH_ENABLE_IMS);
setIsEnabled(true);
}
}
@Override
public void disableIms(int slotId) {
synchronized (mLock) {
countDownLatch(LATCH_DISABLE_IMS);
setIsEnabled(false);
}
}
@Override
public RcsFeature createRcsFeature(int slotId) {
TestImsService.ReadyListener readyListener = () -> {
synchronized (mLock) {
countDownLatch(LATCH_RCS_READY);
}
};
TestImsService.RemovedListener removedListener = () -> {
synchronized (mLock) {
countDownLatch(LATCH_REMOVE_RCS);
mTestRcsFeature = null;
}
};
TestImsService.CapabilitiesSetListener setListener = () -> {
synchronized (mLock) {
countDownLatch(LATCH_RCS_CAP_SET);
}
};
TestImsService.RcsCapabilityExchangeEventListener capExchangeEventListener = () -> {
synchronized (mLock) {
countDownLatch(LATCH_UCE_LISTENER_SET);
}
};
synchronized (mLock) {
countDownLatch(LATCH_CREATE_RCS);
if (mIsTestTypeExecutor) {
mTestRcsFeature = new TestRcsFeature(readyListener, removedListener,
setListener, capExchangeEventListener, mExecutor);
} else {
mTestRcsFeature = new TestRcsFeature(readyListener, removedListener,
setListener, capExchangeEventListener);
}
// Setup UCE request listener
mTestRcsFeature.setDeviceCapPublishListener(() -> {
synchronized (mLock) {
countDownLatch(LATCH_UCE_REQUEST_PUBLISH);
}
});
if (mSetNullRcsBinding) {
return null;
}
return mTestRcsFeature;
}
}
@Override
public ImsConfigImplBase getConfig(int slotId) {
return mTestImsConfig;
}
@Override
public MmTelFeature createMmTelFeature(int slotId) {
TestImsService.ReadyListener readyListener = () -> {
synchronized (mLock) {
countDownLatch(LATCH_MMTEL_READY);
}
};
TestImsService.RemovedListener removedListener = () -> {
synchronized (mLock) {
countDownLatch(LATCH_REMOVE_MMTEL);
mTestMmTelFeature = null;
}
};
TestImsService.CapabilitiesSetListener capSetListener = () -> {
synchronized (mLock) {
countDownLatch(LATCH_MMTEL_CAP_SET);
}
};
synchronized (mLock) {
countDownLatch(LATCH_CREATE_MMTEL);
if (mIsTestTypeExecutor) {
mTestMmTelFeature = new TestMmTelFeature(readyListener, removedListener,
capSetListener, mExecutor);
} else {
mTestMmTelFeature = new TestMmTelFeature(readyListener, removedListener,
capSetListener);
}
return mTestMmTelFeature;
}
}
@Override
public ImsRegistrationImplBase getRegistration(int slotId) {
return mImsRegistrationImplBase;
}
@Nullable
@Override
public SipTransportImplBase getSipTransport(int slotId) {
if (mIsSipTransportImplemented) {
return mTestSipTransport;
} else {
return null;
}
}
@Override
public @NonNull Executor getExecutor() {
if (mIsTestTypeExecutor) {
return mExecutor;
} else {
mExecutor = Runnable::run;
return mExecutor;
}
}
}
private static Looper createLooper(String name) {
HandlerThread thread = new HandlerThread(name);
thread.start();
Looper looper = thread.getLooper();
if (looper == null) {
return Looper.getMainLooper();
}
return looper;
}
/**
* Executes the tasks in the other thread rather than the calling thread.
*/
public class MessageExecutor extends Handler implements Executor {
public MessageExecutor(String name) {
super(createLooper(name));
}
@Override
public void execute(Runnable r) {
Message m = Message.obtain(this, 0, r);
m.sendToTarget();
}
@Override
public void handleMessage(Message msg) {
if (msg.obj instanceof Runnable) {
executeInternal((Runnable) msg.obj);
} else {
Log.d(TAG, "[MessageExecutor] handleMessage :: "
+ "Not runnable object; ignore the msg=" + msg);
}
}
private void executeInternal(Runnable r) {
try {
r.run();
} catch (Throwable t) {
Log.d(TAG, "[MessageExecutor] executeInternal :: run task=" + r);
t.printStackTrace();
}
}
}
private final LocalBinder mBinder = new LocalBinder();
// For local access of this Service.
class LocalBinder extends Binder {
TestImsService getService() {
return TestImsService.this;
}
}
protected ImsService getImsService() {
synchronized (mLock) {
if (mTestImsService != null) {
return mTestImsService;
}
mTestImsService = new ImsServiceUT(this);
return mTestImsService;
}
}
protected ImsService getImsServiceCompat() {
synchronized (mLock) {
if (mTestImsServiceCompat != null) {
return mTestImsServiceCompat;
}
mTestImsServiceCompat = new ImsServiceUT_compat(this);
return mTestImsServiceCompat;
}
}
@Override
public IBinder onBind(Intent intent) {
synchronized (mLock) {
if ("android.telephony.ims.ImsService".equals(intent.getAction())) {
mIsTelephonyBound = true;
if (mIsImsServiceCompat) {
if (ImsUtils.VDBG) {
Log.d(TAG, "onBind-Remote-Compat");
}
return getImsServiceCompat().onBind(intent);
} else {
if (ImsUtils.VDBG) {
Log.d(TAG, "onBind-Remote");
}
return getImsService().onBind(intent);
}
}
if (ImsUtils.VDBG) {
Log.i(TAG, "onBind-Local");
}
return mBinder;
}
}
@Override
public boolean onUnbind(Intent intent) {
synchronized (mLock) {
if ("android.telephony.ims.ImsService".equals(intent.getAction())) {
if (ImsUtils.VDBG) Log.i(TAG, "onUnbind-Remote");
mIsTelephonyBound = false;
countDownLatch(LATCH_ON_UNBIND);
} else {
if (ImsUtils.VDBG) Log.i(TAG, "onUnbind-Local");
}
// return false so that onBind is called next time.
return false;
}
}
public void resetState() {
synchronized (mLock) {
mTestMmTelFeature = null;
mTestRcsFeature = null;
mIsEnabled = false;
mSetNullRcsBinding = false;
mIsSipTransportImplemented = false;
mIsTestTypeExecutor = false;
mIsImsServiceCompat = false;
mCapabilities = 0;
for (int i = 0; i < LATCH_MAX; i++) {
sLatches[i] = new CountDownLatch(1);
}
if (sMessageExecutor != null) {
sMessageExecutor.getLooper().quit();
sMessageExecutor = null;
}
mSubIDs.clear();
}
}
public boolean isTelephonyBound() {
return mIsTelephonyBound;
}
public void setExecutorTestType(boolean type) {
mIsTestTypeExecutor = type;
if (mIsTestTypeExecutor) {
if (sMessageExecutor == null) {
sMessageExecutor = new MessageExecutor("TestImsService");
}
mExecutor = sMessageExecutor;
}
}
public void setImsServiceCompat() {
synchronized (mLock) {
mIsImsServiceCompat = true;
}
}
// Sets the feature configuration. Make sure to call this before initiating Bind to this
// ImsService.
public void setFeatureConfig(ImsFeatureConfiguration f) {
synchronized (mLock) {
mFeatureConfig = f;
}
}
public ImsFeatureConfiguration getFeatureConfig() {
synchronized (mLock) {
return mFeatureConfig;
}
}
public boolean isEnabled() {
synchronized (mLock) {
return mIsEnabled;
}
}
public void setNullRcsBinding() {
synchronized (mLock) {
mSetNullRcsBinding = true;
}
}
public void setIsEnabled(boolean isEnabled) {
synchronized (mLock) {
mIsEnabled = isEnabled;
}
}
public void addCapabilities(long capabilities) {
synchronized (mLock) {
mCapabilities |= capabilities;
}
}
public void setSipTransportImplemented() {
synchronized (mLock) {
mIsSipTransportImplemented = true;
}
}
public boolean waitForLatchCountdown(int latchIndex) {
return waitForLatchCountdown(latchIndex, ImsUtils.TEST_TIMEOUT_MS);
}
public boolean waitForLatchCountdown(int latchIndex, long waitMs) {
boolean complete = false;
try {
CountDownLatch latch;
synchronized (mLock) {
latch = sLatches[latchIndex];
}
long startTime = System.currentTimeMillis();
complete = latch.await(waitMs, TimeUnit.MILLISECONDS);
if (ImsUtils.VDBG) {
Log.i(TAG, "Latch " + latchIndex + " took "
+ (System.currentTimeMillis() - startTime) + " ms to count down.");
}
} catch (InterruptedException e) {
// complete == false
}
synchronized (mLock) {
sLatches[latchIndex] = new CountDownLatch(1);
}
return complete;
}
public void countDownLatch(int latchIndex) {
synchronized (mLock) {
sLatches[latchIndex].countDown();
}
}
public TestMmTelFeature getMmTelFeature() {
synchronized (mLock) {
return mTestMmTelFeature;
}
}
public TestRcsFeature getRcsFeature() {
synchronized (mLock) {
return mTestRcsFeature;
}
}
public TestSipTransport getSipTransport() {
synchronized (mLock) {
return mTestSipTransport;
}
}
public TestImsRegistration getImsRegistration() {
synchronized (mLock) {
return mImsRegistrationImplBase;
}
}
public ImsConfigImplBase getConfig() {
return mTestImsConfig;
}
public HashSet<Integer> getSubIDs() {
return mSubIDs;
}
}