/* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.network.helper; import android.content.Context; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.util.Log; import androidx.annotation.Keep; import androidx.annotation.VisibleForTesting; import com.android.settings.network.helper.SubscriptionAnnotation; import com.android.settingslib.utils.ThreadUtils; import java.util.Collections; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicIntegerArray; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.function.UnaryOperator; import java.util.stream.Collectors; import java.util.stream.IntStream; /** * This is a Callable class to query user selectable subscription list. * * Here's example of creating a Callable for retrieving a list of SubscriptionAnnotation * for active Subscriptions: * * List result = (new SelectableSubscriptions(context, false)).call(); * * Another example for retrieving a list of SubscriptionAnnotation for all subscriptions * accessible in another thread. * * List result = ExecutorService.submit( * new SelectableSubscriptions(context, true)).get() */ public class SelectableSubscriptions implements Callable> { private static final String TAG = "SelectableSubscriptions"; private Context mContext; private Supplier> mSubscriptions; private Predicate mFilter; private Function, List> mFinisher; /** * Constructor of class * @param context * @param disabledSlotsIncluded query both active and inactive slots when true, * only query active slot when false. */ public SelectableSubscriptions(Context context, boolean disabledSlotsIncluded) { mContext = context; mSubscriptions = disabledSlotsIncluded ? (() -> getAvailableSubInfoList(context)) : (() -> getActiveSubInfoList(context)); if (disabledSlotsIncluded) { mFilter = subAnno -> { if (subAnno.isExisted()) { return true; } return ((subAnno.getType() == SubscriptionAnnotation.TYPE_ESIM) && (subAnno.isDisplayAllowed())); }; } else { mFilter = subAnno -> subAnno.isActive(); } mFinisher = annoList -> annoList; } /** * Add UnaryOperator to be applied to the final result. * @param finisher a function to be applied to the final result. */ public SelectableSubscriptions addFinisher( UnaryOperator> finisher) { mFinisher = mFinisher.andThen(finisher); return this; } /** * Implementation of Callable * @return a list of SubscriptionAnnotation which is user selectable */ public List call() { TelephonyManager telMgr = mContext.getSystemService(TelephonyManager.class); try { // query in background thread Future eSimCardId = ThreadUtils.postOnBackgroundThread(new QueryEsimCardId(telMgr)); // query in background thread Future simSlotIndex = ThreadUtils.postOnBackgroundThread( new QuerySimSlotIndex(telMgr, true, true)); // query in background thread Future activeSimSlotIndex = ThreadUtils.postOnBackgroundThread( new QuerySimSlotIndex(telMgr, false, true)); List subInfoList = mSubscriptions.get(); // wait for result from background thread List eSimCardIdList = atomicToList(eSimCardId.get()); List simSlotIndexList = atomicToList(simSlotIndex.get()); List activeSimSlotIndexList = atomicToList(activeSimSlotIndex.get()); // build a list of SubscriptionAnnotation return IntStream.range(0, subInfoList.size()) .mapToObj(subInfoIndex -> new SubscriptionAnnotation.Builder(subInfoList, subInfoIndex)) .map(annoBdr -> annoBdr.build(mContext, eSimCardIdList, simSlotIndexList, activeSimSlotIndexList)) .filter(mFilter) .collect(Collectors.collectingAndThen(Collectors.toList(), mFinisher)); } catch (Exception exception) { Log.w(TAG, "Fail to request subIdList", exception); } return Collections.emptyList(); } protected List getSubInfoList(Context context, Function> convertor) { SubscriptionManager subManager = getSubscriptionManager(context); List result = (subManager == null) ? null : convertor.apply(subManager); return (result == null) ? Collections.emptyList() : result; } protected SubscriptionManager getSubscriptionManager(Context context) { return context.getSystemService(SubscriptionManager.class); } protected List getAvailableSubInfoList(Context context) { return getSubInfoList(context, SubscriptionManager::getAvailableSubscriptionInfoList); } protected List getActiveSubInfoList(Context context) { return getSubInfoList(context, SubscriptionManager::getActiveSubscriptionInfoList); } @Keep @VisibleForTesting protected static List atomicToList(AtomicIntegerArray atomicIntArray) { if (atomicIntArray == null) { return Collections.emptyList(); } return IntStream.range(0, atomicIntArray.length()) .map(idx -> atomicIntArray.get(idx)).boxed() .collect(Collectors.toList()); } }