1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.wifi.coex; 18 19 import static android.net.wifi.CoexUnsafeChannel.POWER_CAP_NONE; 20 import static android.net.wifi.WifiManager.COEX_RESTRICTION_SOFTAP; 21 import static android.net.wifi.WifiManager.COEX_RESTRICTION_WIFI_AWARE; 22 import static android.net.wifi.WifiManager.COEX_RESTRICTION_WIFI_DIRECT; 23 import static android.net.wifi.WifiScanner.WIFI_BAND_24_GHZ; 24 import static android.net.wifi.WifiScanner.WIFI_BAND_5_GHZ; 25 import static android.telephony.TelephonyManager.NETWORK_TYPE_LTE; 26 import static android.telephony.TelephonyManager.NETWORK_TYPE_NR; 27 28 import static com.android.server.wifi.coex.CoexUtils.CHANNEL_SET_5_GHZ; 29 import static com.android.server.wifi.coex.CoexUtils.CHANNEL_SET_5_GHZ_160_MHZ; 30 import static com.android.server.wifi.coex.CoexUtils.CHANNEL_SET_5_GHZ_20_MHZ; 31 import static com.android.server.wifi.coex.CoexUtils.CHANNEL_SET_5_GHZ_40_MHZ; 32 import static com.android.server.wifi.coex.CoexUtils.CHANNEL_SET_5_GHZ_80_MHZ; 33 import static com.android.server.wifi.coex.CoexUtils.NUM_24_GHZ_CHANNELS; 34 import static com.android.server.wifi.coex.CoexUtils.get2gHarmonicCoexUnsafeChannels; 35 import static com.android.server.wifi.coex.CoexUtils.get5gHarmonicCoexUnsafeChannels; 36 import static com.android.server.wifi.coex.CoexUtils.getIntermodCoexUnsafeChannels; 37 import static com.android.server.wifi.coex.CoexUtils.getNeighboringCoexUnsafeChannels; 38 39 import android.annotation.NonNull; 40 import android.annotation.Nullable; 41 import android.content.BroadcastReceiver; 42 import android.content.Context; 43 import android.content.Intent; 44 import android.content.IntentFilter; 45 import android.net.wifi.CoexUnsafeChannel; 46 import android.net.wifi.ICoexCallback; 47 import android.net.wifi.WifiManager; 48 import android.net.wifi.WifiManager.CoexRestriction; 49 import android.os.Build; 50 import android.os.Handler; 51 import android.os.HandlerExecutor; 52 import android.os.PersistableBundle; 53 import android.os.RemoteCallbackList; 54 import android.os.RemoteException; 55 import android.telephony.AccessNetworkConstants; 56 import android.telephony.CarrierConfigManager; 57 import android.telephony.PhysicalChannelConfig; 58 import android.telephony.SubscriptionInfo; 59 import android.telephony.SubscriptionManager; 60 import android.telephony.TelephonyCallback; 61 import android.telephony.TelephonyManager; 62 import android.util.Log; 63 import android.util.Pair; 64 import android.util.SparseArray; 65 import android.util.SparseBooleanArray; 66 67 import androidx.annotation.RequiresApi; 68 69 import com.android.internal.annotations.VisibleForTesting; 70 import com.android.server.wifi.WifiNative; 71 import com.android.wifi.resources.R; 72 73 import java.io.BufferedInputStream; 74 import java.io.File; 75 import java.io.FileInputStream; 76 import java.io.FileNotFoundException; 77 import java.io.InputStream; 78 import java.util.ArrayList; 79 import java.util.Collections; 80 import java.util.HashMap; 81 import java.util.HashSet; 82 import java.util.Iterator; 83 import java.util.List; 84 import java.util.Map; 85 import java.util.Set; 86 import java.util.concurrent.Executor; 87 import java.util.stream.Collectors; 88 89 import javax.annotation.concurrent.NotThreadSafe; 90 91 92 /** 93 * This class handles Wi-Fi/Cellular coexistence by dynamically generating a set of Wi-Fi channels 94 * that would cause interference to/receive interference from the active cellular channels. These 95 * Wi-Fi channels are represented by {@link CoexUnsafeChannel} and may be retrieved through 96 * {@link #getCoexUnsafeChannels()}. 97 * 98 * Clients may be notified of updates to the value of #getCoexUnsafeChannels by implementing an 99 * {@link CoexListener} and listening on 100 * {@link CoexListener#onCoexUnsafeChannelsChanged()} 101 * 102 * Note: This class is not thread-safe. It needs to be invoked from the main Wifi thread only. 103 */ 104 @NotThreadSafe 105 @RequiresApi(Build.VERSION_CODES.S) 106 public class CoexManager { 107 private static final String TAG = "WifiCoexManager"; 108 private boolean mVerboseLoggingEnabled = false; 109 110 @NonNull 111 private final Context mContext; 112 @NonNull 113 private final WifiNative mWifiNative; 114 @NonNull 115 private final TelephonyManager mDefaultTelephonyManager; 116 @NonNull 117 private final SubscriptionManager mSubscriptionManager; 118 @NonNull 119 private final CarrierConfigManager mCarrierConfigManager; 120 @NonNull 121 private final Handler mCallbackHandler; 122 123 private final List<Pair<TelephonyManager, TelephonyCallback>> mTelephonyManagersAndCallbacks = 124 new ArrayList<>(); 125 126 @VisibleForTesting 127 /* package */ class CoexOnSubscriptionsChangedListener 128 extends SubscriptionManager.OnSubscriptionsChangedListener { 129 @java.lang.Override onSubscriptionsChanged()130 public void onSubscriptionsChanged() { 131 final List<SubscriptionInfo> subInfoList = 132 mSubscriptionManager.getAvailableSubscriptionInfoList(); 133 updateCarrierConfigs(subInfoList); 134 Set<Integer> newSubIds = Collections.emptySet(); 135 if (subInfoList != null) { 136 newSubIds = subInfoList.stream() 137 .map(SubscriptionInfo::getSubscriptionId) 138 .collect(Collectors.toSet()); 139 } 140 if (mVerboseLoggingEnabled) { 141 Log.v(TAG, "onSubscriptionsChanged called with subIds: " + newSubIds); 142 } 143 // Unregister callbacks for removed subscriptions 144 Iterator<Pair<TelephonyManager, TelephonyCallback>> iter = 145 mTelephonyManagersAndCallbacks.iterator(); 146 while (iter.hasNext()) { 147 Pair<TelephonyManager, TelephonyCallback> managerCallbackPair = iter.next(); 148 final TelephonyManager telephonyManager = managerCallbackPair.first; 149 final TelephonyCallback telephonyCallback = managerCallbackPair.second; 150 final int subId = telephonyManager.getSubscriptionId(); 151 final boolean subIdRemoved = !newSubIds.remove(subId); 152 if (subIdRemoved) { 153 mCellChannelsPerSubId.remove(subId); 154 telephonyManager.unregisterTelephonyCallback(telephonyCallback); 155 iter.remove(); 156 } 157 } 158 // Register callbacks for new subscriptions 159 for (int subId : newSubIds) { 160 final TelephonyManager telephonyManager = 161 mDefaultTelephonyManager.createForSubscriptionId(subId); 162 if (telephonyManager == null) { 163 Log.e(TAG, "Could not create TelephonyManager for subId: " + subId); 164 } 165 final TelephonyCallback callback = new CoexTelephonyCallback(subId); 166 telephonyManager.registerTelephonyCallback( 167 new HandlerExecutor(mCallbackHandler), callback); 168 mTelephonyManagersAndCallbacks.add(new Pair<>(telephonyManager, callback)); 169 } 170 } 171 } 172 173 @VisibleForTesting 174 /* package */ class CoexTelephonyCallback extends TelephonyCallback 175 implements TelephonyCallback.PhysicalChannelConfigListener { 176 private final int mSubId; 177 CoexTelephonyCallback(int subId)178 private CoexTelephonyCallback(int subId) { 179 super(); 180 mSubId = subId; 181 } 182 183 @java.lang.Override onPhysicalChannelConfigChanged( @onNull List<PhysicalChannelConfig> configs)184 public void onPhysicalChannelConfigChanged( 185 @NonNull List<PhysicalChannelConfig> configs) { 186 if (mVerboseLoggingEnabled) { 187 Log.v(TAG, "onPhysicalChannelConfigChanged for subId: " + mSubId 188 + " called with configs: " + configs); 189 } 190 List<CoexUtils.CoexCellChannel> cellChannels = new ArrayList<>(); 191 for (PhysicalChannelConfig config : configs) { 192 cellChannels.add(new CoexUtils.CoexCellChannel(config, mSubId)); 193 } 194 if (cellChannels.equals(mCellChannelsPerSubId.get(mSubId))) { 195 // No change to cell channels, so no need to recalculate 196 return; 197 } 198 mCellChannelsPerSubId.put(mSubId, cellChannels); 199 if (mIsUsingMockCellChannels) { 200 return; 201 } 202 updateCoexUnsafeChannels(getCellChannelsForAllSubIds()); 203 } 204 } 205 206 private final SparseBooleanArray mAvoid5gSoftApForLaaPerSubId = new SparseBooleanArray(); 207 private final SparseBooleanArray mAvoid5gWifiDirectForLaaPerSubId = new SparseBooleanArray(); 208 private BroadcastReceiver mCarrierConfigChangedReceiver = new BroadcastReceiver() { 209 @java.lang.Override 210 public void onReceive(Context context, Intent intent) { 211 if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED 212 .equals(intent.getAction())) { 213 if (updateCarrierConfigs(mSubscriptionManager.getAvailableSubscriptionInfoList())) { 214 updateCoexUnsafeChannels(getCellChannelsForAllSubIds()); 215 } 216 } 217 } 218 }; 219 220 @NonNull 221 private final SparseArray<List<CoexUtils.CoexCellChannel>> mCellChannelsPerSubId = 222 new SparseArray<>(); 223 @NonNull 224 private final List<CoexUtils.CoexCellChannel> mMockCellChannels = new ArrayList<>(); 225 private boolean mIsUsingMockCellChannels = false; 226 227 @NonNull 228 private final List<CoexUnsafeChannel> mCurrentCoexUnsafeChannels = new ArrayList<>(); 229 private int mCoexRestrictions; 230 231 @NonNull 232 private final SparseArray<Entry> mLteTableEntriesByBand = new SparseArray<>(); 233 @NonNull 234 private final SparseArray<Entry> mNrTableEntriesByBand = new SparseArray<>(); 235 236 @NonNull 237 private final Set<CoexListener> mListeners = new HashSet<>(); 238 @NonNull 239 private final RemoteCallbackList<ICoexCallback> mRemoteCallbackList = 240 new RemoteCallbackList<ICoexCallback>(); 241 CoexManager(@onNull Context context, @NonNull WifiNative wifiNative, @NonNull TelephonyManager defaultTelephonyManager, @NonNull SubscriptionManager subscriptionManager, @NonNull CarrierConfigManager carrierConfigManager, @NonNull Handler handler)242 public CoexManager(@NonNull Context context, 243 @NonNull WifiNative wifiNative, 244 @NonNull TelephonyManager defaultTelephonyManager, 245 @NonNull SubscriptionManager subscriptionManager, 246 @NonNull CarrierConfigManager carrierConfigManager, 247 @NonNull Handler handler) { 248 mContext = context; 249 mWifiNative = wifiNative; 250 mDefaultTelephonyManager = defaultTelephonyManager; 251 mSubscriptionManager = subscriptionManager; 252 mCarrierConfigManager = carrierConfigManager; 253 mCallbackHandler = handler; 254 if (!mContext.getResources().getBoolean(R.bool.config_wifiDefaultCoexAlgorithmEnabled)) { 255 Log.i(TAG, "Default coex algorithm is disabled."); 256 return; 257 } 258 readTableFromXml(); 259 IntentFilter filter = new IntentFilter(); 260 filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED); 261 mContext.registerReceiver(mCarrierConfigChangedReceiver, filter, null, mCallbackHandler); 262 mSubscriptionManager.addOnSubscriptionsChangedListener( 263 new HandlerExecutor(mCallbackHandler), new CoexOnSubscriptionsChangedListener()); 264 } 265 266 /** 267 * Returns the list of current {@link CoexUnsafeChannel} being used for Wi-Fi/Cellular coex 268 * channel avoidance supplied in {@link #setCoexUnsafeChannels(List, int)}. 269 * 270 * If any {@link CoexRestriction} flags are set in {@link #getCoexRestrictions()}, then the 271 * CoexUnsafeChannels should be totally avoided (i.e. not best effort) for the Wi-Fi modes 272 * specified by the flags. 273 * 274 * @return Set of current CoexUnsafeChannels. 275 */ 276 @NonNull getCoexUnsafeChannels()277 public List<CoexUnsafeChannel> getCoexUnsafeChannels() { 278 return mCurrentCoexUnsafeChannels; 279 } 280 281 /** 282 * Returns the current coex restrictions being used for Wi-Fi/Cellular coex 283 * channel avoidance supplied in {@link #setCoexUnsafeChannels(List, int)}. 284 * 285 * @return int containing a bitwise-OR combination of {@link CoexRestriction}. 286 */ getCoexRestrictions()287 public int getCoexRestrictions() { 288 return mCoexRestrictions; 289 } 290 291 /** 292 * Sets the current CoexUnsafeChannels and coex restrictions returned by 293 * {@link #getCoexUnsafeChannels()} and {@link #getCoexRestrictions()} and notifies each 294 * listener with {@link CoexListener#onCoexUnsafeChannelsChanged()} and each 295 * remote callback with {@link ICoexCallback#onCoexUnsafeChannelsChanged()}. 296 * 297 * @param coexUnsafeChannels Set of CoexUnsafeChannels to return in 298 * {@link #getCoexUnsafeChannels()} 299 * @param coexRestrictions int to return in {@link #getCoexRestrictions()} 300 */ setCoexUnsafeChannels(@onNull List<CoexUnsafeChannel> coexUnsafeChannels, int coexRestrictions)301 public void setCoexUnsafeChannels(@NonNull List<CoexUnsafeChannel> coexUnsafeChannels, 302 int coexRestrictions) { 303 if (coexUnsafeChannels == null) { 304 Log.e(TAG, "setCoexUnsafeChannels called with null unsafe channel set"); 305 return; 306 } 307 if ((~(COEX_RESTRICTION_WIFI_DIRECT | COEX_RESTRICTION_SOFTAP 308 | COEX_RESTRICTION_WIFI_AWARE) & coexRestrictions) != 0) { 309 Log.e(TAG, "setCoexUnsafeChannels called with undefined restriction flags"); 310 return; 311 } 312 mCurrentCoexUnsafeChannels.clear(); 313 mCurrentCoexUnsafeChannels.addAll(coexUnsafeChannels); 314 mCoexRestrictions = coexRestrictions; 315 if (mVerboseLoggingEnabled) { 316 Log.v(TAG, "Current unsafe channels: " + mCurrentCoexUnsafeChannels 317 + ", restrictions: " + mCoexRestrictions); 318 } 319 mWifiNative.setCoexUnsafeChannels(mCurrentCoexUnsafeChannels, mCoexRestrictions); 320 notifyListeners(); 321 notifyRemoteCallbacks(); 322 } 323 324 /** 325 * Registers a {@link CoexListener} to be notified with updates. 326 * @param listener CoexListener to be registered. 327 */ registerCoexListener(@onNull CoexListener listener)328 public void registerCoexListener(@NonNull CoexListener listener) { 329 if (listener == null) { 330 Log.e(TAG, "registerCoexListener called with null listener"); 331 return; 332 } 333 mListeners.add(listener); 334 } 335 336 /** 337 * Unregisters a {@link CoexListener}. 338 * @param listener CoexListener to be unregistered. 339 */ unregisterCoexListener(@onNull CoexListener listener)340 public void unregisterCoexListener(@NonNull CoexListener listener) { 341 if (listener == null) { 342 Log.e(TAG, "unregisterCoexListener called with null listener"); 343 return; 344 } 345 if (!mListeners.remove(listener)) { 346 Log.e(TAG, "unregisterCoexListener called on listener that was not registered: " 347 + listener); 348 } 349 } 350 351 /** 352 * Registers a remote ICoexCallback from an external app and immediately notifies it. 353 * see {@link WifiManager#registerCoexCallback(Executor, WifiManager.CoexCallback)} 354 * @param callback ICoexCallback instance to register 355 */ registerRemoteCoexCallback(ICoexCallback callback)356 public void registerRemoteCoexCallback(ICoexCallback callback) { 357 mRemoteCallbackList.register(callback); 358 try { 359 callback.onCoexUnsafeChannelsChanged(mCurrentCoexUnsafeChannels, mCoexRestrictions); 360 } catch (RemoteException e) { 361 Log.e(TAG, "onCoexUnsafeChannelsChanged: remote exception -- " + e); 362 } 363 } 364 365 /** 366 * Unregisters a remote ICoexCallback from an external app. 367 * see {@link WifiManager#unregisterCoexCallback(WifiManager.CoexCallback)} 368 * @param callback ICoexCallback instance to unregister 369 */ unregisterRemoteCoexCallback(ICoexCallback callback)370 public void unregisterRemoteCoexCallback(ICoexCallback callback) { 371 mRemoteCallbackList.unregister(callback); 372 } 373 notifyListeners()374 private void notifyListeners() { 375 for (CoexListener listener : mListeners) { 376 listener.onCoexUnsafeChannelsChanged(); 377 } 378 } 379 notifyRemoteCallbacks()380 private void notifyRemoteCallbacks() { 381 final int itemCount = mRemoteCallbackList.beginBroadcast(); 382 for (int i = 0; i < itemCount; i++) { 383 try { 384 mRemoteCallbackList.getBroadcastItem(i) 385 .onCoexUnsafeChannelsChanged(mCurrentCoexUnsafeChannels, mCoexRestrictions); 386 } catch (RemoteException e) { 387 Log.e(TAG, "onCoexUnsafeChannelsChanged: remote exception -- " + e); 388 } 389 } 390 mRemoteCallbackList.finishBroadcast(); 391 } 392 393 /** 394 * Listener interface for internal Wi-Fi clients to listen to updates to 395 * {@link #getCoexUnsafeChannels()} and {@link #getCoexRestrictions()} 396 */ 397 public interface CoexListener { 398 /** 399 * Called to notify the listener that the values of 400 * {@link CoexManager#getCoexUnsafeChannels()} and/or 401 * {@link CoexManager#getCoexRestrictions()} have changed and should be 402 * retrieved again. 403 */ onCoexUnsafeChannelsChanged()404 void onCoexUnsafeChannelsChanged(); 405 } 406 updateCoexUnsafeChannels(@onNull List<CoexUtils.CoexCellChannel> cellChannels)407 private void updateCoexUnsafeChannels(@NonNull List<CoexUtils.CoexCellChannel> cellChannels) { 408 if (cellChannels == null) { 409 Log.e(TAG, "updateCoexUnsafeChannels called with null cell channel list"); 410 return; 411 } 412 if (mVerboseLoggingEnabled) { 413 Log.v(TAG, "updateCoexUnsafeChannels called with cell channels: " + cellChannels); 414 } 415 int numUnsafe2gChannels = 0; 416 int numUnsafe5gChannels = 0; 417 int default2gChannel = Integer.MAX_VALUE; 418 int default5gChannel = Integer.MAX_VALUE; 419 int coexRestrictions = 0; 420 Map<Pair<Integer, Integer>, CoexUnsafeChannel> coexUnsafeChannelsByBandChannelPair = 421 new HashMap<>(); 422 // Gather all of the CoexUnsafeChannels calculated from each cell channel. 423 for (CoexUtils.CoexCellChannel cellChannel : cellChannels) { 424 final Entry entry; 425 switch (cellChannel.getRat()) { 426 case NETWORK_TYPE_LTE: 427 entry = mLteTableEntriesByBand.get(cellChannel.getBand()); 428 break; 429 case NETWORK_TYPE_NR: 430 entry = mNrTableEntriesByBand.get(cellChannel.getBand()); 431 break; 432 default: 433 entry = null; 434 } 435 final List<CoexUnsafeChannel> currentBandUnsafeChannels = new ArrayList<>(); 436 if (entry != null) { 437 final int powerCapDbm; 438 if (entry.hasPowerCapDbm()) { 439 powerCapDbm = entry.getPowerCapDbm(); 440 if (mVerboseLoggingEnabled) { 441 Log.v(TAG, cellChannel + " sets wifi power cap " + powerCapDbm); 442 } 443 } else { 444 powerCapDbm = POWER_CAP_NONE; 445 } 446 final Params params = entry.getParams(); 447 final Override override = entry.getOverride(); 448 if (params != null) { 449 // Add all of the CoexUnsafeChannels calculated with the given parameters. 450 final int downlinkFreqKhz = cellChannel.getDownlinkFreqKhz(); 451 final int downlinkBandwidthKhz = cellChannel.getDownlinkBandwidthKhz(); 452 final int uplinkFreqKhz = cellChannel.getUplinkFreqKhz(); 453 final int uplinkBandwidthKhz = cellChannel.getUplinkBandwidthKhz(); 454 final NeighborThresholds neighborThresholds = params.getNeighborThresholds(); 455 final HarmonicParams harmonicParams2g = params.getHarmonicParams2g(); 456 final HarmonicParams harmonicParams5g = params.getHarmonicParams5g(); 457 final IntermodParams intermodParams2g = params.getIntermodParams2g(); 458 final IntermodParams intermodParams5g = params.getIntermodParams2g(); 459 final DefaultChannels defaultChannels = params.getDefaultChannels(); 460 // Calculate interference from cell downlink. 461 if (downlinkFreqKhz >= 0 && downlinkBandwidthKhz > 0) { 462 if (neighborThresholds != null && neighborThresholds.hasCellVictimMhz()) { 463 final List<CoexUnsafeChannel> neighboringChannels = 464 getNeighboringCoexUnsafeChannels( 465 downlinkFreqKhz, 466 downlinkBandwidthKhz, 467 neighborThresholds.getCellVictimMhz() * 1000, 468 powerCapDbm); 469 if (!neighboringChannels.isEmpty()) { 470 if (mVerboseLoggingEnabled) { 471 Log.v(TAG, cellChannel + " is neighboring victim of " 472 + neighboringChannels); 473 } 474 currentBandUnsafeChannels.addAll(neighboringChannels); 475 } 476 } 477 } 478 // Calculate interference from cell uplink 479 if (uplinkFreqKhz >= 0 && uplinkBandwidthKhz > 0) { 480 if (neighborThresholds != null && neighborThresholds.hasWifiVictimMhz()) { 481 final List<CoexUnsafeChannel> neighboringChannels = 482 getNeighboringCoexUnsafeChannels( 483 uplinkFreqKhz, 484 uplinkBandwidthKhz, 485 neighborThresholds.getWifiVictimMhz() * 1000, 486 powerCapDbm); 487 if (!neighboringChannels.isEmpty()) { 488 if (mVerboseLoggingEnabled) { 489 Log.v(TAG, cellChannel + " is neighboring aggressor to " 490 + neighboringChannels); 491 } 492 currentBandUnsafeChannels.addAll(neighboringChannels); 493 } 494 } 495 if (harmonicParams2g != null) { 496 final List<CoexUnsafeChannel> harmonicChannels2g = 497 get2gHarmonicCoexUnsafeChannels( 498 uplinkFreqKhz, 499 uplinkBandwidthKhz, 500 harmonicParams2g.getN(), 501 harmonicParams2g.getOverlap(), 502 powerCapDbm); 503 if (!harmonicChannels2g.isEmpty()) { 504 if (mVerboseLoggingEnabled) { 505 Log.v(TAG, cellChannel + " has harmonic interference with " 506 + harmonicChannels2g); 507 } 508 currentBandUnsafeChannels.addAll(harmonicChannels2g); 509 } 510 } 511 if (harmonicParams5g != null) { 512 final List<CoexUnsafeChannel> harmonicChannels5g = 513 get5gHarmonicCoexUnsafeChannels( 514 uplinkFreqKhz, 515 uplinkBandwidthKhz, 516 harmonicParams5g.getN(), 517 harmonicParams5g.getOverlap(), 518 powerCapDbm); 519 if (!harmonicChannels5g.isEmpty()) { 520 if (mVerboseLoggingEnabled) { 521 Log.v(TAG, cellChannel + " has harmonic interference with " 522 + harmonicChannels5g); 523 } 524 currentBandUnsafeChannels.addAll(harmonicChannels5g); 525 } 526 } 527 528 if (intermodParams2g != null) { 529 for (CoexUtils.CoexCellChannel victimCellChannel : cellChannels) { 530 if (victimCellChannel.getDownlinkFreqKhz() >= 0 531 && victimCellChannel.getDownlinkBandwidthKhz() > 0) { 532 final List<CoexUnsafeChannel> intermodChannels2g = 533 getIntermodCoexUnsafeChannels( 534 uplinkFreqKhz, 535 uplinkBandwidthKhz, 536 victimCellChannel.getDownlinkFreqKhz(), 537 victimCellChannel.getDownlinkBandwidthKhz(), 538 intermodParams2g.getN(), 539 intermodParams2g.getM(), 540 intermodParams2g.getOverlap(), 541 WIFI_BAND_24_GHZ, 542 powerCapDbm); 543 if (!intermodChannels2g.isEmpty()) { 544 if (mVerboseLoggingEnabled) { 545 Log.v(TAG, cellChannel + " and " + intermodChannels2g 546 + " have intermod interference on " 547 + victimCellChannel); 548 } 549 currentBandUnsafeChannels.addAll(intermodChannels2g); 550 } 551 } 552 } 553 } 554 if (intermodParams5g != null) { 555 for (CoexUtils.CoexCellChannel victimCellChannel : cellChannels) { 556 if (victimCellChannel.getDownlinkFreqKhz() >= 0 557 && victimCellChannel.getDownlinkBandwidthKhz() > 0) { 558 final List<CoexUnsafeChannel> intermodChannels5g = 559 getIntermodCoexUnsafeChannels( 560 uplinkFreqKhz, 561 uplinkBandwidthKhz, 562 victimCellChannel.getDownlinkFreqKhz(), 563 victimCellChannel.getDownlinkBandwidthKhz(), 564 intermodParams5g.getN(), 565 intermodParams5g.getM(), 566 intermodParams5g.getOverlap(), 567 WIFI_BAND_5_GHZ, 568 powerCapDbm); 569 if (!intermodChannels5g.isEmpty()) { 570 if (mVerboseLoggingEnabled) { 571 Log.v(TAG, cellChannel + " and " + intermodChannels5g 572 + " have intermod interference on " 573 + victimCellChannel); 574 } 575 currentBandUnsafeChannels.addAll(intermodChannels5g); 576 } 577 } 578 } 579 } 580 } 581 // Collect the lowest number default channel for each band to extract from 582 // calculated set of CoexUnsafeChannels later. 583 if (defaultChannels != null) { 584 if (defaultChannels.hasDefault2g()) { 585 int channel = defaultChannels.getDefault2g(); 586 if (channel < default2gChannel) { 587 default2gChannel = channel; 588 } 589 } 590 if (defaultChannels.hasDefault5g()) { 591 int channel = defaultChannels.getDefault5g(); 592 if (channel < default5gChannel) { 593 default5gChannel = channel; 594 } 595 } 596 } 597 } else if (override != null) { 598 // Add all of the CoexUnsafeChannels defined by the override lists. 599 final Override2g override2g = override.getOverride2g(); 600 if (override2g != null) { 601 final List<Integer> channelList2g = override2g.getChannel(); 602 for (OverrideCategory2g category : override2g.getCategory()) { 603 if (OverrideCategory2g.all.equals(category)) { 604 for (int i = 1; i <= 14; i++) { 605 channelList2g.add(i); 606 } 607 } 608 } 609 if (!channelList2g.isEmpty()) { 610 if (mVerboseLoggingEnabled) { 611 Log.v(TAG, cellChannel + " sets override 2g channels " 612 + channelList2g); 613 } 614 for (int channel : channelList2g) { 615 currentBandUnsafeChannels.add(new CoexUnsafeChannel( 616 WIFI_BAND_24_GHZ, channel, powerCapDbm)); 617 } 618 } 619 } 620 final Override5g override5g = override.getOverride5g(); 621 if (override5g != null) { 622 final List<Integer> channelList5g = override5g.getChannel(); 623 for (OverrideCategory5g category : override5g.getCategory()) { 624 if (OverrideCategory5g._20Mhz.equals(category)) { 625 channelList5g.addAll(CHANNEL_SET_5_GHZ_20_MHZ); 626 } else if (OverrideCategory5g._40Mhz.equals(category)) { 627 channelList5g.addAll(CHANNEL_SET_5_GHZ_40_MHZ); 628 } else if (OverrideCategory5g._80Mhz.equals(category)) { 629 channelList5g.addAll(CHANNEL_SET_5_GHZ_80_MHZ); 630 } else if (OverrideCategory5g._160Mhz.equals(category)) { 631 channelList5g.addAll(CHANNEL_SET_5_GHZ_160_MHZ); 632 } else if (OverrideCategory5g.all.equals(category)) { 633 channelList5g.addAll(CHANNEL_SET_5_GHZ); 634 } 635 } 636 if (!channelList5g.isEmpty()) { 637 if (mVerboseLoggingEnabled) { 638 Log.v(TAG, cellChannel + " sets override 5g channels " 639 + channelList5g); 640 } 641 for (int channel : channelList5g) { 642 currentBandUnsafeChannels.add(new CoexUnsafeChannel( 643 WIFI_BAND_5_GHZ, channel, powerCapDbm)); 644 } 645 } 646 } 647 } 648 } 649 // Set coex restrictions for LAA based on carrier config values. 650 if (cellChannel.getRat() == NETWORK_TYPE_LTE 651 && cellChannel.getBand() == AccessNetworkConstants.EutranBand.BAND_46) { 652 final boolean avoid5gSoftAp = 653 mAvoid5gSoftApForLaaPerSubId.get(cellChannel.getSubId()); 654 final boolean avoid5gWifiDirect = 655 mAvoid5gWifiDirectForLaaPerSubId.get(cellChannel.getSubId()); 656 if (avoid5gSoftAp || avoid5gWifiDirect) { 657 for (int channel : CHANNEL_SET_5_GHZ) { 658 currentBandUnsafeChannels.add( 659 new CoexUnsafeChannel(WIFI_BAND_5_GHZ, channel)); 660 } 661 if (avoid5gSoftAp) { 662 if (mVerboseLoggingEnabled) { 663 Log.v(TAG, "Avoiding 5g softap due to LAA channel " + cellChannel); 664 } 665 coexRestrictions |= COEX_RESTRICTION_SOFTAP; 666 } 667 if (avoid5gWifiDirect) { 668 if (mVerboseLoggingEnabled) { 669 Log.v(TAG, "Avoiding 5g wifi direct due to LAA channel " + cellChannel); 670 } 671 coexRestrictions |= COEX_RESTRICTION_WIFI_DIRECT; 672 } 673 } 674 } 675 // Add all of the CoexUnsafeChannels calculated from this cell channel to the total. 676 // If the total already contains a CoexUnsafeChannel for the same band and channel, 677 // keep the one that has the lower power cap. 678 for (CoexUnsafeChannel unsafeChannel : currentBandUnsafeChannels) { 679 final int band = unsafeChannel.getBand(); 680 final int channel = unsafeChannel.getChannel(); 681 final Pair<Integer, Integer> bandChannelPair = new Pair<>(band, channel); 682 final CoexUnsafeChannel existingUnsafeChannel = 683 coexUnsafeChannelsByBandChannelPair.get(bandChannelPair); 684 if (existingUnsafeChannel != null) { 685 if (unsafeChannel.getPowerCapDbm() == POWER_CAP_NONE) { 686 continue; 687 } 688 final int existingPowerCapDbm = existingUnsafeChannel.getPowerCapDbm(); 689 if (existingPowerCapDbm != POWER_CAP_NONE 690 && existingPowerCapDbm < unsafeChannel.getPowerCapDbm()) { 691 continue; 692 } 693 } else { 694 // Count the number of unsafe channels for each band to determine if we need to 695 // remove the default channels before returning. 696 if (band == WIFI_BAND_24_GHZ) { 697 numUnsafe2gChannels++; 698 } else if (band == WIFI_BAND_5_GHZ) { 699 numUnsafe5gChannels++; 700 } 701 } 702 coexUnsafeChannelsByBandChannelPair.put(bandChannelPair, unsafeChannel); 703 } 704 } 705 // Omit the default channel from each band if the entire band is unsafe and there are 706 // no coex restrictions set. 707 if (coexRestrictions == 0) { 708 if (numUnsafe2gChannels == NUM_24_GHZ_CHANNELS) { 709 if (mVerboseLoggingEnabled) { 710 Log.v(TAG, "Omitting default 2g channel " + default2gChannel 711 + " from unsafe set."); 712 } 713 coexUnsafeChannelsByBandChannelPair.remove( 714 new Pair<>(WIFI_BAND_24_GHZ, default2gChannel)); 715 } 716 if (numUnsafe5gChannels == CHANNEL_SET_5_GHZ.size()) { 717 if (mVerboseLoggingEnabled) { 718 Log.v(TAG, "Omitting default 5g channel " + default5gChannel 719 + " from unsafe set."); 720 } 721 coexUnsafeChannelsByBandChannelPair.remove( 722 new Pair<>(WIFI_BAND_5_GHZ, default5gChannel)); 723 } 724 } 725 setCoexUnsafeChannels( 726 new ArrayList<>(coexUnsafeChannelsByBandChannelPair.values()), coexRestrictions); 727 } 728 729 /** 730 * Updates carrier config values and returns true if the values have changed, false otherwise. 731 */ updateCarrierConfigs(@ullable List<SubscriptionInfo> subInfos)732 private boolean updateCarrierConfigs(@Nullable List<SubscriptionInfo> subInfos) { 733 final SparseBooleanArray oldAvoid5gSoftAp = mAvoid5gSoftApForLaaPerSubId.clone(); 734 final SparseBooleanArray oldAvoid5gWifiDirect = mAvoid5gWifiDirectForLaaPerSubId.clone(); 735 mAvoid5gSoftApForLaaPerSubId.clear(); 736 mAvoid5gWifiDirectForLaaPerSubId.clear(); 737 if (subInfos != null) { 738 for (SubscriptionInfo subInfo : subInfos) { 739 final int subId = subInfo.getSubscriptionId(); 740 PersistableBundle bundle = mCarrierConfigManager.getConfigForSubId(subId); 741 if (bundle != null) { 742 mAvoid5gSoftApForLaaPerSubId.put(subId, bundle.getBoolean( 743 CarrierConfigManager.Wifi.KEY_AVOID_5GHZ_SOFTAP_FOR_LAA_BOOL)); 744 mAvoid5gWifiDirectForLaaPerSubId.put(subId, bundle.getBoolean( 745 CarrierConfigManager.Wifi.KEY_AVOID_5GHZ_WIFI_DIRECT_FOR_LAA_BOOL)); 746 } 747 } 748 } 749 return !mAvoid5gSoftApForLaaPerSubId.equals(oldAvoid5gSoftAp) 750 || !mAvoid5gWifiDirectForLaaPerSubId.equals(oldAvoid5gWifiDirect); 751 } 752 753 /** 754 * Parses a coex table xml from the specified File and populates the table entry maps. 755 * Returns {@code true} if the file was found and read successfully, {@code false} otherwise. 756 */ 757 @VisibleForTesting readTableFromXml()758 boolean readTableFromXml() { 759 final String filepath = mContext.getResources().getString( 760 R.string.config_wifiCoexTableFilepath); 761 if (filepath == null) { 762 Log.e(TAG, "Coex table filepath was null"); 763 return false; 764 } 765 final File file = new File(filepath); 766 try (InputStream str = new BufferedInputStream(new FileInputStream(file))) { 767 mLteTableEntriesByBand.clear(); 768 mNrTableEntriesByBand.clear(); 769 for (Entry entry : XmlParser.readTable(str).getEntry()) { 770 if (RatType.LTE.equals(entry.getRat())) { 771 mLteTableEntriesByBand.put(entry.getBand(), entry); 772 } else if (RatType.NR.equals(entry.getRat())) { 773 mNrTableEntriesByBand.put(entry.getBand(), entry); 774 } 775 } 776 Log.i(TAG, "Successfully read coex table from file"); 777 return true; 778 } catch (FileNotFoundException e) { 779 Log.e(TAG, "No coex table file found at " + file); 780 } catch (Exception e) { 781 Log.e(TAG, "Failed to read coex table file: " + e); 782 } 783 return false; 784 } 785 786 /** 787 * Sets the mock CoexCellChannels to use for coex calculations. 788 * @param cellChannels list of mock cell channels 789 */ setMockCellChannels(@onNull List<CoexUtils.CoexCellChannel> cellChannels)790 public void setMockCellChannels(@NonNull List<CoexUtils.CoexCellChannel> cellChannels) { 791 mIsUsingMockCellChannels = true; 792 mMockCellChannels.clear(); 793 mMockCellChannels.addAll(cellChannels); 794 updateCoexUnsafeChannels(mMockCellChannels); 795 } 796 797 /** 798 * Removes all added mock CoexCellChannels. 799 */ resetMockCellChannels()800 public void resetMockCellChannels() { 801 mIsUsingMockCellChannels = false; 802 mMockCellChannels.clear(); 803 updateCoexUnsafeChannels(getCellChannelsForAllSubIds()); 804 } 805 806 /** 807 * Returns all cell channels used for coex calculations. 808 */ getCellChannels()809 public List<CoexUtils.CoexCellChannel> getCellChannels() { 810 if (mIsUsingMockCellChannels) { 811 return mMockCellChannels; 812 } 813 return getCellChannelsForAllSubIds(); 814 } 815 getCellChannelsForAllSubIds()816 private List<CoexUtils.CoexCellChannel> getCellChannelsForAllSubIds() { 817 final List<CoexUtils.CoexCellChannel> cellChannels = new ArrayList<>(); 818 for (int i = 0, size = mCellChannelsPerSubId.size(); i < size; i++) { 819 cellChannels.addAll(mCellChannelsPerSubId.valueAt(i)); 820 } 821 return cellChannels; 822 } 823 824 /** 825 * Enables verbose logging of the coex algorithm. 826 */ enableVerboseLogging(boolean verbose)827 public void enableVerboseLogging(boolean verbose) { 828 mVerboseLoggingEnabled = verbose; 829 } 830 } 831