forked from termux/termux-app
-
Notifications
You must be signed in to change notification settings - Fork 3
/
FeatureFlagUtils.java
181 lines (157 loc) · 8.45 KB
/
FeatureFlagUtils.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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
package com.termux.shared.android;
import android.annotation.SuppressLint;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.termux.shared.logger.Logger;
import com.termux.shared.reflection.ReflectionUtils;
import java.lang.reflect.Method;
import java.util.Map;
/**
* Utils for Developer Options -> Feature Flags. The page won't show in user/production builds and
* is only shown in userdebug builds.
* https://cs.android.com/android/_/android/platform/frameworks/base/+/09dcdad5ebc159861920f090e07da60fac71ac0a:core/java/android/util/FeatureFlagUtils.java
* https://cs.android.com/android/platform/superproject/+/android-12.0.0_r31:packages/apps/Settings/src/com/android/settings/development/featureflags/FeatureFlagsPreferenceController.java;l=42
*
* The feature flags value can be modified in two ways.
*
* 1. sysprops with `setprop` command with root. Will be unset by default.
* Set value: `setprop persist.sys.fflag.override.settings_enable_monitor_phantom_procs false`
* Get value: `getprop persist.sys.fflag.override.settings_enable_monitor_phantom_procs`
* Unset value: `setprop persist.sys.fflag.override.settings_enable_monitor_phantom_procs ""`
* Running `setprop` command requires root and even adb `shell` user cannot modify the values
* since selinux will not allow it by default. Some props like `settings_dynamic_system` can be
* set since they are exempted for `shell` in sepolicy.
*
* init: Unable to set property 'persist.sys.fflag.override.settings_enable_monitor_phantom_procs' from uid:2000 gid:2000 pid:9576: SELinux permission check failed
* [ 1034.877067] type=1107 audit(1644436809.637:34): uid=0 auid=4294967295 ses=4294967295 subj=u:r:init:s0 msg='avc: denied { set } for property=persist.sys.fflag.override.settings_enable_monitor_phantom_procs pid=9576 uid=2000 gid=2000 scontext=u:r:shell:s0 tcontext=u:object_r:system_prop:s0 tclass=property_service permissive=0'
*
* https://cs.android.com/android/platform/superproject/+/android-12.0.0_r4:system/sepolicy/private/property_contexts;l=71
* https://cs.android.com/android/platform/superproject/+/android-12.0.0_r4:system/sepolicy/private/shell.te;l=149
*
* 2. settings global list with adb or root. Will be unset by default. This takes precedence over
* sysprop value since `FeatureFlagUtils.isEnabled()`
* checks its value first. Override precedence: Settings.Global -> sys.fflag.override.* -> static list.
* Set value: `adb shell settings put global settings_enable_monitor_phantom_procs false`
* Get value: adb shell settings get global settings_enable_monitor_phantom_procs`
* Unset value: `adb shell settings delete global settings_enable_monitor_phantom_procs`
*
* https://cs.android.com/android/_/android/platform/frameworks/base/+/refs/tags/android-12.0.0_r31:core/java/android/util/FeatureFlagUtils.java;l=113
*
* The feature flag values can be modified in user builds with settings global list, but since the
* developer options feature flags page is not shown and considering that getprop values for features
* will be unset by default and settings global list will not be set either and there is no shell API,
* it will require an android app process to check if feature is supported on a device and what its
* default value is with reflection after bypassing hidden api restrictions since {@link #FEATURE_FLAGS_CLASS}
* is annotated as `@hide`.
*/
public class FeatureFlagUtils {
public enum FeatureFlagValue {
/** Unknown like due to exception raised while getting value. */
UNKNOWN("<unknown>"),
/** Flag is unsupported on current android build. */
UNSUPPORTED("<unsupported>"),
/** Flag is enabled. */
TRUE("true"),
/** Flag is not enabled. */
FALSE("false");
private final String name;
FeatureFlagValue(final String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public static final String FEATURE_FLAGS_CLASS = "android.util.FeatureFlagUtils";
/**
* If set to false, then will disable trimming of phantom process and processes using excessive
* CPU. Flag is available on Pixel Android 12L beta 3 and Android 13. Availability on other
* devices will depend on if other vendors merged the 09dcdad5 commit or not in their releases
* and if they actually want to support the flag.
*
* https://github.com/termux/termux-app/issues/2366
* https://issuetracker.google.com/u/1/issues/205156966#comment28
* https://cs.android.com/android/_/android/platform/frameworks/base/+/09dcdad5
* https://github.com/agnostic-apollo/Android-Docs/tree/master/ocs/apps/processes/phantom-cached-and-empty-processes.md
*/
public static final String SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS = "settings_enable_monitor_phantom_procs";
private static final String LOG_TAG = "FeatureFlagUtils";
/**
* Get all feature flags in their raw form.
*/
@SuppressWarnings("unchecked")
public static Map<String, String> getAllFeatureFlags() {
ReflectionUtils.bypassHiddenAPIReflectionRestrictions();
try {
@SuppressLint("PrivateApi") Class<?> clazz = Class.forName(FEATURE_FLAGS_CLASS);
Method getAllFeatureFlagsMethod = ReflectionUtils.getDeclaredMethod(clazz, "getAllFeatureFlags");
if (getAllFeatureFlagsMethod == null) return null;
return (Map<String, String>) ReflectionUtils.invokeMethod(getAllFeatureFlagsMethod, null).value;
} catch (Exception e) {
// ClassCastException may be thrown
Logger.logStackTraceWithMessage(LOG_TAG, "Failed to get all feature flags", e);
return null;
}
}
/**
* Check if a feature flag exists.
*
* @return Returns {@code true} if flag exists, otherwise {@code false}. This will be
* {@code null} if an exception is raised.
*/
@Nullable
public static Boolean featureFlagExists(@NonNull String feature) {
Map<String, String> featureFlags = getAllFeatureFlags();
if (featureFlags == null) return null;
return featureFlags.containsKey(feature);
}
/**
* Get {@link FeatureFlagValue} for a feature.
*
* @param context The {@link Context} for operations.
* @param feature The {@link String} name for feature.
* @return Returns {@link FeatureFlagValue}.
*/
public static FeatureFlagValue getFeatureFlagValueString(@NonNull Context context, @NonNull String feature) {
Boolean featureFlagExists = featureFlagExists(feature);
if (featureFlagExists == null) {
Logger.logError(LOG_TAG, "Failed to get feature flags \"" + feature + "\" value");
return FeatureFlagValue.UNKNOWN;
} else if (!featureFlagExists) {
return FeatureFlagValue.UNSUPPORTED;
}
Boolean featureFlagValue = isFeatureEnabled(context, feature);
if (featureFlagValue == null) {
Logger.logError(LOG_TAG, "Failed to get feature flags \"" + feature + "\" value");
return FeatureFlagValue.UNKNOWN;
} else {
return featureFlagValue ? FeatureFlagValue.TRUE : FeatureFlagValue.FALSE;
}
}
/**
* Check if a feature flag exists.
*
* @param context The {@link Context} for operations.
* @param feature The {@link String} name for feature.
* @return Returns {@code true} if flag exists, otherwise {@code false}. This will be
* {@code null} if an exception is raised.
*/
@Nullable
public static Boolean isFeatureEnabled(@NonNull Context context, @NonNull String feature) {
ReflectionUtils.bypassHiddenAPIReflectionRestrictions();
try {
@SuppressLint("PrivateApi") Class<?> clazz = Class.forName(FEATURE_FLAGS_CLASS);
Method isFeatureEnabledMethod = ReflectionUtils.getDeclaredMethod(clazz, "isEnabled", Context.class, String.class);
if (isFeatureEnabledMethod == null) {
Logger.logError(LOG_TAG, "Failed to check if feature flag \"" + feature + "\" is enabled");
return null;
}
return (boolean) ReflectionUtils.invokeMethod(isFeatureEnabledMethod, null, context, feature).value;
} catch (Exception e) {
// ClassCastException may be thrown
Logger.logStackTraceWithMessage(LOG_TAG, "Failed to check if feature flag \"" + feature + "\" is enabled", e);
return null;
}
}
}