blob: cc7915cc35345cee500e40f44930e72988c7edad [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.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.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.List;
/**
* 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 String SERVICE_NAME = "emergency_affordance";
private static final int INITIALIZE_STATE = 1;
/**
* @param arg1 slot Index
* @param arg2 0
* @param obj ISO country code
*/
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;
// Country ISOs that require affordance
private final ArrayList<String> mEmergencyCallCountryIsos;
private SubscriptionManager mSubscriptionManager;
private TelephonyManager mTelephonyManager;
private MyHandler mHandler;
private boolean mAnySimNeedsEmergencyAffordance;
private boolean mAnyNetworkNeedsEmergencyAffordance;
private boolean mEmergencyAffordanceNeeded;
private boolean mAirplaneModeEnabled;
private boolean mVoiceCapable;
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() {
@Override
public void onSubscriptionsChanged() {
mHandler.obtainMessage(SUBSCRIPTION_CHANGED).sendToTarget();
}
};
public EmergencyAffordanceService(Context context) {
super(context);
mContext = context;
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);
}
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);
}
}
}
@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) {
if (DBG) Slog.i(TAG, "onBootPhase");
handleThirdPartyBootPhase();
}
}
/** 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 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 (DBG) Slog.d(TAG, "handleInitializeState");
handleUpdateAirplaneModeStatus();
handleUpdateSimSubscriptionInfo();
updateNetworkCountry();
updateEmergencyAffordanceNeeded();
}
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) {
return;
}
boolean needsAffordance = false;
for (SubscriptionInfo info : activeSubscriptionInfoList) {
if (isoRequiresEmergencyAffordance(info.getCountryIso())) {
needsAffordance = true;
break;
}
}
mAnySimNeedsEmergencyAffordance = needsAffordance;
updateEmergencyAffordanceNeeded();
}
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(),
Settings.Global.EMERGENCY_AFFORDANCE_NEEDED,
mEmergencyAffordanceNeeded ? 1 : 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 final class BinderService extends Binder {
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) {
return;
}
dumpInternal(new IndentingPrintWriter(pw, " "));
}
}
}