/* * 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 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> 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 arrayCi) { Log.d(LOG_TAG, "Cellinfo changed"); for (Map.Entry 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 events, Handler handler) { for (@IwlanEventType int event : events) { if (eventHandlers.contains(event)) { eventHandlers.get(event).add(handler); } else { Set 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 events, Handler handler) { for (int event : events) { if (eventHandlers.contains(event)) { Set 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 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 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 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 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 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 + ")"; } } }