am d2c41457
: Merge "Let\'s reinvent storage, yet again!" into mnc-dev
* commit 'd2c414573242fc59a2d34f66f1dfb610ec7d59a3': Let's reinvent storage, yet again!
This commit is contained in:
@ -504,4 +504,5 @@ interface IPackageManager {
|
||||
|
||||
void grantDefaultPermissions(int userId);
|
||||
void setCarrierAppPackagesProvider(in IPackagesProvider provider);
|
||||
int getMountExternalMode(int uid);
|
||||
}
|
||||
|
@ -643,6 +643,10 @@ public class Process {
|
||||
}
|
||||
if (mountExternal == Zygote.MOUNT_EXTERNAL_DEFAULT) {
|
||||
argsForZygote.add("--mount-external-default");
|
||||
} else if (mountExternal == Zygote.MOUNT_EXTERNAL_READ) {
|
||||
argsForZygote.add("--mount-external-read");
|
||||
} else if (mountExternal == Zygote.MOUNT_EXTERNAL_WRITE) {
|
||||
argsForZygote.add("--mount-external-write");
|
||||
}
|
||||
argsForZygote.add("--target-sdk-version=" + targetSdkVersion);
|
||||
|
||||
@ -802,7 +806,12 @@ public class Process {
|
||||
* @hide
|
||||
*/
|
||||
public static final boolean isIsolated() {
|
||||
int uid = UserHandle.getAppId(myUid());
|
||||
return isIsolated(myUid());
|
||||
}
|
||||
|
||||
/** {@hide} */
|
||||
public static final boolean isIsolated(int uid) {
|
||||
uid = UserHandle.getAppId(uid);
|
||||
return uid >= FIRST_ISOLATED_UID && uid <= LAST_ISOLATED_UID;
|
||||
}
|
||||
|
||||
|
@ -1177,6 +1177,21 @@ public interface IMountService extends IInterface {
|
||||
_data.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remountUid(int uid) throws RemoteException {
|
||||
Parcel _data = Parcel.obtain();
|
||||
Parcel _reply = Parcel.obtain();
|
||||
try {
|
||||
_data.writeInterfaceToken(DESCRIPTOR);
|
||||
_data.writeInt(uid);
|
||||
mRemote.transact(Stub.TRANSACTION_remountUid, _data, _reply, 0);
|
||||
_reply.readException();
|
||||
} finally {
|
||||
_reply.recycle();
|
||||
_data.recycle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final String DESCRIPTOR = "IMountService";
|
||||
@ -1292,6 +1307,8 @@ public interface IMountService extends IInterface {
|
||||
static final int TRANSACTION_benchmark = IBinder.FIRST_CALL_TRANSACTION + 59;
|
||||
static final int TRANSACTION_setDebugFlags = IBinder.FIRST_CALL_TRANSACTION + 60;
|
||||
|
||||
static final int TRANSACTION_remountUid = IBinder.FIRST_CALL_TRANSACTION + 61;
|
||||
|
||||
/**
|
||||
* Cast an IBinder object into an IMountService interface, generating a
|
||||
* proxy if needed.
|
||||
@ -1845,6 +1862,13 @@ public interface IMountService extends IInterface {
|
||||
reply.writeNoException();
|
||||
return true;
|
||||
}
|
||||
case TRANSACTION_remountUid: {
|
||||
data.enforceInterface(DESCRIPTOR);
|
||||
int uid = data.readInt();
|
||||
remountUid(uid);
|
||||
reply.writeNoException();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return super.onTransact(code, data, reply, flags);
|
||||
}
|
||||
@ -2154,4 +2178,6 @@ public interface IMountService extends IInterface {
|
||||
public String getPrimaryStorageUuid() throws RemoteException;
|
||||
public void setPrimaryStorageUuid(String volumeUuid, IPackageMoveObserver callback)
|
||||
throws RemoteException;
|
||||
|
||||
public void remountUid(int uid) throws RemoteException;
|
||||
}
|
||||
|
@ -870,6 +870,15 @@ public class StorageManager {
|
||||
throw new IllegalStateException("Missing primary storage");
|
||||
}
|
||||
|
||||
/** {@hide} */
|
||||
public void remountUid(int uid) {
|
||||
try {
|
||||
mMountService.remountUid(uid);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowAsRuntimeException();
|
||||
}
|
||||
}
|
||||
|
||||
/** {@hide} */
|
||||
private static final int DEFAULT_THRESHOLD_PERCENTAGE = 10;
|
||||
private static final long DEFAULT_THRESHOLD_MAX_BYTES = 500 * MB_IN_BYTES;
|
||||
|
@ -46,8 +46,12 @@ public final class Zygote {
|
||||
|
||||
/** No external storage should be mounted. */
|
||||
public static final int MOUNT_EXTERNAL_NONE = 0;
|
||||
/** Default user-specific external storage should be mounted. */
|
||||
/** Default external storage should be mounted. */
|
||||
public static final int MOUNT_EXTERNAL_DEFAULT = 1;
|
||||
/** Read-only external storage should be mounted. */
|
||||
public static final int MOUNT_EXTERNAL_READ = 2;
|
||||
/** Read-write external storage should be mounted. */
|
||||
public static final int MOUNT_EXTERNAL_WRITE = 3;
|
||||
|
||||
private static final ZygoteHooks VM_HOOKS = new ZygoteHooks();
|
||||
|
||||
|
@ -519,6 +519,10 @@ class ZygoteConnection {
|
||||
niceName = arg.substring(arg.indexOf('=') + 1);
|
||||
} else if (arg.equals("--mount-external-default")) {
|
||||
mountExternal = Zygote.MOUNT_EXTERNAL_DEFAULT;
|
||||
} else if (arg.equals("--mount-external-read")) {
|
||||
mountExternal = Zygote.MOUNT_EXTERNAL_READ;
|
||||
} else if (arg.equals("--mount-external-write")) {
|
||||
mountExternal = Zygote.MOUNT_EXTERNAL_WRITE;
|
||||
} else if (arg.equals("--query-abi-list")) {
|
||||
abiListQuery = true;
|
||||
} else if (arg.startsWith("--instruction-set=")) {
|
||||
|
@ -66,6 +66,8 @@ static jmethodID gCallPostForkChildHooks;
|
||||
enum MountExternalKind {
|
||||
MOUNT_EXTERNAL_NONE = 0,
|
||||
MOUNT_EXTERNAL_DEFAULT = 1,
|
||||
MOUNT_EXTERNAL_READ = 2,
|
||||
MOUNT_EXTERNAL_WRITE = 3,
|
||||
};
|
||||
|
||||
static void RuntimeAbort(JNIEnv* env) {
|
||||
@ -249,10 +251,9 @@ static void SetSchedulerPolicy(JNIEnv* env) {
|
||||
|
||||
// Create a private mount namespace and bind mount appropriate emulated
|
||||
// storage for the given user.
|
||||
static bool MountEmulatedStorage(uid_t uid, jint mount_mode, bool force_mount_namespace) {
|
||||
if (mount_mode == MOUNT_EXTERNAL_NONE && !force_mount_namespace) {
|
||||
return true;
|
||||
}
|
||||
static bool MountEmulatedStorage(uid_t uid, jint mount_mode,
|
||||
bool force_mount_namespace) {
|
||||
// See storage config details at http://source.android.com/tech/storage/
|
||||
|
||||
// Create a second private mount namespace for our process
|
||||
if (unshare(CLONE_NEWNS) == -1) {
|
||||
@ -260,23 +261,35 @@ static bool MountEmulatedStorage(uid_t uid, jint mount_mode, bool force_mount_na
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mount_mode == MOUNT_EXTERNAL_NONE) {
|
||||
// Unmount storage provided by root namespace and mount requested view
|
||||
umount2("/storage", MNT_FORCE);
|
||||
|
||||
String8 storageSource;
|
||||
if (mount_mode == MOUNT_EXTERNAL_DEFAULT) {
|
||||
storageSource = "/mnt/runtime_default";
|
||||
} else if (mount_mode == MOUNT_EXTERNAL_READ) {
|
||||
storageSource = "/mnt/runtime_read";
|
||||
} else if (mount_mode == MOUNT_EXTERNAL_WRITE) {
|
||||
storageSource = "/mnt/runtime_write";
|
||||
} else {
|
||||
// Sane default of no storage visible
|
||||
return true;
|
||||
}
|
||||
|
||||
// See storage config details at http://source.android.com/tech/storage/
|
||||
userid_t user_id = multiuser_get_user_id(uid);
|
||||
|
||||
// Bind mount user-specific storage into place
|
||||
const String8 source(String8::format("/mnt/user/%d", user_id));
|
||||
const String8 target(String8::format("/storage/self"));
|
||||
|
||||
if (fs_prepare_dir(source.string(), 0755, 0, 0) == -1) {
|
||||
if (TEMP_FAILURE_RETRY(mount(storageSource.string(), "/storage",
|
||||
NULL, MS_BIND | MS_REC | MS_SLAVE, NULL)) == -1) {
|
||||
ALOGW("Failed to mount %s to /storage: %s", storageSource.string(), strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (TEMP_FAILURE_RETRY(mount(source.string(), target.string(), NULL, MS_BIND, NULL)) == -1) {
|
||||
ALOGW("Failed to mount %s to %s: %s", source.string(), target.string(), strerror(errno));
|
||||
// Mount user-specific symlink helpers into place
|
||||
userid_t user_id = multiuser_get_user_id(uid);
|
||||
const String8 userSource(String8::format("/mnt/user/%d", user_id));
|
||||
if (fs_prepare_dir(userSource.string(), 0751, 0, 0) == -1) {
|
||||
return false;
|
||||
}
|
||||
if (TEMP_FAILURE_RETRY(mount(userSource.string(), "/storage/self",
|
||||
NULL, MS_BIND, NULL)) == -1) {
|
||||
ALOGW("Failed to mount %s to /storage/self: %s", userSource.string(), strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -543,7 +556,7 @@ static jint com_android_internal_os_Zygote_nativeForkSystemServer(
|
||||
pid_t pid = ForkAndSpecializeCommon(env, uid, gid, gids,
|
||||
debug_flags, rlimits,
|
||||
permittedCapabilities, effectiveCapabilities,
|
||||
MOUNT_EXTERNAL_NONE, NULL, NULL, true, NULL,
|
||||
MOUNT_EXTERNAL_DEFAULT, NULL, NULL, true, NULL,
|
||||
NULL, NULL);
|
||||
if (pid > 0) {
|
||||
// The zygote process checks whether the child process has died or not.
|
||||
|
@ -58,21 +58,6 @@
|
||||
<group gid="log" />
|
||||
</permission>
|
||||
|
||||
<permission name="android.permission.READ_EXTERNAL_STORAGE" perUser="true" >
|
||||
<group gid="sdcard_r" />
|
||||
</permission>
|
||||
|
||||
<permission name="android.permission.WRITE_EXTERNAL_STORAGE" perUser="true" >
|
||||
<group gid="sdcard_r" />
|
||||
<group gid="sdcard_rw" />
|
||||
</permission>
|
||||
|
||||
<permission name="android.permission.ACCESS_ALL_EXTERNAL_STORAGE" >
|
||||
<group gid="sdcard_r" />
|
||||
<group gid="sdcard_rw" />
|
||||
<group gid="sdcard_all" />
|
||||
</permission>
|
||||
|
||||
<permission name="android.permission.WRITE_MEDIA_STORAGE" >
|
||||
<group gid="media_rw" />
|
||||
</permission>
|
||||
|
@ -50,7 +50,9 @@ import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.Process;
|
||||
import android.os.RemoteCallbackList;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
@ -84,6 +86,7 @@ import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.app.IMediaContainerService;
|
||||
import com.android.internal.os.SomeArgs;
|
||||
import com.android.internal.os.Zygote;
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
import com.android.internal.util.FastXmlSerializer;
|
||||
import com.android.internal.util.IndentingPrintWriter;
|
||||
@ -675,13 +678,15 @@ class MountService extends IMountService.Stub
|
||||
}
|
||||
|
||||
private void handleSystemReady() {
|
||||
resetIfReadyAndConnected();
|
||||
synchronized (mLock) {
|
||||
resetIfReadyAndConnectedLocked();
|
||||
}
|
||||
|
||||
// Start scheduling nominally-daily fstrim operations
|
||||
MountServiceIdler.scheduleIdlePass(mContext);
|
||||
}
|
||||
|
||||
private void resetIfReadyAndConnected() {
|
||||
private void resetIfReadyAndConnectedLocked() {
|
||||
Slog.d(TAG, "Thinking about reset, mSystemReady=" + mSystemReady
|
||||
+ ", mDaemonConnected=" + mDaemonConnected);
|
||||
if (mSystemReady && mDaemonConnected) {
|
||||
@ -780,7 +785,9 @@ class MountService extends IMountService.Stub
|
||||
}
|
||||
|
||||
private void handleDaemonConnected() {
|
||||
resetIfReadyAndConnected();
|
||||
synchronized (mLock) {
|
||||
resetIfReadyAndConnectedLocked();
|
||||
}
|
||||
|
||||
/*
|
||||
* Now that we've done our initialization, release
|
||||
@ -1600,7 +1607,7 @@ class MountService extends IMountService.Stub
|
||||
// reset vold so we bind into new volume into place.
|
||||
if (Objects.equals(mPrimaryStorageUuid, fsUuid)) {
|
||||
mPrimaryStorageUuid = getDefaultPrimaryStorageUuid();
|
||||
resetIfReadyAndConnected();
|
||||
resetIfReadyAndConnectedLocked();
|
||||
}
|
||||
|
||||
writeSettingsLocked();
|
||||
@ -1628,7 +1635,7 @@ class MountService extends IMountService.Stub
|
||||
}
|
||||
|
||||
writeSettingsLocked();
|
||||
resetIfReadyAndConnected();
|
||||
resetIfReadyAndConnectedLocked();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1640,6 +1647,30 @@ class MountService extends IMountService.Stub
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remountUid(int uid) {
|
||||
enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
|
||||
waitForReady();
|
||||
|
||||
final int mountExternal = mPms.getMountExternalMode(uid);
|
||||
final String mode;
|
||||
if (mountExternal == Zygote.MOUNT_EXTERNAL_DEFAULT) {
|
||||
mode = "default";
|
||||
} else if (mountExternal == Zygote.MOUNT_EXTERNAL_READ) {
|
||||
mode = "read";
|
||||
} else if (mountExternal == Zygote.MOUNT_EXTERNAL_WRITE) {
|
||||
mode = "write";
|
||||
} else {
|
||||
mode = "none";
|
||||
}
|
||||
|
||||
try {
|
||||
mConnector.execute("volume", "remount_uid", uid, mode);
|
||||
} catch (NativeDaemonConnectorException e) {
|
||||
Slog.w(TAG, "Failed to remount UID " + uid + " as " + mode + ": " + e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDebugFlags(int flags, int mask) {
|
||||
enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
|
||||
@ -1651,7 +1682,7 @@ class MountService extends IMountService.Stub
|
||||
}
|
||||
|
||||
writeSettingsLocked();
|
||||
resetIfReadyAndConnected();
|
||||
resetIfReadyAndConnectedLocked();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1688,7 +1719,7 @@ class MountService extends IMountService.Stub
|
||||
Slog.d(TAG, "Skipping move to/from primary physical");
|
||||
onMoveStatusLocked(MOVE_STATUS_COPY_FINISHED);
|
||||
onMoveStatusLocked(PackageManager.MOVE_SUCCEEDED);
|
||||
resetIfReadyAndConnected();
|
||||
resetIfReadyAndConnectedLocked();
|
||||
|
||||
} else {
|
||||
final VolumeInfo from = Preconditions.checkNotNull(
|
||||
@ -2022,7 +2053,7 @@ class MountService extends IMountService.Stub
|
||||
|
||||
@Override
|
||||
public void finishMediaUpdate() {
|
||||
if (Binder.getCallingUid() != android.os.Process.SYSTEM_UID) {
|
||||
if (Binder.getCallingUid() != Process.SYSTEM_UID) {
|
||||
throw new SecurityException("no permission to call finishMediaUpdate()");
|
||||
}
|
||||
if (mUnmountSignal != null) {
|
||||
|
@ -18,7 +18,9 @@ package com.android.server.am;
|
||||
|
||||
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
|
||||
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
|
||||
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
|
||||
import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
|
||||
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
|
||||
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
||||
import static com.android.internal.util.XmlUtils.readBooleanAttribute;
|
||||
import static com.android.internal.util.XmlUtils.readIntAttribute;
|
||||
@ -3209,13 +3211,14 @@ public final class ActivityManagerService extends ActivityManagerNative
|
||||
|
||||
int uid = app.uid;
|
||||
int[] gids = null;
|
||||
int mountExternal = Zygote.MOUNT_EXTERNAL_DEFAULT;
|
||||
int mountExternal = Zygote.MOUNT_EXTERNAL_NONE;
|
||||
if (!app.isolated) {
|
||||
int[] permGids = null;
|
||||
try {
|
||||
checkTime(startTime, "startProcess: getting gids from package manager");
|
||||
permGids = AppGlobals.getPackageManager().getPackageGids(app.info.packageName,
|
||||
app.userId);
|
||||
final IPackageManager pm = AppGlobals.getPackageManager();
|
||||
permGids = pm.getPackageGids(app.info.packageName, app.userId);
|
||||
mountExternal = pm.getMountExternalMode(uid);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowAsRuntimeException();
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ package com.android.server.pm;
|
||||
|
||||
import static android.Manifest.permission.GRANT_REVOKE_PERMISSIONS;
|
||||
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
|
||||
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
|
||||
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
|
||||
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
|
||||
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
|
||||
@ -54,6 +55,7 @@ import static android.content.pm.PackageManager.MOVE_FAILED_DOESNT_EXIST;
|
||||
import static android.content.pm.PackageManager.MOVE_FAILED_INTERNAL_ERROR;
|
||||
import static android.content.pm.PackageManager.MOVE_FAILED_OPERATION_PENDING;
|
||||
import static android.content.pm.PackageManager.MOVE_FAILED_SYSTEM_PACKAGE;
|
||||
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
||||
import static android.content.pm.PackageParser.isApkFile;
|
||||
import static android.os.Process.PACKAGE_INFO_GID;
|
||||
import static android.os.Process.SYSTEM_UID;
|
||||
@ -196,6 +198,7 @@ import com.android.internal.content.NativeLibraryHelper;
|
||||
import com.android.internal.content.PackageHelper;
|
||||
import com.android.internal.os.IParcelFileDescriptorFactory;
|
||||
import com.android.internal.os.SomeArgs;
|
||||
import com.android.internal.os.Zygote;
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
import com.android.internal.util.FastPrintWriter;
|
||||
import com.android.internal.util.FastXmlSerializer;
|
||||
@ -208,8 +211,8 @@ import com.android.server.LocalServices;
|
||||
import com.android.server.ServiceThread;
|
||||
import com.android.server.SystemConfig;
|
||||
import com.android.server.Watchdog;
|
||||
import com.android.server.pm.Settings.DatabaseVersion;
|
||||
import com.android.server.pm.PermissionsState.PermissionState;
|
||||
import com.android.server.pm.Settings.DatabaseVersion;
|
||||
import com.android.server.storage.DeviceStorageMonitorInternal;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
@ -2562,6 +2565,21 @@ public class PackageManagerService extends IPackageManager.Stub {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMountExternalMode(int uid) {
|
||||
if (Process.isIsolated(uid)) {
|
||||
return Zygote.MOUNT_EXTERNAL_NONE;
|
||||
} else {
|
||||
if (checkUidPermission(WRITE_EXTERNAL_STORAGE, uid) == PERMISSION_GRANTED) {
|
||||
return Zygote.MOUNT_EXTERNAL_WRITE;
|
||||
} else if (checkUidPermission(READ_EXTERNAL_STORAGE, uid) == PERMISSION_GRANTED) {
|
||||
return Zygote.MOUNT_EXTERNAL_READ;
|
||||
} else {
|
||||
return Zygote.MOUNT_EXTERNAL_DEFAULT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static PermissionInfo generatePermissionInfo(
|
||||
BasePermission bp, int flags) {
|
||||
if (bp.perm != null) {
|
||||
@ -3201,6 +3219,7 @@ public class PackageManagerService extends IPackageManager.Stub {
|
||||
enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false,
|
||||
"grantRuntimePermission");
|
||||
|
||||
final int uid;
|
||||
final SettingBase sb;
|
||||
|
||||
synchronized (mPackages) {
|
||||
@ -3216,6 +3235,7 @@ public class PackageManagerService extends IPackageManager.Stub {
|
||||
|
||||
enforceDeclaredAsUsedAndRuntimePermission(pkg, bp);
|
||||
|
||||
uid = pkg.applicationInfo.uid;
|
||||
sb = (SettingBase) pkg.mExtras;
|
||||
if (sb == null) {
|
||||
throw new IllegalArgumentException("Unknown package: " + packageName);
|
||||
@ -3245,11 +3265,22 @@ public class PackageManagerService extends IPackageManager.Stub {
|
||||
} break;
|
||||
}
|
||||
|
||||
mOnPermissionChangeListeners.onPermissionsChanged(pkg.applicationInfo.uid);
|
||||
mOnPermissionChangeListeners.onPermissionsChanged(uid);
|
||||
|
||||
// Not critical if that is lost - app has to request again.
|
||||
mSettings.writeRuntimePermissionsForUserLPr(userId, false);
|
||||
}
|
||||
|
||||
if (READ_EXTERNAL_STORAGE.equals(name)
|
||||
|| WRITE_EXTERNAL_STORAGE.equals(name)) {
|
||||
final long token = Binder.clearCallingIdentity();
|
||||
try {
|
||||
final StorageManager storage = mContext.getSystemService(StorageManager.class);
|
||||
storage.remountUid(uid);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
Reference in New Issue
Block a user