Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a239a6c5 authored by Bonian Chen's avatar Bonian Chen
Browse files

[Settings] Hide subscriptions not existed within device

For non-active subscriptions, the one inserted in slot
but turned off need to be visible to the user. However,
the one un-plugged need to be invisble.

Since SubscriptionUtil#getSelectableSubscriptionInfoList() didn't cover all the cases required. Create this one to fit into the criteria required here.

Note: subscriptions with same group UUID will be displayed seperately.

Bug: 191228344
Test: local
Change-Id: Ia68c23b007164b7520456cb6c7427ca142558b59
(cherry picked from commit 75f1450b)
(cherry picked from commit 0726f263)
parent 2fe6e382
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -486,7 +486,7 @@ public class SubscriptionUtil {
     * @param info the subscriptionInfo to check against.
     * @return true if this subscription should be visible to the API caller.
     */
    private static boolean isSubscriptionVisible(
    public static boolean isSubscriptionVisible(
            SubscriptionManager subscriptionManager, Context context, SubscriptionInfo info) {
        if (info == null) return false;
        // If subscription is NOT grouped opportunistic subscription, it's visible.
+58 −0
Original line number Diff line number Diff line
/*
 * 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.telephony.TelephonyManager;
import android.telephony.UiccCardInfo;

import java.util.List;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicIntegerArray;

/**
 * This is a Callable class which queries valid card ID for eSIM
 */
public class QueryEsimCardId implements Callable<AtomicIntegerArray> {
    private static final String TAG = "QueryEsimCardId";

    private TelephonyManager mTelephonyManager;

    /**
     * Constructor of class
     * @param TelephonyManager
     */
    public QueryEsimCardId(TelephonyManager telephonyManager) {
        mTelephonyManager = telephonyManager;
    }

    /**
     * Implementation of Callable
     * @return card ID(s) in AtomicIntegerArray
     */
    public AtomicIntegerArray call() {
        List<UiccCardInfo> cardInfos = mTelephonyManager.getUiccCardsInfo();
        if (cardInfos == null) {
            return new AtomicIntegerArray(0);
        }
        return new AtomicIntegerArray(cardInfos.stream()
                .filter(Objects::nonNull)
                .filter(cardInfo -> (!cardInfo.isRemovable()
                        && (cardInfo.getCardId() != TelephonyManager.UNSUPPORTED_CARD_ID)))
                .mapToInt(UiccCardInfo::getCardId)
                .toArray());
    }
}
 No newline at end of file
+87 −0
Original line number Diff line number Diff line
/*
 * 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.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.UiccSlotInfo;

import java.util.Arrays;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicIntegerArray;

/**
 * This is a Callable class which query slot index within device
 */
public class QuerySimSlotIndex implements Callable<AtomicIntegerArray> {
    private static final String TAG = "QuerySimSlotIndex";

    private TelephonyManager mTelephonyManager;
    private boolean mDisabledSlotsIncluded;
    private boolean mOnlySlotWithSim;

    /**
     * Constructor of class
     * @param TelephonyManager
     * @param disabledSlotsIncluded query both active and inactive slots when true,
     *                              only query active slot when false.
     * @param onlySlotWithSim query slot index with SIM available when true,
     *                        include absent ones when false.
     */
    public QuerySimSlotIndex(TelephonyManager telephonyManager,
            boolean disabledSlotsIncluded, boolean onlySlotWithSim) {
        mTelephonyManager = telephonyManager;
        mDisabledSlotsIncluded = disabledSlotsIncluded;
        mOnlySlotWithSim = onlySlotWithSim;
    }

    /**
     * Implementation of Callable
     * @return slot index in AtomicIntegerArray
     */
    public AtomicIntegerArray call() {
        UiccSlotInfo [] slotInfo = mTelephonyManager.getUiccSlotsInfo();
        if (slotInfo == null) {
            return new AtomicIntegerArray(0);
        }
        int slotIndexFilter = mOnlySlotWithSim ? 0 : SubscriptionManager.INVALID_SIM_SLOT_INDEX;
        return new AtomicIntegerArray(Arrays.stream(slotInfo)
                .filter(slot -> filterSlot(slot))
                .mapToInt(slot -> mapToSlotIndex(slot))
                .filter(slotIndex -> (slotIndex >= slotIndexFilter))
                .toArray());
    }

    protected boolean filterSlot(UiccSlotInfo slotInfo) {
        if (mDisabledSlotsIncluded) {
            return true;
        }
        if (slotInfo == null) {
            return false;
        }
        return slotInfo.getIsActive();
    }

    protected int mapToSlotIndex(UiccSlotInfo slotInfo) {
        if (slotInfo == null) {
            return SubscriptionManager.INVALID_SIM_SLOT_INDEX;
        }
        if (slotInfo.getCardStateInfo() == UiccSlotInfo.CARD_STATE_INFO_ABSENT) {
            return SubscriptionManager.INVALID_SIM_SLOT_INDEX;
        }
        return slotInfo.getLogicalSlotIdx();
    }
}
 No newline at end of file
+160 −0
Original line number Diff line number Diff line
/*
 * 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<SubscriptionAnnotation> result = (new SelectableSubscriptions(context, false)).call();
 *
 * Another example for retrieving a list of SubscriptionAnnotation for all subscriptions
 * accessible in another thread.
 *
 * List<SubscriptionAnnotation> result = ExecutorService.submit(
 *     new SelectableSubscriptions(context, true)).get()
 */
public class SelectableSubscriptions implements Callable<List<SubscriptionAnnotation>> {
    private static final String TAG = "SelectableSubscriptions";

    private Context mContext;
    private Supplier<List<SubscriptionInfo>> mSubscriptions;
    private Predicate<SubscriptionAnnotation> mFilter;
    private Function<List<SubscriptionAnnotation>, List<SubscriptionAnnotation>> 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));
        mFilter = disabledSlotsIncluded ? (subAnno -> subAnno.isExisted()) :
                (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<List<SubscriptionAnnotation>> finisher) {
        mFinisher = mFinisher.andThen(finisher);
        return this;
    }

    /**
     * Implementation of Callable
     * @return a list of SubscriptionAnnotation which is user selectable
     */
    public List<SubscriptionAnnotation> call() {
        TelephonyManager telMgr = mContext.getSystemService(TelephonyManager.class);

        try {
            // query in background thread
            Future<AtomicIntegerArray> eSimCardId =
                    ThreadUtils.postOnBackgroundThread(new QueryEsimCardId(telMgr));

            // query in background thread
            Future<AtomicIntegerArray> simSlotIndex =
                    ThreadUtils.postOnBackgroundThread(
                    new QuerySimSlotIndex(telMgr, true, true));

            // query in background thread
            Future<AtomicIntegerArray> activeSimSlotIndex =
                    ThreadUtils.postOnBackgroundThread(
                    new QuerySimSlotIndex(telMgr, false, true));

            List<SubscriptionInfo> subInfoList = mSubscriptions.get();

            // wait for result from background thread
            List<Integer> eSimCardIdList = atomicToList(eSimCardId.get());
            List<Integer> simSlotIndexList = atomicToList(simSlotIndex.get());
            List<Integer> 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<SubscriptionInfo> getSubInfoList(Context context,
            Function<SubscriptionManager, List<SubscriptionInfo>> convertor) {
        SubscriptionManager subManager = getSubscriptionManager(context);
        return (subManager == null) ? Collections.emptyList() : convertor.apply(subManager);
    }

    protected SubscriptionManager getSubscriptionManager(Context context) {
        return context.getSystemService(SubscriptionManager.class);
    }

    protected List<SubscriptionInfo> getAvailableSubInfoList(Context context) {
        return getSubInfoList(context, SubscriptionManager::getAvailableSubscriptionInfoList);
    }

    protected List<SubscriptionInfo> getActiveSubInfoList(Context context) {
        return getSubInfoList(context, SubscriptionManager::getActiveSubscriptionInfoList);
    }

    @Keep
    @VisibleForTesting
    protected static List<Integer> atomicToList(AtomicIntegerArray atomicIntArray) {
        if (atomicIntArray == null) {
            return Collections.emptyList();
        }
        return IntStream.range(0, atomicIntArray.length())
                .map(idx -> atomicIntArray.get(idx)).boxed()
                .collect(Collectors.toList());
    }
}
 No newline at end of file
+163 −0
Original line number Diff line number Diff line
/*
 * 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.os.ParcelUuid;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;

import androidx.annotation.Keep;
import androidx.annotation.VisibleForTesting;

import com.android.settings.network.SubscriptionUtil;

import java.util.List;

/**
 * This is a class helps providing additional info required by UI
 * based on SubscriptionInfo.
 */
public class SubscriptionAnnotation {
    private static final String TAG = "SubscriptionAnnotation";

    private SubscriptionInfo mSubInfo;
    private int mOrderWithinList;
    private int mType = TYPE_UNKNOWN;
    private boolean mIsExisted;
    private boolean mIsActive;
    private boolean mIsAllowToDisplay;

    public static final ParcelUuid EMPTY_UUID = ParcelUuid.fromString("0-0-0-0-0");

    public static final int TYPE_UNKNOWN = 0x0;
    public static final int TYPE_PSIM = 0x1;
    public static final int TYPE_ESIM = 0x2;

    /**
     * Builder class for SubscriptionAnnotation
     */
    public static class Builder {

        private List<SubscriptionInfo> mSubInfoList;
        private int mIndexWithinList;

        /**
         * Constructor of builder
         * @param subInfoList list of subscription info
         * @param indexWithinList target index within list provided
         */
        public Builder(List<SubscriptionInfo> subInfoList, int indexWithinList) {
            mSubInfoList = subInfoList;
            mIndexWithinList = indexWithinList;
        }

        public SubscriptionAnnotation build(Context context, List<Integer> eSimCardId,
                List<Integer> simSlotIndex, List<Integer> activeSimSlotIndex) {
            return new SubscriptionAnnotation(mSubInfoList, mIndexWithinList, context,
                    eSimCardId, simSlotIndex, activeSimSlotIndex);
        }
    }

    /**
     * Constructor of class
     */
    @Keep
    @VisibleForTesting
    protected SubscriptionAnnotation(List<SubscriptionInfo> subInfoList, int subInfoIndex,
            Context context, List<Integer> eSimCardId,
            List<Integer> simSlotIndex, List<Integer> activeSimSlotIndexList) {
        if ((subInfoIndex < 0) || (subInfoIndex >= subInfoList.size())) {
            return;
        }
        mSubInfo = subInfoList.get(subInfoIndex);
        if (mSubInfo == null) {
            return;
        }

        mOrderWithinList = subInfoIndex;
        mType = mSubInfo.isEmbedded() ? TYPE_ESIM : TYPE_PSIM;
        if (mType == TYPE_ESIM) {
            int cardId = mSubInfo.getCardId();
            mIsExisted = eSimCardId.contains(cardId);
            if (mIsExisted) {
                mIsActive = activeSimSlotIndexList.contains(mSubInfo.getSimSlotIndex());
                mIsAllowToDisplay = isDisplayAllowed(context);
            }
            return;
        }

        mIsExisted = simSlotIndex.contains(mSubInfo.getSimSlotIndex());
        mIsActive = activeSimSlotIndexList.contains(mSubInfo.getSimSlotIndex());
        if (mIsExisted) {
            mIsAllowToDisplay = isDisplayAllowed(context);
        }
    }

    // the index provided during construction of Builder
    @Keep
    public int getOrderingInList() {
        return mOrderWithinList;
    }

    // type of subscription
    @Keep
    public int getType() {
        return mType;
    }

    // if a subscription is existed within device
    @Keep
    public boolean isExisted() {
        return mIsExisted;
    }

    // if a subscription is currently ON
    @Keep
    public boolean isActive() {
        return mIsActive;
    }

    // if display of subscription is allowed
    @Keep
    public boolean isDisplayAllowed() {
        return mIsAllowToDisplay;
    }

    // the subscription ID
    @Keep
    public int getSubscriptionId() {
        return (mSubInfo == null) ? SubscriptionManager.INVALID_SUBSCRIPTION_ID :
                mSubInfo.getSubscriptionId();
    }

    // the grouping UUID
    @Keep
    public ParcelUuid getGroupUuid() {
        return (mSubInfo == null) ? null : mSubInfo.getGroupUuid();
    }

    // the SubscriptionInfo
    @Keep
    public SubscriptionInfo getSubInfo() {
        return mSubInfo;
    }

    private boolean isDisplayAllowed(Context context) {
        return SubscriptionUtil.isSubscriptionVisible(
                context.getSystemService(SubscriptionManager.class), context, mSubInfo);
    }
}
 No newline at end of file
Loading