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