termux-app
169 строк · 7.7 Кб
1package com.termux.shared.android;
2
3import android.annotation.SuppressLint;
4import android.content.Context;
5
6import androidx.annotation.NonNull;
7import androidx.annotation.Nullable;
8
9import com.termux.shared.logger.Logger;
10import com.termux.shared.reflection.ReflectionUtils;
11
12import java.lang.reflect.Method;
13import java.util.Map;
14
15/**
16* Utils for Developer Options -> Feature Flags. The page won't show in user/production builds and
17* is only shown in userdebug builds.
18* https://cs.android.com/android/_/android/platform/frameworks/base/+/09dcdad5ebc159861920f090e07da60fac71ac0a:core/java/android/util/FeatureFlagUtils.java
19* 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
20*
21* The feature flags value can be modified in two ways.
22*
23* 1. sysprops with `setprop` command with root. Will be unset by default.
24* Set value: `setprop persist.sys.fflag.override.settings_enable_monitor_phantom_procs false`
25* Get value: `getprop persist.sys.fflag.override.settings_enable_monitor_phantom_procs`
26* Unset value: `setprop persist.sys.fflag.override.settings_enable_monitor_phantom_procs ""`
27* Running `setprop` command requires root and even adb `shell` user cannot modify the values
28* since selinux will not allow it by default. Some props like `settings_dynamic_system` can be
29* set since they are exempted for `shell` in sepolicy.
30*
31* 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
32* [ 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'
33*
34* https://cs.android.com/android/platform/superproject/+/android-12.0.0_r4:system/sepolicy/private/property_contexts;l=71
35* https://cs.android.com/android/platform/superproject/+/android-12.0.0_r4:system/sepolicy/private/shell.te;l=149
36*
37* 2. settings global list with adb or root. Will be unset by default. This takes precedence over
38* sysprop value since `FeatureFlagUtils.isEnabled()`
39* checks its value first. Override precedence: Settings.Global -> sys.fflag.override.* -> static list.
40* Set value: `adb shell settings put global settings_enable_monitor_phantom_procs false`
41* Get value: adb shell settings get global settings_enable_monitor_phantom_procs`
42* Unset value: `adb shell settings delete global settings_enable_monitor_phantom_procs`
43*
44* https://cs.android.com/android/_/android/platform/frameworks/base/+/refs/tags/android-12.0.0_r31:core/java/android/util/FeatureFlagUtils.java;l=113
45*
46* The feature flag values can be modified in user builds with settings global list, but since the
47* developer options feature flags page is not shown and considering that getprop values for features
48* will be unset by default and settings global list will not be set either and there is no shell API,
49* it will require an android app process to check if feature is supported on a device and what its
50* default value is with reflection after bypassing hidden api restrictions since {@link #FEATURE_FLAGS_CLASS}
51* is annotated as `@hide`.
52*/
53public class FeatureFlagUtils {
54
55public enum FeatureFlagValue {
56
57/** Unknown like due to exception raised while getting value. */
58UNKNOWN("<unknown>"),
59
60/** Flag is unsupported on current android build. */
61UNSUPPORTED("<unsupported>"),
62
63/** Flag is enabled. */
64TRUE("true"),
65
66/** Flag is not enabled. */
67FALSE("false");
68
69private final String name;
70
71FeatureFlagValue(final String name) {
72this.name = name;
73}
74
75public String getName() {
76return name;
77}
78
79}
80
81public static final String FEATURE_FLAGS_CLASS = "android.util.FeatureFlagUtils";
82
83private static final String LOG_TAG = "FeatureFlagUtils";
84
85/**
86* Get all feature flags in their raw form.
87*/
88@SuppressWarnings("unchecked")
89public static Map<String, String> getAllFeatureFlags() {
90ReflectionUtils.bypassHiddenAPIReflectionRestrictions();
91try {
92@SuppressLint("PrivateApi") Class<?> clazz = Class.forName(FEATURE_FLAGS_CLASS);
93Method getAllFeatureFlagsMethod = ReflectionUtils.getDeclaredMethod(clazz, "getAllFeatureFlags");
94if (getAllFeatureFlagsMethod == null) return null;
95return (Map<String, String>) ReflectionUtils.invokeMethod(getAllFeatureFlagsMethod, null).value;
96} catch (Exception e) {
97// ClassCastException may be thrown
98Logger.logStackTraceWithMessage(LOG_TAG, "Failed to get all feature flags", e);
99return null;
100}
101}
102
103/**
104* Check if a feature flag exists.
105*
106* @return Returns {@code true} if flag exists, otherwise {@code false}. This will be
107* {@code null} if an exception is raised.
108*/
109@Nullable
110public static Boolean featureFlagExists(@NonNull String feature) {
111Map<String, String> featureFlags = getAllFeatureFlags();
112if (featureFlags == null) return null;
113return featureFlags.containsKey(feature);
114}
115
116/**
117* Get {@link FeatureFlagValue} for a feature.
118*
119* @param context The {@link Context} for operations.
120* @param feature The {@link String} name for feature.
121* @return Returns {@link FeatureFlagValue}.
122*/
123@NonNull
124public static FeatureFlagValue getFeatureFlagValueString(@NonNull Context context, @NonNull String feature) {
125Boolean featureFlagExists = featureFlagExists(feature);
126if (featureFlagExists == null) {
127Logger.logError(LOG_TAG, "Failed to get feature flags \"" + feature + "\" value");
128return FeatureFlagValue.UNKNOWN;
129} else if (!featureFlagExists) {
130return FeatureFlagValue.UNSUPPORTED;
131}
132
133Boolean featureFlagValue = isFeatureEnabled(context, feature);
134if (featureFlagValue == null) {
135Logger.logError(LOG_TAG, "Failed to get feature flags \"" + feature + "\" value");
136return FeatureFlagValue.UNKNOWN;
137} else {
138return featureFlagValue ? FeatureFlagValue.TRUE : FeatureFlagValue.FALSE;
139}
140}
141
142/**
143* Check if a feature flag exists.
144*
145* @param context The {@link Context} for operations.
146* @param feature The {@link String} name for feature.
147* @return Returns {@code true} if flag exists, otherwise {@code false}. This will be
148* {@code null} if an exception is raised.
149*/
150@Nullable
151public static Boolean isFeatureEnabled(@NonNull Context context, @NonNull String feature) {
152ReflectionUtils.bypassHiddenAPIReflectionRestrictions();
153try {
154@SuppressLint("PrivateApi") Class<?> clazz = Class.forName(FEATURE_FLAGS_CLASS);
155Method isFeatureEnabledMethod = ReflectionUtils.getDeclaredMethod(clazz, "isEnabled", Context.class, String.class);
156if (isFeatureEnabledMethod == null) {
157Logger.logError(LOG_TAG, "Failed to check if feature flag \"" + feature + "\" is enabled");
158return null;
159}
160
161return (boolean) ReflectionUtils.invokeMethod(isFeatureEnabledMethod, null, context, feature).value;
162} catch (Exception e) {
163// ClassCastException may be thrown
164Logger.logStackTraceWithMessage(LOG_TAG, "Failed to check if feature flag \"" + feature + "\" is enabled", e);
165return null;
166}
167}
168
169}
170