blob: 427f4a788eec7dfd2148caa94916c8b395b92347 [file] [log] [blame]
/*
* Copyright 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.google.android.iwlan;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.database.ContentObserver;
import android.net.Uri;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.telephony.CellInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager;
import android.telephony.ims.ImsManager;
import android.telephony.ims.ImsMmTelManager;
import android.util.Log;
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
import com.google.android.iwlan.flags.FeatureFlags;
import com.google.android.iwlan.flags.FeatureFlagsImpl;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class IwlanEventListener {
private final FeatureFlags mFeatureFlags;
public static final int UNKNOWN_EVENT = -1;
/** On {@link IwlanCarrierConfigChangeListener#onCarrierConfigChanged} is called. */
public static final int CARRIER_CONFIG_CHANGED_EVENT = 1;
/** Wifi turned off or disabled. */
public static final int WIFI_DISABLE_EVENT = 2;
/** Airplane mode turned off or disabled. */
public static final int APM_DISABLE_EVENT = 3;
/** Airplane mode turned on or enabled */
public static final int APM_ENABLE_EVENT = 4;
/** Wifi AccessPoint changed. */
public static final int WIFI_AP_CHANGED_EVENT = 5;
/** Wifi calling turned on or enabled */
public static final int WIFI_CALLING_ENABLE_EVENT = 6;
/** Wifi calling turned off or disabled */
public static final int WIFI_CALLING_DISABLE_EVENT = 7;
/** Cross sim calling enabled */
public static final int CROSS_SIM_CALLING_ENABLE_EVENT = 8;
/** Cross sim calling disabled */
public static final int CROSS_SIM_CALLING_DISABLE_EVENT = 9;
/**
* On {@link IwlanCarrierConfigChangeListener#onCarrierConfigChanged} is called with
* UNKNOWN_CARRIER_ID.
*/
public static final int CARRIER_CONFIG_UNKNOWN_CARRIER_EVENT = 10;
/** On Cellinfo changed */
public static final int CELLINFO_CHANGED_EVENT = 11;
/** On Call state changed */
public static final int CALL_STATE_CHANGED_EVENT = 12;
/** On Preferred Network Type changed */
public static final int PREFERRED_NETWORK_TYPE_CHANGED_EVENT = 13;
/* Events used and handled by IwlanDataService internally */
public static final int DATA_SERVICE_INTERNAL_EVENT_BASE = 100;
/* Events used and handled by IwlanNetworkService internally */
public static final int NETWORK_SERVICE_INTERNAL_EVENT_BASE = 200;
@IntDef({
CARRIER_CONFIG_CHANGED_EVENT,
WIFI_DISABLE_EVENT,
APM_DISABLE_EVENT,
APM_ENABLE_EVENT,
WIFI_AP_CHANGED_EVENT,
WIFI_CALLING_ENABLE_EVENT,
WIFI_CALLING_DISABLE_EVENT,
CROSS_SIM_CALLING_ENABLE_EVENT,
CROSS_SIM_CALLING_DISABLE_EVENT,
CARRIER_CONFIG_UNKNOWN_CARRIER_EVENT,
CELLINFO_CHANGED_EVENT,
CALL_STATE_CHANGED_EVENT,
PREFERRED_NETWORK_TYPE_CHANGED_EVENT,
})
@interface IwlanEventType {}
private static final String LOG_TAG = IwlanEventListener.class.getSimpleName();
private final String SUB_TAG;
private static Boolean sIsAirplaneModeOn;
private static String sWifiSSID = "";
private static final Map<Integer, IwlanEventListener> mInstances = new ConcurrentHashMap<>();
private final Context mContext;
private final int mSlotId;
private int mSubId;
private Uri mCrossSimCallingUri;
private Uri mWfcEnabledUri;
private UserSettingContentObserver mUserSettingContentObserver;
private RadioInfoTelephonyCallback mTelephonyCallback;
SparseArray<Set<Handler>> eventHandlers = new SparseArray<>();
private class UserSettingContentObserver extends ContentObserver {
UserSettingContentObserver(Handler h) {
super(h);
}
@Override
public void onChange(boolean selfChange, Uri uri) {
Objects.requireNonNull(mCrossSimCallingUri, "CrossSimCallingUri must not be null");
Objects.requireNonNull(mWfcEnabledUri, "WfcEnabledUri must not be null");
if (mCrossSimCallingUri.equals(uri)) {
notifyCurrentSetting(uri);
} else if (mWfcEnabledUri.equals(uri)) {
notifyCurrentSetting(uri);
}
}
}
private class RadioInfoTelephonyCallback extends TelephonyCallback
implements TelephonyCallback.CellInfoListener,
TelephonyCallback.CallStateListener,
TelephonyCallback.AllowedNetworkTypesListener {
@Override
public void onCellInfoChanged(List<CellInfo> arrayCi) {
Log.d(LOG_TAG, "Cellinfo changed");
for (Map.Entry<Integer, IwlanEventListener> entry : mInstances.entrySet()) {
IwlanEventListener instance = entry.getValue();
if (instance != null) {
instance.updateHandlers(arrayCi);
}
}
}
@Override
public void onCallStateChanged(int state) {
Log.d(
LOG_TAG,
"Call state changed to " + callStateToString(state) + " for slot " + mSlotId);
IwlanEventListener instance = mInstances.get(mSlotId);
if (instance != null) {
instance.updateHandlers(CALL_STATE_CHANGED_EVENT, state);
}
}
@Override
public void onAllowedNetworkTypesChanged(
@TelephonyManager.AllowedNetworkTypesReason int reason,
@TelephonyManager.NetworkTypeBitMask long allowedNetworkType) {
if (reason != TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER) {
return;
}
IwlanEventListener instance = mInstances.get(mSlotId);
if (instance != null) {
instance.updateHandlers(PREFERRED_NETWORK_TYPE_CHANGED_EVENT, allowedNetworkType);
}
}
}
/**
* Returns IwlanEventListener instance
*/
public static IwlanEventListener getInstance(@NonNull Context context, int slotId) {
return mInstances.computeIfAbsent(
slotId, k -> new IwlanEventListener(context, slotId, new FeatureFlagsImpl()));
}
@VisibleForTesting
public static void resetAllInstances() {
mInstances.clear();
}
/**
* Adds handler for the list of events.
*
* @param events lists of events for which the handler needs to be notified.
* @param handler handler to be called when the events happen
*/
public synchronized void addEventListener(List<Integer> events, Handler handler) {
for (@IwlanEventType int event : events) {
if (eventHandlers.contains(event)) {
eventHandlers.get(event).add(handler);
} else {
Set<Handler> handlers = new HashSet<>();
handlers.add(handler);
eventHandlers.append(event, handlers);
}
}
}
/**
* Removes handler for the list of events.
*
* @param events lists of events for which the handler needs to be removed.
* @param handler handler to be removed
*/
public synchronized void removeEventListener(List<Integer> events, Handler handler) {
for (int event : events) {
if (eventHandlers.contains(event)) {
Set<Handler> handlers = eventHandlers.get(event);
handlers.remove(handler);
if (handlers.isEmpty()) {
eventHandlers.delete(event);
}
}
}
if (eventHandlers.size() == 0) {
mInstances.remove(mSlotId, this);
}
}
/**
* Removes handler for all events it is registered
*
* @param handler handler to be removed
*/
public synchronized void removeEventListener(Handler handler) {
for (int i = 0; i < eventHandlers.size(); i++) {
Set<Handler> handlers = eventHandlers.valueAt(i);
handlers.remove(handler);
if (handlers.isEmpty()) {
eventHandlers.delete(eventHandlers.keyAt(i));
i--;
}
}
if (eventHandlers.size() == 0) {
mInstances.remove(mSlotId, this);
}
}
/**
* Report a Broadcast received. Mainly used by IwlanBroadcastReceiver to report the following
* broadcasts: ACTION_AIRPLANE_MODE_CHANGED, WIFI_STATE_CHANGED_ACTION
*
* @param intent intent
*/
public static synchronized void onBroadcastReceived(Intent intent) {
int event = UNKNOWN_EVENT;
switch (intent.getAction()) {
case Intent.ACTION_AIRPLANE_MODE_CHANGED:
Boolean isAirplaneModeOn = intent.getBooleanExtra("state", false);
if (sIsAirplaneModeOn != null && sIsAirplaneModeOn.equals(isAirplaneModeOn)) {
// no change in apm state
break;
}
sIsAirplaneModeOn = isAirplaneModeOn;
event = sIsAirplaneModeOn ? APM_ENABLE_EVENT : APM_DISABLE_EVENT;
for (Map.Entry<Integer, IwlanEventListener> entry : mInstances.entrySet()) {
IwlanEventListener instance = entry.getValue();
instance.updateHandlers(event);
}
break;
case WifiManager.WIFI_STATE_CHANGED_ACTION:
int wifiState =
intent.getIntExtra(
WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN);
if (wifiState == WifiManager.WIFI_STATE_DISABLED) {
event = WIFI_DISABLE_EVENT;
for (Map.Entry<Integer, IwlanEventListener> entry : mInstances.entrySet()) {
IwlanEventListener instance = entry.getValue();
instance.updateHandlers(event);
}
}
break;
}
}
/**
* Broadcast WIFI_AP_CHANGED_EVENT if Wifi SSID changed after Wifi connected.
*
* @param context context
*/
public static void onWifiConnected(Context context) {
WifiManager wifiManager = context.getSystemService(WifiManager.class);
if (wifiManager == null) {
Log.e(LOG_TAG, "Could not find wifiManager");
return;
}
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
if (wifiInfo == null) {
Log.e(LOG_TAG, "wifiInfo is null");
return;
}
String wifiSSID = wifiInfo.getSSID();
if (wifiSSID.equals(WifiManager.UNKNOWN_SSID)) {
Log.e(LOG_TAG, "Could not get Wifi SSID");
return;
}
// Check sWifiSSID is greater than 0 to avoid trigger event after device first camps on
// Wifi.
if (sWifiSSID.length() > 0 && !sWifiSSID.equals(wifiSSID)) {
Log.d(LOG_TAG, "Wifi SSID changed");
for (Map.Entry<Integer, IwlanEventListener> entry : mInstances.entrySet()) {
IwlanEventListener instance = entry.getValue();
if (instance != null) {
instance.updateHandlers(WIFI_AP_CHANGED_EVENT);
}
}
}
sWifiSSID = wifiSSID;
}
/**
* Report Carrier Config changed. Mainly used by IwlanCarrierConfigChangeListener.
*
* @param context context
* @param slotId slot id which carrier config is changed
* @param subId sub id which carrier config is changed
* @param carrierId carrier id
*/
public static synchronized void onCarrierConfigChanged(
Context context, int slotId, int subId, int carrierId) {
getInstance(context, slotId).onCarrierConfigChanged(subId, carrierId);
}
/**
* Returns the Event id of the String. String that matches the name of the event
*
* @param event String form of the event.
*/
public static int getUnthrottlingEvent(String event) {
int ret = UNKNOWN_EVENT;
switch (event) {
case "CARRIER_CONFIG_CHANGED_EVENT":
ret = CARRIER_CONFIG_CHANGED_EVENT;
break;
case "WIFI_DISABLE_EVENT":
ret = WIFI_DISABLE_EVENT;
break;
case "APM_DISABLE_EVENT":
ret = APM_DISABLE_EVENT;
break;
case "APM_ENABLE_EVENT":
ret = APM_ENABLE_EVENT;
break;
case "WIFI_AP_CHANGED_EVENT":
ret = WIFI_AP_CHANGED_EVENT;
break;
case "WIFI_CALLING_ENABLE_EVENT":
ret = WIFI_CALLING_ENABLE_EVENT;
break;
case "WIFI_CALLING_DISABLE_EVENT":
ret = WIFI_CALLING_DISABLE_EVENT;
break;
case "CROSS_SIM_CALLING_ENABLE_EVENT":
ret = CROSS_SIM_CALLING_ENABLE_EVENT;
break;
case "CROSS_SIM_CALLING_DISABLE_EVENT":
ret = CROSS_SIM_CALLING_DISABLE_EVENT;
break;
case "CARRIER_CONFIG_UNKNOWN_CARRIER_EVENT":
ret = CARRIER_CONFIG_UNKNOWN_CARRIER_EVENT;
break;
case "CELLINFO_CHANGED_EVENT":
ret = CELLINFO_CHANGED_EVENT;
break;
case "PREFERRED_NETWORK_TYPE_CHANGED_EVENT":
ret = PREFERRED_NETWORK_TYPE_CHANGED_EVENT;
break;
}
return ret;
}
IwlanEventListener(Context context, int slotId, FeatureFlags featureFlags) {
mContext = context;
mSlotId = slotId;
mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
SUB_TAG = IwlanEventListener.class.getSimpleName() + "[" + slotId + "]";
sIsAirplaneModeOn = null;
mFeatureFlags = featureFlags;
}
private void onCarrierConfigChanged(int subId, int carrierId) {
Log.d(SUB_TAG, "onCarrierConfigChanged");
if (subId != mSubId) {
unregisterContentObserver();
mSubId = subId;
registerContentObserver();
}
notifyCurrentSetting(mCrossSimCallingUri);
notifyCurrentSetting(mWfcEnabledUri);
int event;
if (carrierId != TelephonyManager.UNKNOWN_CARRIER_ID) {
event = CARRIER_CONFIG_CHANGED_EVENT;
registerTelephonyCallback();
} else {
event = CARRIER_CONFIG_UNKNOWN_CARRIER_EVENT;
}
updateHandlers(event);
}
/** Unregister ContentObserver. */
void unregisterContentObserver() {
if (mUserSettingContentObserver != null) {
mContext.getContentResolver().unregisterContentObserver(mUserSettingContentObserver);
}
mCrossSimCallingUri = null;
mWfcEnabledUri = null;
}
/** Initiate ContentObserver if it is not created. And, register it with the current sub id. */
private void registerContentObserver() {
if (mUserSettingContentObserver == null) {
HandlerThread userSettingHandlerThread =
new HandlerThread(IwlanNetworkService.class.getSimpleName());
userSettingHandlerThread.start();
Looper looper = userSettingHandlerThread.getLooper();
Handler handler = new Handler(looper);
mUserSettingContentObserver = new UserSettingContentObserver(handler);
}
if (mSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
return;
}
ContentResolver resolver = mContext.getContentResolver();
// Register for CrossSimCalling setting uri
mCrossSimCallingUri =
Uri.withAppendedPath(
SubscriptionManager.CROSS_SIM_ENABLED_CONTENT_URI, String.valueOf(mSubId));
resolver.registerContentObserver(mCrossSimCallingUri, true, mUserSettingContentObserver);
// Register for WifiCalling setting uri
mWfcEnabledUri =
Uri.withAppendedPath(
SubscriptionManager.WFC_ENABLED_CONTENT_URI, String.valueOf(mSubId));
resolver.registerContentObserver(mWfcEnabledUri, true, mUserSettingContentObserver);
}
@VisibleForTesting
void notifyCurrentSetting(Uri uri) {
if (uri == null) {
return;
}
String uriString = uri.getPath();
int subIndex = Integer.parseInt(uriString.substring(uriString.lastIndexOf('/') + 1));
int slotIndex = SubscriptionManager.getSlotIndex(subIndex);
if (slotIndex == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
Log.e(SUB_TAG, "Invalid slot index: " + slotIndex);
return;
}
if (uri.equals(mCrossSimCallingUri)) {
boolean isCstEnabled = IwlanHelper.isCrossSimCallingEnabled(mContext, slotIndex);
int event =
(isCstEnabled)
? CROSS_SIM_CALLING_ENABLE_EVENT
: CROSS_SIM_CALLING_DISABLE_EVENT;
getInstance(mContext, slotIndex).updateHandlers(event);
} else if (uri.equals(mWfcEnabledUri)) {
ImsManager imsManager = mContext.getSystemService(ImsManager.class);
if (imsManager == null) {
Log.e(SUB_TAG, "Could not find ImsManager");
return;
}
ImsMmTelManager imsMmTelManager = imsManager.getImsMmTelManager(subIndex);
if (imsMmTelManager == null) {
Log.e(SUB_TAG, "Could not find ImsMmTelManager");
return;
}
boolean wfcEnabled = false;
try {
wfcEnabled = imsMmTelManager.isVoWiFiSettingEnabled();
} catch (IllegalArgumentException e) {
Log.w(SUB_TAG, e.getMessage());
}
int event = (wfcEnabled) ? WIFI_CALLING_ENABLE_EVENT : WIFI_CALLING_DISABLE_EVENT;
getInstance(mContext, slotIndex).updateHandlers(event);
} else {
Log.e(SUB_TAG, "Unknown Uri : " + uri);
}
}
@VisibleForTesting
void registerTelephonyCallback() {
Log.d(SUB_TAG, "registerTelephonyCallback");
TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
telephonyManager =
Objects.requireNonNull(telephonyManager)
.createForSubscriptionId(IwlanHelper.getSubId(mContext, mSlotId));
mTelephonyCallback = new RadioInfoTelephonyCallback();
telephonyManager.registerTelephonyCallback(Runnable::run, mTelephonyCallback);
}
@VisibleForTesting
void setCrossSimCallingUri(Uri uri) {
mCrossSimCallingUri = uri;
}
@VisibleForTesting
void setWfcEnabledUri(Uri uri) {
mWfcEnabledUri = uri;
}
@VisibleForTesting
RadioInfoTelephonyCallback getTelephonyCallback() {
return mTelephonyCallback;
}
private synchronized void updateHandlers(int event) {
if (eventHandlers.contains(event)) {
Log.d(SUB_TAG, "Updating handlers for the event: " + event);
for (Handler handler : eventHandlers.get(event)) {
handler.obtainMessage(event, mSlotId, 0 /* unused */).sendToTarget();
}
}
}
private synchronized void updateHandlers(List<CellInfo> arrayCi) {
int event = IwlanEventListener.CELLINFO_CHANGED_EVENT;
if (eventHandlers.contains(event)) {
Log.d(SUB_TAG, "Updating handlers for the event: " + event);
for (Handler handler : eventHandlers.get(event)) {
handler.obtainMessage(event, mSlotId, 0 /* unused */, arrayCi).sendToTarget();
}
}
}
private synchronized void updateHandlers(int event, int state) {
if (eventHandlers.contains(event)) {
Log.d(SUB_TAG, "Updating handlers for the event: " + event);
for (Handler handler : eventHandlers.get(event)) {
handler.obtainMessage(event, mSlotId, state).sendToTarget();
}
}
}
private synchronized void updateHandlers(int event, long allowedNetworkType) {
if (eventHandlers.contains(event)) {
Log.d(SUB_TAG, "Updating handlers for the event: " + event);
for (Handler handler : eventHandlers.get(event)) {
handler.obtainMessage(event, mSlotId, 0 /* unused */, allowedNetworkType)
.sendToTarget();
}
}
}
private String callStateToString(int state) {
switch (state) {
case TelephonyManager.CALL_STATE_IDLE:
return "CALL_STATE_IDLE";
case TelephonyManager.CALL_STATE_RINGING:
return "CALL_STATE_RINGING";
case TelephonyManager.CALL_STATE_OFFHOOK:
return "CALL_STATE_OFFHOOK";
default:
return "Unknown Call State (" + state + ")";
}
}
}