1 /*
2  * Copyright (C) 2021 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 package com.android.settings.network.helper;
17 
18 import android.content.Context;
19 import android.telephony.SubscriptionInfo;
20 import android.telephony.SubscriptionManager;
21 import android.telephony.TelephonyManager;
22 import android.util.Log;
23 
24 import androidx.annotation.Keep;
25 import androidx.annotation.VisibleForTesting;
26 
27 import com.android.settings.network.helper.SubscriptionAnnotation;
28 import com.android.settingslib.utils.ThreadUtils;
29 
30 import java.util.Collections;
31 import java.util.List;
32 import java.util.concurrent.Callable;
33 import java.util.concurrent.Future;
34 import java.util.concurrent.atomic.AtomicIntegerArray;
35 import java.util.function.Function;
36 import java.util.function.Predicate;
37 import java.util.function.Supplier;
38 import java.util.function.UnaryOperator;
39 import java.util.stream.Collectors;
40 import java.util.stream.IntStream;
41 
42 /**
43  * This is a Callable class to query user selectable subscription list.
44  *
45  * Here's example of creating a Callable for retrieving a list of SubscriptionAnnotation
46  * for active Subscriptions:
47  *
48  * List<SubscriptionAnnotation> result = (new SelectableSubscriptions(context, false)).call();
49  *
50  * Another example for retrieving a list of SubscriptionAnnotation for all subscriptions
51  * accessible in another thread.
52  *
53  * List<SubscriptionAnnotation> result = ExecutorService.submit(
54  *     new SelectableSubscriptions(context, true)).get()
55  */
56 public class SelectableSubscriptions implements Callable<List<SubscriptionAnnotation>> {
57     private static final String TAG = "SelectableSubscriptions";
58 
59     private Context mContext;
60     private Supplier<List<SubscriptionInfo>> mSubscriptions;
61     private Predicate<SubscriptionAnnotation> mFilter;
62     private Function<List<SubscriptionAnnotation>, List<SubscriptionAnnotation>> mFinisher;
63 
64     /**
65      * Constructor of class
66      * @param context
67      * @param disabledSlotsIncluded query both active and inactive slots when true,
68      *                              only query active slot when false.
69      */
SelectableSubscriptions(Context context, boolean disabledSlotsIncluded)70     public SelectableSubscriptions(Context context, boolean disabledSlotsIncluded) {
71         mContext = context;
72         mSubscriptions = disabledSlotsIncluded ? (() -> getAvailableSubInfoList(context)) :
73                 (() -> getActiveSubInfoList(context));
74         if (disabledSlotsIncluded) {
75             mFilter = subAnno -> {
76                 if (subAnno.isExisted()) {
77                     return true;
78                 }
79                 return ((subAnno.getType() == SubscriptionAnnotation.TYPE_ESIM)
80                         && (subAnno.isDisplayAllowed()));
81             };
82         } else {
83             mFilter = subAnno -> subAnno.isActive();
84         }
85         mFinisher = annoList -> annoList;
86     }
87 
88     /**
89      * Add UnaryOperator to be applied to the final result.
90      * @param finisher a function to be applied to the final result.
91      */
addFinisher( UnaryOperator<List<SubscriptionAnnotation>> finisher)92     public SelectableSubscriptions addFinisher(
93             UnaryOperator<List<SubscriptionAnnotation>> finisher) {
94         mFinisher = mFinisher.andThen(finisher);
95         return this;
96     }
97 
98     /**
99      * Implementation of Callable
100      * @return a list of SubscriptionAnnotation which is user selectable
101      */
call()102     public List<SubscriptionAnnotation> call() {
103         TelephonyManager telMgr = mContext.getSystemService(TelephonyManager.class);
104 
105         try {
106             // query in background thread
107             Future<AtomicIntegerArray> eSimCardId =
108                     ThreadUtils.postOnBackgroundThread(new QueryEsimCardId(telMgr));
109 
110             // query in background thread
111             Future<AtomicIntegerArray> simSlotIndex =
112                     ThreadUtils.postOnBackgroundThread(
113                     new QuerySimSlotIndex(telMgr, true, true));
114 
115             // query in background thread
116             Future<AtomicIntegerArray> activeSimSlotIndex =
117                     ThreadUtils.postOnBackgroundThread(
118                     new QuerySimSlotIndex(telMgr, false, true));
119 
120             List<SubscriptionInfo> subInfoList = mSubscriptions.get();
121 
122             // wait for result from background thread
123             List<Integer> eSimCardIdList = atomicToList(eSimCardId.get());
124             List<Integer> simSlotIndexList = atomicToList(simSlotIndex.get());
125             List<Integer> activeSimSlotIndexList = atomicToList(activeSimSlotIndex.get());
126 
127             // build a list of SubscriptionAnnotation
128             return IntStream.range(0, subInfoList.size())
129                     .mapToObj(subInfoIndex ->
130                             new SubscriptionAnnotation.Builder(subInfoList, subInfoIndex))
131                     .map(annoBdr -> annoBdr.build(mContext,
132                             eSimCardIdList, simSlotIndexList, activeSimSlotIndexList))
133                     .filter(mFilter)
134                     .collect(Collectors.collectingAndThen(Collectors.toList(), mFinisher));
135         } catch (Exception exception) {
136             Log.w(TAG, "Fail to request subIdList", exception);
137         }
138         return Collections.emptyList();
139     }
140 
getSubInfoList(Context context, Function<SubscriptionManager, List<SubscriptionInfo>> convertor)141     protected List<SubscriptionInfo> getSubInfoList(Context context,
142             Function<SubscriptionManager, List<SubscriptionInfo>> convertor) {
143         SubscriptionManager subManager = getSubscriptionManager(context);
144         return (subManager == null) ? Collections.emptyList() : convertor.apply(subManager);
145     }
146 
getSubscriptionManager(Context context)147     protected SubscriptionManager getSubscriptionManager(Context context) {
148         return context.getSystemService(SubscriptionManager.class);
149     }
150 
getAvailableSubInfoList(Context context)151     protected List<SubscriptionInfo> getAvailableSubInfoList(Context context) {
152         return getSubInfoList(context, SubscriptionManager::getAvailableSubscriptionInfoList);
153     }
154 
getActiveSubInfoList(Context context)155     protected List<SubscriptionInfo> getActiveSubInfoList(Context context) {
156         return getSubInfoList(context, SubscriptionManager::getActiveSubscriptionInfoList);
157     }
158 
159     @Keep
160     @VisibleForTesting
atomicToList(AtomicIntegerArray atomicIntArray)161     protected static List<Integer> atomicToList(AtomicIntegerArray atomicIntArray) {
162         if (atomicIntArray == null) {
163             return Collections.emptyList();
164         }
165         return IntStream.range(0, atomicIntArray.length())
166                 .map(idx -> atomicIntArray.get(idx)).boxed()
167                 .collect(Collectors.toList());
168     }
169 }