blob: a91fe773a5cfc33052a8e6a0b85c1bdba343283c [file] [log] [blame]
/*
* Copyright (C) 2016 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 android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
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 com.android.server.SystemService;
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
*/
public class EmergencyAffordanceService extends SystemService {
private static final String TAG = "EmergencyAffordanceService";
private static final int NUM_SCANS_UNTIL_ABORT = 4;
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
*/
private static final String EMERGENCY_SIM_INSERTED_SETTING = "emergency_sim_inserted_before";
private final Context mContext;
private final ArrayList<Integer> mEmergencyCallMccNumbers;
private final Object mLock = new Object();
private TelephonyManager mTelephonyManager;
private SubscriptionManager mSubscriptionManager;
private boolean mEmergencyAffordanceNeeded;
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 mVoiceCapable;
private void requestCellScan() {
mHandler.obtainMessage(CELL_INFO_STATE_CHANGED).sendToTarget();
}
private SubscriptionManager.OnSubscriptionsChangedListener mSubscriptionChangedListener
= new SubscriptionManager.OnSubscriptionsChangedListener() {
@Override
public void onSubscriptionsChanged() {
mHandler.obtainMessage(SUBSCRIPTION_CHANGED).sendToTarget();
}
};
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]);
}
}
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();
}
}
}
private void stopScanning() {
synchronized (mLock) {
mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
mScansCompleted = 0;
}
}
private boolean isEmergencyAffordanceNeeded() {
synchronized (mLock) {
return mEmergencyAffordanceNeeded;
}
}
@Override
public void onStart() {
}
@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);
}
}
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) {
switch (msg.what) {
case INITIALIZE_STATE:
handleInitializeState();
break;
case CELL_INFO_STATE_CHANGED:
handleUpdateCellInfo();
break;
case SUBSCRIPTION_CHANGED:
handleUpdateSimSubscriptionInfo();
break;
}
}
}
private void handleInitializeState() {
if (handleUpdateSimSubscriptionInfo()) {
return;
}
if (handleUpdateCellInfo()) {
return;
}
updateEmergencyAffordanceNeeded();
}
private boolean handleUpdateSimSubscriptionInfo() {
boolean neededBefore = simNeededAffordanceBefore();
boolean neededNow = neededBefore;
List<SubscriptionInfo> activeSubscriptionInfoList =
mSubscriptionManager.getActiveSubscriptionInfoList();
if (activeSubscriptionInfoList == null) {
setSimNeedsEmergencyAffordance(neededNow);
return neededNow;
}
for (SubscriptionInfo info : activeSubscriptionInfoList) {
int mcc = info.getMcc();
if (mccRequiresEmergencyAffordance(mcc)) {
neededNow = true;
break;
} else if (mcc != 0 && mcc != Integer.MAX_VALUE){
// a Sim with a different mcc code was found
neededNow = false;
}
String simOperator = mTelephonyManager.getSimOperator(info.getSubscriptionId());
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;
}
private void setSimNeedsEmergencyAffordance(boolean simNeedsEmergencyAffordance) {
if (simNeededAffordanceBefore() != simNeedsEmergencyAffordance) {
Settings.Global.putInt(mContext.getContentResolver(),
EMERGENCY_SIM_INSERTED_SETTING,
simNeedsEmergencyAffordance ? 1 : 0);
}
if (simNeedsEmergencyAffordance != mSimNeedsEmergencyAffordance) {
mSimNeedsEmergencyAffordance = simNeedsEmergencyAffordance;
updateEmergencyAffordanceNeeded();
}
}
private boolean simNeededAffordanceBefore() {
return Settings.Global.getInt(mContext.getContentResolver(),
EMERGENCY_SIM_INSERTED_SETTING, 0) != 0;
}
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();
}
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();
}
}
private void onCellScanFinishedUnsuccessful() {
synchronized (mLock) {
mScansCompleted++;
if (mScansCompleted >= NUM_SCANS_UNTIL_ABORT) {
stopScanning();
}
}
}
private boolean mccRequiresEmergencyAffordance(int mcc) {
return mEmergencyCallMccNumbers.contains(mcc);
}
}