• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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