blob: e5a1833ba58d86c470e4bfe9c6ac3dea83a1a4d2 [file] [log] [blame]
/*
* 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.wifi.coex;
import static android.net.wifi.WifiManager.COEX_RESTRICTION_SOFTAP;
import static android.net.wifi.WifiManager.COEX_RESTRICTION_WIFI_AWARE;
import static android.net.wifi.WifiManager.COEX_RESTRICTION_WIFI_DIRECT;
import static android.net.wifi.WifiScanner.WIFI_BAND_24_GHZ;
import static android.net.wifi.WifiScanner.WIFI_BAND_5_GHZ;
import static android.telephony.TelephonyManager.NETWORK_TYPE_LTE;
import static android.telephony.TelephonyManager.NETWORK_TYPE_NR;
import static com.android.server.wifi.coex.CoexUtils.CHANNEL_SET_5_GHZ;
import static com.android.server.wifi.coex.CoexUtils.CHANNEL_SET_5_GHZ_160_MHZ;
import static com.android.server.wifi.coex.CoexUtils.CHANNEL_SET_5_GHZ_20_MHZ;
import static com.android.server.wifi.coex.CoexUtils.CHANNEL_SET_5_GHZ_40_MHZ;
import static com.android.server.wifi.coex.CoexUtils.CHANNEL_SET_5_GHZ_80_MHZ;
import static com.android.server.wifi.coex.CoexUtils.NUM_24_GHZ_CHANNELS;
import static com.android.server.wifi.coex.CoexUtils.get2gHarmonicCoexUnsafeChannels;
import static com.android.server.wifi.coex.CoexUtils.get5gHarmonicCoexUnsafeChannels;
import static com.android.server.wifi.coex.CoexUtils.getIntermodCoexUnsafeChannels;
import static com.android.server.wifi.coex.CoexUtils.getNeighboringCoexUnsafeChannels;
import android.annotation.NonNull;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.wifi.CoexUnsafeChannel;
import android.net.wifi.ICoexCallback;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.CoexRestriction;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.PersistableBundle;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.telephony.AccessNetworkConstants;
import android.telephony.CarrierConfigManager;
import android.telephony.PhoneStateListener;
import android.telephony.PhysicalChannelConfig;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.util.Pair;
import com.android.internal.annotations.VisibleForTesting;
import com.android.modules.utils.build.SdkLevel;
import com.android.wifi.resources.R;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
import javax.annotation.concurrent.NotThreadSafe;
/**
* This class handles Wi-Fi/Cellular coexistence by dynamically generating a set of Wi-Fi channels
* that would cause interference to/receive interference from the active cellular channels. These
* Wi-Fi channels are represented by {@link CoexUnsafeChannel} and may be retrieved through
* {@link #getCoexUnsafeChannels()}.
*
* Clients may be notified of updates to the value of #getCoexUnsafeChannels by implementing an
* {@link CoexListener} and listening on
* {@link CoexListener#onCoexUnsafeChannelsChanged()}
*
* Note: This class is not thread-safe. It needs to be invoked from the main Wifi thread only.
*/
@NotThreadSafe
public class CoexManager {
private static final String TAG = "WifiCoexManager";
@NonNull
private final Context mContext;
@NonNull
private final TelephonyManager mTelephonyManager;
@NonNull
private final CarrierConfigManager mCarrierConfigManager;
@NonNull
private final List<CoexUtils.CoexCellChannel> mCellChannels =
new ArrayList<CoexUtils.CoexCellChannel>();
private boolean mIsUsingMockCellChannels = false;
@NonNull
private final Set<CoexUnsafeChannel> mCurrentCoexUnsafeChannels = new HashSet<>();
private int mCoexRestrictions;
@NonNull
private final Set<CoexListener> mListeners = new HashSet<>();
@NonNull
private final RemoteCallbackList<ICoexCallback> mRemoteCallbackList =
new RemoteCallbackList<ICoexCallback>();
@NonNull
private final Map<Integer, Entry> mLteTableEntriesByBand = new HashMap<>();
@NonNull
private final Map<Integer, Entry> mNrTableEntriesByBand = new HashMap<>();
private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@java.lang.Override
public void onReceive(Context context, Intent intent) {
if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED
.equals(intent.getAction())) {
if (updateCarrierConfigs(mActiveDataSubId)) {
updateCoexUnsafeChannels(mCellChannels);
}
}
}
};
int mActiveDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
boolean mIs5gSoftApAvoidedForLaa = false;
boolean mIs5gWifiDirectAvoidedForLaa = false;
public CoexManager(@NonNull Context context, @NonNull TelephonyManager telephonyManager,
@NonNull CarrierConfigManager carrierConfigManager,
@NonNull Handler handler) {
mContext = context;
mTelephonyManager = telephonyManager;
mCarrierConfigManager = carrierConfigManager;
if (!SdkLevel.isAtLeastS()) {
return;
}
if (!mContext.getResources().getBoolean(R.bool.config_wifiDefaultCoexAlgorithmEnabled)
|| !readTableFromXml()) {
return;
}
IntentFilter filter = new IntentFilter();
filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
context.registerReceiver(mBroadcastReceiver, filter, null, handler);
mTelephonyManager.registerPhoneStateListener(new HandlerExecutor(handler),
new CoexPhoneStateListener());
}
/**
* Returns the set of current {@link CoexUnsafeChannel} being used for Wi-Fi/Cellular coex
* channel avoidance supplied in {@link #setCoexUnsafeChannels(Set, int)}.
*
* If any {@link CoexRestriction} flags are set in {@link #getCoexRestrictions()}, then the
* CoexUnsafeChannels should be totally avoided (i.e. not best effort) for the Wi-Fi modes
* specified by the flags.
*
* @return Set of current CoexUnsafeChannels.
*/
@NonNull
public Set<CoexUnsafeChannel> getCoexUnsafeChannels() {
return mCurrentCoexUnsafeChannels;
}
/**
* Returns the current coex restrictions being used for Wi-Fi/Cellular coex
* channel avoidance supplied in {@link #setCoexUnsafeChannels(Set, int)}.
*
* @return int containing a bitwise-OR combination of {@link CoexRestriction}.
*/
public int getCoexRestrictions() {
return mCoexRestrictions;
}
/**
* Sets the current CoexUnsafeChannels and coex restrictions returned by
* {@link #getCoexUnsafeChannels()} and {@link #getCoexRestrictions()} and notifies each
* listener with {@link CoexListener#onCoexUnsafeChannelsChanged()} and each
* remote callback with {@link ICoexCallback#onCoexUnsafeChannelsChanged()}.
*
* @param coexUnsafeChannels Set of CoexUnsafeChannels to return in
* {@link #getCoexUnsafeChannels()}
* @param coexRestrictions int to return in {@link #getCoexRestrictions()}
*/
public void setCoexUnsafeChannels(@NonNull Set<CoexUnsafeChannel> coexUnsafeChannels,
int coexRestrictions) {
if (coexUnsafeChannels == null) {
Log.e(TAG, "setCoexUnsafeChannels called with null unsafe channel set");
return;
}
if ((~(COEX_RESTRICTION_WIFI_DIRECT | COEX_RESTRICTION_SOFTAP
| COEX_RESTRICTION_WIFI_AWARE) & coexRestrictions) != 0) {
Log.e(TAG, "setCoexUnsafeChannels called with undefined restriction flags");
return;
}
mCurrentCoexUnsafeChannels.clear();
mCurrentCoexUnsafeChannels.addAll(coexUnsafeChannels);
mCoexRestrictions = coexRestrictions;
notifyListeners();
notifyRemoteCallbacks();
}
/**
* Registers a {@link CoexListener} to be notified with updates.
* @param listener CoexListener to be registered.
*/
public void registerCoexListener(@NonNull CoexListener listener) {
if (listener == null) {
Log.e(TAG, "registerCoexListener called with null listener");
return;
}
mListeners.add(listener);
}
/**
* Unregisters a {@link CoexListener}.
* @param listener CoexListener to be unregistered.
*/
public void unregisterCoexListener(@NonNull CoexListener listener) {
if (listener == null) {
Log.e(TAG, "unregisterCoexListener called with null listener");
return;
}
if (!mListeners.remove(listener)) {
Log.e(TAG, "unregisterCoexListener called on listener that was not registered: "
+ listener);
}
}
/**
* Registers a remote ICoexCallback from an external app.
* see {@link WifiManager#registerCoexCallback(Executor, WifiManager.CoexCallback)}
* @param callback ICoexCallback instance to register
*/
public void registerRemoteCoexCallback(ICoexCallback callback) {
mRemoteCallbackList.register(callback);
}
/**
* Unregisters a remote ICoexCallback from an external app.
* see {@link WifiManager#unregisterCoexCallback(WifiManager.CoexCallback)}
* @param callback ICoexCallback instance to unregister
*/
public void unregisterRemoteCoexCallback(ICoexCallback callback) {
mRemoteCallbackList.unregister(callback);
}
private void notifyListeners() {
for (CoexListener listener : mListeners) {
listener.onCoexUnsafeChannelsChanged();
}
}
private void notifyRemoteCallbacks() {
final int itemCount = mRemoteCallbackList.beginBroadcast();
for (int i = 0; i < itemCount; i++) {
try {
mRemoteCallbackList.getBroadcastItem(i).onCoexUnsafeChannelsChanged();
} catch (RemoteException e) {
Log.e(TAG, "onCoexUnsafeChannelsChanged: remote exception -- " + e);
}
}
mRemoteCallbackList.finishBroadcast();
}
/**
* Listener interface for internal Wi-Fi clients to listen to updates to
* {@link #getCoexUnsafeChannels()} and {@link #getCoexRestrictions()}
*/
public interface CoexListener {
/**
* Called to notify the listener that the values of
* {@link CoexManager#getCoexUnsafeChannels()} and/or
* {@link CoexManager#getCoexRestrictions()} have changed and should be
* retrieved again.
*/
void onCoexUnsafeChannelsChanged();
}
@VisibleForTesting
/* package */ class CoexPhoneStateListener extends PhoneStateListener
implements PhoneStateListener.PhysicalChannelConfigChangedListener,
PhoneStateListener.ActiveDataSubscriptionIdChangedListener {
@java.lang.Override
public void onPhysicalChannelConfigChanged(
@NonNull List<PhysicalChannelConfig> configs) {
if (mIsUsingMockCellChannels) {
return;
}
mCellChannels.clear();
for (PhysicalChannelConfig config : configs) {
mCellChannels.add(new CoexUtils.CoexCellChannel(config));
}
updateCoexUnsafeChannels(mCellChannels);
}
@java.lang.Override
public void onActiveDataSubscriptionIdChanged(int subId) {
mActiveDataSubId = subId;
if (updateCarrierConfigs(mActiveDataSubId)) {
updateCoexUnsafeChannels(mCellChannels);
}
}
}
private void updateCoexUnsafeChannels(@NonNull List<CoexUtils.CoexCellChannel> cellChannels) {
if (cellChannels == null) {
Log.e(TAG, "updateCoexUnsafeChannels called with null cell channel list");
return;
}
Log.v(TAG, "updateCoexUnsafeChannels called with cell channels: " + cellChannels);
int numUnsafe2gChannels = 0;
int numUnsafe5gChannels = 0;
int default2gChannel = Integer.MAX_VALUE;
int default5gChannel = Integer.MAX_VALUE;
boolean isEntire2gBandUnsafe = false;
boolean isEntire5gBandUnsafe = false;
int coexRestrictions = 0;
Map<Pair<Integer, Integer>, CoexUnsafeChannel> coexUnsafeChannelsByBandChannelPair =
new HashMap<>();
// Gather all of the CoexUnsafeChannels calculated from each cell channel.
for (CoexUtils.CoexCellChannel cellChannel : cellChannels) {
final Entry entry;
switch (cellChannel.getRat()) {
case NETWORK_TYPE_LTE:
entry = mLteTableEntriesByBand.get(cellChannel.getBand());
break;
case NETWORK_TYPE_NR:
entry = mNrTableEntriesByBand.get(cellChannel.getBand());
break;
default:
entry = null;
}
if (entry == null) {
continue;
}
final Set<CoexUnsafeChannel> currentBandUnsafeChannels = new HashSet<>();
// Set coex restrictions for LAA based on carrier config values.
if (cellChannel.getRat() == NETWORK_TYPE_LTE
&& cellChannel.getBand() == AccessNetworkConstants.EutranBand.BAND_46) {
if (mIs5gSoftApAvoidedForLaa || mIs5gWifiDirectAvoidedForLaa) {
for (int channel : CHANNEL_SET_5_GHZ) {
currentBandUnsafeChannels.add(
new CoexUnsafeChannel(WIFI_BAND_5_GHZ, channel));
}
if (mIs5gSoftApAvoidedForLaa) {
coexRestrictions |= COEX_RESTRICTION_SOFTAP;
}
if (mIs5gWifiDirectAvoidedForLaa) {
coexRestrictions |= COEX_RESTRICTION_WIFI_DIRECT;
}
}
}
final Params params = entry.getParams();
final Override override = entry.getOverride();
if (params != null) {
// Add all of the CoexUnsafeChannels calculated with the given parameters.
final int downlinkFreqKhz = cellChannel.getDownlinkFreqKhz();
final int downlinkBandwidthKhz = cellChannel.getDownlinkBandwidthKhz();
final int uplinkFreqKhz = cellChannel.getUplinkFreqKhz();
final int uplinkBandwidthKhz = cellChannel.getUplinkBandwidthKhz();
final NeighborThresholds neighborThresholds = params.getNeighborThresholds();
final HarmonicParams harmonicParams2g = params.getHarmonicParams2g();
final HarmonicParams harmonicParams5g = params.getHarmonicParams5g();
final IntermodParams intermodParams2g = params.getIntermodParams2g();
final IntermodParams intermodParams5g = params.getIntermodParams2g();
final DefaultChannels defaultChannels = params.getDefaultChannels();
// Calculate interference from cell downlink.
if (downlinkFreqKhz >= 0 && downlinkBandwidthKhz > 0) {
if (neighborThresholds != null && neighborThresholds.hasCellVictimMhz()) {
currentBandUnsafeChannels.addAll(getNeighboringCoexUnsafeChannels(
downlinkFreqKhz,
downlinkBandwidthKhz,
neighborThresholds.getCellVictimMhz() * 1000));
}
}
// Calculate interference from cell uplink
if (uplinkFreqKhz >= 0 && uplinkBandwidthKhz > 0) {
if (neighborThresholds != null && neighborThresholds.hasWifiVictimMhz()) {
currentBandUnsafeChannels.addAll(getNeighboringCoexUnsafeChannels(
uplinkFreqKhz,
uplinkBandwidthKhz,
neighborThresholds.getWifiVictimMhz() * 1000));
}
if (harmonicParams2g != null && !isEntire2gBandUnsafe) {
currentBandUnsafeChannels.addAll(get2gHarmonicCoexUnsafeChannels(
uplinkFreqKhz,
uplinkBandwidthKhz,
harmonicParams2g.getN(),
harmonicParams2g.getOverlap()));
}
if (harmonicParams5g != null && !isEntire5gBandUnsafe) {
currentBandUnsafeChannels.addAll(get5gHarmonicCoexUnsafeChannels(
uplinkFreqKhz,
uplinkBandwidthKhz,
harmonicParams5g.getN(),
harmonicParams5g.getOverlap()));
}
if (intermodParams2g != null && !isEntire2gBandUnsafe) {
for (CoexUtils.CoexCellChannel victimCellChannel : cellChannels) {
if (victimCellChannel.getDownlinkFreqKhz() >= 0
&& victimCellChannel.getDownlinkBandwidthKhz() > 0) {
currentBandUnsafeChannels.addAll(getIntermodCoexUnsafeChannels(
uplinkFreqKhz,
uplinkBandwidthKhz,
victimCellChannel.getDownlinkFreqKhz(),
victimCellChannel.getDownlinkBandwidthKhz(),
intermodParams2g.getN(),
intermodParams2g.getM(),
intermodParams2g.getOverlap(),
WIFI_BAND_24_GHZ));
}
}
}
if (intermodParams5g != null && !isEntire5gBandUnsafe) {
for (CoexUtils.CoexCellChannel victimCellChannel : cellChannels) {
if (victimCellChannel.getDownlinkFreqKhz() >= 0
&& victimCellChannel.getDownlinkBandwidthKhz() > 0) {
currentBandUnsafeChannels.addAll(getIntermodCoexUnsafeChannels(
uplinkFreqKhz,
uplinkBandwidthKhz,
victimCellChannel.getDownlinkFreqKhz(),
victimCellChannel.getDownlinkBandwidthKhz(),
intermodParams5g.getN(),
intermodParams5g.getM(),
intermodParams5g.getOverlap(),
WIFI_BAND_5_GHZ));
}
}
}
}
// Collect the lowest number default channel for each band to extract from
// calculated set of CoexUnsafeChannels later.
if (defaultChannels != null) {
if (defaultChannels.hasDefault2g()) {
int channel = defaultChannels.getDefault2g();
if (channel < default2gChannel) {
default2gChannel = channel;
}
}
if (defaultChannels.hasDefault5g()) {
int channel = defaultChannels.getDefault5g();
if (channel < default5gChannel) {
default5gChannel = channel;
}
}
}
} else if (override != null) {
// Add all of the CoexUnsafeChannels defined by the override lists.
final Override2g override2g = override.getOverride2g();
if (override2g != null && !isEntire2gBandUnsafe) {
final List<Integer> channelList2g = override2g.getChannel();
for (OverrideCategory2g category : override2g.getCategory()) {
if (OverrideCategory2g.all.equals(category)) {
for (int i = 1; i <= 14; i++) {
channelList2g.add(i);
}
isEntire2gBandUnsafe = true;
}
}
for (int channel : channelList2g) {
currentBandUnsafeChannels.add(
new CoexUnsafeChannel(WIFI_BAND_24_GHZ, channel));
}
}
final Override5g override5g = override.getOverride5g();
if (override5g != null && !isEntire5gBandUnsafe) {
final List<Integer> channelList5g = override5g.getChannel();
for (OverrideCategory5g category : override5g.getCategory()) {
if (OverrideCategory5g._20Mhz.equals(category)) {
channelList5g.addAll(CHANNEL_SET_5_GHZ_20_MHZ);
} else if (OverrideCategory5g._40Mhz.equals(category)) {
channelList5g.addAll(CHANNEL_SET_5_GHZ_40_MHZ);
} else if (OverrideCategory5g._80Mhz.equals(category)) {
channelList5g.addAll(CHANNEL_SET_5_GHZ_80_MHZ);
} else if (OverrideCategory5g._160Mhz.equals(category)) {
channelList5g.addAll(CHANNEL_SET_5_GHZ_160_MHZ);
} else if (OverrideCategory5g.all.equals(category)) {
channelList5g.addAll(CHANNEL_SET_5_GHZ);
isEntire5gBandUnsafe = true;
}
}
for (int channel : channelList5g) {
currentBandUnsafeChannels.add(
new CoexUnsafeChannel(WIFI_BAND_5_GHZ, channel));
}
}
}
// Add the power cap for the band, if there is one.
if (entry.hasPowerCapDbm()) {
for (CoexUnsafeChannel unsafeChannel : currentBandUnsafeChannels) {
unsafeChannel.setPowerCapDbm(entry.getPowerCapDbm());
}
}
// Add all of the CoexUnsafeChannels calculated from this cell channel to the total.
// If the total already contains a CoexUnsafeChannel for the same band and channel,
// keep the one that has the lower power cap.
for (CoexUnsafeChannel unsafeChannel : currentBandUnsafeChannels) {
final int band = unsafeChannel.getBand();
final int channel = unsafeChannel.getChannel();
final Pair<Integer, Integer> bandChannelPair = new Pair<>(band, channel);
final CoexUnsafeChannel existingUnsafeChannel =
coexUnsafeChannelsByBandChannelPair.get(bandChannelPair);
if (existingUnsafeChannel != null) {
if (!unsafeChannel.isPowerCapAvailable()) {
continue;
}
if (existingUnsafeChannel.isPowerCapAvailable()
&& existingUnsafeChannel.getPowerCapDbm()
< unsafeChannel.getPowerCapDbm()) {
continue;
}
}
// Count the number of unsafe channels for each band to determine if we need to
// remove the default channels before returning.
if (band == WIFI_BAND_24_GHZ) {
numUnsafe2gChannels++;
} else if (band == WIFI_BAND_5_GHZ) {
numUnsafe5gChannels++;
}
coexUnsafeChannelsByBandChannelPair.put(bandChannelPair, unsafeChannel);
}
}
// Omit the default channel from each band if the entire band is unsafe and there are
// no coex restrictions set.
if (coexRestrictions == 0) {
if (numUnsafe2gChannels == NUM_24_GHZ_CHANNELS) {
coexUnsafeChannelsByBandChannelPair.remove(
new Pair<>(WIFI_BAND_24_GHZ, default2gChannel));
}
if (numUnsafe5gChannels == CHANNEL_SET_5_GHZ.size()) {
coexUnsafeChannelsByBandChannelPair.remove(
new Pair<>(WIFI_BAND_5_GHZ, default5gChannel));
}
}
setCoexUnsafeChannels(new HashSet<>(coexUnsafeChannelsByBandChannelPair.values()),
coexRestrictions);
}
/**
* Updates carrier config values and returns true if the values have changed, false otherwise.
*/
private boolean updateCarrierConfigs(int subId) {
final boolean oldAvoidSoftAp = mIs5gSoftApAvoidedForLaa;
final boolean oldAvoidWifiDirect = mIs5gWifiDirectAvoidedForLaa;
mIs5gSoftApAvoidedForLaa = false;
mIs5gWifiDirectAvoidedForLaa = false;
PersistableBundle bundle = mCarrierConfigManager.getConfigForSubId(subId);
if (bundle != null) {
mIs5gSoftApAvoidedForLaa = bundle.getBoolean(
CarrierConfigManager.Wifi.KEY_AVOID_5GHZ_SOFTAP_FOR_LAA_BOOL);
mIs5gWifiDirectAvoidedForLaa = bundle.getBoolean(
CarrierConfigManager.Wifi.KEY_AVOID_5GHZ_WIFI_DIRECT_FOR_LAA_BOOL);
}
return (oldAvoidSoftAp != mIs5gSoftApAvoidedForLaa
|| oldAvoidWifiDirect != mIs5gWifiDirectAvoidedForLaa);
}
/**
* Parses a coex table xml from the specified File and populates the table entry maps.
* Returns {@code true} if the file was found and read successfully, {@code false} otherwise.
*/
@VisibleForTesting
boolean readTableFromXml() {
final String filepath = mContext.getResources().getString(
R.string.config_wifiCoexTableFilepath);
if (filepath == null) {
Log.e(TAG, "Coex table filepath was null");
return false;
}
final File file = new File(filepath);
try (InputStream str = new BufferedInputStream(new FileInputStream(file))) {
mLteTableEntriesByBand.clear();
mNrTableEntriesByBand.clear();
for (Entry entry : XmlParser.readTable(str).getEntry()) {
if (RatType.LTE.equals(entry.getRat())) {
mLteTableEntriesByBand.put(entry.getBand(), entry);
} else if (RatType.NR.equals(entry.getRat())) {
mNrTableEntriesByBand.put(entry.getBand(), entry);
}
}
Log.i(TAG, "Successfully read coex table from file");
return true;
} catch (FileNotFoundException e) {
Log.e(TAG, "No coex table file found at " + file);
} catch (Exception e) {
Log.e(TAG, "Failed to read coex table file: " + e);
}
return false;
}
/**
* Sets the mock CoexCellChannels to use for coex calculations.
* @param cellChannels list of mock cell channels
*/
public void setMockCellChannels(@NonNull List<CoexUtils.CoexCellChannel> cellChannels) {
mIsUsingMockCellChannels = true;
mCellChannels.clear();
mCellChannels.addAll(cellChannels);
updateCoexUnsafeChannels(mCellChannels);
}
/**
* Removes all added mock CoexCellChannels.
*/
public void resetMockCellChannels() {
mIsUsingMockCellChannels = false;
mCellChannels.clear();
updateCoexUnsafeChannels(mCellChannels);
}
/**
* Returns all cell channels used for coex calculations.
*/
public List<CoexUtils.CoexCellChannel> getCellChannels() {
return new ArrayList<>(mCellChannels);
}
}