• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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.ons;
18 
19 import static android.telephony.AvailableNetworkInfo.PRIORITY_HIGH;
20 import static android.telephony.AvailableNetworkInfo.PRIORITY_LOW;
21 
22 import android.annotation.NonNull;
23 import android.app.PendingIntent;
24 import android.compat.Compatibility;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.os.AsyncTask;
28 import android.os.Handler;
29 import android.os.HandlerThread;
30 import android.os.Message;
31 import android.os.RemoteException;
32 import android.telephony.AvailableNetworkInfo;
33 import android.telephony.CellInfo;
34 import android.telephony.CellInfoLte;
35 import android.telephony.CellInfoNr;
36 import android.telephony.SignalStrength;
37 import android.telephony.SubscriptionInfo;
38 import android.telephony.SubscriptionManager;
39 import android.telephony.TelephonyManager;
40 import android.telephony.UiccCardInfo;
41 import android.telephony.UiccPortInfo;
42 import android.telephony.euicc.EuiccManager;
43 import android.text.TextUtils;
44 import android.util.IndentingPrintWriter;
45 import android.util.Log;
46 
47 import com.android.internal.annotations.VisibleForTesting;
48 import com.android.internal.telephony.ISetOpportunisticDataCallback;
49 import com.android.internal.telephony.IUpdateAvailableNetworksCallback;
50 
51 import java.io.FileDescriptor;
52 import java.io.PrintWriter;
53 import java.util.ArrayList;
54 import java.util.Arrays;
55 import java.util.Collections;
56 import java.util.Comparator;
57 import java.util.HashSet;
58 import java.util.List;
59 import java.util.stream.Collectors;
60 
61 /**
62  * Profile selector class which will select the right profile based upon
63  * geographic information input and network scan results.
64  */
65 public class ONSProfileSelector {
66     private static final String TAG = "ONSProfileSelector";
67     private static final boolean DBG = true;
68     private final Object mLock = new Object();
69 
70     private static final int INVALID_SEQUENCE_ID = -1;
71     private static final int START_SEQUENCE_ID = 1;
72 
73     /** Message to indicate profile update */
74     private static final int MSG_PROFILE_UPDATE = 1;
75 
76     /** Message to indicate start of profile selection process */
77     private static final int MSG_START_PROFILE_SELECTION = 2;
78 
79     /** Message to indicate Subscription switch completion */
80     private static final int MSG_SUB_SWITCH_COMPLETE = 3;
81 
82     /** Message to stop profile selection process */
83     private static final int MSG_STOP_PROFILE_SELECTION = 4;
84 
85     private boolean mIsEnabled = false;
86 
87     @VisibleForTesting
88     protected Context mContext;
89 
90     @VisibleForTesting
91     protected TelephonyManager mTelephonyManager;
92     @VisibleForTesting
93     protected TelephonyManager mSubscriptionBoundTelephonyManager;
94     @VisibleForTesting
95     protected EuiccManager mEuiccManager;
96 
97     @VisibleForTesting
98     protected ONSNetworkScanCtlr mNetworkScanCtlr;
99 
100     @VisibleForTesting
101     protected SubscriptionManager mSubscriptionManager;
102     @VisibleForTesting
103     protected List<SubscriptionInfo> mOppSubscriptionInfos;
104     @VisibleForTesting
105     protected List<SubscriptionInfo> mStandaloneOppSubInfos;
106     private ONSProfileSelectionCallback mProfileSelectionCallback;
107     private int mSequenceId;
108     private int mSubId;
109     @VisibleForTesting
110     protected int mCurrentDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
111     private List<AvailableNetworkInfo> mAvailableNetworkInfos;
112     private IUpdateAvailableNetworksCallback mNetworkScanCallback;
113 
114     public static final String ACTION_SUB_SWITCH =
115             "android.intent.action.SUBSCRIPTION_SWITCH_REPLY";
116 
117     HandlerThread mThread;
118     @VisibleForTesting
119     protected Handler mHandler;
120 
121     /**
122      * Network scan callback handler
123      */
124     @VisibleForTesting
125     protected ONSNetworkScanCtlr.NetworkAvailableCallBack mNetworkAvailableCallBack =
126             new ONSNetworkScanCtlr.NetworkAvailableCallBack() {
127                 @Override
128                 public void onNetworkAvailability(List<CellInfo> results) {
129                     int subId = retrieveBestSubscription(results);
130                     if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
131                         sendUpdateNetworksCallbackHelper(mNetworkScanCallback,
132                                 TelephonyManager.UPDATE_AVAILABLE_NETWORKS_INVALID_ARGUMENTS);
133                         synchronized (mLock) {
134                             mNetworkScanCallback = null;
135                         }
136                         return;
137                     }
138 
139                     // stop scanning further
140                     mNetworkScanCtlr.stopNetworkScan();
141                     handleNetworkScanResult(subId);
142                 }
143 
144                 @Override
145                 public void onError(int error) {
146                     log("Network scan failed with error " + error);
147                     synchronized (mLock) {
148                         if (mIsEnabled && mAvailableNetworkInfos != null
149                                 && !mAvailableNetworkInfos.isEmpty()) {
150                             handleNetworkScanResult(mAvailableNetworkInfos.getFirst().getSubId());
151                         } else {
152                             if (mNetworkScanCallback != null) {
153                                 if (mIsEnabled) {
154                                     sendUpdateNetworksCallbackHelper(mNetworkScanCallback,
155                                             TelephonyManager
156                                                     .UPDATE_AVAILABLE_NETWORKS_INVALID_ARGUMENTS);
157                                 } else {
158                                     if (Compatibility.isChangeEnabled(
159                                             OpportunisticNetworkService
160                                                     .CALLBACK_ON_MORE_ERROR_CODE_CHANGE)) {
161                                         sendUpdateNetworksCallbackHelper(mNetworkScanCallback,
162                                                 TelephonyManager
163                                                         .UPDATE_AVAILABLE_NETWORKS_SERVICE_IS_DISABLED);
164                                     } else {
165                                         sendUpdateNetworksCallbackHelper(mNetworkScanCallback,
166                                                 TelephonyManager
167                                                         .UPDATE_AVAILABLE_NETWORKS_UNKNOWN_FAILURE);
168                                     }
169                                 }
170                                 mNetworkScanCallback = null;
171                             }
172                         }
173                     }
174                 }
175 
176                 private void handleNetworkScanResult(int subId) {
177                     // if subscription is already active, just enable modem
178                     if (mSubscriptionManager.isActiveSubId(subId)) {
179                         if (enableModem(subId, true)) {
180                             sendUpdateNetworksCallbackHelper(mNetworkScanCallback,
181                                 TelephonyManager.UPDATE_AVAILABLE_NETWORKS_SUCCESS);
182                         } else {
183                             if (Compatibility.isChangeEnabled(
184                                     OpportunisticNetworkService
185                                             .CALLBACK_ON_MORE_ERROR_CODE_CHANGE)) {
186                                 sendUpdateNetworksCallbackHelper(mNetworkScanCallback,
187                                         TelephonyManager
188                                                 .UPDATE_AVAILABLE_NETWORKS_ENABLE_MODEM_FAIL);
189                             } else {
190                                 sendUpdateNetworksCallbackHelper(mNetworkScanCallback,
191                                         TelephonyManager.UPDATE_AVAILABLE_NETWORKS_ABORTED);
192                             }
193                         }
194                         mProfileSelectionCallback.onProfileSelectionDone();
195                         synchronized (mLock) {
196                             mNetworkScanCallback = null;
197                             mAvailableNetworkInfos = null;
198                         }
199                     } else {
200                         logDebug("switch to sub:" + subId);
201                         switchToSubscription(subId, getAvailableESIMPortIndex());
202                     }
203                 }
204             };
205 
206     @VisibleForTesting
207     protected SubscriptionManager.OnOpportunisticSubscriptionsChangedListener
208             mProfileChangeListener =
209             new SubscriptionManager.OnOpportunisticSubscriptionsChangedListener() {
210                 @Override
211                 public void onOpportunisticSubscriptionsChanged() {
212                     logDebug("onOpportunisticSubscriptionsChanged.");
213                     mHandler.sendEmptyMessage(MSG_PROFILE_UPDATE);
214                 }
215             };
216 
217     /**
218      * Interface call back to confirm profile selection
219      */
220     public interface ONSProfileSelectionCallback {
221 
222         /**
223          * Interface call back to confirm profile selection
224          */
onProfileSelectionDone()225         void onProfileSelectionDone();
226     }
227 
228     class SortSubInfo implements Comparator<SubscriptionInfo>
229     {
230         // Used for sorting in ascending order of sub id
compare(SubscriptionInfo a, SubscriptionInfo b)231         public int compare(SubscriptionInfo a, SubscriptionInfo b)
232         {
233             return a.getSubscriptionId() - b.getSubscriptionId();
234         }
235     }
236 
237     class SortAvailableNetworks implements Comparator<AvailableNetworkInfo>
238     {
239         // Used for sorting in ascending order of sub id
compare(AvailableNetworkInfo a, AvailableNetworkInfo b)240         public int compare(AvailableNetworkInfo a, AvailableNetworkInfo b)
241         {
242             return a.getSubId() - b.getSubId();
243         }
244     }
245 
246     class SortAvailableNetworksInPriority implements Comparator<AvailableNetworkInfo>
247     {
248         // Used for sorting in descending order of priority (ascending order of priority numbers)
compare(AvailableNetworkInfo a, AvailableNetworkInfo b)249         public int compare(AvailableNetworkInfo a, AvailableNetworkInfo b)
250         {
251             return a.getPriority() - b.getPriority();
252         }
253     }
254 
255     /**
256      * ONSProfileSelector constructor
257      * @param c context
258      * @param profileSelectionCallback callback to be called once selection is done
259      */
ONSProfileSelector(Context c, ONSProfileSelectionCallback profileSelectionCallback)260     public ONSProfileSelector(Context c, ONSProfileSelectionCallback profileSelectionCallback) {
261         init(c, profileSelectionCallback);
262         log("ONSProfileSelector init complete");
263     }
264 
getSignalLevel(CellInfo cellInfo)265     private int getSignalLevel(CellInfo cellInfo) {
266         if (cellInfo != null) {
267             return cellInfo.getCellSignalStrength().getLevel();
268         } else {
269             return SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
270         }
271     }
272 
273     @VisibleForTesting
getMcc(CellInfo cellInfo)274     protected String getMcc(CellInfo cellInfo) {
275         String mcc = "";
276         if (cellInfo instanceof CellInfoLte) {
277             mcc = ((CellInfoLte) cellInfo).getCellIdentity().getMccString();
278         }
279         else if (cellInfo instanceof CellInfoNr) {
280             mcc = ((CellInfoNr) cellInfo).getCellIdentity().getMccString();
281         }
282 
283         return mcc;
284     }
285 
286     @VisibleForTesting
getMnc(CellInfo cellInfo)287     protected String getMnc(CellInfo cellInfo) {
288         String mnc = "";
289         if (cellInfo instanceof CellInfoLte) {
290             mnc = ((CellInfoLte) cellInfo).getCellIdentity().getMncString();
291         }
292         else if (cellInfo instanceof CellInfoNr) {
293             mnc = ((CellInfoNr) cellInfo).getCellIdentity().getMncString();
294         }
295 
296         return mnc;
297     }
298 
getSubIdUsingAvailableNetworks(String mcc, String mnc, int priorityLevel)299     private int getSubIdUsingAvailableNetworks(String mcc, String mnc, int priorityLevel) {
300         String mccMnc = mcc + mnc;
301         synchronized (mLock) {
302             if (mAvailableNetworkInfos != null) {
303                 for (AvailableNetworkInfo availableNetworkInfo : mAvailableNetworkInfos) {
304                     if (availableNetworkInfo.getPriority() != priorityLevel) {
305                         continue;
306                     }
307                     for (String availableMccMnc : availableNetworkInfo.getMccMncs()) {
308                         if (TextUtils.equals(availableMccMnc, mccMnc)) {
309                             return availableNetworkInfo.getSubId();
310                         }
311                     }
312                 }
313             }
314         }
315         return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
316     }
317 
getOpportunisticSubInfo(int subId)318     public SubscriptionInfo getOpportunisticSubInfo(int subId) {
319         if ((mOppSubscriptionInfos == null) || (mOppSubscriptionInfos.isEmpty())) {
320             return null;
321         }
322         for (SubscriptionInfo subscriptionInfo : mOppSubscriptionInfos) {
323             if (subscriptionInfo.getSubscriptionId() == subId) {
324                 return subscriptionInfo;
325             }
326         }
327         return null;
328     }
329 
330     /**
331      * Return whether the subId is for an opportunistic subscription.
332      *
333      * @param subId the subId of the subscription.
334      * @return true if the subscription is opportunistic
335      */
isOpportunisticSub(int subId)336     public boolean isOpportunisticSub(int subId) {
337         if ((mOppSubscriptionInfos == null) || (mOppSubscriptionInfos.isEmpty())) {
338             return false;
339         }
340         for (SubscriptionInfo subscriptionInfo : mOppSubscriptionInfos) {
341             if (subscriptionInfo.getSubscriptionId() == subId) {
342                 return true;
343             }
344         }
345         return false;
346     }
347 
hasOpportunisticSub(List<AvailableNetworkInfo> availableNetworks)348     public boolean hasOpportunisticSub(List<AvailableNetworkInfo> availableNetworks) {
349         if ((availableNetworks == null) || (availableNetworks.isEmpty())) {
350             return false;
351         }
352         if ((mOppSubscriptionInfos == null) || (mOppSubscriptionInfos.isEmpty())) {
353             return false;
354         }
355 
356         for (AvailableNetworkInfo availableNetworkInfo : availableNetworks) {
357             if (!isOpportunisticSub(availableNetworkInfo.getSubId())) {
358                 return false;
359             }
360         }
361         return true;
362     }
363 
switchToSubscription(int subId, int availableSIMPortIndex)364     private void switchToSubscription(int subId, int availableSIMPortIndex) {
365         Intent callbackIntent = new Intent(ACTION_SUB_SWITCH);
366         callbackIntent.setClass(mContext, OpportunisticNetworkService.class);
367         updateToken();
368         callbackIntent.putExtra("sequenceId", mSequenceId);
369         callbackIntent.putExtra("subId", subId);
370         mSubId = subId;
371         PendingIntent replyIntent = PendingIntent.getService(mContext,
372                 1, callbackIntent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE);
373         if (availableSIMPortIndex == TelephonyManager.INVALID_PORT_INDEX) {
374             sendUpdateNetworksCallbackHelper(mNetworkScanCallback,
375                     TelephonyManager.UPDATE_AVAILABLE_NETWORKS_SIM_PORT_NOT_AVAILABLE);
376             return;
377         }
378 
379         List<SubscriptionInfo> subInfoList = mSubscriptionManager
380                 .getAvailableSubscriptionInfoList();
381         for (SubscriptionInfo subInfo : subInfoList) {
382             if (subId == subInfo.getSubscriptionId()) {
383                 EuiccManager euiccManager = mEuiccManager.createForCardId(subInfo.getCardId());
384                 euiccManager.switchToSubscription(subId, availableSIMPortIndex, replyIntent);
385                 break;
386             }
387         }
388     }
389 
390     @VisibleForTesting
getAvailableESIMPortIndex()391     protected int getAvailableESIMPortIndex() {
392         //Check if an opportunistic subscription is already active. If yes then, use the same port.
393         List<SubscriptionInfo> subscriptionInfos = mSubscriptionManager
394                 .getCompleteActiveSubscriptionInfoList();
395         if (subscriptionInfos != null) {
396             logDebug("[getAvailableESIMPortIndex] subscriptionInfos size:"
397                     + subscriptionInfos.size());
398             for (SubscriptionInfo subscriptionInfo : subscriptionInfos) {
399                 if (subscriptionInfo.isEmbedded() && subscriptionInfo.isOpportunistic()) {
400                     return subscriptionInfo.getPortIndex();
401                 }
402             }
403         }
404 
405         //Look for available port.
406         for (UiccCardInfo uiccCardInfo : mTelephonyManager.getUiccCardsInfo()) {
407             logDebug("[getAvailableESIMPortIndex] CardInfo: " + uiccCardInfo.toString());
408             if (!uiccCardInfo.isEuicc()) {
409                 continue;
410             }
411 
412             EuiccManager euiccManager = mEuiccManager.createForCardId(uiccCardInfo.getCardId());
413             for (UiccPortInfo uiccPortInfo : uiccCardInfo.getPorts()) {
414                 logDebug("[getAvailableESIMPortIndex] PortInfo: " + uiccPortInfo.toString());
415                 //Port is available if no profiles enabled on it.
416                 if (euiccManager.isSimPortAvailable(uiccPortInfo.getPortIndex())) {
417                     return uiccPortInfo.getPortIndex();
418                 }
419             }
420         }
421 
422         logDebug("[getAvailableESIMPortIndex] No Port is available.");
423         return TelephonyManager.INVALID_PORT_INDEX;
424     }
425 
onSubSwitchComplete(Intent intent)426     void onSubSwitchComplete(Intent intent) {
427         int sequenceId = intent.getIntExtra("sequenceId",  INVALID_SEQUENCE_ID);
428         int subId = intent.getIntExtra("subId",
429                 SubscriptionManager.INVALID_SUBSCRIPTION_ID);
430         logDebug("ACTION_SUB_SWITCH sequenceId: " + sequenceId
431                 + " mSequenceId: " + mSequenceId
432                 + " mSubId: " + mSubId
433                 + " subId: " + subId);
434         Message message = Message.obtain(mHandler, MSG_SUB_SWITCH_COMPLETE, subId);
435         message.sendToTarget();
436     }
437 
onSubSwitchComplete(int subId)438     private void onSubSwitchComplete(int subId) {
439         // Ignore if this is callback for an older request
440         if (mSubId != subId) {
441             return;
442         }
443 
444         if (enableModem(subId, true)) {
445             sendUpdateNetworksCallbackHelper(mNetworkScanCallback,
446                 TelephonyManager.UPDATE_AVAILABLE_NETWORKS_SUCCESS);
447         } else {
448             if (Compatibility.isChangeEnabled(
449                     OpportunisticNetworkService.CALLBACK_ON_MORE_ERROR_CODE_CHANGE)) {
450                 sendUpdateNetworksCallbackHelper(mNetworkScanCallback,
451                         TelephonyManager.UPDATE_AVAILABLE_NETWORKS_ENABLE_MODEM_FAIL);
452             } else {
453                 sendUpdateNetworksCallbackHelper(mNetworkScanCallback,
454                         TelephonyManager.UPDATE_AVAILABLE_NETWORKS_ABORTED);
455             }
456         }
457         mProfileSelectionCallback.onProfileSelectionDone();
458         mNetworkScanCallback = null;
459         mAvailableNetworkInfos = null;
460     }
461 
updateToken()462     private void updateToken() {
463         synchronized (mLock) {
464             mSequenceId++;
465         }
466     }
467 
getFilteredAvailableNetworks( List<AvailableNetworkInfo> availableNetworks, List<SubscriptionInfo> subscriptionInfoList)468     private List<AvailableNetworkInfo> getFilteredAvailableNetworks(
469             List<AvailableNetworkInfo> availableNetworks,
470             List<SubscriptionInfo> subscriptionInfoList) {
471         List<AvailableNetworkInfo> filteredAvailableNetworks =
472                 new ArrayList<>();
473 
474         // instead of checking each element of a list every element of the other, sort them in
475         // the order of sub id and compare to improve the filtering performance.
476         Collections.sort(subscriptionInfoList, new SortSubInfo());
477         Collections.sort(availableNetworks, new SortAvailableNetworks());
478         int availableNetworksIndex = 0;
479         int subscriptionInfoListIndex = 0;
480         SubscriptionInfo subscriptionInfo;
481         AvailableNetworkInfo availableNetwork;
482 
483         while (availableNetworksIndex < availableNetworks.size()
484                 && subscriptionInfoListIndex < subscriptionInfoList.size()) {
485             subscriptionInfo = subscriptionInfoList.get(subscriptionInfoListIndex);
486             availableNetwork = availableNetworks.get(availableNetworksIndex);
487             if (subscriptionInfo.getSubscriptionId() == availableNetwork.getSubId()) {
488                 filteredAvailableNetworks.add(availableNetwork);
489                 subscriptionInfoListIndex++;
490                 availableNetworksIndex++;
491             } else if (subscriptionInfo.getSubscriptionId() < availableNetwork.getSubId()) {
492                 subscriptionInfoListIndex++;
493             } else {
494                 availableNetworksIndex++;
495             }
496         }
497         return filteredAvailableNetworks;
498     }
499 
isSame(List<AvailableNetworkInfo> availableNetworks1, List<AvailableNetworkInfo> availableNetworks2)500     private boolean isSame(List<AvailableNetworkInfo> availableNetworks1,
501             List<AvailableNetworkInfo> availableNetworks2) {
502         if ((availableNetworks1 == null) || (availableNetworks2 == null)) {
503             return false;
504         }
505         return new HashSet<>(availableNetworks1).equals(new HashSet<>(availableNetworks2));
506     }
507 
sendUpdateNetworksCallbackHelper(IUpdateAvailableNetworksCallback callback, int result)508     private void sendUpdateNetworksCallbackHelper(IUpdateAvailableNetworksCallback callback,
509             int result) {
510         if (callback == null) {
511             log("callback is null");
512             return;
513         }
514         try {
515             callback.onComplete(result);
516         } catch (RemoteException exception) {
517             log("RemoteException " + exception);
518         }
519     }
520 
checkProfileUpdate(Object[] objects)521     private void checkProfileUpdate(Object[] objects) {
522         List<AvailableNetworkInfo> availableNetworks =
523                 (List<AvailableNetworkInfo>) objects[0];
524         IUpdateAvailableNetworksCallback callbackStub =
525                 (IUpdateAvailableNetworksCallback) objects[1];
526         if (mOppSubscriptionInfos == null) {
527             logDebug("null subscription infos");
528             if (Compatibility.isChangeEnabled(
529                     OpportunisticNetworkService.CALLBACK_ON_MORE_ERROR_CODE_CHANGE)) {
530                 sendUpdateNetworksCallbackHelper(callbackStub,
531                         TelephonyManager.UPDATE_AVAILABLE_NETWORKS_NO_OPPORTUNISTIC_SUB_AVAILABLE);
532             } else {
533                 sendUpdateNetworksCallbackHelper(callbackStub,
534                         TelephonyManager.UPDATE_AVAILABLE_NETWORKS_INVALID_ARGUMENTS);
535             }
536             return;
537         }
538 
539         // Check if ports are available on the embedded slot
540         int availSIMPortIndex = getAvailableESIMPortIndex();
541         if (availSIMPortIndex == TelephonyManager.INVALID_PORT_INDEX) {
542             logDebug("SIM port not available.");
543             sendUpdateNetworksCallbackHelper(callbackStub,
544                     TelephonyManager.UPDATE_AVAILABLE_NETWORKS_SIM_PORT_NOT_AVAILABLE);
545             return;
546         }
547 
548         if (isSame(availableNetworks, mAvailableNetworkInfos)) {
549             logDebug("received duplicate requests");
550             // If we receive same request more than once, send abort response for earlier one
551             // and send actual response for the latest callback.
552             sendUpdateNetworksCallbackHelper(mNetworkScanCallback,
553                 TelephonyManager.UPDATE_AVAILABLE_NETWORKS_ABORTED);
554             mNetworkScanCallback = callbackStub;
555             return;
556         }
557 
558         stopProfileScanningProcedure();
559         mIsEnabled = true;
560         mAvailableNetworkInfos = availableNetworks;
561         // sort in the order of priority
562         Collections.sort(mAvailableNetworkInfos, new SortAvailableNetworksInPriority());
563         logDebug("availableNetworks: " + availableNetworks);
564 
565         if (!mOppSubscriptionInfos.isEmpty()) {
566             logDebug("opportunistic subscriptions size " + mOppSubscriptionInfos.size());
567             List<AvailableNetworkInfo> filteredAvailableNetworks =
568                     getFilteredAvailableNetworks(availableNetworks,
569                             mOppSubscriptionInfos);
570             if ((filteredAvailableNetworks.size() == 1)
571                     && ((filteredAvailableNetworks.getFirst().getMccMncs() == null)
572                     || (filteredAvailableNetworks.getFirst().getMccMncs().isEmpty()))) {
573                 // if subscription is not active, activate the sub
574                 if (!mSubscriptionManager.isActiveSubId(
575                         filteredAvailableNetworks.getFirst().getSubId())) {
576                     mNetworkScanCallback = callbackStub;
577                     switchToSubscription(filteredAvailableNetworks.getFirst().getSubId(),
578                             availSIMPortIndex);
579                 } else {
580                     if (enableModem(filteredAvailableNetworks.getFirst().getSubId(), true)) {
581                         sendUpdateNetworksCallbackHelper(callbackStub,
582                             TelephonyManager.UPDATE_AVAILABLE_NETWORKS_SUCCESS);
583                     } else {
584                         if (Compatibility.isChangeEnabled(
585                                 OpportunisticNetworkService.CALLBACK_ON_MORE_ERROR_CODE_CHANGE)) {
586                             sendUpdateNetworksCallbackHelper(callbackStub,
587                                     TelephonyManager.UPDATE_AVAILABLE_NETWORKS_ENABLE_MODEM_FAIL);
588                         } else {
589                             sendUpdateNetworksCallbackHelper(callbackStub,
590                                     TelephonyManager.UPDATE_AVAILABLE_NETWORKS_ABORTED);
591                         }
592                     }
593                     mProfileSelectionCallback.onProfileSelectionDone();
594                     mAvailableNetworkInfos = null;
595                 }
596             } else {
597                 mNetworkScanCallback = callbackStub;
598                 // start scan immediately
599                 mNetworkScanCtlr.startFastNetworkScan(filteredAvailableNetworks);
600             }
601         } else {
602             if (Compatibility.isChangeEnabled(
603                     OpportunisticNetworkService.CALLBACK_ON_MORE_ERROR_CODE_CHANGE)) {
604                 sendUpdateNetworksCallbackHelper(callbackStub,
605                         TelephonyManager.UPDATE_AVAILABLE_NETWORKS_NO_OPPORTUNISTIC_SUB_AVAILABLE);
606             } else {
607                 sendUpdateNetworksCallbackHelper(callbackStub,
608                         TelephonyManager.UPDATE_AVAILABLE_NETWORKS_INVALID_ARGUMENTS);
609             }
610             // check if no profile
611             logDebug("stopping scan");
612             mNetworkScanCtlr.stopNetworkScan();
613         }
614     }
615 
616     @VisibleForTesting
retrieveBestSubscription(List<CellInfo> results)617     protected int retrieveBestSubscription(List<CellInfo> results) {
618         /* sort the results according to signal strength level */
619         Collections.sort(results, new Comparator<CellInfo>() {
620             @Override
621             public int compare(CellInfo cellInfo1, CellInfo cellInfo2) {
622                 return getSignalLevel(cellInfo1) - getSignalLevel(cellInfo2);
623             }
624         });
625 
626         for (int level = PRIORITY_HIGH; level < PRIORITY_LOW; level++) {
627             for (CellInfo result : results) {
628                 /* get subscription id for the best network scan result */
629                 int subId = getSubIdUsingAvailableNetworks(getMcc(result), getMnc(result), level);
630                 if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
631                     return subId;
632                 }
633             }
634         }
635 
636         return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
637     }
638 
getActiveOpportunisticSubId()639     private int getActiveOpportunisticSubId() {
640         List<SubscriptionInfo> subscriptionInfos =
641             mSubscriptionManager.getActiveSubscriptionInfoList(false);
642         if (subscriptionInfos == null) {
643             return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
644         }
645         for (SubscriptionInfo subscriptionInfo : subscriptionInfos) {
646             if (subscriptionInfo.isOpportunistic()) {
647                 return subscriptionInfo.getSubscriptionId();
648             }
649         }
650 
651         return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
652     }
653 
disableOpportunisticModem(IUpdateAvailableNetworksCallback callbackStub)654     private void disableOpportunisticModem(IUpdateAvailableNetworksCallback callbackStub) {
655         int subId = getActiveOpportunisticSubId();
656         if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
657             if (Compatibility.isChangeEnabled(
658                     OpportunisticNetworkService.CALLBACK_ON_MORE_ERROR_CODE_CHANGE)) {
659                 sendUpdateNetworksCallbackHelper(callbackStub,
660                         TelephonyManager.UPDATE_AVAILABLE_NETWORKS_NO_OPPORTUNISTIC_SUB_AVAILABLE);
661             } else {
662                 sendUpdateNetworksCallbackHelper(callbackStub,
663                         TelephonyManager.UPDATE_AVAILABLE_NETWORKS_INVALID_ARGUMENTS);
664             }
665             return;
666         }
667         if (enableModem(subId, false)) {
668             sendUpdateNetworksCallbackHelper(callbackStub,
669                 TelephonyManager.UPDATE_AVAILABLE_NETWORKS_SUCCESS);
670         } else {
671             if (Compatibility.isChangeEnabled(
672                     OpportunisticNetworkService.CALLBACK_ON_MORE_ERROR_CODE_CHANGE)) {
673                 sendUpdateNetworksCallbackHelper(callbackStub,
674                         TelephonyManager.UPDATE_AVAILABLE_NETWORKS_DISABLE_MODEM_FAIL);
675             } else {
676                 sendUpdateNetworksCallbackHelper(callbackStub,
677                         TelephonyManager.UPDATE_AVAILABLE_NETWORKS_ABORTED);
678             }
679         }
680     }
681 
enableModem(int subId, boolean enable)682     private boolean enableModem(int subId, boolean enable) {
683         SubscriptionInfo info = mSubscriptionManager.getActiveSubscriptionInfo(subId);
684         if (info == null) {
685             // Subscription is not active. Do nothing.
686             return false;
687         }
688 
689         // If disabling modem for opportunistic sub, make sure to switch data back to default sub.
690         if (!enable) {
691             if (mSubscriptionManager.getPreferredDataSubscriptionId() == subId) {
692                 selectProfileForData(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, false, null);
693             }
694         }
695         int phoneId = info.getSimSlotIndex();
696         /*  Todo: b/135067156
697          *  Re-enable this code once 135067156 is fixed
698         if (mSubscriptionBoundTelephonyManager.isModemEnabledForSlot(phoneId) == enable) {
699             logDebug("modem is already enabled ");
700             return true;
701         } */
702 
703         return mSubscriptionBoundTelephonyManager.enableModemForSlot(phoneId, enable);
704     }
705 
stopProfileSelectionProcess(IUpdateAvailableNetworksCallback callbackStub)706     private void stopProfileSelectionProcess(IUpdateAvailableNetworksCallback callbackStub) {
707         stopProfileScanningProcedure();
708         logDebug("stopProfileSelection");
709         disableOpportunisticModem(callbackStub);
710     }
711 
stopProfileScanningProcedure()712     private void stopProfileScanningProcedure() {
713         synchronized (mLock) {
714             if (mNetworkScanCallback != null) {
715                 sendUpdateNetworksCallbackHelper(mNetworkScanCallback,
716                         TelephonyManager.UPDATE_AVAILABLE_NETWORKS_ABORTED);
717                 mNetworkScanCallback = null;
718             }
719             mNetworkScanCtlr.stopNetworkScan();
720 
721             mAvailableNetworkInfos = null;
722             mIsEnabled = false;
723         }
724     }
725 
726     /**
727      * Return {@code true} if the provided {@code availableNetworks} contains standalone oppt subs.
728      */
containStandaloneOppSubs(List<AvailableNetworkInfo> availableNetworks)729     public boolean containStandaloneOppSubs(List<AvailableNetworkInfo> availableNetworks) {
730         if (mStandaloneOppSubInfos == null || mStandaloneOppSubInfos.isEmpty()) {
731             logDebug("received null or empty subscription infos");
732             return false;
733         }
734 
735         logDebug("Standalone opportunistic subInfos size " + mStandaloneOppSubInfos.size());
736         List<AvailableNetworkInfo> filteredAvailableNetworks =
737                 getFilteredAvailableNetworks(availableNetworks, mStandaloneOppSubInfos);
738         return !filteredAvailableNetworks.isEmpty();
739     }
740 
741     /**
742      * Start profile selection.
743      */
startProfileSelection(List<AvailableNetworkInfo> availableNetworks, IUpdateAvailableNetworksCallback callbackStub)744     public void startProfileSelection(List<AvailableNetworkInfo> availableNetworks,
745             IUpdateAvailableNetworksCallback callbackStub) {
746         logDebug("startProfileSelection availableNetworks: " + availableNetworks);
747         if (availableNetworks == null || availableNetworks.isEmpty()) {
748             if (callbackStub != null) {
749                 sendUpdateNetworksCallbackHelper(callbackStub,
750                         TelephonyManager.UPDATE_AVAILABLE_NETWORKS_INVALID_ARGUMENTS);
751             }
752             return;
753         }
754         Object[] objects = new Object[]{availableNetworks, callbackStub};
755         Message message = Message.obtain(mHandler, MSG_START_PROFILE_SELECTION, objects);
756         message.sendToTarget();
757     }
758 
sendSetOpptCallbackHelper(ISetOpportunisticDataCallback callback, int result)759     private void sendSetOpptCallbackHelper(ISetOpportunisticDataCallback callback, int result) {
760         if (callback == null) return;
761         try {
762             callback.onComplete(result);
763         } catch (RemoteException exception) {
764             log("RemoteException " + exception);
765         }
766     }
767 
768     /**
769      * Select opportunistic profile for data if passing a valid subId.
770      * @param subId : opportunistic subId or SubscriptionManager.DEFAULT_SUBSCRIPTION_ID if
771      *              deselecting previously set preference.
772      */
selectProfileForData(int subId, boolean needValidation, ISetOpportunisticDataCallback callbackStub)773     public void selectProfileForData(int subId, boolean needValidation,
774             ISetOpportunisticDataCallback callbackStub) {
775         if ((subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID)
776                 || (isOpportunisticSub(subId) && mSubscriptionManager.isActiveSubId(subId))) {
777             try {
778                 mSubscriptionManager.setPreferredDataSubscriptionId(subId, needValidation,
779                         mHandler::post, result -> sendSetOpptCallbackHelper(callbackStub, result));
780             } catch (Exception ex) {
781                 log("setPreferredDataSubscriptionId failed. subId=" + subId + ", needValidation="
782                         + needValidation + ", ex=" + ex);
783                 if (Compatibility.isChangeEnabled(
784                         OpportunisticNetworkService.CALLBACK_ON_MORE_ERROR_CODE_CHANGE)) {
785                     sendSetOpptCallbackHelper(callbackStub,
786                             TelephonyManager.SET_OPPORTUNISTIC_SUB_REMOTE_SERVICE_EXCEPTION);
787                 } else {
788                     sendSetOpptCallbackHelper(callbackStub,
789                             TelephonyManager.SET_OPPORTUNISTIC_SUB_VALIDATION_FAILED);
790                 }
791                 return;
792             }
793             mCurrentDataSubId = subId;
794         } else {
795             log("Inactive sub passed for preferred data " + subId);
796             if (Compatibility.isChangeEnabled(
797                     OpportunisticNetworkService.CALLBACK_ON_MORE_ERROR_CODE_CHANGE)) {
798                 if (isOpportunisticSub(subId)) {
799                     sendSetOpptCallbackHelper(callbackStub,
800                             TelephonyManager.SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION);
801                 } else {
802                     sendSetOpptCallbackHelper(callbackStub,
803                             TelephonyManager.SET_OPPORTUNISTIC_SUB_NO_OPPORTUNISTIC_SUB_AVAILABLE);
804                 }
805             } else {
806                 sendSetOpptCallbackHelper(callbackStub,
807                         TelephonyManager.SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION);
808             }
809         }
810     }
811 
getPreferredDataSubscriptionId()812     public int getPreferredDataSubscriptionId() {
813         final int preferredDataSubId = mSubscriptionManager.getPreferredDataSubscriptionId();
814         return isOpportunisticSub(preferredDataSubId)
815             ? preferredDataSubId
816             : SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
817     }
818 
819     /**
820      * Stop profile selection procedure
821      */
stopProfileSelection(IUpdateAvailableNetworksCallback callbackStub)822     public void stopProfileSelection(IUpdateAvailableNetworksCallback callbackStub) {
823         logDebug("stopProfileSelection");
824         Message message = Message.obtain(mHandler, MSG_STOP_PROFILE_SELECTION, callbackStub);
825         message.sendToTarget();
826     }
827 
828     @VisibleForTesting
updateOpportunisticSubscriptions()829     protected void updateOpportunisticSubscriptions() {
830         synchronized (mLock) {
831             mOppSubscriptionInfos = mSubscriptionManager
832                     .getOpportunisticSubscriptions().stream()
833                     .filter(subInfo -> !subInfo.isGroupDisabled())
834                     .collect(Collectors.toList());
835             if (mOppSubscriptionInfos != null) {
836                 mStandaloneOppSubInfos = mOppSubscriptionInfos.stream()
837                         .filter(subInfo -> subInfo.getGroupUuid() == null)
838                         .collect(Collectors.toList());
839             }
840         }
841     }
842 
enableModemStackForNonOpportunisticSlots()843     private void enableModemStackForNonOpportunisticSlots() {
844         int phoneCount = mTelephonyManager.getPhoneCount();
845         // Do nothing in single SIM mode.
846         if (phoneCount < 2) return;
847 
848         for (int i = 0; i < phoneCount; i++) {
849             boolean hasActiveOpptProfile = false;
850             for (SubscriptionInfo info : mOppSubscriptionInfos) {
851                 if (info.getSimSlotIndex() == i) {
852                     hasActiveOpptProfile = true;
853                 }
854             }
855             // If the slot doesn't have active opportunistic profile anymore, it's back to
856             // DSDS use-case. Make sure the modem stack is enabled.
857             if (!hasActiveOpptProfile) mTelephonyManager.enableModemForSlot(i, true);
858         }
859     }
860 
861     @VisibleForTesting
init(Context c, ONSProfileSelectionCallback profileSelectionCallback)862     protected void init(Context c, ONSProfileSelectionCallback profileSelectionCallback) {
863         mContext = c;
864         mSequenceId = START_SEQUENCE_ID;
865         mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
866         mProfileSelectionCallback = profileSelectionCallback;
867         mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
868         mSubscriptionBoundTelephonyManager = mTelephonyManager.createForSubscriptionId(
869                 SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
870         mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class);
871         mNetworkScanCtlr = new ONSNetworkScanCtlr(mContext, mSubscriptionBoundTelephonyManager,
872                 mNetworkAvailableCallBack);
873         mEuiccManager = c.getSystemService(EuiccManager.class);
874         updateOpportunisticSubscriptions();
875         mThread = new HandlerThread(TAG);
876         mThread.start();
877         mHandler = new Handler(mThread.getLooper()) {
878             @Override
879             public void handleMessage(Message msg) {
880                 switch (msg.what) {
881                     case MSG_PROFILE_UPDATE:
882                         synchronized (mLock) {
883                             updateOpportunisticSubscriptions();
884                             enableModemStackForNonOpportunisticSlots();
885                         }
886                         break;
887                     case MSG_START_PROFILE_SELECTION:
888                         logDebug("Msg received for profile update");
889                         synchronized (mLock) {
890                             checkProfileUpdate((Object[]) msg.obj);
891                         }
892                         break;
893                     case MSG_STOP_PROFILE_SELECTION:
894                         logDebug("Msg received to stop profile selection");
895                         synchronized (mLock) {
896                             stopProfileSelectionProcess((IUpdateAvailableNetworksCallback) msg.obj);
897                         }
898                         break;
899                     case MSG_SUB_SWITCH_COMPLETE:
900                         logDebug("Msg received for sub switch");
901                         synchronized (mLock) {
902                             onSubSwitchComplete((int) msg.obj);
903                         }
904                         break;
905                     default:
906                         log("invalid message");
907                         break;
908                 }
909             }
910         };
911         // register for profile update events
912         mSubscriptionManager.addOnOpportunisticSubscriptionsChangedListener(
913                 AsyncTask.SERIAL_EXECUTOR, mProfileChangeListener);
914     }
915 
log(String msg)916     private void log(String msg) {
917         Log.d(TAG, msg);
918     }
919 
logDebug(String msg)920     private void logDebug(String msg) {
921         if (DBG) {
922             Log.d(TAG, msg);
923         }
924     }
925 
926     /**
927      * Dump the state of {@link ONSProfileSelector}.
928      */
dump(@onNull FileDescriptor fd, @NonNull PrintWriter printWriter, @NonNull String[] args)929     public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter printWriter,
930             @NonNull String[] args) {
931         IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
932         pw.println(TAG + ":");
933         pw.println("  subId: " + mSubId);
934         pw.println("  mCurrentDataSubId: " + mCurrentDataSubId);
935         pw.println("  mOppSubscriptionInfos: " + (
936                 mOppSubscriptionInfos == null || mOppSubscriptionInfos.isEmpty() ? "[]"
937                         : Arrays.toString(mOppSubscriptionInfos.stream().map(
938                                 SubscriptionInfo::getSubscriptionId).toArray())));
939         pw.println("  mStandaloneOppSubInfos: " + (
940                 mStandaloneOppSubInfos == null || mStandaloneOppSubInfos.isEmpty() ? "[]"
941                         : Arrays.toString(mStandaloneOppSubInfos.stream().map(
942                                 SubscriptionInfo::getSubscriptionId).toArray())));
943         pw.println("  mAvailableNetworkInfos:");
944         if (mAvailableNetworkInfos != null && !mAvailableNetworkInfos.isEmpty()) {
945             pw.increaseIndent();
946             for (AvailableNetworkInfo availableNetworkInfo : mAvailableNetworkInfos) {
947                 pw.println(availableNetworkInfo);
948             }
949             pw.decreaseIndent();
950         }
951         mNetworkScanCtlr.dump(fd, printWriter, args);
952     }
953 }
954