IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android S关闭定位开关后,定位权限被回收。 -> 正文阅读

[移动开发]Android S关闭定位开关后,定位权限被回收。

一、背景

在Android S上,有一个新增的设计——定位开关被关闭以后,应用申请的定位权限OP_FINE_LOCATION和OP_COARSE_LOCATION会被系统回收。

二、问题

基于上面的背景,有些app会出现这种问题。app在打开以后,已经动态申请了权限android.permission.ACCESS_FINE_LOCATION和android.permission.ACCESS_COARSE_LOCATION,但是后来用户关闭了定位开关,这时候,如果app需要执行一些与定位无关但是却需要定位权限的操作时(例如进行蓝牙扫描,建立P2P连接等),就会发现上述操作无法正确执行成功。

三、 代码设计

?在LocationManagerService.java中,当定位开关被用户开启或者关闭的时候,会走到

onLocationModeChangedhttp://aosp.opersys.com/xref/android-12.0.0_r2/s?refs=onLocationModeChanged&project=frameworks
    private void onLocationModeChanged(int userId) {
        boolean enabled = mInjector.getSettingsHelper().isLocationEnabled(userId);
        LocationManager.invalidateLocalLocationEnabledCaches();

        if (D) {
            Log.d(TAG, "[u" + userId + "] location enabled = " + enabled);
        }

        EVENT_LOG.logLocationEnabled(userId, enabled);

        Intent intent = new Intent(LocationManager.MODE_CHANGED_ACTION)
                .putExtra(LocationManager.EXTRA_LOCATION_ENABLED, enabled)
                .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
                .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
        mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));

        refreshAppOpsRestrictions(userId);
    }

然后会执行到一个新增的方法:refreshAppOpsRestrictionshttp://aosp.opersys.com/xref/android-12.0.0_r2/s?refs=refreshAppOpsRestrictions&project=frameworks

    private void refreshAppOpsRestrictions(int userId) {
        if (userId == UserHandle.USER_ALL) {
            final int[] runningUserIds = mInjector.getUserInfoHelper().getRunningUserIds();
            for (int i = 0; i < runningUserIds.length; i++) {
                refreshAppOpsRestrictions(runningUserIds[i]);
            }
            return;
        }

        Preconditions.checkArgument(userId >= 0);

        boolean enabled = mInjector.getSettingsHelper().isLocationEnabled(userId);

        PackageTagsList allowedPackages = null;
        if (!enabled) {
            PackageTagsList.Builder builder = new PackageTagsList.Builder();
            for (LocationProviderManager manager : mProviderManagers) {
                CallerIdentity identity = manager.getIdentity();
                if (identity != null) {
                    builder.add(identity.getPackageName(), identity.getAttributionTag());
                }
            }
            builder.add(mInjector.getSettingsHelper().getIgnoreSettingsAllowlist());
            allowedPackages = builder.build();
        }

        AppOpsManager appOpsManager = Objects.requireNonNull(
                mContext.getSystemService(AppOpsManager.class));
        appOpsManager.setUserRestrictionForUser(
                AppOpsManager.OP_COARSE_LOCATION,
                !enabled,
                LocationManagerService.this,
                allowedPackages,
                userId);
        appOpsManager.setUserRestrictionForUser(
                AppOpsManager.OP_FINE_LOCATION,
                !enabled,
                LocationManagerService.this,
                allowedPackages,
                userId);
    }

当定位开关被关闭后,

boolean enabled = mInjector.getSettingsHelper().isLocationEnabled(userId);

得到的enabled的值为false,

下面的if判断就会走进去:

        PackageTagsList allowedPackages = null;
        if (!enabled) {
            PackageTagsList.Builder builder = new PackageTagsList.Builder();
            for (LocationProviderManager manager : mProviderManagers) {
                CallerIdentity identity = manager.getIdentity();
                if (identity != null) {
                    builder.add(identity.getPackageName(), identity.getAttributionTag());
                }
            }
            builder.add(mInjector.getSettingsHelper().getIgnoreSettingsAllowlist());
            allowedPackages = builder.build();
        }

allowedPackages看命名方式,它就是白名单的意思。

上述的变量赋值以后,会走到下面的回收权限的地方:

        AppOpsManager appOpsManager = Objects.requireNonNull(
                mContext.getSystemService(AppOpsManager.class));
        appOpsManager.setUserRestrictionForUser(
                AppOpsManager.OP_COARSE_LOCATION,
                !enabled,
                LocationManagerService.this,
                allowedPackages,
                userId);
        appOpsManager.setUserRestrictionForUser(
                AppOpsManager.OP_FINE_LOCATION,
                !enabled,
                LocationManagerService.this,
                allowedPackages,
                userId);

当enabled为false的时候,会将userId的用户空间的所有除了allowedPackages的package的OP_FINE_LOCATION和OP_COARSE_LOCATION回收;

当enabled为true的时候,会将userId的用户空间的所有除了allowedPackages的package的OP_FINE_LOCATION和OP_COARSE_LOCATION释放;

总结一下就是,当定位开关被关闭后,所有白名单以外的package的定位权限会被回收,只有当定位开关重新打开以后才会被释放。

四、 解决方案

在上面的代码设计中讲到,下面的if语句是添加白名单的地方,

        PackageTagsList allowedPackages = null;
        if (!enabled) {
            PackageTagsList.Builder builder = new PackageTagsList.Builder();
            for (LocationProviderManager manager : mProviderManagers) {
                CallerIdentity identity = manager.getIdentity();
                if (identity != null) {
                    builder.add(identity.getPackageName(), identity.getAttributionTag());
                }
            }
            builder.add(mInjector.getSettingsHelper().getIgnoreSettingsAllowlist());
            allowedPackages = builder.build();
        }

当enabled为true的时候,allowedPackages为null,不谈;

当enabled为false的时候,先遍历LocationProviderManager,将4种provider(passive network fused gps)加入到白名单中;然后通过mInjector.getSettingsHelper().getIgnoreSettingsAllowlist()去获取已经添加过后并保存好的白名单。

那么,问题解决的方案可以从mInjector.getSettingsHelper().getIgnoreSettingsAllowlist()入手了。

    @Override
    public PackageTagsList getIgnoreSettingsAllowlist() {
        return mIgnoreSettingsPackageAllowlist.getValue();
    }

mIgnoreSettingsPackageAllowlist定义处:

private final PackageTagsListSetting mIgnoreSettingsPackageAllowlist;

mIgnoreSettingsPackageAllowlist初始化:

        mIgnoreSettingsPackageAllowlist = new PackageTagsListSetting(
                IGNORE_SETTINGS_ALLOWLIST,
                () -> SystemConfig.getInstance().getAllowIgnoreLocationSettings());

从SystemConfig.java中获取AllowIgnoreLocationSettings

    public ArrayMap<String, ArraySet<String>> getAllowIgnoreLocationSettings() {
        return mAllowIgnoreLocationSettings;
    }

mAllowIgnoreLocationSettings定义、初始化:

final ArrayMap<String, ArraySet<String>> mAllowIgnoreLocationSettings = new ArrayMap<>();

新增元素,这边就是新增白名单的地方。

                    case "allow-ignore-location-settings": {
                        if (allowOverrideAppRestrictions) {
                            String pkgname = parser.getAttributeValue(null, "package");
                            String attributionTag = parser.getAttributeValue(null,
                                    "attributionTag");
                            if (pkgname == null) {
                                Slog.w(TAG, "<" + name + "> without package in "
                                        + permFile + " at " + parser.getPositionDescription());
                            } else {
                                ArraySet<String> tags = mAllowIgnoreLocationSettings.get(pkgname);
                                if (tags == null || !tags.isEmpty()) {
                                    if (tags == null) {
                                        tags = new ArraySet<>(1);
                                        mAllowIgnoreLocationSettings.put(pkgname, tags);
                                    }
                                    if (!"*".equals(attributionTag)) {
                                        if ("null".equals(attributionTag)) {
                                            attributionTag = null;
                                        }
                                        tags.add(attributionTag);
                                    }
                                }
                            }
                        } else {
                            logNotAllowedInPartition(name, permFile, parser);
                        }
                        XmlUtils.skipCurrentTag(parser);
                    } break;

看代码实现,这边是解析xml元素"package"和"attributionTag"的属性值,然后解析后的属性值添加到mAllowIgnoreLocationSettings中。

读取Permissons


    private void readPermissionsFromXml(File permFile, int permissionFlag) {
        FileReader permReader = null;
        try {
            permReader = new FileReader(permFile);
        } catch (FileNotFoundException e) {
            Slog.w(TAG, "Couldn't find or open permissions file " + permFile);
            return;
        }
        Slog.i(TAG, "Reading permissions from " + permFile);

        final boolean lowRam = ActivityManager.isLowRamDeviceStatic();

        try {
            XmlPullParser parser = Xml.newPullParser();
            parser.setInput(permReader);

            int type;
            while ((type=parser.next()) != parser.START_TAG
                       && type != parser.END_DOCUMENT) {
                ;
            }

            if (type != parser.START_TAG) {
                throw new XmlPullParserException("No start tag found");
            }

            if (!parser.getName().equals("permissions") && !parser.getName().equals("config")) {
                throw new XmlPullParserException("Unexpected start tag in " + permFile
                        + ": found " + parser.getName() + ", expected 'permissions' or 'config'");
            }

            final boolean allowAll = permissionFlag == ALLOW_ALL;
            final boolean allowLibs = (permissionFlag & ALLOW_LIBS) != 0;
            final boolean allowFeatures = (permissionFlag & ALLOW_FEATURES) != 0;
            final boolean allowPermissions = (permissionFlag & ALLOW_PERMISSIONS) != 0;
            final boolean allowAppConfigs = (permissionFlag & ALLOW_APP_CONFIGS) != 0;
            final boolean allowPrivappPermissions = (permissionFlag & ALLOW_PRIVAPP_PERMISSIONS)
                    != 0;
            final boolean allowOemPermissions = (permissionFlag & ALLOW_OEM_PERMISSIONS) != 0;
            final boolean allowApiWhitelisting = (permissionFlag & ALLOW_HIDDENAPI_WHITELISTING)
                    != 0;
            final boolean allowAssociations = (permissionFlag & ALLOW_ASSOCIATIONS) != 0;
            final boolean allowOverrideAppRestrictions =
                    (permissionFlag & ALLOW_OVERRIDE_APP_RESTRICTIONS) != 0;
            final boolean allowImplicitBroadcasts = (permissionFlag & ALLOW_IMPLICIT_BROADCASTS)
                    != 0;
            final boolean allowVendorApex = (permissionFlag & ALLOW_VENDOR_APEX) != 0;
            while (true) {
                XmlUtils.nextElement(parser);
                if (parser.getEventType() == XmlPullParser.END_DOCUMENT) {
                    break;
                }

                String name = parser.getName();
                if (name == null) {
                    XmlUtils.skipCurrentTag(parser);
                    continue;
                }
                switch (name) {
                // ......
                    case "allow-ignore-location-settings": {
                        if (allowOverrideAppRestrictions) {
                            String pkgname = parser.getAttributeValue(null, "package");
                            String attributionTag = parser.getAttributeValue(null,
                                    "attributionTag");
                            if (pkgname == null) {
                                Slog.w(TAG, "<" + name + "> without package in "
                                        + permFile + " at " + parser.getPositionDescription());
                            } else {
                                ArraySet<String> tags = mAllowIgnoreLocationSettings.get(pkgname);
                                if (tags == null || !tags.isEmpty()) {
                                    if (tags == null) {
                                        tags = new ArraySet<>(1);
                                        mAllowIgnoreLocationSettings.put(pkgname, tags);
                                    }
                                    if (!"*".equals(attributionTag)) {
                                        if ("null".equals(attributionTag)) {
                                            attributionTag = null;
                                        }
                                        tags.add(attributionTag);
                                    }
                                }
                            }
                        } else {
                            logNotAllowedInPartition(name, permFile, parser);
                        }
                        XmlUtils.skipCurrentTag(parser);
                    } break;
                // ......
                }
            }
        } catch (XmlPullParserException e) {
            Slog.w(TAG, "Got exception parsing permissions.", e);
        } catch (IOException e) {
            Slog.w(TAG, "Got exception parsing permissions.", e);
        } finally {
            IoUtils.closeQuietly(permReader);
        }
        // ......
    }

加载XML,从手机的各个目录下的/etc/的sysconfig和permissions中加载。


    private void readAllPermissions() {
        // Read configuration from system
        readPermissions(Environment.buildPath(
                Environment.getRootDirectory(), "etc", "sysconfig"), ALLOW_ALL);

        // Read configuration from the old permissions dir
        readPermissions(Environment.buildPath(
                Environment.getRootDirectory(), "etc", "permissions"), ALLOW_ALL);

        // Vendors are only allowed to customize these
        int vendorPermissionFlag = ALLOW_LIBS | ALLOW_FEATURES | ALLOW_PRIVAPP_PERMISSIONS
                | ALLOW_ASSOCIATIONS | ALLOW_VENDOR_APEX;
        if (Build.VERSION.DEVICE_INITIAL_SDK_INT <= Build.VERSION_CODES.O_MR1) {
            // For backward compatibility
            vendorPermissionFlag |= (ALLOW_PERMISSIONS | ALLOW_APP_CONFIGS);
        }
        readPermissions(Environment.buildPath(
                Environment.getVendorDirectory(), "etc", "sysconfig"), vendorPermissionFlag);
        readPermissions(Environment.buildPath(
                Environment.getVendorDirectory(), "etc", "permissions"), vendorPermissionFlag);

        String vendorSkuProperty = SystemProperties.get(VENDOR_SKU_PROPERTY, "");
        if (!vendorSkuProperty.isEmpty()) {
            String vendorSkuDir = "sku_" + vendorSkuProperty;
            readPermissions(Environment.buildPath(
                    Environment.getVendorDirectory(), "etc", "sysconfig", vendorSkuDir),
                    vendorPermissionFlag);
            readPermissions(Environment.buildPath(
                    Environment.getVendorDirectory(), "etc", "permissions", vendorSkuDir),
                    vendorPermissionFlag);
        }

        // Allow ODM to customize system configs as much as Vendor, because /odm is another
        // vendor partition other than /vendor.
        int odmPermissionFlag = vendorPermissionFlag;
        readPermissions(Environment.buildPath(
                Environment.getOdmDirectory(), "etc", "sysconfig"), odmPermissionFlag);
        readPermissions(Environment.buildPath(
                Environment.getOdmDirectory(), "etc", "permissions"), odmPermissionFlag);

        String skuProperty = SystemProperties.get(SKU_PROPERTY, "");
        if (!skuProperty.isEmpty()) {
            String skuDir = "sku_" + skuProperty;

            readPermissions(Environment.buildPath(
                    Environment.getOdmDirectory(), "etc", "sysconfig", skuDir), odmPermissionFlag);
            readPermissions(Environment.buildPath(
                    Environment.getOdmDirectory(), "etc", "permissions", skuDir),
                    odmPermissionFlag);
        }

        // Allow OEM to customize these
        int oemPermissionFlag = ALLOW_FEATURES | ALLOW_OEM_PERMISSIONS | ALLOW_ASSOCIATIONS
                | ALLOW_VENDOR_APEX;
        readPermissions(Environment.buildPath(
                Environment.getOemDirectory(), "etc", "sysconfig"), oemPermissionFlag);
        readPermissions(Environment.buildPath(
                Environment.getOemDirectory(), "etc", "permissions"), oemPermissionFlag);

        // Allow Product to customize these configs
        // TODO(b/157203468): ALLOW_HIDDENAPI_WHITELISTING must be removed because we prohibited
        // the use of hidden APIs from the product partition.
        int productPermissionFlag = ALLOW_FEATURES | ALLOW_LIBS | ALLOW_PERMISSIONS
                | ALLOW_APP_CONFIGS | ALLOW_PRIVAPP_PERMISSIONS | ALLOW_HIDDENAPI_WHITELISTING
                | ALLOW_ASSOCIATIONS | ALLOW_OVERRIDE_APP_RESTRICTIONS | ALLOW_IMPLICIT_BROADCASTS
                | ALLOW_VENDOR_APEX;
        if (Build.VERSION.DEVICE_INITIAL_SDK_INT <= Build.VERSION_CODES.R) {
            // TODO(b/157393157): This must check product interface enforcement instead of
            // DEVICE_INITIAL_SDK_INT for the devices without product interface enforcement.
            productPermissionFlag = ALLOW_ALL;
        }
        readPermissions(Environment.buildPath(
                Environment.getProductDirectory(), "etc", "sysconfig"), productPermissionFlag);
        readPermissions(Environment.buildPath(
                Environment.getProductDirectory(), "etc", "permissions"), productPermissionFlag);

        // Allow /system_ext to customize all system configs
        readPermissions(Environment.buildPath(
                Environment.getSystemExtDirectory(), "etc", "sysconfig"), ALLOW_ALL);
        readPermissions(Environment.buildPath(
                Environment.getSystemExtDirectory(), "etc", "permissions"), ALLOW_ALL);

        // Skip loading configuration from apex if it is not a system process.
        if (!isSystemProcess()) {
            return;
        }
        // Read configuration of libs from apex module.
        // TODO: Use a solid way to filter apex module folders?
        for (File f: FileUtils.listFilesOrEmpty(Environment.getApexDirectory())) {
            if (f.isFile() || f.getPath().contains("@")) {
                continue;
            }
            readPermissions(Environment.buildPath(f, "etc", "permissions"), ALLOW_LIBS);
        }
    }

    @VisibleForTesting
    public void readPermissions(File libraryDir, int permissionFlag) {
        // Read permissions from given directory.
        if (!libraryDir.exists() || !libraryDir.isDirectory()) {
            if (permissionFlag == ALLOW_ALL) {
                Slog.w(TAG, "No directory " + libraryDir + ", skipping");
            }
            return;
        }
        if (!libraryDir.canRead()) {
            Slog.w(TAG, "Directory " + libraryDir + " cannot be read");
            return;
        }

        // Iterate over the files in the directory and scan .xml files
        File platformFile = null;
        for (File f : libraryDir.listFiles()) {
            if (!f.isFile()) {
                continue;
            }

            // We'll read platform.xml last
            if (f.getPath().endsWith("etc/permissions/platform.xml")) {
                platformFile = f;
                continue;
            }

            if (!f.getPath().endsWith(".xml")) {
                Slog.i(TAG, "Non-xml file " + f + " in " + libraryDir + " directory, ignoring");
                continue;
            }
            if (!f.canRead()) {
                Slog.w(TAG, "Permissions library file " + f + " cannot be read");
                continue;
            }

            readPermissionsFromXml(f, permissionFlag);
        }

        // Read platform permissions last so it will take precedence
        if (platformFile != null) {
            readPermissionsFromXml(platformFile, permissionFlag);
        }
    }

最后我们参照google添加的默认的白名单:

    Bypass Allow Packages:
      com.google.android.dialer[*]
      com.google.android.gms[.thunderbird]

我们可以在/product/etc/sysconfig/google.xml中,可以我们需要的内容:

<?xml version="1.0" encoding="utf-8"?>
<!-- These are configurations that must exist on all GMS devices. -->
<config>

    <allow-ignore-location-settings package="com.google.android.gms" attributionTag="com.google.android.gms.thunderbird" />
    <allow-ignore-location-settings package="com.google.android.dialer" attributionTag="*" />

</config>

到这里解决方案呼之欲出了,我们新开一个小节单独总结下。

五、 总结

我们可以在/system/etc/sysconfig/下新增一个文件,这里我们可以命名(也可以定义为你想要的fashion的名字)为location-settings-conf.xml,

<?xml version="1.0" encoding="utf-8"?>
<config>
     <allow-ignore-location-settings package="com.android.package.one" attributionTag="*" />
</config>

元素package的属性值,写入你要添加进白名单的package;

元素attributionTAG的属性值,可以直接写入*,也可以写上你想要的字符。

添加成功后,我们通过adb shell dumpsys location可以看到下面的dump信息。

    Bypass Allow Packages:
      com.android.package.one[*]
      com.google.android.dialer[*]
      com.google.android.gms[.thunderbird]

再去验证之前碰到的问题,pass。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-11-23 12:28:54  更:2021-11-23 12:30:01 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 6:01:48-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码