Android 11 旧版本兼容修改:TelephonyManager的getDeviceId和getSubscriberId; WifiManager.setWifiEnabled

概述

    随着Android的更新,越新的版本收紧的权限越来越多,伴随着很多曾经可用的接口慢慢地出现了问题。

比如: TelephonyManager.getSubscriberId()

public static void testGetSubId(Context ctx){
        //Android 11:
        // java.lang.SecurityException: getSubscriberId: The user 10125 does not meet the requirements to access device identifiers.
        TelephonyManager telMgr = (TelephonyManager) ctx.getSystemService(TELEPHONY_SERVICE);
        @SuppressLint("MissingPermission")
        String subId = telMgr.getSubscriberId();
        android.util.Log.d(TAG, "testGetSubId subId=" + subId);
    }

原因

在代码注释中已经写得很明白:
The user 10125 does not meet the requirements to access device identifiers

要做的事情很简单, 找到权限检查的地方, 去掉它
权限检查的位置

frameworks/base/telephony/common/com/android/internal/telephony/TelephonyPermissions.java

    public static boolean checkCallingOrSelfReadDeviceIdentifiers(Context context, int subId,
            String callingPackage, @Nullable String callingFeatureId, String message) {
        return true || checkPrivilegedReadPermissionOrCarrierPrivilegePermission(
                context, subId, callingPackage, callingFeatureId, message, true);
    }
    public static boolean checkCallingOrSelfReadSubscriberIdentifiers(Context context, int subId,
            String callingPackage, @Nullable String callingFeatureId, String message) {
        return true || checkPrivilegedReadPermissionOrCarrierPrivilegePermission(
                context, subId, callingPackage, callingFeatureId, message, false);
    }
    private static boolean checkPrivilegedReadPermissionOrCarrierPrivilegePermission(
            Context context, int subId, String callingPackage, @Nullable String callingFeatureId,
            String message, boolean allowCarrierPrivilegeOnAnySub) {
        int uid = Binder.getCallingUid();
        int pid = Binder.getCallingPid();

        // If the calling package has carrier privileges for specified sub, then allow access.
        if (checkCarrierPrivilegeForSubId(context, subId)) return true;

        // If the calling package has carrier privileges for any subscription
        // and allowCarrierPrivilegeOnAnySub is set true, then allow access.
        if (allowCarrierPrivilegeOnAnySub && checkCarrierPrivilegeForAnySubId(context, uid)) {
            return true;
        }

        PermissionManager permissionManager = (PermissionManager) context.getSystemService(
                Context.PERMISSION_SERVICE);
        if (permissionManager.checkDeviceIdentifierAccess(callingPackage, message, callingFeatureId,
                pid, uid) == PackageManager.PERMISSION_GRANTED) {
            return true;
        }

        return reportAccessDeniedToReadIdentifiers(context, subId, pid, uid, callingPackage,
                message);
    }
    /**
     * Reports a failure when the app with the given pid/uid cannot access the requested identifier.
     *
     * @returns false if the caller is targeting pre-Q and does have the READ_PHONE_STATE
     * permission or carrier privileges.
     * @throws SecurityException if the caller does not meet any of the requirements for the
     *                           requested identifier and is targeting Q or is targeting pre-Q
     *                           and does not have the READ_PHONE_STATE permission or carrier
     *                           privileges.
     */
    private static boolean reportAccessDeniedToReadIdentifiers(Context context, int subId, int pid,
            int uid, String callingPackage, String message) {
        ApplicationInfo callingPackageInfo = null;
        try {
            callingPackageInfo = context.getPackageManager().getApplicationInfoAsUser(
                    callingPackage, 0, UserHandle.getUserHandleForUid(uid));
        } catch (PackageManager.NameNotFoundException e) {
            // If the application info for the calling package could not be found then assume the
            // calling app is a non-preinstalled app to detect any issues with the check
            Log.e(LOG_TAG, "Exception caught obtaining package info for package " + callingPackage,
                    e);
        }
        // The current package should only be reported in StatsLog if it has not previously been
        // reported for the currently invoked device identifier method.
        boolean packageReported = sReportedDeviceIDPackages.containsKey(callingPackage);
        if (!packageReported || !sReportedDeviceIDPackages.get(callingPackage).contains(
                message)) {
            Set invokedMethods;
            if (!packageReported) {
                invokedMethods = new HashSet<String>();
                sReportedDeviceIDPackages.put(callingPackage, invokedMethods);
            } else {
                invokedMethods = sReportedDeviceIDPackages.get(callingPackage);
            }
            invokedMethods.add(message);
            TelephonyCommonStatsLog.write(TelephonyCommonStatsLog.DEVICE_IDENTIFIER_ACCESS_DENIED,
                    callingPackage, message, /* isPreinstalled= */ false, false);
        }
        Log.w(LOG_TAG, "reportAccessDeniedToReadIdentifiers:" + callingPackage + ":" + message + ":"
                + subId);
        // if the target SDK is pre-Q then check if the calling package would have previously
        // had access to device identifiers.
        if (callingPackageInfo != null && (
                callingPackageInfo.targetSdkVersion < Build.VERSION_CODES.Q)) {
            if (context.checkPermission(
                    android.Manifest.permission.READ_PHONE_STATE,
                    pid,
                    uid) == PackageManager.PERMISSION_GRANTED) {
                return false;
            }
            if (checkCarrierPrivilegeForSubId(context, subId)) {
                return false;
            }
        }
        throw new SecurityException(message + ": The user " + uid
                + " does not meet the requirements to access device identifiers.");
    }

frameworks/base/services/core/java/com/android/server/pm/permission/PermissionManagerService.java

    @Override
    public int checkDeviceIdentifierAccess(@Nullable String packageName, @Nullable String message,
            @Nullable String callingFeatureId, int pid, int uid) {
        // If the check is being requested by an app then only allow the app to query its own
        // access status.
        int callingUid = mInjector.getCallingUid();
        int callingPid = mInjector.getCallingPid();
        if (UserHandle.getAppId(callingUid) >= Process.FIRST_APPLICATION_UID && (callingUid != uid
                || callingPid != pid)) {
            String response = String.format(
                    "Calling uid %d, pid %d cannot check device identifier access for package %s "
                            + "(uid=%d, pid=%d)",
                    callingUid, callingPid, packageName, uid, pid);
            Log.w(TAG, response);
            throw new SecurityException(response);
        }
        // Allow system and root access to the device identifiers.
        final int appId = UserHandle.getAppId(uid);
        if (appId == Process.SYSTEM_UID || appId == Process.ROOT_UID) {
            return PackageManager.PERMISSION_GRANTED;
        }
        // Allow access to packages that have the READ_PRIVILEGED_PHONE_STATE permission.
        if (mInjector.checkPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, pid,
                uid) == PackageManager.PERMISSION_GRANTED) {
            return PackageManager.PERMISSION_GRANTED;
        }
        // If the calling package is not null then perform the appop and device / profile owner
        // check.
        if (packageName != null) {
            // Allow access to a package that has been granted the READ_DEVICE_IDENTIFIERS appop.
            long token = mInjector.clearCallingIdentity();
            AppOpsManager appOpsManager = (AppOpsManager) mInjector.getSystemService(
                    Context.APP_OPS_SERVICE);
            try {
                if (appOpsManager.noteOpNoThrow(AppOpsManager.OPSTR_READ_DEVICE_IDENTIFIERS, uid,
                        packageName, callingFeatureId, message) == AppOpsManager.MODE_ALLOWED) {
                    return PackageManager.PERMISSION_GRANTED;
                }
            } finally {
                mInjector.restoreCallingIdentity(token);
            }
            // Check if the calling packages meets the device / profile owner requirements for
            // identifier access.
            DevicePolicyManager devicePolicyManager =
                    (DevicePolicyManager) mInjector.getSystemService(Context.DEVICE_POLICY_SERVICE);
            if (devicePolicyManager != null && devicePolicyManager.hasDeviceIdentifierAccess(
                    packageName, pid, uid)) {
                return PackageManager.PERMISSION_GRANTED;
            }
        }
        return PackageManager.PERMISSION_DENIED;
    }


WifiManager.setWifiEnabled

    从官方文档中已经说明: SDK >= Q 这个接口会直接返回 false,不再生效
在这里插入图片描述
示例

build.gradle 注意targetSdkVersion

    defaultConfig {
        applicationId "com.xxx.apitester"
        minSdkVersion 21
        targetSdkVersion 30
		//...
    }
		WifiManager wifiMgr = (WifiManager) ctx.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
        boolean en = wifiMgr.isWifiEnabled();
        //返回false
        boolean res = wifiMgr.setWifiEnabled(!en);

如果调用了从LOG中可以找到: setWifiEnabled not allowed for uid=xxx

原因

frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiServiceImpl.java

    /**
     * see {@link android.net.wifi.WifiManager#setWifiEnabled(boolean)}
     * @param enable {@code true} to enable, {@code false} to disable.
     * @return {@code true} if the enable/disable operation was
     *         started or is already in the queue.
     */
    @Override
    public synchronized boolean setWifiEnabled(String packageName, boolean enable) {
        if (enforceChangePermission(packageName) != MODE_ALLOWED) {
            return false;
        }
/*********************************/
//检查APP的SDK版本等信息。
/*********************************/
        boolean isPrivileged = isPrivileged(Binder.getCallingPid(), Binder.getCallingUid());
        if (!isPrivileged && !isDeviceOrProfileOwner(Binder.getCallingUid(), packageName)
                && !mWifiPermissionsUtil.isTargetSdkLessThan(packageName, Build.VERSION_CODES.Q,
                  Binder.getCallingUid())
                && !isSystem(packageName, Binder.getCallingUid())) {
            mLog.info("setWifiEnabled not allowed for uid=%")
                    .c(Binder.getCallingUid()).flush();
            return false;
        }
        // If Airplane mode is enabled, only privileged apps are allowed to toggle Wifi
        if (mSettingsStore.isAirplaneModeOn() && !isPrivileged) {
            mLog.err("setWifiEnabled in Airplane mode: only Settings can toggle wifi").flush();
            return false;
        }

        // If SoftAp is enabled, only privileged apps are allowed to toggle wifi
        if (!isPrivileged && mTetheredSoftApTracker.getState() == WIFI_AP_STATE_ENABLED) {
            mLog.err("setWifiEnabled with SoftAp enabled: only Settings can toggle wifi").flush();
            return false;
        }

        mLog.info("setWifiEnabled package=% uid=% enable=%").c(packageName)
                .c(Binder.getCallingUid()).c(enable).flush();
        long ident = Binder.clearCallingIdentity();
        try {
            if (!mSettingsStore.handleWifiToggled(enable)) {
                // Nothing to do if wifi cannot be toggled
                return true;
            }
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
        if (mWifiPermissionsUtil.checkNetworkSettingsPermission(Binder.getCallingUid())) {
            if (enable) {
                mWifiMetrics.logUserActionEvent(UserActionEvent.EVENT_TOGGLE_WIFI_ON);
            } else {
                WifiInfo wifiInfo = mClientModeImpl.syncRequestConnectionInfo();
                mWifiMetrics.logUserActionEvent(UserActionEvent.EVENT_TOGGLE_WIFI_OFF,
                        wifiInfo == null ? -1 : wifiInfo.getNetworkId());
            }
        }
        mWifiMetrics.incrementNumWifiToggles(isPrivileged, enable);
        mActiveModeWarden.wifiToggled();
        return true;
    }

检查版本低于:Build.VERSION_CODES.Q

frameworks/opt/net/wifi/service/java/com/android/server/wifi/util/WifiPermissionsUtil.java


    /**
     * Checks whether than the target SDK of the package is less than the specified version code.
     */
    public boolean isTargetSdkLessThan(String packageName, int versionCode, int callingUid) {
    	//强制返回,版本检测将不再生效
    	if(true)return true;
        long ident = Binder.clearCallingIdentity();
        try {
            if (mContext.getPackageManager().getApplicationInfoAsUser(
                    packageName, 0,
                    UserHandle.getUserHandleForUid(callingUid)).targetSdkVersion
                    < versionCode) {
                return true;
            }
        } catch (PackageManager.NameNotFoundException e) {
            // In case of exception, assume unknown app (more strict checking)
            // Note: This case will never happen since checkPackage is
            // called to verify validity before checking App's version.
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
        return false;
    }

参考

  • 第三方APP无法申请READ_PRIVILEGED_PHONE_STATE权限:
Android 10 changes the permissions for device identifiers so that all device identifiers are now protected by the READ_PRIVILEGED_PHONE_STATE permission. 
Prior to Android 10, persistent device identifiers (IMEI/MEID, IMSI, SIM, and build serial) were protected behind the READ_PHONE_STATE runtime permission. 
The READ_PRIVILEGED_PHONE_STATE permission is only granted to apps signed with the platform key and privileged system apps.

How to request for permission: READ_PRIVILEGED_PHONE_STATE?

Android 获取双卡手机IMEI,IMSI,ICCID

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值