Merge "add LatencyTracter per action device properties" into tm-qpr-dev
This commit is contained in:
@@ -37,12 +37,17 @@ import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPOR
|
|||||||
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_UDFPS_ILLUMINATE;
|
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_UDFPS_ILLUMINATE;
|
||||||
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_USER_SWITCH;
|
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_USER_SWITCH;
|
||||||
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__UNKNOWN_ACTION;
|
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__UNKNOWN_ACTION;
|
||||||
|
import static com.android.internal.util.LatencyTracker.ActionProperties.ENABLE_SUFFIX;
|
||||||
|
import static com.android.internal.util.LatencyTracker.ActionProperties.LEGACY_TRACE_THRESHOLD_SUFFIX;
|
||||||
|
import static com.android.internal.util.LatencyTracker.ActionProperties.SAMPLE_INTERVAL_SUFFIX;
|
||||||
|
import static com.android.internal.util.LatencyTracker.ActionProperties.TRACE_THRESHOLD_SUFFIX;
|
||||||
|
|
||||||
import android.annotation.IntDef;
|
import android.annotation.IntDef;
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.os.ConditionVariable;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.os.Trace;
|
import android.os.Trace;
|
||||||
import android.provider.DeviceConfig;
|
import android.provider.DeviceConfig;
|
||||||
@@ -58,6 +63,7 @@ import com.android.internal.os.BackgroundThread;
|
|||||||
|
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.concurrent.ThreadLocalRandom;
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@@ -261,11 +267,11 @@ public class LatencyTracker {
|
|||||||
@GuardedBy("mLock")
|
@GuardedBy("mLock")
|
||||||
private final SparseArray<Session> mSessions = new SparseArray<>();
|
private final SparseArray<Session> mSessions = new SparseArray<>();
|
||||||
@GuardedBy("mLock")
|
@GuardedBy("mLock")
|
||||||
private final int[] mTraceThresholdPerAction = new int[ACTIONS_ALL.length];
|
private final SparseArray<ActionProperties> mActionPropertiesMap = new SparseArray<>();
|
||||||
@GuardedBy("mLock")
|
|
||||||
private int mSamplingInterval;
|
|
||||||
@GuardedBy("mLock")
|
@GuardedBy("mLock")
|
||||||
private boolean mEnabled;
|
private boolean mEnabled;
|
||||||
|
@VisibleForTesting
|
||||||
|
public final ConditionVariable mDeviceConfigPropertiesUpdated = new ConditionVariable();
|
||||||
|
|
||||||
public static LatencyTracker getInstance(Context context) {
|
public static LatencyTracker getInstance(Context context) {
|
||||||
if (sLatencyTracker == null) {
|
if (sLatencyTracker == null) {
|
||||||
@@ -278,9 +284,9 @@ public class LatencyTracker {
|
|||||||
return sLatencyTracker;
|
return sLatencyTracker;
|
||||||
}
|
}
|
||||||
|
|
||||||
private LatencyTracker() {
|
@VisibleForTesting
|
||||||
|
public LatencyTracker() {
|
||||||
mEnabled = DEFAULT_ENABLED;
|
mEnabled = DEFAULT_ENABLED;
|
||||||
mSamplingInterval = DEFAULT_SAMPLING_INTERVAL;
|
|
||||||
|
|
||||||
// Post initialization to the background in case we're running on the main thread.
|
// Post initialization to the background in case we're running on the main thread.
|
||||||
BackgroundThread.getHandler().post(() -> this.updateProperties(
|
BackgroundThread.getHandler().post(() -> this.updateProperties(
|
||||||
@@ -291,14 +297,24 @@ public class LatencyTracker {
|
|||||||
|
|
||||||
private void updateProperties(DeviceConfig.Properties properties) {
|
private void updateProperties(DeviceConfig.Properties properties) {
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
mSamplingInterval = properties.getInt(SETTINGS_SAMPLING_INTERVAL_KEY,
|
int samplingInterval = properties.getInt(SETTINGS_SAMPLING_INTERVAL_KEY,
|
||||||
DEFAULT_SAMPLING_INTERVAL);
|
DEFAULT_SAMPLING_INTERVAL);
|
||||||
mEnabled = properties.getBoolean(SETTINGS_ENABLED_KEY, DEFAULT_ENABLED);
|
mEnabled = properties.getBoolean(SETTINGS_ENABLED_KEY, DEFAULT_ENABLED);
|
||||||
for (int action : ACTIONS_ALL) {
|
for (int action : ACTIONS_ALL) {
|
||||||
mTraceThresholdPerAction[action] =
|
String actionName = getNameOfAction(STATSD_ACTION[action]).toLowerCase(Locale.ROOT);
|
||||||
properties.getInt(getNameOfAction(STATSD_ACTION[action]), -1);
|
int legacyActionTraceThreshold = properties.getInt(
|
||||||
|
actionName + LEGACY_TRACE_THRESHOLD_SUFFIX, -1);
|
||||||
|
mActionPropertiesMap.put(action, new ActionProperties(action,
|
||||||
|
properties.getBoolean(actionName + ENABLE_SUFFIX, mEnabled),
|
||||||
|
properties.getInt(actionName + SAMPLE_INTERVAL_SUFFIX, samplingInterval),
|
||||||
|
properties.getInt(actionName + TRACE_THRESHOLD_SUFFIX,
|
||||||
|
legacyActionTraceThreshold)));
|
||||||
|
}
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "updated action properties: " + mActionPropertiesMap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
mDeviceConfigPropertiesUpdated.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -369,16 +385,38 @@ public class LatencyTracker {
|
|||||||
return "com.android.telemetry.latency-tracker-" + getNameOfAction(STATSD_ACTION[action]);
|
return "com.android.telemetry.latency-tracker-" + getNameOfAction(STATSD_ACTION[action]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #isEnabled(Context, int)}
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
public static boolean isEnabled(Context ctx) {
|
public static boolean isEnabled(Context ctx) {
|
||||||
return getInstance(ctx).isEnabled();
|
return getInstance(ctx).isEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Used {@link #isEnabled(int)}
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
public boolean isEnabled() {
|
public boolean isEnabled() {
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
return mEnabled;
|
return mEnabled;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isEnabled(Context ctx, int action) {
|
||||||
|
return getInstance(ctx).isEnabled(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnabled(int action) {
|
||||||
|
synchronized (mLock) {
|
||||||
|
ActionProperties actionProperties = mActionPropertiesMap.get(action);
|
||||||
|
if (actionProperties != null) {
|
||||||
|
return actionProperties.isEnabled();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notifies that an action is starting. <s>This needs to be called from the main thread.</s>
|
* Notifies that an action is starting. <s>This needs to be called from the main thread.</s>
|
||||||
*
|
*
|
||||||
@@ -468,8 +506,14 @@ public class LatencyTracker {
|
|||||||
boolean shouldSample;
|
boolean shouldSample;
|
||||||
int traceThreshold;
|
int traceThreshold;
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
shouldSample = ThreadLocalRandom.current().nextInt() % mSamplingInterval == 0;
|
ActionProperties actionProperties = mActionPropertiesMap.get(action);
|
||||||
traceThreshold = mTraceThresholdPerAction[action];
|
if (actionProperties == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int nextRandNum = ThreadLocalRandom.current().nextInt(
|
||||||
|
actionProperties.getSamplingInterval());
|
||||||
|
shouldSample = nextRandNum == 0;
|
||||||
|
traceThreshold = actionProperties.getTraceThreshold();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (traceThreshold > 0 && duration >= traceThreshold) {
|
if (traceThreshold > 0 && duration >= traceThreshold) {
|
||||||
@@ -549,4 +593,59 @@ public class LatencyTracker {
|
|||||||
return (int) (mEndRtc - mStartRtc);
|
return (int) (mEndRtc - mStartRtc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
static class ActionProperties {
|
||||||
|
static final String ENABLE_SUFFIX = "_enable";
|
||||||
|
static final String SAMPLE_INTERVAL_SUFFIX = "_sample_interval";
|
||||||
|
// TODO: migrate all usages of the legacy trace theshold property
|
||||||
|
static final String LEGACY_TRACE_THRESHOLD_SUFFIX = "";
|
||||||
|
static final String TRACE_THRESHOLD_SUFFIX = "_trace_threshold";
|
||||||
|
|
||||||
|
@Action
|
||||||
|
private final int mAction;
|
||||||
|
private final boolean mEnabled;
|
||||||
|
private final int mSamplingInterval;
|
||||||
|
private final int mTraceThreshold;
|
||||||
|
|
||||||
|
ActionProperties(
|
||||||
|
@Action int action,
|
||||||
|
boolean enabled,
|
||||||
|
int samplingInterval,
|
||||||
|
int traceThreshold) {
|
||||||
|
this.mAction = action;
|
||||||
|
com.android.internal.util.AnnotationValidations.validate(
|
||||||
|
Action.class, null, mAction);
|
||||||
|
this.mEnabled = enabled;
|
||||||
|
this.mSamplingInterval = samplingInterval;
|
||||||
|
this.mTraceThreshold = traceThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Action
|
||||||
|
int getAction() {
|
||||||
|
return mAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isEnabled() {
|
||||||
|
return mEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getSamplingInterval() {
|
||||||
|
return mSamplingInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getTraceThreshold() {
|
||||||
|
return mTraceThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ActionProperties{"
|
||||||
|
+ " mAction=" + mAction
|
||||||
|
+ ", mEnabled=" + mEnabled
|
||||||
|
+ ", mSamplingInterval=" + mSamplingInterval
|
||||||
|
+ ", mTraceThreshold=" + mTraceThreshold
|
||||||
|
+ "}";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,36 +23,53 @@ import static com.android.internal.util.LatencyTracker.STATSD_ACTION;
|
|||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static com.google.common.truth.Truth.assertWithMessage;
|
import static com.google.common.truth.Truth.assertWithMessage;
|
||||||
|
|
||||||
|
import android.provider.DeviceConfig;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import androidx.test.filters.SmallTest;
|
import androidx.test.filters.SmallTest;
|
||||||
|
|
||||||
import com.google.common.truth.Expect;
|
import com.google.common.truth.Expect;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.time.Duration;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@SmallTest
|
@SmallTest
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class LatencyTrackerTest {
|
public class LatencyTrackerTest {
|
||||||
|
private static final String TAG = LatencyTrackerTest.class.getSimpleName();
|
||||||
private static final String ENUM_NAME_PREFIX = "UIACTION_LATENCY_REPORTED__ACTION__";
|
private static final String ENUM_NAME_PREFIX = "UIACTION_LATENCY_REPORTED__ACTION__";
|
||||||
|
private static final String ACTION_ENABLE_SUFFIX = "_enable";
|
||||||
|
private static final Duration TEST_TIMEOUT = Duration.ofMillis(500);
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public final Expect mExpect = Expect.create();
|
public final Expect mExpect = Expect.create();
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
DeviceConfig.deleteProperty(DeviceConfig.NAMESPACE_LATENCY_TRACKER,
|
||||||
|
LatencyTracker.SETTINGS_ENABLED_KEY);
|
||||||
|
getAllActions().forEach(action -> {
|
||||||
|
DeviceConfig.deleteProperty(DeviceConfig.NAMESPACE_LATENCY_TRACKER,
|
||||||
|
action.getName().toLowerCase() + ACTION_ENABLE_SUFFIX);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCujsMapToEnumsCorrectly() {
|
public void testCujsMapToEnumsCorrectly() {
|
||||||
List<Field> actions = Arrays.stream(LatencyTracker.class.getDeclaredFields())
|
List<Field> actions = getAllActions();
|
||||||
.filter(f -> f.getName().startsWith("ACTION_")
|
|
||||||
&& Modifier.isStatic(f.getModifiers())
|
|
||||||
&& f.getType() == int.class)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
Map<Integer, String> enumsMap = Arrays.stream(FrameworkStatsLog.class.getDeclaredFields())
|
Map<Integer, String> enumsMap = Arrays.stream(FrameworkStatsLog.class.getDeclaredFields())
|
||||||
.filter(f -> f.getName().startsWith(ENUM_NAME_PREFIX)
|
.filter(f -> f.getName().startsWith(ENUM_NAME_PREFIX)
|
||||||
&& Modifier.isStatic(f.getModifiers())
|
&& Modifier.isStatic(f.getModifiers())
|
||||||
@@ -84,13 +101,7 @@ public class LatencyTrackerTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCujTypeEnumCorrectlyDefined() throws Exception {
|
public void testCujTypeEnumCorrectlyDefined() throws Exception {
|
||||||
List<Field> cujEnumFields =
|
List<Field> cujEnumFields = getAllActions();
|
||||||
Arrays.stream(LatencyTracker.class.getDeclaredFields())
|
|
||||||
.filter(field -> field.getName().startsWith("ACTION_")
|
|
||||||
&& Modifier.isStatic(field.getModifiers())
|
|
||||||
&& field.getType() == int.class)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
HashSet<Integer> allValues = new HashSet<>();
|
HashSet<Integer> allValues = new HashSet<>();
|
||||||
for (Field field : cujEnumFields) {
|
for (Field field : cujEnumFields) {
|
||||||
int fieldValue = field.getInt(null);
|
int fieldValue = field.getInt(null);
|
||||||
@@ -106,6 +117,95 @@ public class LatencyTrackerTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsEnabled_globalEnabled() {
|
||||||
|
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_LATENCY_TRACKER,
|
||||||
|
LatencyTracker.SETTINGS_ENABLED_KEY, "true", false);
|
||||||
|
LatencyTracker latencyTracker = new LatencyTracker();
|
||||||
|
waitForLatencyTrackerToUpdateProperties(latencyTracker);
|
||||||
|
assertThat(latencyTracker.isEnabled()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsEnabled_globalDisabled() {
|
||||||
|
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_LATENCY_TRACKER,
|
||||||
|
LatencyTracker.SETTINGS_ENABLED_KEY, "false", false);
|
||||||
|
LatencyTracker latencyTracker = new LatencyTracker();
|
||||||
|
waitForLatencyTrackerToUpdateProperties(latencyTracker);
|
||||||
|
assertThat(latencyTracker.isEnabled()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsEnabledAction_useGlobalValueWhenActionEnableIsNotSet() {
|
||||||
|
LatencyTracker latencyTracker = new LatencyTracker();
|
||||||
|
// using a single test action, but this applies to all actions
|
||||||
|
int action = LatencyTracker.ACTION_SHOW_VOICE_INTERACTION;
|
||||||
|
Log.i(TAG, "setting property=" + LatencyTracker.SETTINGS_ENABLED_KEY + ", value=true");
|
||||||
|
latencyTracker.mDeviceConfigPropertiesUpdated.close();
|
||||||
|
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_LATENCY_TRACKER,
|
||||||
|
LatencyTracker.SETTINGS_ENABLED_KEY, "true", false);
|
||||||
|
waitForLatencyTrackerToUpdateProperties(latencyTracker);
|
||||||
|
assertThat(
|
||||||
|
latencyTracker.isEnabled(action)).isTrue();
|
||||||
|
|
||||||
|
Log.i(TAG, "setting property=" + LatencyTracker.SETTINGS_ENABLED_KEY
|
||||||
|
+ ", value=false");
|
||||||
|
latencyTracker.mDeviceConfigPropertiesUpdated.close();
|
||||||
|
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_LATENCY_TRACKER,
|
||||||
|
LatencyTracker.SETTINGS_ENABLED_KEY, "false", false);
|
||||||
|
waitForLatencyTrackerToUpdateProperties(latencyTracker);
|
||||||
|
assertThat(latencyTracker.isEnabled(action)).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsEnabledAction_actionPropertyOverridesGlobalProperty()
|
||||||
|
throws DeviceConfig.BadConfigException {
|
||||||
|
LatencyTracker latencyTracker = new LatencyTracker();
|
||||||
|
// using a single test action, but this applies to all actions
|
||||||
|
int action = LatencyTracker.ACTION_SHOW_VOICE_INTERACTION;
|
||||||
|
String actionEnableProperty = "action_show_voice_interaction" + ACTION_ENABLE_SUFFIX;
|
||||||
|
Log.i(TAG, "setting property=" + actionEnableProperty + ", value=true");
|
||||||
|
|
||||||
|
latencyTracker.mDeviceConfigPropertiesUpdated.close();
|
||||||
|
Map<String, String> properties = new HashMap<String, String>() {{
|
||||||
|
put(LatencyTracker.SETTINGS_ENABLED_KEY, "false");
|
||||||
|
put(actionEnableProperty, "true");
|
||||||
|
}};
|
||||||
|
DeviceConfig.setProperties(
|
||||||
|
new DeviceConfig.Properties(DeviceConfig.NAMESPACE_LATENCY_TRACKER,
|
||||||
|
properties));
|
||||||
|
waitForLatencyTrackerToUpdateProperties(latencyTracker);
|
||||||
|
assertThat(latencyTracker.isEnabled(action)).isTrue();
|
||||||
|
|
||||||
|
latencyTracker.mDeviceConfigPropertiesUpdated.close();
|
||||||
|
Log.i(TAG, "setting property=" + actionEnableProperty + ", value=false");
|
||||||
|
properties.put(LatencyTracker.SETTINGS_ENABLED_KEY, "true");
|
||||||
|
properties.put(actionEnableProperty, "false");
|
||||||
|
DeviceConfig.setProperties(
|
||||||
|
new DeviceConfig.Properties(DeviceConfig.NAMESPACE_LATENCY_TRACKER,
|
||||||
|
properties));
|
||||||
|
waitForLatencyTrackerToUpdateProperties(latencyTracker);
|
||||||
|
assertThat(latencyTracker.isEnabled(action)).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void waitForLatencyTrackerToUpdateProperties(LatencyTracker latencyTracker) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(TEST_TIMEOUT.toMillis());
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
assertThat(latencyTracker.mDeviceConfigPropertiesUpdated.block(
|
||||||
|
TEST_TIMEOUT.toMillis())).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Field> getAllActions() {
|
||||||
|
return Arrays.stream(LatencyTracker.class.getDeclaredFields())
|
||||||
|
.filter(field -> field.getName().startsWith("ACTION_")
|
||||||
|
&& Modifier.isStatic(field.getModifiers())
|
||||||
|
&& field.getType() == int.class)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
private int getIntFieldChecked(Field field) {
|
private int getIntFieldChecked(Field field) {
|
||||||
try {
|
try {
|
||||||
return field.getInt(null);
|
return field.getInt(null);
|
||||||
|
|||||||
Reference in New Issue
Block a user