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