android8.1 修改系统默认语言为中文
1,android 系统的默认语言配置文件路径如下:
build/make/target/product/full_base.mk
在文件中找到变量 "PRODUCT_LOCALES"
然后修改为如下形式即可:
PRODUCT_LOCALES := zh_CN
2,删除多余的语言包
配置文件路径如下:
build/make/target/product/locales_full.mk
在文件中找到变量"PRODUCT_LOCALES"
把不需要的语言删除就好,例如:
PRODUCT_LOCALES := en_US zh_HK zh_CN
- 1. 配置SELinux权限
SELinux(或SEAndroid)主要将app划分为三种类型(根据user不同,也有其他的domain类型):
-
1)untrusted_app 第三方app,没有android平台签名,没有system权限
-
2)platform_app 有android平台签名,没有system权限
-
3)system_app 有android平台签名和system权限
-
从上面划分,权限等级,理论上:untrusted_app < platform_app < system_app
-
TE介绍:
-
allow netd proc:file write
-
这条语句的语法为:
-
allow:TE的allow语句,表示授权。除了allow之外,还有allowaudit、dontaudit、neverallow等。
-
netd:source type。也叫subject,domain。
-
proc:target type。它代表其后的file所对应的Type。
-
file:代表Object Class。它代表能够给subject操作的一类东西。例如File、Dir、socket等。在Android系统中,有一个其他Linux系统没有的Object Class,那就是Binder。
-
write:在该类Object Class中所定义的操作。
-
根据SELinux规范,完整的allow相关的语句格式为:
-
rule_name source_type target_type : class perm_set
-
我们直接来看几个实例:
-
[例子3]
-
//SEAndroid中的安全策略文件policy.conf
-
#允许zygote域中的进程向init type的进程(Object Class为process)发送sigchld信号
-
allow zygote init:process sigchld;
-
#允许zygote域中的进程search或getattr类型为appdomain的目录。注意,多个perm_set
-
#可用{}括起来
-
allow zygote appdomain:dir { getattr search };
-
#来个复杂点的:
-
#source_type为unconfineddomain target_type为一组type,由
-
#{ fs_type dev_type file_type }构成。object_class也包含两个,为{ chr_file file }
-
#perm_set语法比较奇特,前面有一个~号。它表示除了{entrypoint relabelto}之外,{chr_file #file}这两个object_class所拥有的其他操作
-
allow unconfineddomain {fs_type dev_type file_type}:{ chr_file file } \
-
~{entrypoint relabelto};
-
#特殊符号除了~外,还有-号和*号,其中:
-
# 1):-号表示去除某项内容。
-
# 2):*号表示所有内容。
-
#下面这条语句中,source_type为属于appdomain,但不属于unconfinedomain的进程。
-
#而 *表示所有和capability2相关的权限
-
#neverallow:表示绝不允许。
-
neverallow { appdomain -unconfineddomain } self:capability2 *;
-
type的定义:
-
type命令的完整格式为:type type_id [alias alias_id,] [attribute_id]
-
其中,方括号中的内容为可选。alias指定了type的别名,可以指定多个别名
-
/device/qcom/sepolicy/common/
-
1 file.te 定义 sysfs_brightness
-
type sysfs_brightness, sysfs_type, fs_type;
-
2 file_comtexts 配置sysfs_brightness
-
/sys/class/leds/lcd-backlight/brightness u:object_r:sysfs_brightness:s0
-
3 app.te 配置权限
-
allow appdomain sysfs_brightness:file rw_file_perms;
-
4 system_app.te 配置权限
-
allow system_app sysfs_brightness:file rw_file_perms;
- 5. 默认设置第三方输入法
-
framework/base/packages/SettingsProvider/res/values/customize.xml
-
<string name="def_enable_input_methods" translatable="false">com.baidu.input/.ImeAppMainActivity</string>
-
百度:com.baidu.input/.ImeService
-
讯飞:com.iflytek.inputmethod/.FlyIME
-
腾讯:com.tencent.qqpinyin/.QQPYInputMethodService
-
谷歌:com.google.android.inputmethod.pinyin/.PinyinIME
-
搜狗:com.sohu.inputmethod.sogou/.SogouIME
-
触宝:com.cootek.smartinput5/.TouchPalIME
-
LOCAL_PATH := $(call my-dir)
-
include $(CLEAR_VARS)
-
#Module name should match apk name to be installed
-
LOCAL_MODULE := baiduinput
-
LOCAL_MODULE_TAGS := optional
-
LOCAL_SRC_FILES := $(LOCAL_MODULE).apk
-
LOCAL_MODULE_CLASS := APPS
-
LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)
-
LOCAL_CERTIFICATE := PRESIGNED
-
LOCAL_MULTILIB:=32
-
LOCAL_PREBUILT_JNI_LIBS:= \
-
@lib/armeabi/libbdEASRAndroid.so \
-
@lib/armeabi/libbdEASRAndroid.v1.8.8.so \
-
@lib/armeabi/libbdinput_gif_v1_0_10.so \
-
@lib/armeabi/libBDVoiceRecognitionClient_MFE_V1.so \
-
@lib/armeabi/libchiperencoder_v1_2_1.so \
-
@lib/armeabi/libprocmoi_v1_4.so \
-
@lib/armeabi/libprocmox_v1_4.so
-
include $(BUILD_PREBUILT)
-
#LOCAL_PRIVILEGED_MODULE := true
-
#LOCAL_OVERRIDES_PACKAGES := Home
- 6. Android4.4之后配置应用写入短信权限
-
framework/opt/telephony/*/SmsApplication.java
-
getDefaultSmsApplicationPackageName()
-
参考BLUETOOTH_PACKAGE_NAME添加USERCENTER_PACKAGE_NAME
- 设置拨号盘暗码
-
com.android.dialer.SpecialCharSequenceMgr.java
-
参考MMI_IMEI_DISPLAY,handleDeviceIdDisplay()
- 7. app默认权限授予
frameworks/base/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
- 8. 添加全局变量开关(三种方式)
-
1. android.provider.Settings.System.getInt(getContentResolver(),"test", 0);
-
android.provider.Settings.System.putInt(getContentResolver(),"test", 1);
-
Settings有System Global Secure
-
2.1 定义: ./frameworks/base/core/res/res/values/symbols.xml:
-
<java-symbol type="bool" name="config_sms_ringtone_incall" />
-
2.2 定义: ./frameworks/base/core/res/res/values/config.xml:
-
<bool name="config_sms_ringtone_incall">true</bool>
-
读取:
-
context.getResources().getBoolean(com.android.internal.R.bool.config_sms_ringtone_incall)
-
3.system.prop中添加:
-
tinno_gms_able = false
-
使用:
-
import android.os.SystemProperties;
-
SystemProperties.getBoolean("tinno_gms_able", false);
-
system.prop生成的位置
-
out/target/product/l5261/system/build.prop
-
对属性的读写操作
-
adb shell getprop key
-
adb shell setprop key value (eng版本)
- 9. 编译中预拷贝文件或文件夹
-
/device/qucii/qucii8976v3_64/qucii8976v3_64.mk:
-
配置文件的拷贝路径:
-
PRODUCT_COPY_FILES += device/qucii/qucii8976v3_64/Homecare_Chinese_93sec_color.mp4:system/media/Homecare_Chinese_93sec_color.mp4
-
配置文件夹拷贝路径:
-
PRODUCT_COPY_FILES += $(call find-copy-subdir-files,*,vendor/qcom/proprietary/Tone,storage/emulated/0/Tone)
-
或:
-
$(shell mkdir -p ${OUT}"/system/etc/")源码编译的时候,先读取该mk文件,该目录还没创建,所以要建一个,否则拷贝失败。
-
$(shell cp -rf $(LOCAL_PATH)/usb_modeswitch.d ${OUT)}"/system/etc/"),然后通过shell拷贝任意文件。
-
注意:
-
如果拷贝的是apk文件,可能会报错:
-
原因是build/core/Makefile中做了检测,注释build/core/Makefile中的define check-product-copy-file函数即可
- 10. 默认勾选 未知来源
<bool name="def_install_non_market_apps">false</bool>
- 11. 修改系统默认值(WiFi,蓝牙初始开关);默认铃声, 通知, 闹钟, 音乐,字体
-
1. framework/base/packages/SettingsProvider/res/values/defaults.xml
-
2. 可以在system.prop 分别配置
-
ro.config.ringtone=Playa.ogg (默认铃声设置,文件在/system/media/audio/ringtones 把喜欢的铃声放这里,比如123.MP3放入ringtones文件夹中,这里代码改为ro.config.ringtone=123.mp3)
-
ro.config.notification_sound=regulus.ogg (默认提示音,文件在/system/media/audio/notifications 修改方法同上)
-
ro.config.alarm_alert=Alarm_Beep_03.ogg (默认闹铃,文件在/system/media/audio/alarms 修改方法同上)
-
3.默认字体:
-
public void readFontSizePreference(ListPreference pref) {
-
ActivityManagerNative.getDefault().getConfiguration()
-
系统的Configuration是从ActivityManagerService设置的:
-
<\frameworks\base\services\java\com\android\server\am\ActivityManagerService.java>
-
mConfiguration.setToDefaults();
-
fontScale = Float.parseFloat(SystemProperties.get("ro.fontScale","1"));
- 11. 设置-->开发者选项--修改后台进程限制
-
frameworks/base/services/java/com/android/server$ vi ./am/ActivityManagerService.java
-
把int mProcessLimitOverride = -1;改成4(不得超过4个进程)
-
标准限制 对应 -1
-
不允许后台进程 对应 0
-
不得超过1个进程 对应 1
-
不得超过2个进程 对应 2
-
不得超过3个进程 对应 3
- 11.默认不要锁屏,设置-安全--屏幕锁定 (无)
overlay/frameworks/base/packages/SettingsProvider/res/values/defaults.xml <bool name="def_lockscreen_disabled">true</bool>
- 12. 修改默认字体
-
2.1 在4.1上修改默认字体大小:
-
比如改成大:
-
<frameworks\base\packages\SettingsProvider\res\values\defaults.xml>
-
<fraction name="def_font_scale">115%</fraction>
-
修改默认(Settting.apk 中选项默认值一般存在于 frameworks\base\packages\SettingsProvider\res\values\defaults.xml)
- 13. 获取正在播放音乐app包名
-
// com.android.server.audio.AudioService callingPackageName为当前获取焦点app包名
-
public int requestAudioFocus(AudioAttributes aa, int durationHint, IBinder cb,
-
IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags,
-
IAudioPolicyCallback pcb) {
-
}
-
AudioManager audio = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
-
audio.isMusicActive() // 判断是否有音乐播放
- 14.如何将非系统app预置到Doze的白名单中
1.在framework/base/data下创建data目录
2.在framework/base/data/data下创建deviceidle.xml 然后把白名单app按以下格式写入
deviceidle.xml
-
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
-
<config>
-
<wl n="com.tencent.mm" />
-
<wl n="com.tencent.mobileqq" />
-
</config>
3.在framework/base/data/data下创建Android.mk文件
-
LOCAL_PATH := $(my-dir)
-
include $(CLEAR_VARS)
-
LOCAL_MODULE := deviceidle.xml
-
LOCAL_MODULE_CLASS := DATA
-
# This will install the file in /data/system
-
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/system
-
LOCAL_SRC_FILES := $(LOCAL_MODULE)
-
include $(BUILD_PREBUILT)
3.定义deviceidle.xml在 build/target/product/base.mk中
-
PRODUCT_PACKAGES += \
-
...
-
monkey \
-
mtpd \
-
ndc \
-
netd \
-
ping \
-
ping6 \
-
platform.xml \
-
deviceidle.xml \
-
pppd \
-
pm \
系列目录请点击这里: 全方位理解Android权限
因为东忙西忙没时间整理这一块的东西,拖了有点久,现在继续更新
权限的性质
我们知道,Android应用都运行在沙盒中,默认情况下这些应用只能访问他们自己的域,即自己的文件和非常少量的系统服务。为了能够和系统或者其他应用交互,app就需要申请额外的一些权限。
permission(权限)实际上就是一个简单的字串,申明需要做哪些类型的操作。
比如文件读权限就是这么一个字串:
lang-xml
lang-java
public static final String WRITE_EXTERNAL_STORAGE = "android.permission.WRITE_EXTERNAL_STORAGE";
Android系统预置了很多这样的权限,可以在官网查看文档:Manifest.permission。
权限的定义是在/frameworks/base/core/res/AndroidManifest.xml
在AOSP的生成目录内./out/target/common/R/android/Manifest.java
有对应定义(已删掉部分代码):
lang-java
package android;
public final class Manifest {
public static final class permission {
@android.annotation.SystemApi
public static final String ACCESS_CACHE_FILESYSTEM="android.permission.ACCESS_CACHE_FILESYSTEM";
@android.annotation.SystemApi
public static final String ACCESS_CHECKIN_PROPERTIES="android.permission.ACCESS_CHECKIN_PROPERTIES";
public static final String ACCESS_COARSE_LOCATION="android.permission.ACCESS_COARSE_LOCATION";
}
public static final class permission_group {
public static final String CALENDAR="android.permission-group.CALENDAR";
public static final String CAMERA="android.permission-group.CAMERA";
public static final String CONTACTS="android.permission-group.CONTACTS";
public static final String STORAGE="android.permission-group.STORAGE";
}
如果要看系统内已知的权限列表,可以使用pm list permissions
命令:
lang-shell
> adb shell pm list permissions
All Permissions:
permission:android.permission.REBOOT
permission:android.permission.BIND_VPN_SERVICE
permission:com.google.android.gallery3d.permission.GALLERY_PROVIDER
permission:com.android.launcher3.permission.RECEIVE_LAUNCH_BROADCASTS
...
如果加上-f
选项,可以打印出定义权限的package、label、description和protection level。
lang-shell
> adb shell pm list permissions -f
All Permissions:
+ permission:android.permission.REBOOT
package:android
label:null
description:null
protectionLevel:signature|privileged
+ permission:android.permission.BIND_VPN_SERVICE
package:android
label:null
description:null
protectionLevel:signature
+ permission:android.permission.GET_PACKAGE_SIZE
package:android
label:计算应用存储空间
description:允许应用检索其代码、数据和缓存大小
protectionLevel:normal
+ permission:android.permission.BROADCAST_STICKY
package:android
label:发送持久广播
description:允许该应用发送持久广播消息,此类消息在广播结束后仍会保留。过度使用可能会导致手机使用过多内存,从而降低其速度或稳定性。
protectionLevel:normal
一般,权限名的前缀是定义它的包名+.permission.
。因为内置的权限都定义在android
包内,所以系统权限命名都是android.permission.
开头。
比如上面的例子,REBOOT和BIND_VPN_SERVICE都是系统内部权限,GALLERY_PROVIDER是Gallery应用定义的,RECEIVE_LAUNCH_BROADCASTS是默认launcher定义的。
申请权限
一般权限是在AndroidManifest.xml文件中添加<uses-permission>
标签完成,关于AndroidManifest可以参考我前面写的AndroidManifest解析流程wossoneri.github.io。当然也可以使用<permission>
标签定义新的权限。
lang-xml
权限管理
在应用安装的时候,PackageManagerService就对每个应用授予了权限。package manager管理了一个数据库,用来维护预置或者用户安装的package。维护的内容包括:安装路径,版本号,签名证书,每个package拿到的权限列表和一个在本设备上定义的所有权限列表。
其实pm list permissions命令就是通过package manager查询权限列表的
这个package数据库保存在/data/system/packages.xml路径下,每当有应用安装,更新或者卸载的时候都会更新这个xml文件。
看一下这个文件是怎么记录爱奇艺HD的:
lang-xml
这个xml文件还有很多譬如<shared-user>
这类标签,这些在后面分析userId的地方再看。现在看一下permission相关的内容。
每个package都是由一个<package>
标签包裹,里面包含了
- 分配的UID userId=”10031”
- 签名证书
<cert>
标签 - 分配的权限 在
<perm>
标签下
当用代码获取已安装的应用的信息时,得到的就是包含了<package>
标签下面所有内容的PackageInfo实例对象。
使用android.content.pm.PackageManager.getPackageInfo()通过代码获取
权限组
在权限定义文件里会定义权限组,然后在单独的权限中指定该权限属于哪个权限组。
lang-java
- 如果应用没有获得与当前申请的权限在同一权限组的其他权限的授权,那么系统将以这个权限组的描述信息去提示用户,而不是具体申请的权限的描述信息。比如,一个应用申请了READ_CONTACTS权限,系统会提示用户”应用需要访问设备的联系人(包含读写)”,如果用户同意授权,系统只会赋予应用之前申请的权限(在这里就只是READ_CONTACTS)。
- 如果应用已经获得了与正在申请的权限同一个权限组的其他权限的授权,那么系统会自动将正在申请的权限授予应用,不需要任何与用户的交互行为。比如,如果一个应用之前已经获得了READ_CONTACTS权限的授权,那么在之后应用请求WRITE_CONTACTS权限时,系统会自动将该权限授予应用。
权限保护等级ProtectionLevel
在查看权限定义时frameworks/base/core/res/AndroidManifest.xml
,可以看到有一个标签叫做protectionLevel
,下面放一个典型例子SYSTEM_ALERT_WINDOW的权限定义:
lang-xml
protectionLevel
是定义权限时的一个重要属性,它表示一个权限的级别,在很大程度上它也决定了一个权限被授权的方式(由系统安装时自动授权或者由用户来决定是否授权)。protectionLevel
可以分为两类:基础权限级别和附加权限级别。
基础权限级别
normal
这是个默认值,也就是没有指定protectionLevel
的权限默认获得此level。它表示这是一个对系统和其他应用低风险的权限。有该标记的权限是不需要用户确认就可以直接赋予应用程序的。
dangerous
较高风险的权限。该标记的权限一般涉及到访问用户的隐私数据或者其他一些控制设备的行为,可能会给用户带来影响。比如READ_SMS,读取短消息;比如CAMERA,允许使用摄像头。所以系统不会自动授权这类权限,而是会弹出对话框告诉用户,由用户进行选择是否授予权限。
signature
如果请求权限的app与声明权限的app签名一致,系统会自动赋予权限,而不会通知用户或者征求用户的同意。 否则需要通过intent将用户引导到权限管理界面由用户决定是否授权。
这属于最高级的权限等级,因为它需要有加密密钥的拥有权,而这个密钥只有这个app或者系统平台才会拥有。这也就意味着其他人无法随意使用这个权限。系统内置的signature权限一般都是由管理设备的系统App使用,也就是需要系统签名。
signatureOrSystem
相当于signature | privileged
这个权限等级有两种应用可以自动获取该类型权限的授权:
与定义这个权限的apk拥有相同的签名的应用(这一点和Signature类型的权限相同)。
在/system/priv-app目录下的应用(即拥有超级权限的系统应用)。
这可以让制造商的预置应用即使没有与该权限一致的签名也可以通过作为系统应用去使用该权限。
附加权限级别
除了基础权限级别的其他权限级别都属于附加权限级别。它们必须附加在基础权限级别上使用。从目前系统定义的权限来看,附加权限级别基本都是与signature基础权限级别搭配使用。
可以理解为附加权限级别是在为signature级别的权限开后门,使signature级别的权限在特定的条件下能够授权给特定类型的应用。
- privileged:只能与signature同时使用。signature | privileged与signatureOrSystem意义相同。
- system:与privileged相同,是privileged的老版本。
- development:development applications可以被自动授予此权限。
- appop:此类权限会与AppOpsManager来配合完成对应用操作的限制(AppOpsManager在后面的小节介绍)。
- pre23:应用请求此类权限后,系统将在应用安装时自动授权给那些targetSdkVersion在23(Android 6.0)以下的应用。
- installer:此类权限自动被授权给那些负责安装apk的系统app。
- verifier:此类权限自动被授权给那些负责验证apk的系统app。
- preinstalled:此类权限可以自动被授权给任何预安装在system image中的app,不只是privileged app。
- setup:此类权限自动被授予安装向导app。
可以通过dumpsys命令查看应用权限授予情况:
lang-shell
> dumpsys package com.xxxx.test
requested permissions:
android.permission.SYSTEM_ALERT_WINDOW
Shared users:
SharedUser [android.uid.system] (c5f2604):
userId=1000
install permissions:
android.permission.SYSTEM_ALERT_WINDOW: granted=true, flags=[ GRANTED_BY_DEFAULT ]
所以对于前面提到的SYSTEM_ALERT_WINDOW权限,因为其权限类别为:"signature|preinstalled|appop|pre23|development"
,所以,如果不是系统应用,也不是预置应用,那么就可以通过把app的targetSdkVersion调至23以下,来默认获得此权限。这里面还有一个appop权限,稍后会介绍这个东西。
默认授予权限
前面介绍protectionLevel
的时候知道,只要App满足对应权限的保护级别,就可以默认获取对应权限。但是有一个没有提到,就是dangerous的权限。
我们知道,dangerous权限是需要用户手动确认的,所以要怎样默认授予其对应权限呢?
这个涉及到Framework层的修改。Framework中pm目录下有一个专门负责应用权限授予的类:
frameworks/base/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
里面一个核心方法是grantRuntimePermissionsLPw
。这篇主要做介绍,源码分析下一篇再做。
lang-java
private void grantRuntimePermissionsLPw(PackageParser.Package pkg, Set permissions, boolean systemFixed, boolean isDefaultPhoneOrSms, int userId) {
if (pkg.requestedPermissions.isEmpty()) {
return;
}
List requestedPermissions = pkg.requestedPermissions;
Set grantablePermissions = null;
// If this is the default Phone or SMS app we grant permissions regardless
// whether the version on the system image declares the permission as used since
// selecting the app as the default Phone or SMS the user makes a deliberate
// choice to grant this app the permissions needed to function. For all other
// apps, (default grants on first boot and user creation) we don't grant default
// permissions if the version on the system image does not declare them.
if (!isDefaultPhoneOrSms && pkg.isUpdatedSystemApp()) {
PackageSetting sysPs = mService.mSettings.getDisabledSystemPkgLPr(pkg.packageName);
if (sysPs != null) {
if (sysPs.pkg.requestedPermissions.isEmpty()) {
return;
}
if (!requestedPermissions.equals(sysPs.pkg.requestedPermissions)) {
grantablePermissions = new ArraySet<>(requestedPermissions);
requestedPermissions = sysPs.pkg.requestedPermissions;
}
}
}
final int grantablePermissionCount = requestedPermissions.size();
for (int i = 0; i < grantablePermissionCount; i++) {
String permission = requestedPermissions.get(i);
// If there is a disabled system app it may request a permission the updated
// version ot the data partition doesn't, In this case skip the permission.
if (grantablePermissions != null && !grantablePermissions.contains(permission)) {
continue;
}
if (permissions.contains(permission)) {
final int flags = mService.getPermissionFlags(permission, pkg.packageName, userId);
// If any flags are set to the permission, then it is either set in
// its current state by the system or device/profile owner or the user.
// In all these cases we do not want to clobber the current state.
// Unless the caller wants to override user choices. The override is
// to make sure we can grant the needed permission to the default
// sms and phone apps after the user chooses this in the UI.
if (flags == 0 || isDefaultPhoneOrSms) {
// Never clobber policy or system.
final int fixedFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED
| PackageManager.FLAG_PERMISSION_POLICY_FIXED;
if ((flags & fixedFlags) != 0) {
continue;
}
mService.grantRuntimePermission(pkg.packageName, permission, userId);
if (DEBUG) {
Log.i(TAG, "Granted " + (systemFixed ? "fixed " : "not fixed ")
+ permission + " to default handler " + pkg.packageName);
}
int newFlags = PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
if (systemFixed) {
newFlags |= PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
}
mService.updatePermissionFlags(permission, pkg.packageName,
newFlags, newFlags, userId);
}
// If a component gets a permission for being the default handler A
// and also default handler B, we grant the weaker grant form.
if ((flags & PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT) != 0
&& (flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0
&& !systemFixed) {
if (DEBUG) {
Log.i(TAG, "Granted not fixed " + permission + " to default handler "
+ pkg.packageName);
}
mService.updatePermissionFlags(permission, pkg.packageName,
PackageManager.FLAG_PERMISSION_SYSTEM_FIXED, 0, userId);
}
}
}
}
AppOps
初次接触Appops是因为,现在国内很多App都要过多的权限,比如随便一个App都要访问手机号,不给它权限的话它就退出不让你使用。后来发现,可以通过Appops默默修改权限,比如第一次打开App的时候给它所有权限,然后再用Appops偷偷把权限禁用掉,这样App可以正常打开使用,但它也偷不到对应的信息了。
它的使用也很简单,一条命令的事情:
lang-shell
adb shell appops set com.tencent.gamehelper.smoba READ_EXTERNAL_STORAGE ignore
这样就禁掉了腾讯游戏对设备存储的访问。当然除了这种用法,对于一些特殊权限比如悬浮窗权限的授权也可以使用AppOps来解决。
AppOps 是什么
Appops是Application Operations的简称,是关于应用权限管理的一套方案,但这里的应用指的是系统应用,这些API不对第三方应用开放。Google从4.3开始推出Appops, 但一直到最新的Android N都没有在Settings里面开放Appops的入口,但这套方案却一直在后台默默的运行着。
上面的命令用到了ignore
模式,具体在AppOpsManager.java有4种模式定义:
lang-java
// 允许执行相关权限
public static final int MODE_ALLOWED = 0;
// 表示当前应用没有此权限,如果尝试使用该权限,就会静态地进入失败状态,出现应用莫名其妙crash。
public static final int MODE_IGNORED = 1;
// 表示当前应用没有此权限,并且如果使用此权限会导致SecurityException
public static final int MODE_ERRORED = 2;
// 表示默认值,应该使用其默认的安全检查。这个模式并不常用,它应该和appop权限一起使用,并且调用者必须显式地检查和使用它
public static final int MODE_DEFAULT = 3;
AppOps工作流程
Appops工作框架如下
appops
可以看到Appops的两个重要组成部分是AppOpsManager
和AppOpsService
,它们是典型的客户端和服务端设计,通过Binder跨进程调用。
AppOpsService
是做最终检查的系统服务,它的注册名字是appops,应用可以类似于
lang-java
mAppOps=(AppOpsManager)getContext().getSystemService(Context.APP_OPS_SERVICE);
的方式来获取这个服务。
AppOpsManager
提供了接口,访问AppOpsService的核心方法。
AppOpsService
是在AMS构造函数中启动的:
lang-java
mAppOpsService = new AppOpsService(new File(systemDir, "appops.xml"), mHandler);
mAppOpsService.startWatchingMode(AppOpsManager.OP_RUN_IN_BACKGROUND, null,
new IAppOpsCallback.Stub() {
@Override
public void opChanged(int op, int uid, String packageName) {
if (op == AppOpsManager.OP_RUN_IN_BACKGROUND && packageName != null) {
if (mAppOpsService.checkOperation(op, uid, packageName)
!= AppOpsManager.MODE_ALLOWED) {
runInBackgroundDisabled(uid);
}
}
}
});
从启动来看,该服务创建了一个appops.xml
,这个文件最终位于/data/system/
目录下,用来存储各个app的权限设置和操作信息。
我们可以看一下这个文件,以高德导航为例:
lang-txt
然后设置悬浮窗权限
lang-shell
adb shell appops set com.autonavi.amapauto SYSTEM_ALERT_WINDOW allow
再次查看高德导航的权限
lang-txt
<----------发生了变化
从上面的定义来看,m="0"
指的是mode=MODE_ALLOWED=0
。
n=”24”指的是
lang-java
public static final int OP_SYSTEM_ALERT_WINDOW = 24;
就是定义的悬浮窗权限了。
Api使用
AppOpsManager提供标准的API供APP调用,但google有明确说明,大部分只针对系统应用。但是想使用的话,可以尝试把Android源码里AppOpsManager.java打包一下,把jar包导入自己的工程,就可以使用了。
int checkOp(Stringop, int uid,StringpackageName)
Op对应一个权限操作,该接口来检测应用是否具有该项操作权限。
int noteOp(Stringop, int uid,StringpackageName)
和checkOp基本相同,但是在检验后会做记录。
int checkOpNoThrow(Stringop, int uid,StringpackageName)
和checkOp类似,但是权限错误,不会抛出SecurityException,而是返回AppOpsManager.MODE_ERRORED。
int noteOpNoThrow(Stringop, int uid,StringpackageName)
类似noteOp,但不会抛出SecurityException。
void setMode( int code, int uid, String packageName, int mode)
code代表具体的操作权限,mode代表要更改成的类型(允许/禁止/提示)
比较关键的就是这个setMode方法,比如通过代码设置悬浮窗权限的话,就需要这样:
lang-java
mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
mAppOps.setMode(AppOpsManager.OP_SYSTEM_ALERT_WINDOW, getAppUid(packageGaode), packageGaode, AppOpsManager.MODE_ALLOWED);
private int getAppUid(String packageName) {
int uid = 0;
try {
PackageManager pm = mContext.getPackageManager();
ApplicationInfo ai = pm.getApplicationInfo(packageName, 0);
uid = ai.uid;
} catch (NameNotFoundException e) {
e.printStackTrace();
}
return uid;
}
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
PackageManagerService根据权限等级管理权限流程分析(默认赋予apk权限)
1、PackageManager框架详解
2、PackageManagerService启动分析
3、PackageManagerService之app数据类(Settings)分析
4、PackageManagerService扫描安装apk详解
5、本文PackageManagerService根据权限等级管理权限(默认赋予apk权限)
android权限管理分为两种:
SDK<23:在manifest中声明,安装时赋予所有声明权限,不同意则不安装;
SDK>=23:1、普通权限、声明即可直接赋予,不会在设置中显示给客户;
2、危险权限和特殊权限,需要声明且发送请求用户授权的intent,可自由开关每个权限;
以下主要针对SDK>=23
系统中的定义权限位置在:
framework/base/core/res/AndroidManifest.xml
framework/base/data/etc/platform.xml
一、权限级别(ProtectionLevel)
<permission android:name="android.permission.SEND_SMS"
android:permissionGroup="android.permission-group.COST_MONEY"
android:protectionLevel="dangerous"
android:label="@string/permlab_sendSms"
android:description="@string/permdesc_sendSms" />
protectionLevel定义了权限的级别
1.Normal
2.Dangerous
3.Signature
4.SignatureOrSystem
1.1 Normal
android:protectionLevel="normal"
对用户隐私或者安全都不会带来影响
1.2 Dangerous
android:protectionLevel="dangerous"
需要在mainifest中声明且发送请求用户授权的intent,赋予某个组中的其中一个权限,自动赋予组内其他所有权限;
可用命令adb shell pm list permissions -d -g查看
1.3 Signature
android:protectionLevel="signature"
两类应用可使用
1.只有和定义了这个权限的apk用相同的私钥签名的应用才可以申请该权限
2.与系统签名相同的system app,即与厂商签名(厂商ROM中的系统app签名)相同的app
1.4 SignatureOrSystem
android:protectionLevel="signature|privileged"
三类应用可使用
1.只有和定义了这个权限的apk用相同的私钥签名的应用才可以申请该权限
2.与系统签名相同的app,即与厂商签名(厂商ROM中的系统app签名)相同的app
3.任意app只要标记了privileged(可暂理解为放到了/system/priv-app)就可以使用signatureOrSystem级别的权限
1.5 install权限和runtime权限
install权限:
安装时权限,是指在安装app的时候,赋予app的权限。normal和signature级别(包括SignatureOrSystem)的权限都是安装时权限。不会给用户提示界面,系统自动决定权限的赋予或拒绝。
runtime权限:
运行时权限,是指在app运行过程中,赋予app的权限。这个过程中,会显示明显的权限授予界面,让用户决定是否授予权限。dangerous权限就是运行时权限。(以下主要针对SDK<23时dangerous权限就变成了install权限)
二、app种类
1、system app (有ApplicationInfo.FLAG_SYSTEM标记)
2、privileged app (有ApplicationInfo.FLAG_SYSTEM和ApplicationInfo.PRIVATE_FLAG_PRIVILEGE两个标记)
2.1 system app
system app 定义很明了,就是在PMS初始化安装app的时候赋予了ApplicationInfo.FLAG_SYSTEM这个标记
1、特定shareUID的app
代码在PMS的构造函数中
mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.phone", RADIO_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.log", LOG_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.nfc", NFC_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.bluetooth", BLUETOOTH_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.shell", SHELL_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
2、扫描安装特定目录的app
代码在PMS的构造函数中,扫描安装时给予PackageParser.PARSE_IS_SYSTEM标记的app,例如/vendor/overlay,/system/framework,/system/priv-app,/system/app,/vendor/app,/oem/app等,给予的PackageParser.PARSE_IS_SYSTEM最终会转换为ApplicationInfo.FLAG_SYSTEM,部分代码如下
File vendorOverlayDir = new File(VENDOR_OVERLAY_DIR);
scanDirTracedLI(vendorOverlayDir, mDefParseFlags
| PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR
| PackageParser.PARSE_TRUSTED_OVERLAY, scanFlags | SCAN_TRUSTED_OVERLAY, 0);
File customFrameworkDir = new File("/custom/framework");
scanDirLI(customFrameworkDir, PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR,
scanFlags | SCAN_NO_DEX, 0);
scanDirTracedLI(frameworkDir, mDefParseFlags
| PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR
| PackageParser.PARSE_IS_PRIVILEGED,
scanFlags | SCAN_NO_DEX, 0);
flag转换的过程大致如下
-> scanDirLI(File dir, final int parseFlags, int scanFlags, long currentTime)
-> scanPackageTracedLI(PackageParser.Package pkg, final int policyFlags,
int scanFlags, long currentTime, UserHandle user)
-> scanPackageLI(PackageParser.Package pkg, final int policyFlags,
int scanFlags, long currentTime, UserHandle user)
-> scanPackageDirtyLI(PackageParser.Package pkg,
final int policyFlags, final int scanFlags, long currentTime, UserHandle user)
//在scanPackageDirtyLI方法中将flag转换
// Apply policy
if ((policyFlags&PackageParser.PARSE_IS_SYSTEM) != 0) {
pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
...
}
2.2 privileged app
privileged app在ApplicationInfo.FLAG_SYSTEM基础上还必须有ApplicationInfo.PRIVATE_FLAG_PRIVILEGED标记
1、特定shareUID的app
特定shareUID的app有ApplicationInfo.FLAG_SYSTEM的同时都有ApplicationInfo.PRIVATE_FLAG_PRIVILEGED
2、扫描特定目录app时,给予了PackageParser.PARSE_IS_SYSTEM标记和PackageParser.PARSE_IS_PRIVILEGED标记,目录有三个:system/framework,system/priv-app,vendor/priv-app
例如:
//vender/framework目录下有PackageParser.PARSE_IS_SYSTEM没有PackageParser.PARSE_IS_PRIVILEGED标记
File vendorFrameworkDir = new File(Environment.getVendorDirectory(), "framework");
scanDirTracedLI(vendorFrameworkDir, PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR,
scanFlags | SCAN_NO_DEX, 0);
//system/framework目录下两个标记都有
scanDirTracedLI(frameworkDir, mDefParseFlags
| PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR
| PackageParser.PARSE_IS_PRIVILEGED,
scanFlags | SCAN_NO_DEX, 0);
PackageParser.PARSE_IS_PRIVILEGED也是在scanPackageDirtyLI方法中转换的
if ((policyFlags&PackageParser.PARSE_IS_PRIVILEGED) != 0) {
pkg.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
}
三、PMS赋予apk的runtime权限
3.1 入口
PMS(PackageManagerService)在SystemServer中初始化完成最后调用PMS::systemReady,在systemReady方法中开始授予默认权限
final DefaultPermissionGrantPolicy mDefaultPermissionPolicy;
for (int userId : grantPermissionsUserIds) {
mDefaultPermissionPolicy.grantDefaultPermissions(userId);
}
DefaultPermissionGrantPolicy这个类就是管理默认权限的,最终的实现都是在PMS代码中
public void grantDefaultPermissions(int userId) {
// 系统组件赋予Dangerous权限
grantPermissionsToSysComponentsAndPrivApps(userId);
// 指定app的指定权限
grantDefaultSystemHandlerPermissions(userId);
}
3.2 系统组件赋予Dangerous权限
private void grantPermissionsToSysComponentsAndPrivApps(int userId) {
Log.i(TAG, "Granting permissions to platform components for user " + userId);
synchronized (mService.mPackages) {
for (PackageParser.Package pkg : mService.mPackages.values()) {
// 过滤掉privileged app , FLAG_PERSISTENT标记app ,系统签名相同的app
if (!isSysComponentOrPersistentPlatformSignedPrivAppLPr(pkg)
// 判断targetSdkVersion > 22
|| !doesPackageSupportRuntimePermissions(pkg)
// 请求权限列表为空
|| pkg.requestedPermissions.isEmpty()) {
continue;
}
Set<String> permissions = new ArraySet<>();
final int permissionCount = pkg.requestedPermissions.size();
for (int i = 0; i < permissionCount; i++) {
String permission = pkg.requestedPermissions.get(i);
BasePermission bp = mService.mSettings.mPermissions.get(permission);
// 权限为Dangerous权限
if (bp != null && bp.isRuntime()) {
permissions.add(permission);
}
}
if (!permissions.isEmpty()) {
// 添加权限
grantRuntimePermissionsLPw(pkg, permissions, true, userId);
}
}
}
}
接下来看下isSysComponentOrPersistentPlatformSignedPrivAppLPr和isRuntime的内容
private boolean isSysComponentOrPersistentPlatformSignedPrivAppLPr(PackageParser.Package pkg) {
// UserID 小于10000
if (UserHandle.getAppId(pkg.applicationInfo.uid) < FIRST_APPLICATION_UID) {
return true;
}
// privileged app过滤
if (!pkg.isPrivilegedApp()) {
return false;
}
// 这两个判断过滤掉FLAG_PERSISTENT
PackageSetting sysPkg = mService.mSettings.getDisabledSystemPkgLPr(pkg.packageName);
if (sysPkg != null && sysPkg.pkg != null) {
if ((sysPkg.pkg.applicationInfo.flags & ApplicationInfo.FLAG_PERSISTENT) == 0) {
return false;
}
} else if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_PERSISTENT) == 0) {
return false;
}
// 过滤掉系统签名相同的apk
return PackageManagerService.compareSignatures(mService.mPlatformPackage.mSignatures,
pkg.mSignatures) == PackageManager.SIGNATURE_MATCH;
}
public boolean isRuntime() {
// protectionLevel & PermissionInfo.PROTECTION_MASK_BASE 表示当前等级
return (protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
== PermissionInfo.PROTECTION_DANGEROUS;
}
3.3 指定app的指定权限
这里是给指定某个app,给予它指定的几个权限,这里的代码十分类似,截取片段如下
private void grantDefaultSystemHandlerPermissions(int userId) {
...
// Dialer
if (dialerAppPackageNames == null) {
Intent dialerIntent = new Intent(Intent.ACTION_DIAL);
PackageParser.Package dialerPackage = getDefaultSystemHandlerActivityPackageLPr(
dialerIntent, userId);
if (dialerPackage != null) {
grantDefaultPermissionsToDefaultSystemDialerAppLPr(dialerPackage, userId);
}
} else {
for (String dialerAppPackageName : dialerAppPackageNames) {
PackageParser.Package dialerPackage = getSystemPackageLPr(dialerAppPackageName);
if (dialerPackage != null) {
grantDefaultPermissionsToDefaultSystemDialerAppLPr(dialerPackage, userId);
}
}
}
// SMS
if (smsAppPackageNames == null) {
Intent smsIntent = new Intent(Intent.ACTION_MAIN);
smsIntent.addCategory(Intent.CATEGORY_APP_MESSAGING);
PackageParser.Package smsPackage = getDefaultSystemHandlerActivityPackageLPr(
smsIntent, userId);
if (smsPackage != null) {
grantDefaultPermissionsToDefaultSystemSmsAppLPr(smsPackage, userId);
}
} else {
for (String smsPackageName : smsAppPackageNames) {
PackageParser.Package smsPackage = getSystemPackageLPr(smsPackageName);
if (smsPackage != null) {
grantDefaultPermissionsToDefaultSystemSmsAppLPr(smsPackage, userId);
}
}
}
...
}
private void grantDefaultPermissionsToDefaultSystemDialerAppLPr(
PackageParser.Package dialerPackage, int userId) {
if (doesPackageSupportRuntimePermissions(dialerPackage)) {
boolean isPhonePermFixed =
mService.hasSystemFeature(PackageManager.FEATURE_WATCH, 0);
grantRuntimePermissionsLPw(
dialerPackage, PHONE_PERMISSIONS, isPhonePermFixed, userId);
grantRuntimePermissionsLPw(dialerPackage, CONTACTS_PERMISSIONS, userId);
grantRuntimePermissionsLPw(dialerPackage, SMS_PERMISSIONS, userId);
grantRuntimePermissionsLPw(dialerPackage, MICROPHONE_PERMISSIONS, userId);
}
}
两种添加最后都走到了grantRuntimePermissionsLPw,我们接着分析grantRuntimePermissionsLPw函数
3.4 添加权限grantRuntimePermissionsLPw
grantRuntimePermissionsLPw赋予权限的代码,最终是交给PMS来处理,经过一些列判断后调用关键方法mService.grantRuntimePermission和mService.updatePermissionFlags,代码片段如下
private final PackageManagerService mService;
private void grantRuntimePermissionsLPw(PackageParser.Package pkg, Set<String> permissions,
boolean systemFixed, boolean isDefaultPhoneOrSms, int userId) {
...
mService.grantRuntimePermission(pkg.packageName, permission, userId);
...
mService.updatePermissionFlags(permission, pkg.packageName,
newFlags, newFlags, userId);
...
}
接着看PMS的grantRuntimePermission如何添加权限
public void grantRuntimePermission(String packageName, String name, final int userId) {
...
// 要添加权限,也需要“添加”权限
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
"grantRuntimePermission");
enforceCrossUserPermission(Binder.getCallingUid(), userId,
true /* requireFullPermission */, true /* checkShell */,
"grantRuntimePermission");
...
// 添加权限
final int result = permissionsState.grantRuntimePermission(bp, userId);
switch (result) {
case PermissionsState.PERMISSION_OPERATION_FAILURE: {
return;
}
case PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED: {
final int appId = UserHandle.getAppId(pkg.applicationInfo.uid);
mHandler.post(new Runnable() {
@Override
public void run() {
killUid(appId, userId, KILL_APP_REASON_GIDS_CHANGED);
}
});
}
break;
}
// PermissionsChanged监听
mOnPermissionChangeListeners.onPermissionsChanged(uid);
// 把数据更新到runtime-permissions.xml中
mSettings.writeRuntimePermissionsForUserLPr(userId, false);
}
最后grantRuntimePermission就是把permission存到mPermissions数据map中,再把数据跟新到/data/system/users/0/runtime-permissions.xml中,片段如下
<pkg name="com.google.android.apps.messaging">
<item name="android.permission.READ_SMS" granted="true" flags="20" />
<item name="android.permission.RECEIVE_WAP_PUSH" granted="true" flags="20" />
<item name="android.permission.RECEIVE_MMS" granted="true" flags="20" />
<item name="android.permission.RECEIVE_SMS" granted="true" flags="20" />
<item name="android.permission.READ_PHONE_STATE" granted="true" flags="20" />
<item name="android.permission.SEND_SMS" granted="true" flags="20" />
<item name="android.permission.CALL_PHONE" granted="true" flags="20" />
<item name="android.permission.WRITE_CONTACTS" granted="true" flags="20" />
<item name="android.permission.READ_CONTACTS" granted="true" flags="20" />
</pkg>
<pkg name="com.google.android.setupwizard">
<item name="android.permission.ACCESS_COARSE_LOCATION" granted="true" flags="20" />
<item name="android.permission.CALL_PHONE" granted="true" flags="20" />
<item name="android.permission.WRITE_CONTACTS" granted="true" flags="20" />
<item name="android.permission.CAMERA" granted="true" flags="20" />
<item name="android.permission.PROCESS_OUTGOING_CALLS" granted="true" flags="20" />
<item name="android.permission.READ_CONTACTS" granted="true" flags="20" />
</pkg>
四、PMS赋予apk安装权限
过程精简如下:
-> scanDirLI(File dir, final int parseFlags, int scanFlags, long currentTime)
-> scanPackageTracedLI(PackageParser.Package pkg, final int policyFlags,
int scanFlags, long currentTime, UserHandle user)
-> scanPackageLI(PackageParser.Package pkg, final int policyFlags,
int scanFlags, long currentTime, UserHandle user)
-> scanPackageDirtyLI(PackageParser.Package pkg,
final int policyFlags, final int scanFlags, long currentTime, UserHandle user)
-> updateSettingsLI(PackageParser.Package newPackage, String installerPackageName,
int[] allUsers, PackageInstalledInfo res, UserHandle user)
-> updateSettingsInternalLI(PackageParser.Package newPackage,
String installerPackageName, int[] allUsers, int[] installedForUsers,
PackageInstalledInfo res, UserHandle user)
-> updatePermissionsLPw(String changingPkg, PackageParser.Package pkgInfo,
int flags)
-> updatePermissionsLPw(String changingPkg,
PackageParser.Package pkgInfo, String replaceVolumeUuid, int flags)
-> grantPermissionsLPw(PackageParser.Package pkg, boolean replace,
String packageOfInterest)
在grantPermissionsLPw函数中做最后权限赋予操作,代码片段如下:
private void grantPermissionsLPw(PackageParser.Package pkg, boolean replace,
String packageOfInterest) {
for (int i=0; i<N; i++) {
final String name = pkg.requestedPermissions.get(i); // 权限名字
final BasePermission bp = mSettings.mPermissions.get(name); // 权限信息
...
final int level = bp.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE; // 权限级别ProtectionLevel
final boolean appSupportsRuntimePermissions = pkg.applicationInfo.targetSdkVersion
>= Build.VERSION_CODES.M;
// 根据权限级别定义grant
switch (level) {
case PermissionInfo.PROTECTION_NORMAL: {
// For all apps normal permissions are install time ones.
grant = GRANT_INSTALL;
} break;
case PermissionInfo.PROTECTION_DANGEROUS: {
if (!appSupportsRuntimePermissions && !Build.isPermissionReviewRequired()) {
// For legacy apps dangerous permissions are install time ones.
grant = GRANT_INSTALL;
} else if (origPermissions.hasInstallPermission(bp.name)) {
// For legacy apps that became modern, install becomes runtime.
grant = GRANT_UPGRADE;
} else if (mPromoteSystemApps
&& isSystemApp(ps)
&& mExistingSystemPackages.contains(ps.name)) {
// For legacy system apps, install becomes runtime.
// We cannot check hasInstallPermission() for system apps since those
// permissions were granted implicitly and not persisted pre-M.
grant = GRANT_UPGRADE;
} else {
// For modern apps keep runtime permissions unchanged.
grant = GRANT_RUNTIME;
}
} break;
case PermissionInfo.PROTECTION_SIGNATURE: {
// For all apps signature permissions are install time ones.
allowedSig = grantSignaturePermission(perm, pkg, bp, origPermissions);
if (allowedSig) {
grant = GRANT_INSTALL;
}
} break;
}
...
if (grant != GRANT_DENIED) {
if (!isSystemApp(ps) && ps.installPermissionsFixed) {
// If this is an existing, non-system package, then
// we can't add any new permissions to it.
if (!allowedSig && !origPermissions.hasInstallPermission(perm)) {
// Except... if this is a permission that was added
// to the platform (note: need to only do this when
// updating the platform).
if (!isNewPlatformPermissionForPackage(perm, pkg)) {
grant = GRANT_DENIED;
}
}
}
// 根据grant 选择权限给予方式
switch (grant) {
case GRANT_INSTALL: {
for (int userId : UserManagerService.getInstance().getUserIds()) {
if (origPermissions.getRuntimePermissionState(
bp.name, userId) != null) {
// Revoke the runtime permission and clear the flags.
origPermissions.revokeRuntimePermission(bp, userId);
origPermissions.updatePermissionFlags(bp, userId,
PackageManager.MASK_PERMISSION_FLAGS, 0);
// If we revoked a permission permission, we have to write.
changedRuntimePermissionUserIds = ArrayUtils.appendInt(
changedRuntimePermissionUserIds, userId);
}
}
// install权限
if (permissionsState.grantInstallPermission(bp) !=
PermissionsState.PERMISSION_OPERATION_FAILURE) {
changedInstallPermission = true;
}
} break;
case GRANT_RUNTIME: {
// Grant previously granted runtime permissions.
for (int userId : UserManagerService.getInstance().getUserIds()) {
PermissionState permissionState = origPermissions
.getRuntimePermissionState(bp.name, userId);
int flags = permissionState != null
? permissionState.getFlags() : 0;
if (origPermissions.hasRuntimePermission(bp.name, userId)) {
if (permissionsState.grantRuntimePermission(bp, userId) ==
PermissionsState.PERMISSION_OPERATION_FAILURE) {
// If we cannot put the permission as it was, we have to write.
changedRuntimePermissionUserIds = ArrayUtils.appendInt(
changedRuntimePermissionUserIds, userId);
}
// If the app supports runtime permissions no need for a review.
/// M: CTA requirement - permission control
if (Build.isPermissionReviewRequired()
&& appSupportsRuntimePermissions
&& (flags & PackageManager
.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
flags &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
// Since we changed the flags, we have to write.
changedRuntimePermissionUserIds = ArrayUtils.appendInt(
changedRuntimePermissionUserIds, userId);
}
/// M: CTA requirement - permission control
} else if (Build.isPermissionReviewRequired()
&& !appSupportsRuntimePermissions) {
if (CtaUtils.isPlatformPermission(bp.sourcePackage, bp.name)
&& pkgReviewRequired) {
if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) == 0) {
/// M: CTA requirement - review UI for all apps @{
Slog.d(TAG, "add review UI for legacy pkg = " +
pkg.packageName + ", permission = " +
bp.name + ", userId = " + userId +
", uid = " + pkg.mSharedUserId);
///@}
flags |= FLAG_PERMISSION_REVIEW_REQUIRED;
// We changed the flags, hence have to write.
changedRuntimePermissionUserIds = ArrayUtils.appendInt(
changedRuntimePermissionUserIds, userId);
}
}
// runtime权限
if (permissionsState.grantRuntimePermission(bp, userId)
!= PermissionsState.PERMISSION_OPERATION_FAILURE) {
// We changed the permission, hence have to write.
changedRuntimePermissionUserIds = ArrayUtils.appendInt(
changedRuntimePermissionUserIds, userId);
}
/// M: CTA requirement - review UI for all apps @{
} else if (appSupportsRuntimePermissions &&
pkgReviewRequired) {
if (CtaUtils.isPlatformPermission(bp.sourcePackage, bp.name)) {
if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) == 0 &&
(flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED)
== 0) {
Slog.d(TAG, "add review UI for non-legacy pkg = " +
pkg.packageName + ", permission = " +
bp.name + ", userId = " + userId +
", uid = " + pkg.mSharedUserId);
flags |= FLAG_PERMISSION_REVIEW_REQUIRED;
// We changed the flags, hence have to write.
changedRuntimePermissionUserIds = ArrayUtils.appendInt(
changedRuntimePermissionUserIds, userId);
}
}
}
///@}
// Propagate the permission flags.
permissionsState.updatePermissionFlags(bp, userId, flags, flags);
}
} break;
case GRANT_UPGRADE: {
// Grant runtime permissions for a previously held install permission.
PermissionState permissionState = origPermissions
.getInstallPermissionState(bp.name);
final int flags = permissionState != null ? permissionState.getFlags() : 0;
if (origPermissions.revokeInstallPermission(bp)
!= PermissionsState.PERMISSION_OPERATION_FAILURE) {
// 跟新应用的权限
origPermissions.updatePermissionFlags(bp, UserHandle.USER_ALL,
PackageManager.MASK_PERMISSION_FLAGS, 0);
changedInstallPermission = true;
}
// If the permission runtime-permissions.xmlis not to be promoted to runtime we ignore it and
// also its other flags as they are not applicable to install permissions.
if ((flags & PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE) == 0) {
for (int userId : currentUserIds) {
if (permissionsState.grantRuntimePermission(bp, userId) !=
PermissionsState.PERMISSION_OPERATION_FAILURE) {
// Transfer the permission flags.
permissionsState.updatePermissionFlags(bp, userId,
flags, flags);
// If we granted the permission, we have to write.
changedRuntimePermissionUserIds = ArrayUtils.appendInt(
changedRuntimePermissionUserIds, userId);
}
}
}
} break;
default: {
if (packageOfInterest == null
|| packageOfInterest.equals(pkg.packageName)) {
Slog.w(TAG, "Not granting permission " + perm
+ " to package " + pkg.packageName
+ " because it was previously installed without");
}
} break;
}
} else {
if (permissionsState.revokeInstallPermission(bp) !=
PermissionsState.PERMISSION_OPERATION_FAILURE) {
// Also drop the permission flags.
permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL,
PackageManager.MASK_PERMISSION_FLAGS, 0);
changedInstallPermission = true;
Slog.i(TAG, "Un-granting permission " + perm
+ " from package " + pkg.packageName
+ " (protectionLevel=" + bp.protectionLevel
+ " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags)
+ ")");
} else if ((bp.protectionLevel&PermissionInfo.PROTECTION_FLAG_APPOP) == 0) {
// Don't print warning for app op permissions, since it is fine for them
// not to be granted, there is a UI for the user to decide.
if (packageOfInterest == null || packageOfInterest.equals(pkg.packageName)) {
Slog.w(TAG, "Not granting permission " + perm
+ " to package " + pkg.packageName
+ " (protectionLevel=" + bp.protectionLevel
+ " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags)
+ ")");
}
}
}
}
if ((changedInstallPermission || replace) && !ps.installPermissionsFixed &&
!isSystemApp(ps) || isUpdatedSystemApp(ps)){
// This is the first that we have heard about this package, so the
// permissions we have now selected are fixed until explicitly
// changed.
ps.installPermissionsFixed = true;
}
// 把runtime权限跟新到runtime-permissions.xml中
for (int userId : changedRuntimePermissionUserIds) {
mSettings.writeRuntimePermissionsForUserLPr(userId, runtimePermissionsRevoked);
}
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
grantPermissionsLPw函数根据权限等级,来赋予权限。注意:这里的安装时赋予的runtime权限是之前已经赋予过此apk的权限才会执行到GRANT_RUNTIME
回到上面的精简过程:
updateSettingsInternalLI方法中调用updatePermissionsLPw后,最后会调用mSettings.writeLPr();
这些权限信息就被写到data/system/packages.xml中了,举例片段代码:
...
<package name="com.android.settings" codePath="/system/priv-app/Settings" nativeLibraryPath="/system/priv-app/Settings/lib" primaryCpuAbi="arm64-v8a" publicFlags="944258629" privateFlags="8" ft="15f7a5ec8a8" it="15f7a5ec8a8" ut="15f7a5ec8a8" version="25" sharedUserId="1000" isOrphaned="true">
<sigs count="1">
<cert index="1" />
</sigs>
<perms>
<item name="android.permission.BIND_INCALL_SERVICE" granted="true" flags="0" />
<item name="android.permission.WRITE_SETTINGS" granted="true" flags="0" />
<item name="com.google.android.gm.permission.WRITE_GMAIL" granted="true" flags="0" />
<item name="android.permission.CONFIGURE_WIFI_DISPLAY" granted="true" flags="0" />
<item name="android.permission.CONFIGURE_DISPLAY_COLOR_MODE" granted="true" flags="0" />
<item name="android.permission.ACCESS_WIMAX_STATE" granted="true" flags="0" />
<item name="com.qualcomm.permission.READPROC" granted="true" flags="0" />
<item name="android.permission.RESTART_PACKAGES" granted="true" flags="0" />
<item name="android.permission.USE_CREDENTIALS" granted="true" flags="0" />
<item name="android.permission.MODIFY_AUDIO_SETTINGS" granted="true" flags="0" />
<item name="android.permission.ACCESS_CHECKIN_PROPERTIES" granted="true" flags="0" />
<item name="android.permission.MODIFY_AUDIO_ROUTING" granted="true" flags="0" />
...
五、小结
1、PMS扫描安装时赋予安装权限
2、PMS在systemReady函数中调用mDefaultPermissionPolicy.grantDefaultPermissions来赋予运行权限即dangerous权限
权限的等级划分,大大的提高了安卓的安全,比以往的一刀切着实好了很多。
====================================================================
Android P包管理机制之PackageManagerService授予权限流程解析
PackageManagerService中默认给系统app授予相关需要的权限。
PackageManagerService中的systemReady是在SystemServer中执行的。
\frameworks\base\services\java\com\android\server\SystemServer.java
/**
* Starts a miscellaneous grab bag of stuff that has yet to be refactored
* and organized.
*/
private void startOtherServices() {
//省略一部分代码
//...
traceBeginAndSlog("MakePackageManagerServiceReady");
mPackageManagerService.systemReady();
traceEnd();
//省略一部分代码
//...
}
\frameworks\base\services\core\java\com\android\server\pm\PackageManagerService.java
@Override
public void systemReady() {
int[] grantPermissionsUserIds = EMPTY_INT_ARRAY;
synchronized (mPackages) {
// Verify that all of the preferred activity components actually
// exist. It is possible for applications to be updated and at
// that point remove a previously declared activity component that
// had been set as a preferred activity. We try to clean this up
// the next time we encounter that preferred activity, but it is
// possible for the user flow to never be able to return to that
// situation so here we do a sanity check to make sure we haven't
// left any junk around.
ArrayList<PreferredActivity> removed = new ArrayList<PreferredActivity>();
for (int i=0; i<mSettings.mPreferredActivities.size(); i++) {
PreferredIntentResolver pir = mSettings.mPreferredActivities.valueAt(i);
removed.clear();
for (PreferredActivity pa : pir.filterSet()) {
if (mActivities.mActivities.get(pa.mPref.mComponent) == null) {
removed.add(pa);
}
}
if (removed.size() > 0) {
for (int r=0; r<removed.size(); r++) {
PreferredActivity pa = removed.get(r);
Slog.w(TAG, "Removing dangling preferred activity: "
+ pa.mPref.mComponent);
pir.removeFilter(pa);
}
mSettings.writePackageRestrictionsLPr(
mSettings.mPreferredActivities.keyAt(i));
}
}
for (int userId : UserManagerService.getInstance().getUserIds()) {
/* 为了方便授予权限修改了此处,注释掉了grantPermissionsUserIds初始化的条件
* areDefaultRuntimePermissionsGrantedLPr(userId)的判断条件的含义:
* true,正常启动,没有发生升级的情况
* false,表示升级后第一次启动
* 所以,该出的意思是,只有升级后第一次启动,发生了升级,才满足条件,执行赋予权限grantPermissionsUserIds
*/
//modify by sunxiaolin 20190912
//if (!mSettings.areDefaultRuntimePermissionsGrantedLPr(userId)) {
grantPermissionsUserIds = ArrayUtils.appendInt(grantPermissionsUserIds, userId);
//}
}
}
sUserManager.systemReady();
/* 跟上面的grantPermissionsUserIds条件有关,没有发生升级,grantPermissionsUserIds 为null,不执行 grantDefaultPermissions赋予权限
* 发生升级,才执行mDefaultPermissionPolicy.grantDefaultPermissions(userId),执行默认授予权限的处理
*/
// If we upgraded grant all default permissions before kicking off.
for (int userId : grantPermissionsUserIds) {
Log.d(TAG, "sunxiaolin,systemReady,grantDefaultPermissions,userId:" + userId);
mDefaultPermissionPolicy.grantDefaultPermissions(userId);
}
/*
* 没有发生升级,grantPermissionsUserIds 为null
* mDefaultPermissionPolicy.scheduleReadDefaultPermissionExceptions()为执行读取默认权限xml的处理
* xml路径为:/system/etc/default-permissions/default-car-permissions.xml
*/
if (grantPermissionsUserIds == EMPTY_INT_ARRAY) {
// If we did not grant default permissions, we preload from this the
// default permission exceptions lazily to ensure we don't hit the
// disk on a new user creation.
Log.d(TAG, "sunxiaolin,systemReady,grantPermissionsUserIds == EMPTY_INT_ARRAY");
mDefaultPermissionPolicy.scheduleReadDefaultPermissionExceptions();
}
}
授予权限的处理主要是在DefaultPermissionGrantPolicy中处理的。
\frameworks\base\services\core\java\com\android\server\pm\permission\DefaultPermissionGrantPolicy.java
public void grantDefaultPermissions(int userId) {
//给系统组件和priv-app应用授予权限
grantPermissionsToSysComponentsAndPrivApps(userId);
//给系统中重要的应用包授予权限
grantDefaultSystemHandlerPermissions(userId);
//处理权限授予异常的情况,会去读default-car-permissions.xml的权限配置
grantDefaultPermissionExceptions(userId);
}
不管grantPermissionsToSysComponentsAndPrivApps,grantDefaultSystemHandlerPermissions还是grantDefaultPermissionExceptions,最终都是执行了grantRuntimePermissions()方法,在grantRuntimePermissions中执行授权操作。
关于给第三方应用授权问题:
private void grantDefaultPermissionExceptions(int userId) {
mHandler.removeMessages(MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS);
synchronized (mLock) {
// mGrantExceptions is null only before the first read and then
// it serves as a cache of the default grants that should be
// performed for every user. If there is an entry then the app
// is on the system image and supports runtime permissions.
if (mGrantExceptions == null) {
mGrantExceptions = readDefaultPermissionExceptionsLocked();
}
}
Set<String> permissions = null;
final int exceptionCount = mGrantExceptions.size();
for (int i = 0; i < exceptionCount; i++) {
String packageName = mGrantExceptions.keyAt(i);
//modify by sunxiaolin 20190912
//PackageParser.Package pkg = getSystemPackage(packageName);
PackageParser.Package pkg = getPackage(packageName);
List<DefaultPermissionGrant> permissionGrants = mGrantExceptions.valueAt(i);
final int permissionGrantCount = permissionGrants.size();
for (int j = 0; j < permissionGrantCount; j++) {
DefaultPermissionGrant permissionGrant = permissionGrants.get(j);
if (permissions == null) {
permissions = new ArraySet<>();
} else {
permissions.clear();
}
permissions.add(permissionGrant.name);
grantRuntimePermissions(pkg, permissions,permissionGrant.fixed, userId);
}
}
}
修改:
PackageParser.Package pkg = getSystemPackage(packageName);
为:
PackageParser.Package pkg = getPackage(packageName);
private void parseExceptions(XmlPullParser parser, Map<String, List<DefaultPermissionGrant>>
outGrantExceptions) throws IOException, XmlPullParserException {
final int outerDepth = parser.getDepth();
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
continue;
}
if (TAG_EXCEPTION.equals(parser.getName())) {
String packageName = parser.getAttributeValue(null, ATTR_PACKAGE);
Log.i(TAG, "sunxiaolin,parseExceptions packageName:" + packageName);
List<DefaultPermissionGrant> packageExceptions =
outGrantExceptions.get(packageName);
if (packageExceptions == null) {
// The package must be on the system image
//modify by sunxiaolin getSystemPackage()-->getPackage()
PackageParser.Package pkg = getPackage(packageName);
//PackageParser.Package pkg = getSystemPackage(packageName);
if (pkg == null) {
Log.w(TAG, "Unknown package:" + packageName);
XmlUtils.skipCurrentTag(parser);
continue;
}
// The package must support runtime permissions
if (!doesPackageSupportRuntimePermissions(pkg)) {
Log.w(TAG, "Skipping non supporting runtime permissions package:" + packageName);
XmlUtils.skipCurrentTag(parser);
continue;
}
packageExceptions = new ArrayList<>();
outGrantExceptions.put(packageName, packageExceptions);
}
Log.i(TAG, "sunxiaolin,parseExceptions packageName:" + packageName);
parsePermission(parser, packageExceptions);
} else {
Log.e(TAG, "Unknown tag " + parser.getName() + "under <exceptions>");
}
}
}
修改:
PackageParser.Package pkg = getSystemPackage(packageName);
为:
PackageParser.Package pkg = getPackage(packageName);
默认权限文件xml解析
private File[] getDefaultPermissionFiles() {
ArrayList<File> ret = new ArrayList<File>();
File dir = new File(Environment.getRootDirectory(), "etc/default-permissions");
if (dir.isDirectory() && dir.canRead()) {
Collections.addAll(ret, dir.listFiles());
}
dir = new File(Environment.getVendorDirectory(), "etc/default-permissions");
if (dir.isDirectory() && dir.canRead()) {
Collections.addAll(ret, dir.listFiles());
}
dir = new File(Environment.getOdmDirectory(), "etc/default-permissions");
if (dir.isDirectory() && dir.canRead()) {
Collections.addAll(ret, dir.listFiles());
}
dir = new File(Environment.getProductDirectory(), "etc/default-permissions");
if (dir.isDirectory() && dir.canRead()) {
Collections.addAll(ret, dir.listFiles());
}
// For IoT devices, we check the oem partition for default permissions for each app.
if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_EMBEDDED, 0)) {
dir = new File(Environment.getOemDirectory(), "etc/default-permissions");
if (dir.isDirectory() && dir.canRead()) {
Collections.addAll(ret, dir.listFiles());
}
}
return ret.isEmpty() ? null : ret.toArray(new File[0]);
}
default-car-permissions.xml中添加应用权限方法:
packages/services/Car/car_product/build/default-car-permissions.xml:system/etc/default-permissions/default-car-permissions.xml
packages\services\Car\car_product\build\default-car-permissions.xml
<exceptions>
<!-- This is an example of an exception:
<exception
package="foo.bar.permission"
<permission name="android.permission.READ_CONTACTS" fixed="true"/>
<permission name="android.permission.READ_CALENDAR" fixed="false"/>
</exception>
-->
<exception
package="com.android.car.messenger">
<!-- Contacts -->
<permission name="android.permission.READ_CONTACTS" fixed="false"/>
<!-- SMS -->
<permission name="android.permission.SEND_SMS" fixed="false"/>
<permission name="android.permission.READ_SMS" fixed="false"/>
</exception>
</exceptions>
============================================================================
App授予默认权限不需要权限弹框问题解决
给App默认授予权限的方法。
自从android 6.0之后,安卓加强了对权限的管理。上层App必须通过弹框申请权限,来让用户去选择时候授予选择。
但是在实际开发中,我们希望,app安装之后就可以直接使用,默认获取相关的权限,而不需要弹框请求权限。
第一种方法
将targetSdkVersion改成小于23,因为从23后,开始进行了权限限制
defaultConfig {
applicationId "com.ad.audiotest"
minSdkVersion 19
targetSdkVersion 22
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
测试可行。
当targetSdkVersion>=23时,则无法默认获取权限。
第二种方法
frameworks/base/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
frameworks/base/core/res/res/values/arrays.xml
frameworks/base/core/res/res/values/symbols.xml
frameworks/base/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
在DefaultPermissionGrantPolicy.java中,加入:
private void grantDefaultSystemHandlerPermissions(int userId) {
//省略一部分代码
//...
//add by sunxiaolin start 20190905
// add Storage permission
grantStoragePermissionsToCustomApp(userId);
//add by sunxiaolin end 20190905
//省略一部分代码
//...
}
//add by sunxiaolin start 20190905
private void grantStoragePermissionsToCustomApp(int userId){
Log.i(TAG, "sunxiaolin,grantStoragePermissionsToCustomApp userId=" + userId);
final String[] itemString = mContext.getResources()
.getStringArray(com.android.internal.R.array.storage_permission_custom_packagename);
for (int i = 0; i < itemString.length; i++) {
PackageParser.Package customPackage = getPackage(itemString[i]);
Log.i(TAG, "sunxiaolin,grantStoragePermissionsToCustomApp customPackage=" + customPackage);
if ((customPackage != null) && doesPackageSupportRuntimePermissions(customPackage)) {
grantRuntimePermissions(customPackage, STORAGE_PERMISSIONS, userId);
}
}
}
//add by sunxiaolin end 20190905
重点: grantRuntimePermissions(customPackage, STORAGE_PERMISSIONS, userId);中给app授权STORAGE_PERMISSIONS权限。
frameworks/base/core/res/res/values/arrays.xml
<!-- Add by sunxiaolin start 20190905 -->
<string-array name="storage_permission_custom_packagename">
<item>com.ad.audiotest</item>
</string-array>
<!-- Add by sunxiaolin end 20190905 -->
在item中加入你想授予STORAGE_PERMISSIONS权限的app包名。
frameworks/base/core/res/res/values/symbols.xml
<java-symbol type="array" name="storage_permission_custom_packagename" />
添加storage_permission_custom_packagename的定义,否则编译不过。
编译:/frameworks/base/services/下,mm,测试OK.
第三种方法
在system/etc/default-permissions目录下有一个文件:default-car-permissions.xml
在DefaultPermissionGrantPolicy中会去读这个文件,来给app赋予权限。
<exception
package="com.android.car.messenger">
<!-- Contacts -->
<permission name="android.permission.READ_CONTACTS" fixed="false"/>
<!-- SMS -->
<permission name="android.permission.SEND_SMS" fixed="false"/>
<permission name="android.permission.READ_SMS" fixed="false"/>
</exception>
<!-- add by sunxiaolin start 20190912 -->
<exception
package="com.ad.audiotest">
<!-- Contacts -->
<permission name="android.permission.ACCESS_COARSE_LOCATION" fixed="false"/>
<!-- SMS -->
<permission name="android.permission.WRITE_EXTERNAL_STORAGE" fixed="false"/>
<permission name="android.permission.READ_PHONE_STATE" fixed="false"/>
</exception>
<!-- add by sunxiaolin end 20190912 -->
在里面加入你想赋予权限的app包名和权限。
其中第二种和第三种方法可以清楚存储空间中可以看到。
并且只对于默认打包在系统中的Image生效。对于第三方应用,需要修改下代码逻辑。
https://wossoneri.github.io/2019/11/17/[Android][Framework]Android-permission-2/
https://www.jeesns.cn/article/detail/9382
https://blog.csdn.net/zhujini2b/article/details/79907896
//blog.https:csdn.net/Sunxiaolin2016/article/details/10077450