Merge "Fix EmergencyAffordanceService" into rvc-dev

This commit is contained in:
Rambo Wang
2020-04-22 01:18:54 +00:00
committed by Android (Google) Code Review
4 changed files with 670 additions and 202 deletions

View File

@@ -3496,10 +3496,9 @@
<!-- Do not translate. Mcc codes whose existence trigger the presence of emergency
affordances-->
<integer-array name="config_emergency_mcc_codes" translatable="false">
<item>404</item>
<item>405</item>
</integer-array>
<string-array name="config_emergency_iso_country_codes" translatable="false">
<item>in</item>
</string-array>
<!-- Package name for the device provisioning package. -->
<string name="config_deviceProvisioningPackage"></string>

View File

@@ -3085,7 +3085,7 @@
<java-symbol type="string" name="global_action_emergency" />
<java-symbol type="string" name="config_emergency_call_number" />
<java-symbol type="string" name="config_emergency_dialer_package" />
<java-symbol type="array" name="config_emergency_mcc_codes" />
<java-symbol type="array" name="config_emergency_iso_country_codes" />
<java-symbol type="string" name="config_dozeDoubleTapSensorType" />
<java-symbol type="string" name="config_dozeTapSensorType" />

View File

@@ -20,90 +20,80 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.provider.Settings;
import android.telephony.CellInfo;
import android.telephony.CellInfoGsm;
import android.telephony.CellInfoLte;
import android.telephony.CellInfoWcdma;
import android.telephony.CellLocation;
import android.telephony.PhoneStateListener;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Slog;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.SystemService;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* A service that listens to connectivity and SIM card changes and determines if the emergency mode
* should be enabled
* A service that listens to connectivity and SIM card changes and determines if the emergency
* affordance should be enabled.
*/
public class EmergencyAffordanceService extends SystemService {
private static final String TAG = "EmergencyAffordanceService";
private static final boolean DBG = false;
private static final int NUM_SCANS_UNTIL_ABORT = 4;
private static final String SERVICE_NAME = "emergency_affordance";
private static final int INITIALIZE_STATE = 1;
private static final int CELL_INFO_STATE_CHANGED = 2;
private static final int SUBSCRIPTION_CHANGED = 3;
/**
* Global setting, whether the last scan of the sim cards reveal that a sim was inserted that
* requires the emergency affordance. The value is a boolean (1 or 0).
* @hide
* @param arg1 slot Index
* @param arg2 0
* @param obj ISO country code
*/
private static final String EMERGENCY_SIM_INSERTED_SETTING = "emergency_sim_inserted_before";
private static final int NETWORK_COUNTRY_CHANGED = 2;
private static final int SUBSCRIPTION_CHANGED = 3;
private static final int UPDATE_AIRPLANE_MODE_STATUS = 4;
// Global Settings to override emergency affordance country ISO for debugging.
// Available only on debug build. The value is a country ISO string in lower case (eg. "us").
private static final String EMERGENCY_AFFORDANCE_OVERRIDE_ISO =
"emergency_affordance_override_iso";
private final Context mContext;
private final ArrayList<Integer> mEmergencyCallMccNumbers;
private final Object mLock = new Object();
private TelephonyManager mTelephonyManager;
// Country ISOs that require affordance
private final ArrayList<String> mEmergencyCallCountryIsos;
private SubscriptionManager mSubscriptionManager;
private boolean mEmergencyAffordanceNeeded;
private TelephonyManager mTelephonyManager;
private MyHandler mHandler;
private int mScansCompleted;
private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
@Override
public void onCellInfoChanged(List<CellInfo> cellInfo) {
if (!isEmergencyAffordanceNeeded()) {
requestCellScan();
}
}
@Override
public void onCellLocationChanged(CellLocation location) {
if (!isEmergencyAffordanceNeeded()) {
requestCellScan();
}
}
};
private BroadcastReceiver mAirplaneModeReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (Settings.Global.getInt(context.getContentResolver(),
Settings.Global.AIRPLANE_MODE_ON, 0) == 0) {
startScanning();
requestCellScan();
}
}
};
private boolean mSimNeedsEmergencyAffordance;
private boolean mNetworkNeedsEmergencyAffordance;
private boolean mAnySimNeedsEmergencyAffordance;
private boolean mAnyNetworkNeedsEmergencyAffordance;
private boolean mEmergencyAffordanceNeeded;
private boolean mAirplaneModeEnabled;
private boolean mVoiceCapable;
private void requestCellScan() {
mHandler.obtainMessage(CELL_INFO_STATE_CHANGED).sendToTarget();
}
private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED.equals(intent.getAction())) {
String countryCode = intent.getStringExtra(TelephonyManager.EXTRA_NETWORK_COUNTRY);
int slotId = intent.getIntExtra(SubscriptionManager.EXTRA_SLOT_INDEX,
SubscriptionManager.INVALID_SIM_SLOT_INDEX);
mHandler.obtainMessage(
NETWORK_COUNTRY_CHANGED, slotId, 0, countryCode).sendToTarget();
} else if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(intent.getAction())) {
mHandler.obtainMessage(UPDATE_AIRPLANE_MODE_STATUS).sendToTarget();
}
}
};
private SubscriptionManager.OnSubscriptionsChangedListener mSubscriptionChangedListener
= new SubscriptionManager.OnSubscriptionsChangedListener() {
@@ -116,207 +106,200 @@ public class EmergencyAffordanceService extends SystemService {
public EmergencyAffordanceService(Context context) {
super(context);
mContext = context;
int[] numbers = context.getResources().getIntArray(
com.android.internal.R.array.config_emergency_mcc_codes);
mEmergencyCallMccNumbers = new ArrayList<>(numbers.length);
for (int i = 0; i < numbers.length; i++) {
mEmergencyCallMccNumbers.add(numbers[i]);
String[] isos = context.getResources().getStringArray(
com.android.internal.R.array.config_emergency_iso_country_codes);
mEmergencyCallCountryIsos = new ArrayList<>(isos.length);
for (String iso : isos) {
mEmergencyCallCountryIsos.add(iso);
}
}
private void updateEmergencyAffordanceNeeded() {
synchronized (mLock) {
mEmergencyAffordanceNeeded = mVoiceCapable && (mSimNeedsEmergencyAffordance ||
mNetworkNeedsEmergencyAffordance);
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.EMERGENCY_AFFORDANCE_NEEDED,
mEmergencyAffordanceNeeded ? 1 : 0);
if (mEmergencyAffordanceNeeded) {
stopScanning();
if (Build.IS_DEBUGGABLE) {
String overrideIso = Settings.Global.getString(
mContext.getContentResolver(), EMERGENCY_AFFORDANCE_OVERRIDE_ISO);
if (!TextUtils.isEmpty(overrideIso)) {
if (DBG) Slog.d(TAG, "Override ISO to " + overrideIso);
mEmergencyCallCountryIsos.clear();
mEmergencyCallCountryIsos.add(overrideIso);
}
}
}
private void stopScanning() {
synchronized (mLock) {
mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
mScansCompleted = 0;
}
}
private boolean isEmergencyAffordanceNeeded() {
synchronized (mLock) {
return mEmergencyAffordanceNeeded;
}
}
@Override
public void onStart() {
if (DBG) Slog.i(TAG, "onStart");
publishBinderService(SERVICE_NAME, new BinderService());
}
@Override
public void onBootPhase(int phase) {
if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
mVoiceCapable = mTelephonyManager.isVoiceCapable();
if (!mVoiceCapable) {
updateEmergencyAffordanceNeeded();
return;
}
mSubscriptionManager = SubscriptionManager.from(mContext);
HandlerThread thread = new HandlerThread(TAG);
thread.start();
mHandler = new MyHandler(thread.getLooper());
mHandler.obtainMessage(INITIALIZE_STATE).sendToTarget();
startScanning();
IntentFilter filter = new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED);
mContext.registerReceiver(mAirplaneModeReceiver, filter);
mSubscriptionManager.addOnSubscriptionsChangedListener(mSubscriptionChangedListener);
if (DBG) Slog.i(TAG, "onBootPhase");
handleThirdPartyBootPhase();
}
}
private void startScanning() {
mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CELL_INFO
| PhoneStateListener.LISTEN_CELL_LOCATION);
}
/** Handler to do the heavier work on */
private class MyHandler extends Handler {
public MyHandler(Looper l) {
super(l);
}
@Override
public void handleMessage(Message msg) {
if (DBG) Slog.d(TAG, "handleMessage: " + msg.what);
switch (msg.what) {
case INITIALIZE_STATE:
handleInitializeState();
break;
case CELL_INFO_STATE_CHANGED:
handleUpdateCellInfo();
case NETWORK_COUNTRY_CHANGED:
final String countryIso = (String) msg.obj;
final int slotId = msg.arg1;
handleNetworkCountryChanged(countryIso, slotId);
break;
case SUBSCRIPTION_CHANGED:
handleUpdateSimSubscriptionInfo();
break;
case UPDATE_AIRPLANE_MODE_STATUS:
handleUpdateAirplaneModeStatus();
break;
default:
Slog.e(TAG, "Unexpected message received: " + msg.what);
}
}
}
private void handleInitializeState() {
if (handleUpdateSimSubscriptionInfo()) {
return;
}
if (handleUpdateCellInfo()) {
return;
}
if (DBG) Slog.d(TAG, "handleInitializeState");
handleUpdateAirplaneModeStatus();
handleUpdateSimSubscriptionInfo();
updateNetworkCountry();
updateEmergencyAffordanceNeeded();
}
private boolean handleUpdateSimSubscriptionInfo() {
boolean neededBefore = simNeededAffordanceBefore();
boolean neededNow = neededBefore;
private void handleThirdPartyBootPhase() {
if (DBG) Slog.d(TAG, "handleThirdPartyBootPhase");
mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
mVoiceCapable = mTelephonyManager.isVoiceCapable();
if (!mVoiceCapable) {
updateEmergencyAffordanceNeeded();
return;
}
HandlerThread thread = new HandlerThread(TAG);
thread.start();
mHandler = new MyHandler(thread.getLooper());
mSubscriptionManager = SubscriptionManager.from(mContext);
mSubscriptionManager.addOnSubscriptionsChangedListener(mSubscriptionChangedListener);
IntentFilter filter = new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED);
filter.addAction(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED);
mContext.registerReceiver(mBroadcastReceiver, filter);
mHandler.obtainMessage(INITIALIZE_STATE).sendToTarget();
}
private void handleUpdateAirplaneModeStatus() {
mAirplaneModeEnabled = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.AIRPLANE_MODE_ON, 0) == 1;
if (DBG) Slog.d(TAG, "APM status updated to " + mAirplaneModeEnabled);
}
private void handleUpdateSimSubscriptionInfo() {
List<SubscriptionInfo> activeSubscriptionInfoList =
mSubscriptionManager.getActiveSubscriptionInfoList();
if (DBG) Slog.d(TAG, "handleUpdateSimSubscriptionInfo: " + activeSubscriptionInfoList);
if (activeSubscriptionInfoList == null) {
setSimNeedsEmergencyAffordance(neededNow);
return neededNow;
return;
}
boolean needsAffordance = false;
for (SubscriptionInfo info : activeSubscriptionInfoList) {
int mcc = info.getMcc();
if (mccRequiresEmergencyAffordance(mcc)) {
neededNow = true;
if (isoRequiresEmergencyAffordance(info.getCountryIso())) {
needsAffordance = true;
break;
} else if (mcc != 0 && mcc != Integer.MAX_VALUE){
// a Sim with a different mcc code was found
neededNow = false;
}
String simOperator = mTelephonyManager
.createForSubscriptionId(info.getSubscriptionId()).getSimOperator();
mcc = 0;
if (simOperator != null && simOperator.length() >= 3) {
mcc = Integer.parseInt(simOperator.substring(0, 3));
}
if (mcc != 0) {
if (mccRequiresEmergencyAffordance(mcc)) {
neededNow = true;
break;
} else {
// a Sim with a different mcc code was found
neededNow = false;
}
}
}
setSimNeedsEmergencyAffordance(neededNow);
return neededNow;
mAnySimNeedsEmergencyAffordance = needsAffordance;
updateEmergencyAffordanceNeeded();
}
private void setSimNeedsEmergencyAffordance(boolean simNeedsEmergencyAffordance) {
if (simNeededAffordanceBefore() != simNeedsEmergencyAffordance) {
private void handleNetworkCountryChanged(String countryIso, int slotId) {
if (DBG) {
Slog.d(TAG, "handleNetworkCountryChanged: countryIso=" + countryIso
+ ", slotId=" + slotId);
}
if (TextUtils.isEmpty(countryIso) && mAirplaneModeEnabled) {
Slog.w(TAG, "Ignore empty countryIso report when APM is on.");
return;
}
updateNetworkCountry();
updateEmergencyAffordanceNeeded();
}
private void updateNetworkCountry() {
boolean needsAffordance = false;
final int activeModems = mTelephonyManager.getActiveModemCount();
for (int i = 0; i < activeModems; i++) {
String countryIso = mTelephonyManager.getNetworkCountryIso(i);
if (DBG) Slog.d(TAG, "UpdateNetworkCountry: slotId=" + i + " countryIso=" + countryIso);
if (isoRequiresEmergencyAffordance(countryIso)) {
needsAffordance = true;
break;
}
}
mAnyNetworkNeedsEmergencyAffordance = needsAffordance;
updateEmergencyAffordanceNeeded();
}
private boolean isoRequiresEmergencyAffordance(String iso) {
return mEmergencyCallCountryIsos.contains(iso);
}
private void updateEmergencyAffordanceNeeded() {
if (DBG) {
Slog.d(TAG, "updateEmergencyAffordanceNeeded: mEmergencyAffordanceNeeded="
+ mEmergencyAffordanceNeeded + ", mVoiceCapable=" + mVoiceCapable
+ ", mAnySimNeedsEmergencyAffordance=" + mAnySimNeedsEmergencyAffordance
+ ", mAnyNetworkNeedsEmergencyAffordance="
+ mAnyNetworkNeedsEmergencyAffordance);
}
boolean lastAffordanceNeeded = mEmergencyAffordanceNeeded;
mEmergencyAffordanceNeeded = mVoiceCapable
&& (mAnySimNeedsEmergencyAffordance || mAnyNetworkNeedsEmergencyAffordance);
if (lastAffordanceNeeded != mEmergencyAffordanceNeeded) {
Settings.Global.putInt(mContext.getContentResolver(),
EMERGENCY_SIM_INSERTED_SETTING,
simNeedsEmergencyAffordance ? 1 : 0);
}
if (simNeedsEmergencyAffordance != mSimNeedsEmergencyAffordance) {
mSimNeedsEmergencyAffordance = simNeedsEmergencyAffordance;
updateEmergencyAffordanceNeeded();
Settings.Global.EMERGENCY_AFFORDANCE_NEEDED,
mEmergencyAffordanceNeeded ? 1 : 0);
}
}
private boolean simNeededAffordanceBefore() {
return Settings.Global.getInt(mContext.getContentResolver(),
EMERGENCY_SIM_INSERTED_SETTING, 0) != 0;
private void dumpInternal(IndentingPrintWriter ipw) {
ipw.println("EmergencyAffordanceService (dumpsys emergency_affordance) state:\n");
ipw.println("mEmergencyAffordanceNeeded=" + mEmergencyAffordanceNeeded);
ipw.println("mVoiceCapable=" + mVoiceCapable);
ipw.println("mAnySimNeedsEmergencyAffordance=" + mAnySimNeedsEmergencyAffordance);
ipw.println("mAnyNetworkNeedsEmergencyAffordance=" + mAnyNetworkNeedsEmergencyAffordance);
ipw.println("mEmergencyCallCountryIsos=" + String.join(",", mEmergencyCallCountryIsos));
}
private boolean handleUpdateCellInfo() {
List<CellInfo> cellInfos = mTelephonyManager.getAllCellInfo();
if (cellInfos == null) {
return false;
}
boolean stopScanningAfterScan = false;
for (CellInfo cellInfo : cellInfos) {
int mcc = 0;
if (cellInfo instanceof CellInfoGsm) {
mcc = ((CellInfoGsm) cellInfo).getCellIdentity().getMcc();
} else if (cellInfo instanceof CellInfoLte) {
mcc = ((CellInfoLte) cellInfo).getCellIdentity().getMcc();
} else if (cellInfo instanceof CellInfoWcdma) {
mcc = ((CellInfoWcdma) cellInfo).getCellIdentity().getMcc();
private final class BinderService extends Binder {
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) {
return;
}
if (mccRequiresEmergencyAffordance(mcc)) {
setNetworkNeedsEmergencyAffordance(true);
return true;
} else if (mcc != 0 && mcc != Integer.MAX_VALUE) {
// we found an mcc that isn't in the list, abort
stopScanningAfterScan = true;
}
}
if (stopScanningAfterScan) {
stopScanning();
} else {
onCellScanFinishedUnsuccessful();
}
setNetworkNeedsEmergencyAffordance(false);
return false;
}
private void setNetworkNeedsEmergencyAffordance(boolean needsAffordance) {
synchronized (mLock) {
mNetworkNeedsEmergencyAffordance = needsAffordance;
updateEmergencyAffordanceNeeded();
dumpInternal(new IndentingPrintWriter(pw, " "));
}
}
private void onCellScanFinishedUnsuccessful() {
synchronized (mLock) {
mScansCompleted++;
if (mScansCompleted >= NUM_SCANS_UNTIL_ABORT) {
stopScanning();
}
}
}
private boolean mccRequiresEmergencyAffordance(int mcc) {
return mEmergencyCallMccNumbers.contains(mcc);
}
}

View File

@@ -0,0 +1,486 @@
/*
* Copyright (C) 2020 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 com.android.server.emergency;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.os.UserHandle;
import android.provider.Settings;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
import android.telephony.TelephonyManager;
import android.test.mock.MockContentResolver;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import androidx.test.InstrumentationRegistry;
import com.android.internal.util.test.BroadcastInterceptingContext;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.server.SystemService;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
/**
* Unit test for EmergencyAffordanceService (EAS for short) which determines when
* should we enable Emergency Affordance feature (EA for short).
*
* Please refer to https://source.android.com/devices/tech/connect/emergency-affordance
* to see the details of the feature.
*/
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class EmergencyAffordanceServiceTest {
// Default country ISO that should enable EA. Value comes from resource
// com.android.internal.R.array.config_emergency_iso_country_codes
private static final String EMERGENCY_ISO_CODE = "in";
// Randomly picked country ISO that should not enable EA.
private static final String NON_EMERGENCY_ISO_CODE = "us";
// Valid values for Settings.Global.EMERGENCY_AFFORDANCE_NEEDED
private static final int OFF = 0; // which means feature disabled
private static final int ON = 1; // which means feature enabled
private static final int ACTIVE_MODEM_COUNT = 2;
@Mock private Resources mResources;
@Mock private SubscriptionManager mSubscriptionManager;
@Mock private TelephonyManager mTelephonyManager;
private TestContext mServiceContext;
private MockContentResolver mContentResolver;
private OnSubscriptionsChangedListener mSubscriptionChangedListener;
private EmergencyAffordanceService mService;
// Testable Context that mocks resources, content resolver and system services
private class TestContext extends BroadcastInterceptingContext {
TestContext(Context base) {
super(base);
}
@Override
public ContentResolver getContentResolver() {
return mContentResolver;
}
@Override
public Resources getResources() {
return mResources;
}
@Override
public Object getSystemService(String name) {
switch (name) {
case Context.TELEPHONY_SUBSCRIPTION_SERVICE:
return mSubscriptionManager;
case Context.TELEPHONY_SERVICE:
return mTelephonyManager;
default:
return super.getSystemService(name);
}
}
}
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
doReturn(new String[] { EMERGENCY_ISO_CODE }).when(mResources)
.getStringArray(com.android.internal.R.array.config_emergency_iso_country_codes);
final Context context = InstrumentationRegistry.getContext();
mServiceContext = new TestContext(context);
mContentResolver = new MockContentResolver(mServiceContext);
mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
// Initialize feature off, to have constant starting
Settings.Global.putInt(mContentResolver, Settings.Global.EMERGENCY_AFFORDANCE_NEEDED, 0);
mService = new EmergencyAffordanceService(mServiceContext);
}
/**
* Verify if the device is not voice capable, the feature should be disabled.
*/
@Test
public void testSettings_shouldBeOff_whenVoiceCapableIsFalse() throws Exception {
// Given: the device is not voice capable
// When: setup device and boot service
setUpDevice(false /* withVoiceCapable */, true /* withEmergencyIsoInSim */,
true /* withEmergencyIsoInCell */);
// Then: EA setting will should be 0
verifyEmergencyAffordanceNeededSettings(OFF);
}
/**
* Verify the voice capable device is booted up without EA-enabled cell network, with
* no EA-enabled SIM installed, feature should be disabled.
*/
@Test
public void testSettings_shouldBeOff_whenWithoutEAEanbledNetworkNorSim() throws Exception {
// Given: the device is voice capble, no EA-enable SIM, no EA-enabled Cell
setUpDevice(true /* withVoiceCapable */, false /* withEmergencyIsoInSim */,
false /* withEmergencyIsoInCell */);
// Then: EA setting will should be 0
verifyEmergencyAffordanceNeededSettings(OFF);
}
/**
* Verify the voice capable device is booted up with EA-enabled SIM installed, the
* feature should be enabled.
*/
@Test
public void testSettings_shouldBeOn_whenBootUpWithEAEanbledSim() throws Exception {
// Given: the device is voice capble, with EA-enable SIM, no EA-enabled Cell
setUpDevice(true /* withVoiceCapable */, true /* withEmergencyIsoInSim */,
false /* withEmergencyIsoInCell */);
// Then: EA setting will immediately update to 1
verifyEmergencyAffordanceNeededSettings(ON);
}
/**
* Verify the voice capable device is booted up with EA-enabled Cell network, the
* feature should be enabled.
*/
@Test
public void testSettings_shouldBeOn_whenBootUpWithEAEanbledCell() throws Exception {
// Given: the device is voice capble, with EA-enable SIM, with EA-enabled Cell
setUpDevice(true /* withVoiceCapable */, false /* withEmergencyIsoInSim */,
true /* withEmergencyIsoInCell */);
// Then: EA setting will immediately update to 1
verifyEmergencyAffordanceNeededSettings(ON);
}
/**
* Verify when device boot up with no EA-enabled SIM, but later install one,
* feature should be enabled.
*/
@Test
public void testSettings_shouldBeOn_whenSubscriptionInfoChangedWithEmergencyIso()
throws Exception {
// Given: the device is voice capable, boot up with no EA-enabled SIM, no EA-enabled Cell
setUpDevice(true /* withVoiceCapable */, false/* withEmergencyIsoInSim */,
false /* withEmergencyIsoInCell */);
// When: Insert EA-enabled SIM and get notified
setUpSim(true /* withEmergencyIsoInSim */);
mSubscriptionChangedListener.onSubscriptionsChanged();
// Then: EA Setting will update to 1
verifyEmergencyAffordanceNeededSettings(ON);
}
/**
* Verify when feature was on, device re-insert with no EA-enabled SIMs,
* feature should be disabled.
*/
@Test
public void testSettings_shouldBeOff_whenSubscriptionInfoChangedWithoutEmergencyIso()
throws Exception {
// Given: the device is voice capable, no EA-enabled Cell, with EA-enabled SIM
setUpDevice(true /* withVoiceCapable */, true /* withEmergencyIsoInSim */,
false /* withEmergencyIsoInCell */);
// When: All SIMs are replaced with EA-disabled ones.
setUpSim(false /* withEmergencyIsoInSim */);
mSubscriptionChangedListener.onSubscriptionsChanged();
// Then: EA Setting will update to 0
verifyEmergencyAffordanceNeededSettings(OFF);
}
/**
* Verify when device boot up with no EA-enabled Cell, but later move into one,
* feature should be enabled.
*/
@Test
public void testSettings_shouldBeOn_whenCountryIsoChangedWithEmergencyIso()
throws Exception {
// Given: the device is voice capable, boot up with no EA-enabled SIM, no EA-enabled Cell
setUpDevice(true /* withVoiceCapable */, false/* withEmergencyIsoInSim */,
false /* withEmergencyIsoInCell */);
// When: device locale change to EA-enabled Cell and get notified
resetCell(true /* withEmergencyIsoInSim */);
sendBroadcastNetworkCountryChanged(EMERGENCY_COUNTRY_ISO);
// Then: EA Setting will update to 1
verifyEmergencyAffordanceNeededSettings(ON);
}
/**
* Verify when device boot up with EA-enabled Cell, but later move out of it,
* feature should be enabled.
*/
@Test
public void testSettings_shouldBeOff_whenCountryIsoChangedWithoutEmergencyIso()
throws Exception {
// Given: the device is voice capable, boot up with no EA-enabled SIM, with EA-enabled Cell
setUpDevice(true /* withVoiceCapable */, false/* withEmergencyIsoInSim */,
true /* withEmergencyIsoInCell */);
// When: device locale change to no EA-enabled Cell and get notified
resetCell(false /* withEmergencyIsoInSim */);
sendBroadcastNetworkCountryChanged(NON_EMERGENCY_COUNTRY_ISO);
// Then: EA Setting will update to 0
verifyEmergencyAffordanceNeededSettings(OFF);
}
/**
* Verify if device is not in EA-enabled Mobile Network without EA-enable SIM(s) installed,
* when receive SubscriptionInfo change, the feature should not be enabled.
*/
@Test
public void testSettings_shouldBeOff_whenNoEmergencyIsoInCellNorSim() throws Exception {
// Given: the device is voice capable, no EA-enabled Cell, no EA-enabled SIM
setUpDevice(true /* withVoiceCapable */, false /* withEmergencyIsoInSim */,
false /* withEmergencyIsoInCell */);
// When: Subscription changed event received
mSubscriptionChangedListener.onSubscriptionsChanged();
// Then: EA Settings should be 0
verifyEmergencyAffordanceNeededSettings(OFF);
}
/**
* Verify while feature was on, when device receive empty country iso change, while APM is
* enabled, feature status should keep on.
*/
@Test
public void testSettings_shouldOn_whenReceiveEmptyISOWithAPMEnabled() throws Exception {
// Given: the device is voice capable, no EA-enabled SIM, with EA-enabled Cell
setUpDevice(true /* withVoiceCapable */, false /* withEmergencyIsoInSim */,
true /* withEmergencyIsoInCell */);
// Then: EA Settings will update to 1
verifyEmergencyAffordanceNeededSettings(ON);
// When: Airplane mode is enabled, and we receive EMPTY ISO in locale change
setAirplaneMode(true);
sendBroadcastNetworkCountryChanged(EMPTY_COUNTRY_ISO);
// Then: EA Settings will keep to 1
verifyEmergencyAffordanceNeededSettings(ON);
}
/**
* Verify while feature was on, when device receive empty country iso change, while APM is
* disabled, feature should be disabled.
*/
@Test
public void testSettings_shouldOff_whenReceiveEmptyISOWithAPMDisabled() throws Exception {
// Given: the device is voice capable, no EA-enabled SIM, with EA-enabled Cell
setUpDevice(true /* withVoiceCapable */, false /* withEmergencyIsoInSim */,
true /* withEmergencyIsoInCell */);
// Then: EA Settings will update to 1
verifyEmergencyAffordanceNeededSettings(ON);
// When: Airplane mode is disabled, and we receive valid empty ISO in locale change
setUpCell(false /* withEmergencyIsoInCell */);
setAirplaneMode(false);
sendBroadcastNetworkCountryChanged(EMPTY_COUNTRY_ISO);
// Then: EA Settings will keep to 0
verifyEmergencyAffordanceNeededSettings(OFF);
}
/**
* Verify when airplane mode is turn on and off in cell network with EA-enabled ISO,
* feature should keep enabled.
*/
@Test
public void testSettings_shouldBeOn_whenAirplaneModeOnOffWithEmergencyIsoInCell()
throws Exception {
// Given: the device is voice capable, no EA-enabled SIM, with EA-enabled Cell
setUpDevice(true /* withVoiceCapable */, false /* withEmergencyIsoInSim */,
true /* withEmergencyIsoInCell */);
// When: Device receive locale change with EA-enabled iso
sendBroadcastNetworkCountryChanged(EMERGENCY_COUNTRY_ISO);
// When: Airplane mode is disabled
setAirplaneMode(false);
// Then: EA Settings will keep with 1
verifyEmergencyAffordanceNeededSettings(ON);
// When: Airplane mode is enabled
setAirplaneMode(true);
// Then: EA Settings is still 1
verifyEmergencyAffordanceNeededSettings(ON);
}
/**
* Verify when airplane mode is turn on and off with EA-enabled ISO in SIM,
* feature should keep enabled.
*/
@Test
public void testSettings_shouldBeOn_whenAirplaneModeOnOffWithEmergencyIsoInSim()
throws Exception {
// Given: the device is voice capable, no EA-enabled Cell Network, with EA-enabled SIM
setUpDevice(true /* withVoiceCapable */, true /* withEmergencyIsoInSim */,
false /* withEmergencyIsoInCell */);
// When: Airplane mode is disabled
setAirplaneMode(false);
// Then: EA Settings will keep with 1
verifyEmergencyAffordanceNeededSettings(ON);
// When: Airplane mode is enabled
setAirplaneMode(true);
// Then: EA Settings is still 1
verifyEmergencyAffordanceNeededSettings(ON);
}
// EAS reads voice capable during boot up and cache it. To test non voice capable device,
// we can not put this in setUp
private void setUpDevice(boolean withVoiceCapable, boolean withEmergencyIsoInSim,
boolean withEmergencyIsoInCell) throws Exception {
setUpVoiceCapable(withVoiceCapable);
setUpSim(withEmergencyIsoInSim);
setUpCell(withEmergencyIsoInCell);
// bypass onStart which is used to publish binder service and need sepolicy policy update
// mService.onStart();
mService.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
if (!withVoiceCapable) {
return;
}
captureSubscriptionChangeListener();
}
private void setUpVoiceCapable(boolean voiceCapable) {
doReturn(voiceCapable).when(mTelephonyManager).isVoiceCapable();
}
private static final Supplier<String> EMPTY_COUNTRY_ISO = () -> "";
private static final Supplier<String> EMERGENCY_COUNTRY_ISO = () -> EMERGENCY_ISO_CODE;
private static final Supplier<String> NON_EMERGENCY_COUNTRY_ISO = () -> NON_EMERGENCY_ISO_CODE;
private void sendBroadcastNetworkCountryChanged(Supplier<String> countryIso) {
Intent intent = new Intent(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED);
intent.putExtra(TelephonyManager.EXTRA_NETWORK_COUNTRY, countryIso.get());
SubscriptionManager.putPhoneIdAndSubIdExtra(intent, 0);
mServiceContext.sendBroadcastAsUser(intent, UserHandle.ALL);
}
private void setUpSim(boolean withEmergencyIsoInSim) {
List<SubscriptionInfo> subInfos = getSubscriptionInfoList(withEmergencyIsoInSim);
doReturn(subInfos).when(mSubscriptionManager).getActiveSubscriptionInfoList();
}
private void setUpCell(boolean withEmergencyIsoInCell) {
doReturn(ACTIVE_MODEM_COUNT).when(mTelephonyManager).getActiveModemCount();
doReturn(NON_EMERGENCY_ISO_CODE).when(mTelephonyManager).getNetworkCountryIso(0);
doReturn(withEmergencyIsoInCell ? EMERGENCY_ISO_CODE : NON_EMERGENCY_ISO_CODE)
.when(mTelephonyManager).getNetworkCountryIso(1);
}
private void resetCell(boolean withEmergencyIsoInCell) {
doReturn(withEmergencyIsoInCell ? EMERGENCY_ISO_CODE : NON_EMERGENCY_ISO_CODE)
.when(mTelephonyManager).getNetworkCountryIso(1);
}
private void captureSubscriptionChangeListener() {
final ArgumentCaptor<OnSubscriptionsChangedListener> subChangedListenerCaptor =
ArgumentCaptor.forClass(OnSubscriptionsChangedListener.class);
verify(mSubscriptionManager).addOnSubscriptionsChangedListener(
subChangedListenerCaptor.capture());
mSubscriptionChangedListener = subChangedListenerCaptor.getValue();
}
private void setAirplaneMode(boolean enabled) {
// Change the system settings
Settings.Global.putInt(mContentResolver, Settings.Global.AIRPLANE_MODE_ON,
enabled ? 1 : 0);
// Post the intent
final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
intent.putExtra("state", enabled);
mServiceContext.sendBroadcastAsUser(intent, UserHandle.ALL);
}
private List<SubscriptionInfo> getSubscriptionInfoList(boolean withEmergencyIso) {
List<SubscriptionInfo> subInfos = new ArrayList<>(2);
// Test with Multiple SIMs. SIM1 is a non-EA SIM
// Only country iso is valuable, all other info are filled with dummy values
SubscriptionInfo subInfo = new SubscriptionInfo(1, "890126042XXXXXXXXXXX", 0, "T-mobile",
"T-mobile", 0, 255, "12345", 0, null,
"310", "226", NON_EMERGENCY_ISO_CODE, false, null, null);
subInfos.add(subInfo);
// SIM2 can configured to be non-EA or EA SIM according parameter withEmergencyIso
SubscriptionInfo subInfo2 = new SubscriptionInfo(1, "890126042XXXXXXXXXXX", 0, "Airtel",
"Aritel", 0, 255, "12345", 0, null, "310", "226",
withEmergencyIso ? EMERGENCY_ISO_CODE : NON_EMERGENCY_ISO_CODE,
false, null, null);
subInfos.add(subInfo2);
return subInfos;
}
// EAS has handler thread to perform heavy work, while FakeSettingProvider does not support
// ContentObserver. To make sure consistent result, we use a simple sleep & retry to wait for
// real work finished before verify result.
private static final int TIME_DELAY_BEFORE_VERIFY_IN_MS = 50;
private static final int RETRIES_BEFORE_VERIFY = 20;
private void verifyEmergencyAffordanceNeededSettings(int expected) throws Exception {
try {
int ct = 0;
int actual = -1;
while (ct++ < RETRIES_BEFORE_VERIFY
&& (actual = Settings.Global.getInt(mContentResolver,
Settings.Global.EMERGENCY_AFFORDANCE_NEEDED)) != expected) {
Thread.sleep(TIME_DELAY_BEFORE_VERIFY_IN_MS);
}
assertEquals(expected, actual);
} catch (Settings.SettingNotFoundException e) {
fail("SettingNotFoundException thrown for Settings.Global.EMERGENCY_AFFORDANCE_NEEDED");
}
}
}