blob: 3c323941177c314f10c8d19517fee7a8f22ab6bc [file] [log] [blame]
/*
* Copyright (C) 2021 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.telephonyprovider.cts;
import static android.provider.Telephony.ServiceStateTable.DATA_NETWORK_TYPE;
import static android.provider.Telephony.ServiceStateTable.DUPLEX_MODE;
import static android.provider.Telephony.ServiceStateTable.VOICE_REG_STATE;
import static android.telephony.NetworkRegistrationInfo.REGISTRATION_STATE_HOME;
import static androidx.test.InstrumentationRegistry.getInstrumentation;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import android.Manifest;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.Parcel;
import android.provider.Telephony;
import android.telephony.AccessNetworkConstants;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.ServiceState;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
import com.android.compatibility.common.util.PollingCheck;
import com.android.compatibility.common.util.SystemUtil;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
@SmallTest
public class ServiceStateTest {
private static final int DEFAULT_TIMEOUT = 1000;
// Keep the same as ServiceStateProvider#SERVICE_STATE which is NOT same as
// Telephony.ServiceStateTable.AUTHORITY;
private static final String SERVICE_STATE = "service_state";
private ContentResolver mContentResolver;
private TelephonyManager mTelephonyManager;
private int mSubId;
private ServiceState mInitialServiceState;
@Before
public void setUp() {
mContentResolver = getInstrumentation().getContext().getContentResolver();
mTelephonyManager =
getInstrumentation().getContext().getSystemService(TelephonyManager.class);
mSubId = SubscriptionManager.getDefaultSubscriptionId();
mInitialServiceState = mTelephonyManager.getServiceState();
}
@After
public void tearDown() {
// Recover the initial ServiceState to remove the impact of manual ServiceState insertion.
insertServiceState(mInitialServiceState);
}
/**
* Verifies that the ServiceStateTable CONTENT_URI and AUTHORITY is valid.
*/
@Test
public void testUriAndAuthority() {
Uri uri = Telephony.ServiceStateTable.CONTENT_URI;
assertThat(uri).isEqualTo(Uri.parse("content://service-state/"));
String authority = Telephony.ServiceStateTable.AUTHORITY;
assertThat(authority).isEqualTo("service-state");
}
/**
* Verifies that the voice reg state is valid and matches ServiceState#getState().
*/
@Test
public void testGetVoiceRegState_query() {
try (Cursor cursor = mContentResolver.query(Telephony.ServiceStateTable.CONTENT_URI,
new String[]{VOICE_REG_STATE}, null, null)) {
assertThat(cursor.getCount()).isEqualTo(1);
cursor.moveToNext();
int voiceRegState = cursor.getInt(cursor.getColumnIndex(VOICE_REG_STATE));
assertThat(voiceRegState).isEqualTo(mTelephonyManager.getServiceState().getState());
}
}
/**
* Verifies that when voice reg state did not change, the observer should not receive the
* notification.
*/
@Test
public void testGetVoiceRegState_noChangeObserved() throws Exception {
ServiceState oldSS = new ServiceState();
oldSS.setStateOutOfService();
oldSS.setState(ServiceState.STATE_OUT_OF_SERVICE);
ServiceState copyOfOldSS = new ServiceState();
copyOfOldSS.setStateOutOfService();
copyOfOldSS.setState(ServiceState.STATE_OUT_OF_SERVICE);
// set additional fields which is not related to voice reg state
copyOfOldSS.setChannelNumber(65536);
copyOfOldSS.setIsManualSelection(true);
verifyNotificationObservedWhenFieldChanged(
VOICE_REG_STATE, oldSS, copyOfOldSS, false /*expectChange*/);
}
/**
* Verifies that when voice reg state changed, the observer should receive the notification.
*/
@Test
public void testGetVoiceRegState_changeObserved() throws Exception {
ServiceState oldSS = new ServiceState();
oldSS.setStateOutOfService();
oldSS.setState(ServiceState.STATE_OUT_OF_SERVICE);
ServiceState newSS = new ServiceState();
newSS.setStateOutOfService();
newSS.setState(ServiceState.STATE_POWER_OFF);
verifyNotificationObservedWhenFieldChanged(
VOICE_REG_STATE, oldSS, newSS, true /*expectChange*/);
}
/**
* Verifies that the data network type is valid and matches ServiceState#getDataNetworkType()
*/
@Test
public void testGetDataNetworkType_query() {
try (Cursor cursor = mContentResolver.query(Telephony.ServiceStateTable.CONTENT_URI,
new String[]{DATA_NETWORK_TYPE}, null, null)) {
assertThat(cursor.getCount()).isEqualTo(1);
cursor.moveToNext();
int dataNetworkType = cursor.getInt(cursor.getColumnIndex(DATA_NETWORK_TYPE));
assertThat(dataNetworkType).isEqualTo(
mTelephonyManager.getServiceState().getDataNetworkType());
}
}
/**
* Verifies that when data network type did not change, the observer should not receive the
* notification.
*/
@Test
public void testDataNetworkType_noChangeObserved() throws Exception {
ServiceState oldSS = new ServiceState();
oldSS.setStateOutOfService();
ServiceState copyOfOldSS = new ServiceState();
copyOfOldSS.setStateOutOfService();
// Add a DOMAIN_CS NRI which should not update DataNetworkType
NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder()
.setDomain(NetworkRegistrationInfo.DOMAIN_CS)
.setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
.setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_GPRS)
.setRegistrationState(REGISTRATION_STATE_HOME)
.build();
copyOfOldSS.addNetworkRegistrationInfo(nri);
verifyNotificationObservedWhenFieldChanged(
DATA_NETWORK_TYPE, oldSS, copyOfOldSS, false /*expectChange*/);
}
/**
* Verifies that when data network type changed, the observer should receive the notification.
*/
@Test
public void testDataNetworkType_changeObserved() throws Exception {
// While we don't have a method to directly set dataNetworkType, we emulate a ServiceState
// change that will trigger the change of dataNetworkType, according to the logic in
// ServiceState#getDataNetworkType()
ServiceState oldSS = new ServiceState();
oldSS.setStateOutOfService();
ServiceState newSS = new ServiceState();
newSS.setStateOutOfService();
NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder()
.setDomain(NetworkRegistrationInfo.DOMAIN_PS)
.setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
.setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
.setRegistrationState(REGISTRATION_STATE_HOME)
.build();
newSS.addNetworkRegistrationInfo(nri);
verifyNotificationObservedWhenFieldChanged(
DATA_NETWORK_TYPE, oldSS, newSS, true /*expectChange*/);
}
/**
* Verifies that the duplex mode is valid and matches ServiceState#getDuplexMode().
*/
@Test
public void testGetDuplexMode_query() {
try (Cursor cursor = mContentResolver.query(Telephony.ServiceStateTable.CONTENT_URI,
new String[]{DUPLEX_MODE}, null, null)) {
assertThat(cursor.getCount()).isEqualTo(1);
cursor.moveToNext();
int duplexMode = cursor.getInt(cursor.getColumnIndex(DUPLEX_MODE));
assertThat(duplexMode).isEqualTo(mTelephonyManager.getServiceState().getDuplexMode());
}
}
/**
* Verifies that even we have duplex mode change, the observer should not receive the
* notification (duplex mode is a poll-only field).
*/
@Test
public void testGetDuplexMode_noChangeObserved() throws Exception {
ServiceState oldSS = new ServiceState();
oldSS.setStateOutOfService();
ServiceState newSS = new ServiceState();
newSS.setStateOutOfService();
// Add NRI to trigger SS with duplex mode updated
NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder()
.setDomain(NetworkRegistrationInfo.DOMAIN_PS)
.setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
.setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
.build();
newSS.addNetworkRegistrationInfo(nri);
newSS.setChannelNumber(65536); // EutranBand.BAND_65, DUPLEX_MODE_FDD
verifyNotificationObservedWhenFieldChanged(
DUPLEX_MODE, oldSS, newSS, false /*expectChange*/);
}
/**
* Insert new ServiceState over the old ServiceState and expect the observer receiving the
* notification over the observed field change.
*/
private void verifyNotificationObservedWhenFieldChanged(String field, ServiceState oldSS,
ServiceState newSS, boolean expectChange) throws Exception {
final Uri uriForSubAndField =
Telephony.ServiceStateTable.getUriForSubscriptionIdAndField(mSubId, field);
insertServiceState(oldSS);
RecordingContentObserver observer = new RecordingContentObserver();
mContentResolver.registerContentObserver(uriForSubAndField, false, observer);
assertWithMessage("Observer is NOT empty in the beginning.").that(
observer.mObserved).isEmpty();
insertServiceState(newSS);
if (expectChange) {
// Only verify we did receive the notification for the expected field, instead of the
// number of notifications we received to remove flakiness for different cases.
PollingCheck.check(
"Expect notification when " + field + " updated.",
DEFAULT_TIMEOUT, () -> observer.mObserved.contains(uriForSubAndField));
} else {
// Let the bullets fly for a while before we check the target.
try {
Thread.sleep(DEFAULT_TIMEOUT);
} catch (InterruptedException ignored) {
}
// Fields in ServiceState are not orthogonal. In case we do receive notification(s),
// further check if it is for the expected field.
assertWithMessage("Unexpected notification for " + field).that(
observer.mObserved).doesNotContain(uriForSubAndField);
}
mContentResolver.unregisterContentObserver(observer);
}
// Manually insert the ServiceState into table to test the notification.
private void insertServiceState(ServiceState state) {
ContentValues values = getContentValuesForServiceState(state);
SystemUtil.runWithShellPermissionIdentity(
() -> mContentResolver.insert(
Telephony.ServiceStateTable.getUriForSubscriptionId(mSubId), values),
Manifest.permission.MODIFY_PHONE_STATE);
}
// Copied from ServiceStateProvider#getContentValuesForServiceState
private static ContentValues getContentValuesForServiceState(ServiceState state) {
ContentValues values = new ContentValues();
final Parcel p = Parcel.obtain();
state.writeToParcel(p, 0);
values.put(SERVICE_STATE, p.marshall());
return values;
}
private static class RecordingContentObserver extends ContentObserver {
List<Uri> mObserved = new CopyOnWriteArrayList<>();
RecordingContentObserver() {
super(new Handler(Looper.getMainLooper()));
}
@Override
public void onChange(boolean selfChange, @Nullable Uri uri) {
mObserved.add(uri);
}
}
}