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