Merge "Add default frame rate setting" into rvc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
3d6a1cfb1b
@@ -4165,9 +4165,21 @@
|
||||
and a second time clipped to the fill level to indicate charge -->
|
||||
<bool name="config_batterymeterDualTone">false</bool>
|
||||
|
||||
<!-- The default peak refresh rate for a given device. Change this value if you want to allow
|
||||
for higher refresh rates to be automatically used out of the box -->
|
||||
<integer name="config_defaultPeakRefreshRate">60</integer>
|
||||
<!-- The default refresh rate for a given device. Change this value to set a higher default
|
||||
refresh rate. If the hardware composer on the device supports display modes with a higher
|
||||
refresh rate than the default value specified here, the framework may use those higher
|
||||
refresh rate modes if an app chooses one by setting preferredDisplayModeId or calling
|
||||
setFrameRate().
|
||||
If a non-zero value is set for config_defaultPeakRefreshRate, then
|
||||
config_defaultRefreshRate may be set to 0, in which case the value set for
|
||||
config_defaultPeakRefreshRate will act as the default frame rate. -->
|
||||
<integer name="config_defaultRefreshRate">60</integer>
|
||||
|
||||
<!-- The default peak refresh rate for a given device. Change this value if you want to prevent
|
||||
the framework from using higher refresh rates, even if display modes with higher refresh
|
||||
rates are available from hardware composer. Only has an effect if the value is
|
||||
non-zero. -->
|
||||
<integer name="config_defaultPeakRefreshRate">0</integer>
|
||||
|
||||
<!-- The display uses different gamma curves for different refresh rates. It's hard for panel
|
||||
vendor to tune the curves to have exact same brightness for different refresh rate. So
|
||||
|
||||
@@ -3772,6 +3772,7 @@
|
||||
<java-symbol type="string" name="bluetooth_airplane_mode_toast" />
|
||||
|
||||
<!-- For high refresh rate displays -->
|
||||
<java-symbol type="integer" name="config_defaultRefreshRate" />
|
||||
<java-symbol type="integer" name="config_defaultPeakRefreshRate" />
|
||||
<java-symbol type="integer" name="config_defaultRefreshRateInZone" />
|
||||
<java-symbol type="array" name="config_brightnessThresholdsOfPeakRefreshRate" />
|
||||
|
||||
@@ -92,7 +92,7 @@ public class DisplayModeDirector {
|
||||
private final AppRequestObserver mAppRequestObserver;
|
||||
private final SettingsObserver mSettingsObserver;
|
||||
private final DisplayObserver mDisplayObserver;
|
||||
private final BrightnessObserver mBrightnessObserver;
|
||||
private BrightnessObserver mBrightnessObserver;
|
||||
|
||||
private final DeviceConfigDisplaySettings mDeviceConfigDisplaySettings;
|
||||
private DesiredDisplayModeSpecsListener mDesiredDisplayModeSpecsListener;
|
||||
@@ -460,6 +460,21 @@ public class DisplayModeDirector {
|
||||
mVotesByDisplay = votesByDisplay;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void injectBrightnessObserver(BrightnessObserver brightnessObserver) {
|
||||
mBrightnessObserver = brightnessObserver;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
DesiredDisplayModeSpecs getDesiredDisplayModeSpecsWithInjectedFpsSettings(
|
||||
float minRefreshRate, float peakRefreshRate, float defaultRefreshRate) {
|
||||
synchronized (mLock) {
|
||||
mSettingsObserver.updateRefreshRateSettingLocked(
|
||||
minRefreshRate, peakRefreshRate, defaultRefreshRate);
|
||||
return getDesiredDisplayModeSpecs(Display.DEFAULT_DISPLAY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens for changes refresh rate coordination.
|
||||
*/
|
||||
@@ -666,14 +681,18 @@ public class DisplayModeDirector {
|
||||
|
||||
@VisibleForTesting
|
||||
static final class Vote {
|
||||
// DEFAULT_FRAME_RATE votes for [0, DEFAULT]. As the lowest priority vote, it's overridden
|
||||
// by all other considerations. It acts to set a default frame rate for a device.
|
||||
public static final int PRIORITY_DEFAULT_REFRESH_RATE = 0;
|
||||
|
||||
// LOW_BRIGHTNESS votes for a single refresh rate like [60,60], [90,90] or null.
|
||||
// If the higher voters result is a range, it will fix the rate to a single choice.
|
||||
// It's used to avoid rate switch in certain conditions.
|
||||
public static final int PRIORITY_LOW_BRIGHTNESS = 0;
|
||||
public static final int PRIORITY_LOW_BRIGHTNESS = 1;
|
||||
|
||||
// SETTING_MIN_REFRESH_RATE is used to propose a lower bound of display refresh rate.
|
||||
// It votes [MIN_REFRESH_RATE, Float.POSITIVE_INFINITY]
|
||||
public static final int PRIORITY_USER_SETTING_MIN_REFRESH_RATE = 1;
|
||||
public static final int PRIORITY_USER_SETTING_MIN_REFRESH_RATE = 2;
|
||||
|
||||
// We split the app request into different priorities in case we can satisfy one desire
|
||||
// without the other.
|
||||
@@ -683,20 +702,20 @@ public class DisplayModeDirector {
|
||||
// @see android.view.WindowManager.LayoutParams#preferredDisplayModeId
|
||||
// System also forces some apps like blacklisted app to run at a lower refresh rate.
|
||||
// @see android.R.array#config_highRefreshRateBlacklist
|
||||
public static final int PRIORITY_APP_REQUEST_REFRESH_RATE = 2;
|
||||
public static final int PRIORITY_APP_REQUEST_SIZE = 3;
|
||||
public static final int PRIORITY_APP_REQUEST_REFRESH_RATE = 3;
|
||||
public static final int PRIORITY_APP_REQUEST_SIZE = 4;
|
||||
|
||||
// SETTING_PEAK_REFRESH_RATE has a high priority and will restrict the bounds of the rest
|
||||
// of low priority voters. It votes [0, max(PEAK, MIN)]
|
||||
public static final int PRIORITY_USER_SETTING_PEAK_REFRESH_RATE = 4;
|
||||
public static final int PRIORITY_USER_SETTING_PEAK_REFRESH_RATE = 5;
|
||||
|
||||
// LOW_POWER_MODE force display to [0, 60HZ] if Settings.Global.LOW_POWER_MODE is on.
|
||||
public static final int PRIORITY_LOW_POWER_MODE = 5;
|
||||
public static final int PRIORITY_LOW_POWER_MODE = 6;
|
||||
|
||||
// Whenever a new priority is added, remember to update MIN_PRIORITY, MAX_PRIORITY, and
|
||||
// APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF, as well as priorityToString.
|
||||
|
||||
public static final int MIN_PRIORITY = PRIORITY_LOW_BRIGHTNESS;
|
||||
public static final int MIN_PRIORITY = PRIORITY_DEFAULT_REFRESH_RATE;
|
||||
public static final int MAX_PRIORITY = PRIORITY_LOW_POWER_MODE;
|
||||
|
||||
// The cutoff for the app request refresh rate range. Votes with priorities lower than this
|
||||
@@ -740,6 +759,8 @@ public class DisplayModeDirector {
|
||||
|
||||
public static String priorityToString(int priority) {
|
||||
switch (priority) {
|
||||
case PRIORITY_DEFAULT_REFRESH_RATE:
|
||||
return "PRIORITY_DEFAULT_REFRESH_RATE";
|
||||
case PRIORITY_LOW_BRIGHTNESS:
|
||||
return "PRIORITY_LOW_BRIGHTNESS";
|
||||
case PRIORITY_USER_SETTING_MIN_REFRESH_RATE:
|
||||
@@ -776,12 +797,15 @@ public class DisplayModeDirector {
|
||||
|
||||
private final Context mContext;
|
||||
private float mDefaultPeakRefreshRate;
|
||||
private float mDefaultRefreshRate;
|
||||
|
||||
SettingsObserver(@NonNull Context context, @NonNull Handler handler) {
|
||||
super(handler);
|
||||
mContext = context;
|
||||
mDefaultPeakRefreshRate = (float) context.getResources().getInteger(
|
||||
R.integer.config_defaultPeakRefreshRate);
|
||||
mDefaultRefreshRate =
|
||||
(float) context.getResources().getInteger(R.integer.config_defaultRefreshRate);
|
||||
}
|
||||
|
||||
public void observe() {
|
||||
@@ -849,17 +873,48 @@ public class DisplayModeDirector {
|
||||
Settings.System.MIN_REFRESH_RATE, 0f);
|
||||
float peakRefreshRate = Settings.System.getFloat(mContext.getContentResolver(),
|
||||
Settings.System.PEAK_REFRESH_RATE, mDefaultPeakRefreshRate);
|
||||
updateRefreshRateSettingLocked(minRefreshRate, peakRefreshRate, mDefaultRefreshRate);
|
||||
}
|
||||
|
||||
updateVoteLocked(Vote.PRIORITY_USER_SETTING_PEAK_REFRESH_RATE,
|
||||
Vote.forRefreshRates(0f, Math.max(minRefreshRate, peakRefreshRate)));
|
||||
private void updateRefreshRateSettingLocked(
|
||||
float minRefreshRate, float peakRefreshRate, float defaultRefreshRate) {
|
||||
// TODO(b/156304339): The logic in here, aside from updating the refresh rate votes, is
|
||||
// used to predict if we're going to be doing frequent refresh rate switching, and if
|
||||
// so, enable the brightness observer. The logic here is more complicated and fragile
|
||||
// than necessary, and we should improve it. See b/156304339 for more info.
|
||||
Vote peakVote = peakRefreshRate == 0f
|
||||
? null
|
||||
: Vote.forRefreshRates(0f, Math.max(minRefreshRate, peakRefreshRate));
|
||||
updateVoteLocked(Vote.PRIORITY_USER_SETTING_PEAK_REFRESH_RATE, peakVote);
|
||||
updateVoteLocked(Vote.PRIORITY_USER_SETTING_MIN_REFRESH_RATE,
|
||||
Vote.forRefreshRates(minRefreshRate, Float.POSITIVE_INFINITY));
|
||||
Vote defaultVote =
|
||||
defaultRefreshRate == 0f ? null : Vote.forRefreshRates(0f, defaultRefreshRate);
|
||||
updateVoteLocked(Vote.PRIORITY_DEFAULT_REFRESH_RATE, defaultVote);
|
||||
|
||||
mBrightnessObserver.onRefreshRateSettingChangedLocked(minRefreshRate, peakRefreshRate);
|
||||
float maxRefreshRate;
|
||||
if (peakRefreshRate == 0f && defaultRefreshRate == 0f) {
|
||||
// We require that at least one of the peak or default refresh rate values are
|
||||
// set. The brightness observer requires that we're able to predict whether or not
|
||||
// we're going to do frequent refresh rate switching, and with the way the code is
|
||||
// currently written, we need either a default or peak refresh rate value for that.
|
||||
Slog.e(TAG, "Default and peak refresh rates are both 0. One of them should be set"
|
||||
+ " to a valid value.");
|
||||
maxRefreshRate = minRefreshRate;
|
||||
} else if (peakRefreshRate == 0f) {
|
||||
maxRefreshRate = defaultRefreshRate;
|
||||
} else if (defaultRefreshRate == 0f) {
|
||||
maxRefreshRate = peakRefreshRate;
|
||||
} else {
|
||||
maxRefreshRate = Math.min(defaultRefreshRate, peakRefreshRate);
|
||||
}
|
||||
|
||||
mBrightnessObserver.onRefreshRateSettingChangedLocked(minRefreshRate, maxRefreshRate);
|
||||
}
|
||||
|
||||
public void dumpLocked(PrintWriter pw) {
|
||||
pw.println(" SettingsObserver");
|
||||
pw.println(" mDefaultRefreshRate: " + mDefaultRefreshRate);
|
||||
pw.println(" mDefaultPeakRefreshRate: " + mDefaultPeakRefreshRate);
|
||||
}
|
||||
}
|
||||
@@ -1014,7 +1069,8 @@ public class DisplayModeDirector {
|
||||
* {@link R.array#config_brightnessThresholdsOfPeakRefreshRate} and
|
||||
* {@link R.array#config_ambientThresholdsOfPeakRefreshRate}.
|
||||
*/
|
||||
private class BrightnessObserver extends ContentObserver {
|
||||
@VisibleForTesting
|
||||
public class BrightnessObserver extends ContentObserver {
|
||||
// TODO: brightnessfloat: change this to the float setting
|
||||
private final Uri mDisplayBrightnessSetting =
|
||||
Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS);
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.android.server.display;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
@@ -28,6 +29,7 @@ import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.server.display.DisplayModeDirector.BrightnessObserver;
|
||||
import com.android.server.display.DisplayModeDirector.DesiredDisplayModeSpecs;
|
||||
import com.android.server.display.DisplayModeDirector.Vote;
|
||||
|
||||
@@ -36,6 +38,7 @@ import com.google.common.truth.Truth;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
@SmallTest
|
||||
@@ -52,16 +55,15 @@ public class DisplayModeDirectorTest {
|
||||
mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
}
|
||||
|
||||
private DisplayModeDirector createDisplayModeDirectorWithDisplayFpsRange(
|
||||
int minFps, int maxFps) {
|
||||
private DisplayModeDirector createDirectorFromRefreshRateArray(
|
||||
float[] refreshRates, int baseModeId) {
|
||||
DisplayModeDirector director =
|
||||
new DisplayModeDirector(mContext, new Handler(Looper.getMainLooper()));
|
||||
int displayId = 0;
|
||||
int numModes = maxFps - minFps + 1;
|
||||
Display.Mode[] modes = new Display.Mode[numModes];
|
||||
for (int i = minFps; i <= maxFps; i++) {
|
||||
modes[i - minFps] = new Display.Mode(
|
||||
/*modeId=*/i, /*width=*/1000, /*height=*/1000, /*refreshRate=*/i);
|
||||
Display.Mode[] modes = new Display.Mode[refreshRates.length];
|
||||
for (int i = 0; i < refreshRates.length; i++) {
|
||||
modes[i] = new Display.Mode(
|
||||
/*modeId=*/baseModeId + i, /*width=*/1000, /*height=*/1000, refreshRates[i]);
|
||||
}
|
||||
SparseArray<Display.Mode[]> supportedModesByDisplay = new SparseArray<>();
|
||||
supportedModesByDisplay.put(displayId, modes);
|
||||
@@ -72,14 +74,22 @@ public class DisplayModeDirectorTest {
|
||||
return director;
|
||||
}
|
||||
|
||||
private DisplayModeDirector createDirectorFromFpsRange(int minFps, int maxFps) {
|
||||
int numRefreshRates = maxFps - minFps + 1;
|
||||
float[] refreshRates = new float[numRefreshRates];
|
||||
for (int i = 0; i < numRefreshRates; i++) {
|
||||
refreshRates[i] = minFps + i;
|
||||
}
|
||||
return createDirectorFromRefreshRateArray(refreshRates, /*baseModeId=*/minFps);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisplayModeVoting() {
|
||||
int displayId = 0;
|
||||
|
||||
// With no votes present, DisplayModeDirector should allow any refresh rate.
|
||||
DesiredDisplayModeSpecs modeSpecs =
|
||||
createDisplayModeDirectorWithDisplayFpsRange(60, 90).getDesiredDisplayModeSpecs(
|
||||
displayId);
|
||||
createDirectorFromFpsRange(60, 90).getDesiredDisplayModeSpecs(displayId);
|
||||
Truth.assertThat(modeSpecs.baseModeId).isEqualTo(60);
|
||||
Truth.assertThat(modeSpecs.primaryRefreshRateRange.min).isEqualTo(0f);
|
||||
Truth.assertThat(modeSpecs.primaryRefreshRateRange.max).isEqualTo(Float.POSITIVE_INFINITY);
|
||||
@@ -92,7 +102,7 @@ public class DisplayModeDirectorTest {
|
||||
{
|
||||
int minFps = 60;
|
||||
int maxFps = 90;
|
||||
DisplayModeDirector director = createDisplayModeDirectorWithDisplayFpsRange(60, 90);
|
||||
DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
|
||||
assertTrue(2 * numPriorities < maxFps - minFps + 1);
|
||||
SparseArray<Vote> votes = new SparseArray<>();
|
||||
SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
|
||||
@@ -114,7 +124,7 @@ public class DisplayModeDirectorTest {
|
||||
// presence of higher priority votes.
|
||||
{
|
||||
assertTrue(numPriorities >= 2);
|
||||
DisplayModeDirector director = createDisplayModeDirectorWithDisplayFpsRange(60, 90);
|
||||
DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
|
||||
SparseArray<Vote> votes = new SparseArray<>();
|
||||
SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
|
||||
votesByDisplay.put(displayId, votes);
|
||||
@@ -131,7 +141,7 @@ public class DisplayModeDirectorTest {
|
||||
@Test
|
||||
public void testVotingWithFloatingPointErrors() {
|
||||
int displayId = 0;
|
||||
DisplayModeDirector director = createDisplayModeDirectorWithDisplayFpsRange(50, 90);
|
||||
DisplayModeDirector director = createDirectorFromFpsRange(50, 90);
|
||||
SparseArray<Vote> votes = new SparseArray<>();
|
||||
SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
|
||||
votesByDisplay.put(displayId, votes);
|
||||
@@ -154,7 +164,7 @@ public class DisplayModeDirectorTest {
|
||||
assertTrue(Vote.PRIORITY_LOW_BRIGHTNESS < Vote.PRIORITY_APP_REQUEST_SIZE);
|
||||
|
||||
int displayId = 0;
|
||||
DisplayModeDirector director = createDisplayModeDirectorWithDisplayFpsRange(60, 90);
|
||||
DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
|
||||
SparseArray<Vote> votes = new SparseArray<>();
|
||||
SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
|
||||
votesByDisplay.put(displayId, votes);
|
||||
@@ -202,7 +212,7 @@ public class DisplayModeDirectorTest {
|
||||
>= Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF);
|
||||
|
||||
int displayId = 0;
|
||||
DisplayModeDirector director = createDisplayModeDirectorWithDisplayFpsRange(60, 90);
|
||||
DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
|
||||
SparseArray<Vote> votes = new SparseArray<>();
|
||||
SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
|
||||
votesByDisplay.put(displayId, votes);
|
||||
@@ -235,4 +245,61 @@ public class DisplayModeDirectorTest {
|
||||
.isWithin(FLOAT_TOLERANCE)
|
||||
.of(75);
|
||||
}
|
||||
|
||||
void verifySpecsWithRefreshRateSettings(DisplayModeDirector director, float minFps,
|
||||
float peakFps, float defaultFps, float primaryMin, float primaryMax,
|
||||
float appRequestMin, float appRequestMax) {
|
||||
DesiredDisplayModeSpecs specs = director.getDesiredDisplayModeSpecsWithInjectedFpsSettings(
|
||||
minFps, peakFps, defaultFps);
|
||||
Truth.assertThat(specs.primaryRefreshRateRange.min).isEqualTo(primaryMin);
|
||||
Truth.assertThat(specs.primaryRefreshRateRange.max).isEqualTo(primaryMax);
|
||||
Truth.assertThat(specs.appRequestRefreshRateRange.min).isEqualTo(appRequestMin);
|
||||
Truth.assertThat(specs.appRequestRefreshRateRange.max).isEqualTo(appRequestMax);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSpecsFromRefreshRateSettings() {
|
||||
// Confirm that, with varying settings for min, peak, and default refresh rate,
|
||||
// DesiredDisplayModeSpecs is calculated correctly.
|
||||
float[] refreshRates = {30.f, 60.f, 90.f, 120.f, 150.f};
|
||||
DisplayModeDirector director =
|
||||
createDirectorFromRefreshRateArray(refreshRates, /*baseModeId=*/0);
|
||||
|
||||
float inf = Float.POSITIVE_INFINITY;
|
||||
verifySpecsWithRefreshRateSettings(director, 0, 0, 0, 0, inf, 0, inf);
|
||||
verifySpecsWithRefreshRateSettings(director, 0, 0, 90, 0, 90, 0, inf);
|
||||
verifySpecsWithRefreshRateSettings(director, 0, 90, 0, 0, 90, 0, 90);
|
||||
verifySpecsWithRefreshRateSettings(director, 0, 90, 60, 0, 60, 0, 90);
|
||||
verifySpecsWithRefreshRateSettings(director, 0, 90, 120, 0, 90, 0, 90);
|
||||
verifySpecsWithRefreshRateSettings(director, 90, 0, 0, 90, inf, 0, inf);
|
||||
verifySpecsWithRefreshRateSettings(director, 90, 0, 120, 90, 120, 0, inf);
|
||||
verifySpecsWithRefreshRateSettings(director, 90, 0, 60, 90, inf, 0, inf);
|
||||
verifySpecsWithRefreshRateSettings(director, 90, 120, 0, 90, 120, 0, 120);
|
||||
verifySpecsWithRefreshRateSettings(director, 90, 60, 0, 90, 90, 0, 90);
|
||||
verifySpecsWithRefreshRateSettings(director, 60, 120, 90, 60, 90, 0, 120);
|
||||
}
|
||||
|
||||
void verifyBrightnessObserverCall(DisplayModeDirector director, float minFps, float peakFps,
|
||||
float defaultFps, float brightnessObserverMin, float brightnessObserverMax) {
|
||||
BrightnessObserver brightnessObserver = Mockito.mock(BrightnessObserver.class);
|
||||
director.injectBrightnessObserver(brightnessObserver);
|
||||
director.getDesiredDisplayModeSpecsWithInjectedFpsSettings(minFps, peakFps, defaultFps);
|
||||
verify(brightnessObserver)
|
||||
.onRefreshRateSettingChangedLocked(brightnessObserverMin, brightnessObserverMax);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBrightnessObserverCallWithRefreshRateSettings() {
|
||||
// Confirm that, with varying settings for min, peak, and default refresh rate, we make the
|
||||
// correct call to the brightness observer.
|
||||
float[] refreshRates = {60.f, 90.f, 120.f};
|
||||
DisplayModeDirector director =
|
||||
createDirectorFromRefreshRateArray(refreshRates, /*baseModeId=*/0);
|
||||
verifyBrightnessObserverCall(director, 0, 0, 0, 0, 0);
|
||||
verifyBrightnessObserverCall(director, 0, 0, 90, 0, 90);
|
||||
verifyBrightnessObserverCall(director, 0, 90, 0, 0, 90);
|
||||
verifyBrightnessObserverCall(director, 0, 90, 60, 0, 60);
|
||||
verifyBrightnessObserverCall(director, 90, 90, 0, 90, 90);
|
||||
verifyBrightnessObserverCall(director, 120, 90, 0, 120, 90);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user