XpNavbar模块在7.0适配时发现的一些问题的总结

昨天晚上刚刚完成XpNavbar模块在7.0上的适配工作,发现了一些问题。

SharedPreferences 和 MODE_WORLD_READABLE

在Nougat上,关于SharedPreferences有一些新的变更
https://developer.android.com/about/versions/nougat/android-7.0-changes.html?hl=zh-cn

私有文件的文件权限不应再由所有者放宽,为使用 MODE_WORLD_READABLE 和/或 MODE_WORLD_WRITEABLE 而进行的此类尝试将触发 SecurityException。
注:迄今为止,这种限制尚不能完全执行。应用仍可能使用原生 API 或 File API 来修改它们的私有目录权限。但是,我们强烈反对放宽私有目录的权限。

在自己的模块中使用了sp而且由于xp需要使用的原因设置成了MODE_WORLD_READABLE,但是这个模式在Nougat上直接抛出了异常。

检索源码发现这个检测模式的方法是这样的:

1
2
3
4
5
6
7
8
9
10
11
http://androidxref.com/7.1.1_r6/xref/frameworks/base/core/java/android/app/ContextImpl.java
2131 private void checkMode(int mode) {
2132 if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.N) {
2133 if ((mode & MODE_WORLD_READABLE) != 0) {
2134 throw new SecurityException("MODE_WORLD_READABLE no longer supported");
2135 }
2136 if ((mode & MODE_WORLD_WRITEABLE) != 0) {
2137 throw new SecurityException("MODE_WORLD_WRITEABLE no longer supported");
2138 }
2139 }
2140 }

使用xp进行整个方法替换处理以后发现仍然无法读取,但是这个时候已经不会再报错了,只是在xp hook的对应进程中无法读取到内容。后来看了一下sp的结构,发现其实是一个arraymap,由两个数组构成,然后在保存信息时会生成一个文件,通过file.setReadable(true, false),仍然无效,检索了最后文件的保存以及权限检查的代码,发现和M中没有什么太大的区别,可能是在更深的层次处理了权限问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/**
* 因为这个模块的功能部分的数据存储其实是使用SharedPreferences以json形式存储在机器上的,而xp读取它
* 则它需要使用MODE_WORLD_READABLE这个模式,但是在android Nougat中,使用这个权限会报错,在源码中检索后发现
* ContextImpl中有一个checkMode方法来对其进行检测,所以此处其实是对ContextImpl的checkmode进行hook,越过其对
* sp模式的检测导致报错
* 这里仅仅是一个尝试
*/
public void hookSharedPreferences(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
Class<?> contextImplClass = lpparam.classLoader.loadClass("android.app.ContextImpl");
XposedHelpers.findAndHookMethod(contextImplClass, "checkMode", int.class, new XC_MethodReplacement() {

@Override
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
XposedBridge.log("===replace success===");
XSharedPreferences sharedPreferences = new XSharedPreferences("com.egguncle.xposednavigationbar", "XposedNavigationBar");
String json = sharedPreferences.getString(SHORT_CUT_DATA, "null");
XposedBridge.log(json);
return null;
}
});
XposedHelpers.findAndHookMethod(contextImplClass, "getSharedPreferences", File.class, int.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
//-rw-rw-r-- 1 u0_a72 u0_a72 400 2017-08-09 16:12 XposedNavigationBar.xml
//-rw-rw-r-- u0_a62 u0_a62 398 2017-08-05 05:39 XposedNavigationBar.xml

XposedBridge.log("===setReadable===");
//这个方法的第一个参数是文件,修改其存取权限尝试来绕过nougat的限制
File file=new File("/data/data/com.egguncle.xposednavigationbar/shared_prefs/XposedNavigationBar.xml");
file.setReadable(true, false);
XposedBridge.log(file.getPath());
file.getParentFile().setReadable(true, false);
XposedBridge.log(file.getParentFile().getPath());
file.getParentFile().getParentFile().setReadable(true, false);
XposedBridge.log(file.getParentFile().getParentFile().getPath());
}
});

XposedHelpers.findAndHookMethod(contextImplClass, "setFilePermissionsFromMode", String.class, int.class, int.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
XposedBridge.log("===MODE_WORLD_READABLE===");
param.args[1] = Activity.MODE_WORLD_READABLE;
}
});
}

先看看布局文件
6.0的布局文件是这样的:
http://androidxref.com/6.0.1_r10/xref/frameworks/base/packages/SystemUI/res/layout/navigation_bar.xml

而7.0是这样的:
http://androidxref.com/7.1.1_r6/xref/frameworks/base/packages/SystemUI/res/layout/navigation_bar.xml

跟进一下看看7.0这个layout文件里面的NavigationBarInflaterView相关的东西看看,发现了一个这样的文件
布局:
http://androidxref.com/7.1.1_r6/xref/frameworks/base/packages/SystemUI/res/layout/navigation_layout.xml
View代码:
http://androidxref.com/7.1.1_r6/xref/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java

来看一下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
17<FrameLayout
18 xmlns:android="http://schemas.android.com/apk/res/android"
19 xmlns:systemui="http://schemas.android.com/apk/res-auto"
20 android:layout_width="match_parent"
21 android:layout_height="match_parent">
22
23 <FrameLayout
24 android:id="@+id/nav_buttons"
25 android:layout_width="match_parent"
26 android:layout_height="match_parent">
27
28 <LinearLayout
29 android:id="@+id/ends_group"
30 android:layout_width="match_parent"
31 android:layout_height="match_parent"
32 android:orientation="horizontal"
33 android:clipChildren="false" />
34
35 <LinearLayout
36 android:id="@+id/center_group"
37 android:layout_width="match_parent"
38 android:layout_height="match_parent"
39 android:gravity="center"
40 android:orientation="horizontal"
41 android:clipChildren="false" />
42
43 </FrameLayout>
44
45 <com.android.systemui.statusbar.policy.DeadZone
46 android:id="@+id/deadzone"
47 android:layout_height="match_parent"
48 android:layout_width="match_parent"
49 android:layout_gravity="top"
50 systemui:minSize="@dimen/navigation_bar_deadzone_size"
51 systemui:maxSize="@dimen/navigation_bar_deadzone_size_max"
52 systemui:holdTime="@integer/navigation_bar_deadzone_hold"
53 systemui:decayTime="@integer/navigation_bar_deadzone_decay"
54 systemui:orientation="horizontal"
55 />
56
57</FrameLayout>

和6.0也是差了非常多,6.0在布局文件的nav_buttons里面就直接写死了三个按钮的布局,而7.0并没有这么做,是在 inflateButton中动态的添加,个人觉得这里这样处理可能是因为7.0上导航栏功能增多了(可以在代码里面发现CLIPBOARD等新功能)。