Merge "CSD: expose TestApi methods to test the feature"
This commit is contained in:
@@ -1629,20 +1629,27 @@ package android.media {
|
||||
|
||||
public class AudioManager {
|
||||
method @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public int abandonAudioFocusForTest(@NonNull android.media.AudioFocusRequest, @NonNull String);
|
||||
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS) public void forceComputeCsdOnAllDevices(boolean);
|
||||
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS) public void forceUseFrameworkMel(boolean);
|
||||
method @NonNull @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public android.media.AudioRecord getCallDownlinkExtractionAudioRecord(@NonNull android.media.AudioFormat);
|
||||
method @NonNull @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public android.media.AudioTrack getCallUplinkInjectionAudioTrack(@NonNull android.media.AudioFormat);
|
||||
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS) public float getCsd();
|
||||
method @Nullable public static android.media.AudioDeviceInfo getDeviceInfoFromType(int);
|
||||
method @IntRange(from=0) @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public long getFadeOutDurationOnFocusLossMillis(@NonNull android.media.AudioAttributes);
|
||||
method @Nullable public static android.media.AudioHalVersionInfo getHalVersion();
|
||||
method public static final int[] getPublicStreamTypes();
|
||||
method @NonNull public java.util.List<java.lang.Integer> getReportedSurroundFormats();
|
||||
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS) public float getRs2Value();
|
||||
method public int getStreamMinVolumeInt(int);
|
||||
method @NonNull public java.util.Map<java.lang.Integer,java.lang.Boolean> getSurroundFormats();
|
||||
method public boolean hasRegisteredDynamicPolicy();
|
||||
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS) public boolean isCsdEnabled();
|
||||
method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, android.Manifest.permission.QUERY_AUDIO_STATE}) public boolean isFullVolumeDevice();
|
||||
method @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public boolean isPstnCallAudioInterceptable();
|
||||
method @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public int requestAudioFocusForTest(@NonNull android.media.AudioFocusRequest, @NonNull String, int, int);
|
||||
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS) public void setCsd(float);
|
||||
method public void setRampingRingerEnabled(boolean);
|
||||
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS) public void setRs2Value(float);
|
||||
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setTestDeviceConnectionState(@NonNull android.media.AudioDeviceAttributes, boolean);
|
||||
}
|
||||
|
||||
|
||||
@@ -6441,6 +6441,20 @@ public class AudioManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the registered volume controller interface.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@Nullable
|
||||
public IVolumeController getVolumeController() {
|
||||
try {
|
||||
return getService().getVolumeController();
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify audio manager about volume controller visibility changes.
|
||||
* Currently limited to SystemUI.
|
||||
@@ -6503,6 +6517,106 @@ public class AudioManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* @return the RS2 value used for momentary exposure warnings
|
||||
*/
|
||||
@TestApi
|
||||
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
|
||||
public float getRs2Value() {
|
||||
try {
|
||||
return getService().getRs2Value();
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* Sets the RS2 value used for momentary exposure warnings
|
||||
*/
|
||||
@TestApi
|
||||
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
|
||||
public void setRs2Value(float rs2Value) {
|
||||
try {
|
||||
getService().setRs2Value(rs2Value);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* @return the current computed sound dose value
|
||||
*/
|
||||
@TestApi
|
||||
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
|
||||
public float getCsd() {
|
||||
try {
|
||||
return getService().getCsd();
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* Sets the computed sound dose value to {@code csd}
|
||||
*/
|
||||
@TestApi
|
||||
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
|
||||
public void setCsd(float csd) {
|
||||
try {
|
||||
getService().setCsd(csd);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* Forces the computation of MEL values (used for CSD) on framework level. This will have the
|
||||
* result of ignoring the MEL values computed on HAL level. Should only be used in testing
|
||||
* since this can affect the certification of a device with EN50332-3 regulation.
|
||||
*/
|
||||
@TestApi
|
||||
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
|
||||
public void forceUseFrameworkMel(boolean useFrameworkMel) {
|
||||
try {
|
||||
getService().forceUseFrameworkMel(useFrameworkMel);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* Forces the computation of CSD on all output devices.
|
||||
*/
|
||||
@TestApi
|
||||
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
|
||||
public void forceComputeCsdOnAllDevices(boolean computeCsdOnAllDevices) {
|
||||
try {
|
||||
getService().forceComputeCsdOnAllDevices(computeCsdOnAllDevices);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* Returns whether CSD is enabled on this device.
|
||||
*/
|
||||
@TestApi
|
||||
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
|
||||
public boolean isCsdEnabled() {
|
||||
try {
|
||||
return getService().isCsdEnabled();
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* Sound dose warning at every 100% of dose during integration window
|
||||
|
||||
@@ -260,6 +260,8 @@ interface IAudioService {
|
||||
|
||||
void setVolumeController(in IVolumeController controller);
|
||||
|
||||
@nullable IVolumeController getVolumeController();
|
||||
|
||||
void notifyVolumeControllerVisible(in IVolumeController controller, boolean visible);
|
||||
|
||||
boolean isStreamAffectedByRingerMode(int streamType);
|
||||
@@ -270,6 +272,27 @@ interface IAudioService {
|
||||
|
||||
void lowerVolumeToRs1(String callingPackage);
|
||||
|
||||
@EnforcePermission("MODIFY_AUDIO_SYSTEM_SETTINGS")
|
||||
float getRs2Value();
|
||||
|
||||
@EnforcePermission("MODIFY_AUDIO_SYSTEM_SETTINGS")
|
||||
oneway void setRs2Value(float rs2Value);
|
||||
|
||||
@EnforcePermission("MODIFY_AUDIO_SYSTEM_SETTINGS")
|
||||
float getCsd();
|
||||
|
||||
@EnforcePermission("MODIFY_AUDIO_SYSTEM_SETTINGS")
|
||||
oneway void setCsd(float csd);
|
||||
|
||||
@EnforcePermission("MODIFY_AUDIO_SYSTEM_SETTINGS")
|
||||
oneway void forceUseFrameworkMel(boolean useFrameworkMel);
|
||||
|
||||
@EnforcePermission("MODIFY_AUDIO_SYSTEM_SETTINGS")
|
||||
oneway void forceComputeCsdOnAllDevices(boolean computeCsdOnAllDevices);
|
||||
|
||||
@EnforcePermission("MODIFY_AUDIO_SYSTEM_SETTINGS")
|
||||
boolean isCsdEnabled();
|
||||
|
||||
int setHdmiSystemAudioSupported(boolean on);
|
||||
|
||||
boolean isHdmiSystemAudioSupported();
|
||||
|
||||
@@ -646,6 +646,9 @@
|
||||
<uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL" />
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" />
|
||||
|
||||
<!-- Permission required for CTS test - SoundDoseHelperTest -->
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SYSTEM_SETTINGS" />
|
||||
|
||||
<!-- Permission required for CTS test - CtsAmbientContextDetectionServiceDeviceTest -->
|
||||
<uses-permission android:name="android.permission.ACCESS_AMBIENT_CONTEXT_EVENT" />
|
||||
|
||||
|
||||
@@ -908,9 +908,6 @@ public class AudioService extends IAudioService.Stub
|
||||
|
||||
private final SoundDoseHelper mSoundDoseHelper;
|
||||
|
||||
@GuardedBy("mSettingsLock")
|
||||
private int mCurrentImeUid;
|
||||
|
||||
private final Object mSupportedSystemUsagesLock = new Object();
|
||||
@GuardedBy("mSupportedSystemUsagesLock")
|
||||
private @AttributeSystemUsage int[] mSupportedSystemUsages =
|
||||
@@ -10110,6 +10107,55 @@ public class AudioService extends IAudioService.Stub
|
||||
"com.android.server.audio", "AudioService");
|
||||
}
|
||||
|
||||
@Override
|
||||
@android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
|
||||
public float getRs2Value() {
|
||||
super.getRs2Value_enforcePermission();
|
||||
return mSoundDoseHelper.getRs2Value();
|
||||
}
|
||||
|
||||
@Override
|
||||
@android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
|
||||
public void setRs2Value(float rs2Value) {
|
||||
super.setRs2Value_enforcePermission();
|
||||
mSoundDoseHelper.setRs2Value(rs2Value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
|
||||
public float getCsd() {
|
||||
super.getCsd_enforcePermission();
|
||||
return mSoundDoseHelper.getCsd();
|
||||
}
|
||||
|
||||
@Override
|
||||
@android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
|
||||
public void setCsd(float csd) {
|
||||
super.setCsd_enforcePermission();
|
||||
mSoundDoseHelper.setCsd(csd);
|
||||
}
|
||||
|
||||
@Override
|
||||
@android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
|
||||
public void forceUseFrameworkMel(boolean useFrameworkMel) {
|
||||
super.forceUseFrameworkMel_enforcePermission();
|
||||
mSoundDoseHelper.forceUseFrameworkMel(useFrameworkMel);
|
||||
}
|
||||
|
||||
@Override
|
||||
@android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
|
||||
public void forceComputeCsdOnAllDevices(boolean computeCsdOnAllDevices) {
|
||||
super.forceComputeCsdOnAllDevices_enforcePermission();
|
||||
mSoundDoseHelper.forceComputeCsdOnAllDevices(computeCsdOnAllDevices);
|
||||
}
|
||||
|
||||
@Override
|
||||
@android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
|
||||
public boolean isCsdEnabled() {
|
||||
super.isCsdEnabled_enforcePermission();
|
||||
return mSoundDoseHelper.isCsdEnabled();
|
||||
}
|
||||
|
||||
//==========================================================================================
|
||||
// Hdmi CEC:
|
||||
// - System audio mode:
|
||||
@@ -10461,7 +10507,6 @@ public class AudioService extends IAudioService.Stub
|
||||
+ " FromRestrictions=" + mMicMuteFromRestrictions
|
||||
+ " FromApi=" + mMicMuteFromApi
|
||||
+ " from system=" + mMicMuteFromSystemCached);
|
||||
pw.print(" mCurrentImeUid="); pw.println(mCurrentImeUid);
|
||||
dumpAccessibilityServiceUids(pw);
|
||||
dumpAssistantServicesUids(pw);
|
||||
|
||||
@@ -10595,6 +10640,15 @@ public class AudioService extends IAudioService.Stub
|
||||
if (DEBUG_VOL) Log.d(TAG, "Volume controller: " + mVolumeController);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public IVolumeController getVolumeController() {
|
||||
enforceVolumeController("get the volume controller");
|
||||
if (DEBUG_VOL) Log.d(TAG, "Volume controller: " + mVolumeController);
|
||||
|
||||
return mVolumeController.getController();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyVolumeControllerVisible(final IVolumeController controller, boolean visible) {
|
||||
enforceVolumeController("notify about volume controller visibility");
|
||||
@@ -10640,6 +10694,10 @@ public class AudioService extends IAudioService.Stub
|
||||
mVisible = false;
|
||||
}
|
||||
|
||||
public IVolumeController getController() {
|
||||
return mController;
|
||||
}
|
||||
|
||||
public void loadSettings(ContentResolver cr) {
|
||||
mLongPressTimeout = mSettings.getSecureIntForUser(cr,
|
||||
Settings.Secure.LONG_PRESS_TIMEOUT, 500, UserHandle.USER_CURRENT);
|
||||
|
||||
@@ -53,6 +53,7 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@@ -70,9 +71,6 @@ public class SoundDoseHelper {
|
||||
/*package*/ static final String ACTION_CHECK_MUSIC_ACTIVE =
|
||||
"com.android.server.audio.action.CHECK_MUSIC_ACTIVE";
|
||||
|
||||
/** Flag to enable/disable the sound dose computation. */
|
||||
private static final boolean USE_CSD_FOR_SAFE_HEARING = false;
|
||||
|
||||
// mSafeMediaVolumeState indicates whether the media volume is limited over headphones.
|
||||
// It is SAFE_MEDIA_VOLUME_NOT_CONFIGURED at boot time until a network service is connected
|
||||
// or the configure time is elapsed. It is then set to SAFE_MEDIA_VOLUME_ACTIVE or
|
||||
@@ -96,8 +94,6 @@ public class SoundDoseHelper {
|
||||
private static final int MUSIC_ACTIVE_POLL_PERIOD_MS = 60000; // 1 minute polling interval
|
||||
private static final int REQUEST_CODE_CHECK_MUSIC_ACTIVE = 1;
|
||||
|
||||
private static final float CUSTOM_RS2_VALUE = 90;
|
||||
|
||||
// timeouts for the CSD warnings, -1 means no timeout (dialog must be ack'd by user)
|
||||
private static final int CSD_WARNING_TIMEOUT_MS_DOSE_1X = 7000;
|
||||
private static final int CSD_WARNING_TIMEOUT_MS_DOSE_5X = 5000;
|
||||
@@ -233,6 +229,94 @@ public class SoundDoseHelper {
|
||||
initCsd();
|
||||
}
|
||||
|
||||
float getRs2Value() {
|
||||
if (!mEnableCsd) {
|
||||
return 0.f;
|
||||
}
|
||||
|
||||
Objects.requireNonNull(mSoundDose, "Sound dose interface not initialized");
|
||||
try {
|
||||
return mSoundDose.getOutputRs2();
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Exception while getting the RS2 exposure value", e);
|
||||
return 0.f;
|
||||
}
|
||||
}
|
||||
|
||||
void setRs2Value(float rs2Value) {
|
||||
if (!mEnableCsd) {
|
||||
return;
|
||||
}
|
||||
|
||||
Objects.requireNonNull(mSoundDose, "Sound dose interface not initialized");
|
||||
try {
|
||||
mSoundDose.setOutputRs2(rs2Value);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Exception while setting the RS2 exposure value", e);
|
||||
}
|
||||
}
|
||||
|
||||
float getCsd() {
|
||||
if (!mEnableCsd) {
|
||||
return -1.f;
|
||||
}
|
||||
|
||||
Objects.requireNonNull(mSoundDose, "Sound dose interface not initialized");
|
||||
try {
|
||||
return mSoundDose.getCsd();
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Exception while getting the CSD value", e);
|
||||
return -1.f;
|
||||
}
|
||||
}
|
||||
|
||||
void setCsd(float csd) {
|
||||
if (!mEnableCsd) {
|
||||
return;
|
||||
}
|
||||
|
||||
Objects.requireNonNull(mSoundDose, "Sound dose interface not initialized");
|
||||
try {
|
||||
final SoundDoseRecord record = new SoundDoseRecord();
|
||||
record.timestamp = System.currentTimeMillis();
|
||||
record.value = csd;
|
||||
final SoundDoseRecord[] recordArray = new SoundDoseRecord[] { record };
|
||||
mSoundDose.resetCsd(csd, recordArray);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Exception while setting the CSD value", e);
|
||||
}
|
||||
}
|
||||
|
||||
void forceUseFrameworkMel(boolean useFrameworkMel) {
|
||||
if (!mEnableCsd) {
|
||||
return;
|
||||
}
|
||||
|
||||
Objects.requireNonNull(mSoundDose, "Sound dose interface not initialized");
|
||||
try {
|
||||
mSoundDose.forceUseFrameworkMel(useFrameworkMel);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Exception while forcing the internal MEL computation", e);
|
||||
}
|
||||
}
|
||||
|
||||
void forceComputeCsdOnAllDevices(boolean computeCsdOnAllDevices) {
|
||||
if (!mEnableCsd) {
|
||||
return;
|
||||
}
|
||||
|
||||
Objects.requireNonNull(mSoundDose, "Sound dose interface not initialized");
|
||||
try {
|
||||
mSoundDose.forceComputeCsdOnAllDevices(computeCsdOnAllDevices);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Exception while forcing CSD computation on all devices", e);
|
||||
}
|
||||
}
|
||||
|
||||
boolean isCsdEnabled() {
|
||||
return mEnableCsd;
|
||||
}
|
||||
|
||||
/*package*/ int safeMediaVolumeIndex(int device) {
|
||||
if (!mSafeMediaVolumeDevices.contains(device)) {
|
||||
return MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC];
|
||||
@@ -293,7 +377,7 @@ public class SoundDoseHelper {
|
||||
|| (AudioService.mStreamVolumeAlias[streamType] != AudioSystem.STREAM_MUSIC)
|
||||
|| (!mSafeMediaVolumeDevices.contains(device))
|
||||
|| (index <= safeMediaVolumeIndex(device))
|
||||
|| USE_CSD_FOR_SAFE_HEARING;
|
||||
|| mEnableCsd;
|
||||
}
|
||||
|
||||
/*package*/ boolean willDisplayWarningAfterCheckVolume(int streamType, int index, int device,
|
||||
@@ -329,7 +413,7 @@ public class SoundDoseHelper {
|
||||
/*package*/ void scheduleMusicActiveCheck() {
|
||||
synchronized (mSafeMediaVolumeStateLock) {
|
||||
cancelMusicActiveCheck();
|
||||
if (!USE_CSD_FOR_SAFE_HEARING) {
|
||||
if (!mEnableCsd) {
|
||||
mMusicActiveIntent = PendingIntent.getBroadcast(mContext,
|
||||
REQUEST_CODE_CHECK_MUSIC_ACTIVE,
|
||||
new Intent(ACTION_CHECK_MUSIC_ACTIVE),
|
||||
@@ -460,14 +544,13 @@ public class SoundDoseHelper {
|
||||
|
||||
private void initCsd() {
|
||||
synchronized (mSafeMediaVolumeStateLock) {
|
||||
if (USE_CSD_FOR_SAFE_HEARING) {
|
||||
if (mEnableCsd) {
|
||||
Log.v(TAG, "Initializing sound dose");
|
||||
|
||||
mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_DISABLED;
|
||||
mSoundDose = AudioSystem.getSoundDoseInterface(mSoundDoseCallback);
|
||||
try {
|
||||
if (mSoundDose != null && mSoundDose.asBinder().isBinderAlive()) {
|
||||
mSoundDose.setOutputRs2(CUSTOM_RS2_VALUE);
|
||||
if (mCurrentCsd != 0.f) {
|
||||
Log.d(TAG, "Resetting the saved sound dose value " + mCurrentCsd);
|
||||
SoundDoseRecord[] records = mDoseRecords.toArray(
|
||||
@@ -502,7 +585,7 @@ public class SoundDoseHelper {
|
||||
// The persisted state is either "disabled" or "active": this is the state applied
|
||||
// next time we boot and cannot be "inactive"
|
||||
int persistedState;
|
||||
if (safeMediaVolumeEnabled && !safeMediaVolumeBypass && !USE_CSD_FOR_SAFE_HEARING) {
|
||||
if (safeMediaVolumeEnabled && !safeMediaVolumeBypass && !mEnableCsd) {
|
||||
persistedState = SAFE_MEDIA_VOLUME_ACTIVE;
|
||||
// The state can already be "inactive" here if the user has forced it before
|
||||
// the 30 seconds timeout for forced configuration. In this case we don't reset
|
||||
|
||||
Reference in New Issue
Block a user