TelephonyManagerCompat.java
/*
* Copyright 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 androidx.core.telephony;
import static android.telephony.SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
import android.annotation.SuppressLint;
import android.os.Build.VERSION;
import android.telephony.TelephonyManager;
import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RequiresPermission;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* Helper for accessing features in {@link TelephonyManager}.
*/
public class TelephonyManagerCompat {
private static Method sGetDeviceIdMethod;
private static Method sGetSubIdMethod;
/**
* Returns the IMEI (International Mobile Equipment Identity) associated with the
* subscription id of the given TelephonyManager, or null if not available.
*
* <p>Below Android 10, this API requires any of:
* <ul>
* <li>the caller holds the READ_PHONE_STATE permission</li>
* <li>the caller has carrier privileges (see
* {@link TelephonyManager#hasCarrierPrivileges()})</li>
* </ul>
*
* <p>On Android 10 and above, this API requires any of:
* <ul>
* <li>the caller holds the READ_PRIVILEGED_PHONE_STATE permission</li>
* <li>the caller is the device or profile owner and holds the READ_PHONE_STATE
* permission</li>
* <li>the caller has carrier privileges (see
* {@link TelephonyManager#hasCarrierPrivileges()})</li>
* <li>the caller is the default SMS role holder (see
* {@link android.app.role.RoleManager#isRoleHeld(String)})</li>
* <li>the caller holds the USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER permission</li>
* </ul>
*/
@SuppressLint("MissingPermission")
@RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
@Nullable
public static String getImei(@NonNull TelephonyManager telephonyManager) {
if (VERSION.SDK_INT >= 26) {
return Api26Impl.getImei(telephonyManager);
} else if (VERSION.SDK_INT >= 22) {
// below Android O the telephony manager has a severe bug (b/137114239) where many
// methods do not properly respect the subscription id and always use the default
// subscription id. so if we have a non-default subscription id, we need to do the work
// ourselves...
int subId = getSubscriptionId(telephonyManager);
if (subId != DEFAULT_SUBSCRIPTION_ID && subId != INVALID_SUBSCRIPTION_ID) {
int slotIndex = SubscriptionManagerCompat.getSlotIndex(subId);
if (VERSION.SDK_INT >= 23) {
return Api23Impl.getDeviceId(telephonyManager, slotIndex);
} else {
try {
if (sGetDeviceIdMethod == null) {
sGetDeviceIdMethod = TelephonyManager.class.getDeclaredMethod(
"getDeviceId",
int.class);
sGetDeviceIdMethod.setAccessible(true);
}
return (String) sGetDeviceIdMethod.invoke(telephonyManager, slotIndex);
} catch (NoSuchMethodException ignored) {
} catch (IllegalAccessException ignored) {
} catch (InvocationTargetException ignored) {
}
return null;
}
}
}
return telephonyManager.getDeviceId();
}
/**
* Return the subscription ID the TelephonyManager was created with (via
* {@link TelephonyManager#createForSubscriptionId(int)}) if applicable, and otherwise the
* default subscription ID.
*/
@SuppressLint("SoonBlockedPrivateApi")
public static int getSubscriptionId(@NonNull TelephonyManager telephonyManager) {
if (VERSION.SDK_INT >= 30) {
return Api30Impl.getSubscriptionId(telephonyManager);
} else if (VERSION.SDK_INT >= 22) {
try {
if (sGetSubIdMethod == null) {
sGetSubIdMethod = TelephonyManager.class.getDeclaredMethod("getSubId");
sGetSubIdMethod.setAccessible(true);
}
Integer subId = (Integer) sGetSubIdMethod.invoke(telephonyManager);
if (subId != null && subId != INVALID_SUBSCRIPTION_ID) {
return subId;
}
} catch (InvocationTargetException ignored) {
} catch (IllegalAccessException ignored) {
} catch (NoSuchMethodException ignored) {
}
}
return DEFAULT_SUBSCRIPTION_ID;
}
private TelephonyManagerCompat() {}
@RequiresApi(30)
private static class Api30Impl {
private Api30Impl() {}
@DoNotInline
static int getSubscriptionId(TelephonyManager telephonyManager) {
return telephonyManager.getSubscriptionId();
}
}
@RequiresApi(26)
private static class Api26Impl {
private Api26Impl() {}
@SuppressLint("MissingPermission")
@RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
@DoNotInline
@Nullable
static String getImei(TelephonyManager telephonyManager) {
return telephonyManager.getImei();
}
}
@RequiresApi(23)
private static class Api23Impl {
private Api23Impl() {}
@SuppressLint("MissingPermission")
@RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
@DoNotInline
@Nullable
static String getDeviceId(TelephonyManager telephonyManager, int slotIndex) {
return telephonyManager.getDeviceId(slotIndex);
}
}
}