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