Merge "Fix EmergencyAffordanceService"

This commit is contained in:
Treehugger Robot
2020-04-24 01:48:19 +00:00
committed by Gerrit Code Review
4 changed files with 670 additions and 202 deletions

View File

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

View File

@@ -3168,7 +3168,7 @@
<java-symbol type="string" name="global_action_emergency" /> <java-symbol type="string" name="global_action_emergency" />
<java-symbol type="string" name="config_emergency_call_number" /> <java-symbol type="string" name="config_emergency_call_number" />
<java-symbol type="string" name="config_emergency_dialer_package" /> <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_dozeDoubleTapSensorType" />
<java-symbol type="string" name="config_dozeTapSensorType" /> <java-symbol type="string" name="config_dozeTapSensorType" />

View File

@@ -20,90 +20,80 @@ import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.os.Binder;
import android.os.Build;
import android.os.Handler; import android.os.Handler;
import android.os.HandlerThread; import android.os.HandlerThread;
import android.os.Looper; import android.os.Looper;
import android.os.Message; import android.os.Message;
import android.provider.Settings; 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.SubscriptionInfo;
import android.telephony.SubscriptionManager; import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager; 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 com.android.server.SystemService;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
/** /**
* A service that listens to connectivity and SIM card changes and determines if the emergency mode * A service that listens to connectivity and SIM card changes and determines if the emergency
* should be enabled * affordance should be enabled.
*/ */
public class EmergencyAffordanceService extends SystemService { public class EmergencyAffordanceService extends SystemService {
private static final String TAG = "EmergencyAffordanceService"; 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 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 * @param arg1 slot Index
* requires the emergency affordance. The value is a boolean (1 or 0). * @param arg2 0
* @hide * @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 Context mContext;
private final ArrayList<Integer> mEmergencyCallMccNumbers; // Country ISOs that require affordance
private final ArrayList<String> mEmergencyCallCountryIsos;
private final Object mLock = new Object();
private TelephonyManager mTelephonyManager;
private SubscriptionManager mSubscriptionManager; private SubscriptionManager mSubscriptionManager;
private boolean mEmergencyAffordanceNeeded; private TelephonyManager mTelephonyManager;
private MyHandler mHandler; private MyHandler mHandler;
private int mScansCompleted; private boolean mAnySimNeedsEmergencyAffordance;
private PhoneStateListener mPhoneStateListener = new PhoneStateListener() { private boolean mAnyNetworkNeedsEmergencyAffordance;
@Override private boolean mEmergencyAffordanceNeeded;
public void onCellInfoChanged(List<CellInfo> cellInfo) { private boolean mAirplaneModeEnabled;
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 mVoiceCapable; private boolean mVoiceCapable;
private void requestCellScan() { private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
mHandler.obtainMessage(CELL_INFO_STATE_CHANGED).sendToTarget(); @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 private SubscriptionManager.OnSubscriptionsChangedListener mSubscriptionChangedListener
= new SubscriptionManager.OnSubscriptionsChangedListener() { = new SubscriptionManager.OnSubscriptionsChangedListener() {
@@ -116,207 +106,200 @@ public class EmergencyAffordanceService extends SystemService {
public EmergencyAffordanceService(Context context) { public EmergencyAffordanceService(Context context) {
super(context); super(context);
mContext = context; mContext = context;
int[] numbers = context.getResources().getIntArray( String[] isos = context.getResources().getStringArray(
com.android.internal.R.array.config_emergency_mcc_codes); com.android.internal.R.array.config_emergency_iso_country_codes);
mEmergencyCallMccNumbers = new ArrayList<>(numbers.length); mEmergencyCallCountryIsos = new ArrayList<>(isos.length);
for (int i = 0; i < numbers.length; i++) { for (String iso : isos) {
mEmergencyCallMccNumbers.add(numbers[i]); mEmergencyCallCountryIsos.add(iso);
} }
}
private void updateEmergencyAffordanceNeeded() { if (Build.IS_DEBUGGABLE) {
synchronized (mLock) { String overrideIso = Settings.Global.getString(
mEmergencyAffordanceNeeded = mVoiceCapable && (mSimNeedsEmergencyAffordance || mContext.getContentResolver(), EMERGENCY_AFFORDANCE_OVERRIDE_ISO);
mNetworkNeedsEmergencyAffordance); if (!TextUtils.isEmpty(overrideIso)) {
Settings.Global.putInt(mContext.getContentResolver(), if (DBG) Slog.d(TAG, "Override ISO to " + overrideIso);
Settings.Global.EMERGENCY_AFFORDANCE_NEEDED, mEmergencyCallCountryIsos.clear();
mEmergencyAffordanceNeeded ? 1 : 0); mEmergencyCallCountryIsos.add(overrideIso);
if (mEmergencyAffordanceNeeded) {
stopScanning();
} }
} }
} }
private void stopScanning() {
synchronized (mLock) {
mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
mScansCompleted = 0;
}
}
private boolean isEmergencyAffordanceNeeded() {
synchronized (mLock) {
return mEmergencyAffordanceNeeded;
}
}
@Override @Override
public void onStart() { public void onStart() {
if (DBG) Slog.i(TAG, "onStart");
publishBinderService(SERVICE_NAME, new BinderService());
} }
@Override @Override
public void onBootPhase(int phase) { public void onBootPhase(int phase) {
if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) { if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
mTelephonyManager = mContext.getSystemService(TelephonyManager.class); if (DBG) Slog.i(TAG, "onBootPhase");
mVoiceCapable = mTelephonyManager.isVoiceCapable(); handleThirdPartyBootPhase();
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);
} }
} }
private void startScanning() {
mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CELL_INFO
| PhoneStateListener.LISTEN_CELL_LOCATION);
}
/** Handler to do the heavier work on */ /** Handler to do the heavier work on */
private class MyHandler extends Handler { private class MyHandler extends Handler {
public MyHandler(Looper l) { public MyHandler(Looper l) {
super(l); super(l);
} }
@Override @Override
public void handleMessage(Message msg) { public void handleMessage(Message msg) {
if (DBG) Slog.d(TAG, "handleMessage: " + msg.what);
switch (msg.what) { switch (msg.what) {
case INITIALIZE_STATE: case INITIALIZE_STATE:
handleInitializeState(); handleInitializeState();
break; break;
case CELL_INFO_STATE_CHANGED: case NETWORK_COUNTRY_CHANGED:
handleUpdateCellInfo(); final String countryIso = (String) msg.obj;
final int slotId = msg.arg1;
handleNetworkCountryChanged(countryIso, slotId);
break; break;
case SUBSCRIPTION_CHANGED: case SUBSCRIPTION_CHANGED:
handleUpdateSimSubscriptionInfo(); handleUpdateSimSubscriptionInfo();
break; break;
case UPDATE_AIRPLANE_MODE_STATUS:
handleUpdateAirplaneModeStatus();
break;
default:
Slog.e(TAG, "Unexpected message received: " + msg.what);
} }
} }
} }
private void handleInitializeState() { private void handleInitializeState() {
if (handleUpdateSimSubscriptionInfo()) { if (DBG) Slog.d(TAG, "handleInitializeState");
return; handleUpdateAirplaneModeStatus();
} handleUpdateSimSubscriptionInfo();
if (handleUpdateCellInfo()) { updateNetworkCountry();
return;
}
updateEmergencyAffordanceNeeded(); updateEmergencyAffordanceNeeded();
} }
private boolean handleUpdateSimSubscriptionInfo() { private void handleThirdPartyBootPhase() {
boolean neededBefore = simNeededAffordanceBefore(); if (DBG) Slog.d(TAG, "handleThirdPartyBootPhase");
boolean neededNow = neededBefore; 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 = List<SubscriptionInfo> activeSubscriptionInfoList =
mSubscriptionManager.getActiveSubscriptionInfoList(); mSubscriptionManager.getActiveSubscriptionInfoList();
if (DBG) Slog.d(TAG, "handleUpdateSimSubscriptionInfo: " + activeSubscriptionInfoList);
if (activeSubscriptionInfoList == null) { if (activeSubscriptionInfoList == null) {
setSimNeedsEmergencyAffordance(neededNow); return;
return neededNow;
} }
boolean needsAffordance = false;
for (SubscriptionInfo info : activeSubscriptionInfoList) { for (SubscriptionInfo info : activeSubscriptionInfoList) {
int mcc = info.getMcc(); if (isoRequiresEmergencyAffordance(info.getCountryIso())) {
if (mccRequiresEmergencyAffordance(mcc)) { needsAffordance = true;
neededNow = true;
break; 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) { private void handleNetworkCountryChanged(String countryIso, int slotId) {
if (simNeededAffordanceBefore() != simNeedsEmergencyAffordance) { 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(), Settings.Global.putInt(mContext.getContentResolver(),
EMERGENCY_SIM_INSERTED_SETTING, Settings.Global.EMERGENCY_AFFORDANCE_NEEDED,
simNeedsEmergencyAffordance ? 1 : 0); mEmergencyAffordanceNeeded ? 1 : 0);
}
if (simNeedsEmergencyAffordance != mSimNeedsEmergencyAffordance) {
mSimNeedsEmergencyAffordance = simNeedsEmergencyAffordance;
updateEmergencyAffordanceNeeded();
} }
} }
private boolean simNeededAffordanceBefore() { private void dumpInternal(IndentingPrintWriter ipw) {
return Settings.Global.getInt(mContext.getContentResolver(), ipw.println("EmergencyAffordanceService (dumpsys emergency_affordance) state:\n");
EMERGENCY_SIM_INSERTED_SETTING, 0) != 0; 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() { private final class BinderService extends Binder {
List<CellInfo> cellInfos = mTelephonyManager.getAllCellInfo(); @Override
if (cellInfos == null) { protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
return false; if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) {
} return;
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();
} }
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) { dumpInternal(new IndentingPrintWriter(pw, " "));
synchronized (mLock) {
mNetworkNeedsEmergencyAffordance = needsAffordance;
updateEmergencyAffordanceNeeded();
} }
} }
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");
}
}
}