月度归档:2017年06月

Android 双卡手机 sim 卡状态获取

总结一下 Android 双卡手机对于 sim 卡状态的获取方法。

判断是否插入了双卡

该方法判断是否两张 sim 卡都可用:

public static boolean isMultiSim(Context context) {
    int simCount = 0;
    SparseIntArray mActiveSubIds = getActiveSubscriptionIds(context);
    if (mActiveSubIds != null) {
        simCount = mActiveSubIds.size();
    }
    Log.d(TAG, "isMultiSim: " + (simCount > 1));
    return simCount > 1;
}

获取用户设置的主卡

该方法获取用户设置的主卡,分为三种情况:

  • 双卡
    一张主卡,一张副卡
  • 单卡
    一张主卡
  • 没有卡
    无主卡
private static final String TAG = "SimUtil";
public static final int MASTER_CARD_TYPE_FIRST = 1; // 卡1是主卡
public static final int MASTER_CARD_TYPE_SECOND = 2; // 卡2是主卡
public static final int MASTER_CARD_TYPE_NONE = 0; // 无卡
private static int MASTER_CARD = -1; // 主卡

public static int getMasterSimId(Context context) {
    // 获取主卡的 SubId
    int mDefaultSubId = -1;
    PhoneAccountHandle defAccount = null;
    TelecomManager telecomManager =
            (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE);
    try {
        Method m =                telecomManager.getClass().getDeclaredMethod("getUserSelectedOutgoingPhoneAccount");
        defAccount = (PhoneAccountHandle) m.invoke(telecomManager);
    } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
        Log.e(TAG, "getDeclaredMethod");
        e.printStackTrace();
    }
    Log.d(TAG, "defAccount: " + defAccount);
    if (defAccount != null) {
        try {
            Log.d(TAG, "defAccount.getId(): " + defAccount.getId());
            mDefaultSubId = Integer.parseInt(defAccount.getId());
        } catch (Exception e) {
            Log.e(TAG, "getSlotIndexBySubId");
            e.printStackTrace();
        }
    }

    Log.d(TAG, "mDefaultSubId: " + mDefaultSubId);

    int simCount = 0;
    int defaultSlotIndex = -1;

    SparseIntArray mActiveSubIds = getActiveSubscriptionIds(context);
    if (mActiveSubIds != null) {
        simCount = mActiveSubIds.size();
        // 获取主卡的 SlotId
        defaultSlotIndex = getSlotIndexBySubId(mDefaultSubId, context);
    } else {
        Log.d(TAG, "mActiveSubIds == null");
    }

    Log.d(TAG, "defaultSlotIndex: " + defaultSlotIndex);

    // 双卡,使用 defaultSlotId 来判断谁是主卡
    if (simCount == 2) {
        if (defaultSlotIndex == 1) {
            MASTER_CARD = MASTER_CARD_TYPE_SECOND;
        } else {
            MASTER_CARD = MASTER_CARD_TYPE_FIRST;
        }
    } else if (simCount == 1) { // 单卡,哪个卡能用,哪个就是主卡
        if (defaultSlotIndex == -1 && mActiveSubIds.size() > 0) {
            int size = mActiveSubIds.size();
            for (int i = 0; i < size; i++) {
                defaultSlotIndex = mActiveSubIds.valueAt(i);
            }
        }
        if (defaultSlotIndex == 0) {
            MASTER_CARD = MASTER_CARD_TYPE_FIRST;
        } else {
            MASTER_CARD = MASTER_CARD_TYPE_SECOND;
        }
    } else if (simCount == 0) { // 无卡,没有主卡
        MASTER_CARD = MASTER_CARD_TYPE_NONE;
    }

    Log.d(TAG, "simCount: " + simCount + " getMasterSimId = " + defaultSlotIndex + " MASTER_CARD: " + MASTER_CARD);
    return MASTER_CARD;
}

// 通过对应关系用 SubId 获取 SlotId
private static int getSlotIndexBySubId(int subId, Context context) {
    SparseIntArray map = getActiveSubscriptionIds(context);
    return getSlotIndexBySubId(map, subId);
}

private static int getSlotIndexBySubId(SparseIntArray map, int subId) {
    if (map != null) {
        return map.get(subId, -1);
    }
    return -1;
}

// 获取 sim 卡的 SubId 和 SlotId 的对应关系
private static SparseIntArray getActiveSubscriptionIds(Context context) {
    // 获取可用的 sim 卡
    List<SubscriptionInfo> list = SubscriptionManager.from(context).getActiveSubscriptionInfoList();
    return convertSubscriptionIds(list);
}

private static SparseIntArray convertSubscriptionIds(List<SubscriptionInfo> list) {
    SparseIntArray result = new SparseIntArray();
    if (list != null) {
        for (SubscriptionInfo item : list) {
            if (item == null) {
                continue;
            }

            int subId = item.getSubscriptionId();
            int slotIndex = item.getSimSlotIndex();
            result.put(subId, slotIndex);
        }
    }
    return result.size() > 0 ? result : null;
}

SubId 和 SlotId

说一下我对这两个值的理解:

  • SubId 是 SubscriptionInfo 这个类的 id 值,Android 记录的 sim 卡信息都保存为 SubscriptionInfo 对象,存在数据库里,每一张 sim 卡对应一个 SubId。系统操作 sim 卡状态,都以 SubId 为准。
  • SlotId 是手机卡槽的 id,更贴近人的理解,卡槽1的 SlotId 是0,卡槽2的 SlotId 是1。我们一般展示给用户的卡1和卡2分别代表卡槽1里的卡和卡槽2里的卡,所以需要用 SlotId 来获取 sim 卡信息。
  • SubscriptionInfo 类中同时包含 SubId 和 SlotId 两种信息,因此可以通过获取所有的 SubscriptionInfo 来建立一个 SubId 和 SlotId 的对应表。这样就可以彼此互相匹配了。

getSlotId(int subId)

上面是通过对应关系来使用 subId 获取 SlotId,其实 SubscriptionManager 中也提供了该方法,只是 hide了,想要使用的话需要通过反射。

public int getSlotIdUsingSubId(int subId, Context context) {
    int result = -1;
    try {
        String subscription_manager = "android.telephony.SubscriptionManager";
        Class<?> clz = Class.forName(subscription_manager);
        Object subSm;
        Constructor<?> constructor = clz.getDeclaredConstructor(Context.class);
        subSm = constructor.newInstance(context);
        Method mth = clz.getMethod("getSlotId", int.class);
        result = (int) mth.invoke(subSm, subId);
    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | IllegalArgumentException | NoSuchMethodException | InvocationTargetException e) {
        e.printStackTrace();
    }
    Logger.d(TAG, "subId = " + subId + " result = " + result);

    return result;
}

更多

想了解更多内容,请关注我的微信公众账号:

FindBugs 检出的三种问题小记

FindBugs™ – Find Bugs in Java Programs

本文记录了三种 FindBugs 检出来的问题。

一、Boxing/unboxing to parse a primitive

A boxed primitive is created from a String, just to extract the unboxed primitive value. It is more efficient to just call the static parseXXX method.

该问题发生场景如下图所示:

Integer 的 valueOf 方法可以优化为 parseInt 方法。mDefaultSubId 是一个 int 类型的变量,而 defAccount.getId 返回的是一个 String 类型。这两个方法的作用都是把 String 类型转换为 int 类型,区别是调用 Integer 的 valueOf 方法会返回一个 Integer 类型的结果,再经 Java 的自动拆箱机制而转换成 int 类型,而 parseInt 方法会直接返回一个 int 类型的结果。

从 Integer 的源码中也可以看到:

valueOf 方法内部也是先调用了 parseInt 转成 int 类型,再转成 Integer 类型,在该场景下属于冗余操作了。

因此,此处应该进行方法的替换优化。

二、May expose internal representation by returning reference to mutable object

Returning a reference to a mutable object value stored in one of the object’s fields exposes the internal representation of the object. If instances are accessed by untrusted code, and unchecked changes to the mutable object would compromise security or other important properties, you will need to do something different. Returning a new copy of the object is better approach in many situations.

该问题发生在如下场景:

getCatIds 方法返回一个 String[] 类型的对象,该对象是一个可变对象。由于该对象可以自行更改内部的值而不通过相应的 set 方法,就可能产生安全问题或错误。更好的做法是在 getCatIds 方法中返回一个 catIds 的拷贝。

三、May expose internal representation by incorporating reference to mutable object

This code stores a reference to an externally mutable object into the internal representation of the object. If instances are accessed by untrusted code, and unchecked changes to the mutable object would compromise security or other important properties, you will need to do something different. Storing a copy of the object is better approach in many situations.

该问题发生在如下场景:

setCatIds 方法直接保存了一个 String[] 类型的可变对象,如果在 setCatIds 方法调用之后 catIds 进行了更改,那么这些更改也会影响到调用 setCatIds 的那个对象。因此,和上面的问题类似,更好的做法还是在 setCatIds 方法中保存一个参数对象的拷贝。

当然,这些问题都是一点性能问题或者安全问题,可能并不会在使用中出错,但是了解它对于提升自己的能力还是有帮助的。😜