Merge "Combining widget broadcasts" into tm-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
e6e061e5d5
@@ -42,12 +42,15 @@ import android.os.Handler;
|
|||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.os.UserHandle;
|
import android.os.UserHandle;
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
|
import android.util.Log;
|
||||||
import android.widget.RemoteViews;
|
import android.widget.RemoteViews;
|
||||||
|
|
||||||
import com.android.internal.appwidget.IAppWidgetService;
|
import com.android.internal.appwidget.IAppWidgetService;
|
||||||
|
import com.android.internal.os.BackgroundThread;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates AppWidget state; gets information about installed AppWidget providers and other
|
* Updates AppWidget state; gets information about installed AppWidget providers and other
|
||||||
@@ -63,6 +66,7 @@ import java.util.List;
|
|||||||
@RequiresFeature(PackageManager.FEATURE_APP_WIDGETS)
|
@RequiresFeature(PackageManager.FEATURE_APP_WIDGETS)
|
||||||
public class AppWidgetManager {
|
public class AppWidgetManager {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activity action to launch from your {@link AppWidgetHost} activity when you want to
|
* Activity action to launch from your {@link AppWidgetHost} activity when you want to
|
||||||
* pick an AppWidget to display. The AppWidget picker activity will be launched.
|
* pick an AppWidget to display. The AppWidget picker activity will be launched.
|
||||||
@@ -331,6 +335,17 @@ public class AppWidgetManager {
|
|||||||
@BroadcastBehavior(explicitOnly = true)
|
@BroadcastBehavior(explicitOnly = true)
|
||||||
public static final String ACTION_APPWIDGET_UPDATE = "android.appwidget.action.APPWIDGET_UPDATE";
|
public static final String ACTION_APPWIDGET_UPDATE = "android.appwidget.action.APPWIDGET_UPDATE";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A combination broadcast of APPWIDGET_ENABLED and APPWIDGET_UPDATE.
|
||||||
|
* Sent during boot time and when the host is binding the widget for the very first time
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
|
||||||
|
@BroadcastBehavior(explicitOnly = true)
|
||||||
|
public static final String ACTION_APPWIDGET_ENABLE_AND_UPDATE = "android.appwidget.action"
|
||||||
|
+ ".APPWIDGET_ENABLE_AND_UPDATE";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sent when the custom extras for an AppWidget change.
|
* Sent when the custom extras for an AppWidget change.
|
||||||
*
|
*
|
||||||
@@ -456,6 +471,8 @@ public class AppWidgetManager {
|
|||||||
public static final String ACTION_APPWIDGET_HOST_RESTORED
|
public static final String ACTION_APPWIDGET_HOST_RESTORED
|
||||||
= "android.appwidget.action.APPWIDGET_HOST_RESTORED";
|
= "android.appwidget.action.APPWIDGET_HOST_RESTORED";
|
||||||
|
|
||||||
|
private static final String TAG = "AppWidgetManager";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An intent extra that contains multiple appWidgetIds. These are id values as
|
* An intent extra that contains multiple appWidgetIds. These are id values as
|
||||||
* they were provided to the application during a recent restore from backup. It is
|
* they were provided to the application during a recent restore from backup. It is
|
||||||
@@ -511,6 +528,26 @@ public class AppWidgetManager {
|
|||||||
mPackageName = context.getOpPackageName();
|
mPackageName = context.getOpPackageName();
|
||||||
mService = service;
|
mService = service;
|
||||||
mDisplayMetrics = context.getResources().getDisplayMetrics();
|
mDisplayMetrics = context.getResources().getDisplayMetrics();
|
||||||
|
if (mService == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
BackgroundThread.getExecutor().execute(() -> {
|
||||||
|
try {
|
||||||
|
mService.notifyProviderInheritance(getInstalledProvidersForPackage(mPackageName,
|
||||||
|
null)
|
||||||
|
.stream().filter(Objects::nonNull)
|
||||||
|
.map(info -> info.provider).filter(p -> {
|
||||||
|
try {
|
||||||
|
Class clazz = Class.forName(p.getClassName());
|
||||||
|
return AppWidgetProvider.class.isAssignableFrom(clazz);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}).toArray(ComponentName[]::new));
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Nofity service of inheritance info", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -58,7 +58,12 @@ public class AppWidgetProvider extends BroadcastReceiver {
|
|||||||
// Protect against rogue update broadcasts (not really a security issue,
|
// Protect against rogue update broadcasts (not really a security issue,
|
||||||
// just filter bad broacasts out so subclasses are less likely to crash).
|
// just filter bad broacasts out so subclasses are less likely to crash).
|
||||||
String action = intent.getAction();
|
String action = intent.getAction();
|
||||||
if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
|
if (AppWidgetManager.ACTION_APPWIDGET_ENABLE_AND_UPDATE.equals(action)) {
|
||||||
|
this.onReceive(context, new Intent(intent)
|
||||||
|
.setAction(AppWidgetManager.ACTION_APPWIDGET_ENABLED));
|
||||||
|
this.onReceive(context, new Intent(intent)
|
||||||
|
.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE));
|
||||||
|
} else if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
|
||||||
Bundle extras = intent.getExtras();
|
Bundle extras = intent.getExtras();
|
||||||
if (extras != null) {
|
if (extras != null) {
|
||||||
int[] appWidgetIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);
|
int[] appWidgetIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);
|
||||||
|
|||||||
@@ -578,6 +578,11 @@ public final class SystemUiDeviceConfigFlags {
|
|||||||
*/
|
*/
|
||||||
public static final String CLIPBOARD_OVERLAY_SHOW_ACTIONS = "clipboard_overlay_show_actions";
|
public static final String CLIPBOARD_OVERLAY_SHOW_ACTIONS = "clipboard_overlay_show_actions";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (boolean) Whether to combine the broadcasts APPWIDGET_ENABLED and APPWIDGET_UPDATE
|
||||||
|
*/
|
||||||
|
public static final String COMBINED_BROADCAST_ENABLED = "combined_broadcast_enabled";
|
||||||
|
|
||||||
private SystemUiDeviceConfigFlags() {
|
private SystemUiDeviceConfigFlags() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -142,6 +142,7 @@
|
|||||||
<protected-broadcast android:name="android.appwidget.action.APPWIDGET_ENABLED" />
|
<protected-broadcast android:name="android.appwidget.action.APPWIDGET_ENABLED" />
|
||||||
<protected-broadcast android:name="android.appwidget.action.APPWIDGET_HOST_RESTORED" />
|
<protected-broadcast android:name="android.appwidget.action.APPWIDGET_HOST_RESTORED" />
|
||||||
<protected-broadcast android:name="android.appwidget.action.APPWIDGET_RESTORED" />
|
<protected-broadcast android:name="android.appwidget.action.APPWIDGET_RESTORED" />
|
||||||
|
<protected-broadcast android:name="android.appwidget.action.APPWIDGET_ENABLE_AND_UPDATE" />
|
||||||
|
|
||||||
<protected-broadcast android:name="android.os.action.SETTING_RESTORED" />
|
<protected-broadcast android:name="android.os.action.SETTING_RESTORED" />
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
|
|||||||
|
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
|
import android.annotation.RequiresPermission;
|
||||||
import android.annotation.UserIdInt;
|
import android.annotation.UserIdInt;
|
||||||
import android.app.ActivityManager;
|
import android.app.ActivityManager;
|
||||||
import android.app.ActivityManagerInternal;
|
import android.app.ActivityManagerInternal;
|
||||||
@@ -254,11 +255,13 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
|
|||||||
private boolean mSafeMode;
|
private boolean mSafeMode;
|
||||||
private int mMaxWidgetBitmapMemory;
|
private int mMaxWidgetBitmapMemory;
|
||||||
private boolean mIsProviderInfoPersisted;
|
private boolean mIsProviderInfoPersisted;
|
||||||
|
private boolean mIsCombinedBroadcastEnabled;
|
||||||
|
|
||||||
AppWidgetServiceImpl(Context context) {
|
AppWidgetServiceImpl(Context context) {
|
||||||
mContext = context;
|
mContext = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG)
|
||||||
public void onStart() {
|
public void onStart() {
|
||||||
mPackageManager = AppGlobals.getPackageManager();
|
mPackageManager = AppGlobals.getPackageManager();
|
||||||
mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
|
mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
|
||||||
@@ -277,6 +280,8 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
|
|||||||
mIsProviderInfoPersisted = !ActivityManager.isLowRamDeviceStatic()
|
mIsProviderInfoPersisted = !ActivityManager.isLowRamDeviceStatic()
|
||||||
&& DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
|
&& DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
|
||||||
SystemUiDeviceConfigFlags.PERSISTS_WIDGET_PROVIDER_INFO, true);
|
SystemUiDeviceConfigFlags.PERSISTS_WIDGET_PROVIDER_INFO, true);
|
||||||
|
mIsCombinedBroadcastEnabled = DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
|
||||||
|
SystemUiDeviceConfigFlags.COMBINED_BROADCAST_ENABLED, true);
|
||||||
if (DEBUG_PROVIDER_INFO_CACHE && !mIsProviderInfoPersisted) {
|
if (DEBUG_PROVIDER_INFO_CACHE && !mIsProviderInfoPersisted) {
|
||||||
Slog.d(TAG, "App widget provider info will not be persisted on this device");
|
Slog.d(TAG, "App widget provider info will not be persisted on this device");
|
||||||
}
|
}
|
||||||
@@ -1123,16 +1128,16 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
|
|||||||
|
|
||||||
final int widgetCount = provider.widgets.size();
|
final int widgetCount = provider.widgets.size();
|
||||||
if (widgetCount == 1) {
|
if (widgetCount == 1) {
|
||||||
// Tell the provider that it's ready.
|
// If we are binding the very first widget from a provider, we will send
|
||||||
sendEnableIntentLocked(provider);
|
// a combined broadcast or 2 separate broadcasts to tell the provider that
|
||||||
|
// it's ready, and we need them to provide the update now.
|
||||||
|
sendEnableAndUpdateIntentLocked(provider, new int[]{appWidgetId});
|
||||||
|
} else {
|
||||||
|
// For any widget other then the first one, we just send update intent
|
||||||
|
// as we normally would.
|
||||||
|
sendUpdateIntentLocked(provider, new int[]{appWidgetId});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send an update now -- We need this update now, and just for this appWidgetId.
|
|
||||||
// It's less critical when the next one happens, so when we schedule the next one,
|
|
||||||
// we add updatePeriodMillis to its start time. That time will have some slop,
|
|
||||||
// but that's okay.
|
|
||||||
sendUpdateIntentLocked(provider, new int[] {appWidgetId});
|
|
||||||
|
|
||||||
// Schedule the future updates.
|
// Schedule the future updates.
|
||||||
registerForBroadcastsLocked(provider, getWidgetIds(provider.widgets));
|
registerForBroadcastsLocked(provider, getWidgetIds(provider.widgets));
|
||||||
|
|
||||||
@@ -2361,6 +2366,22 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
|
|||||||
cancelBroadcastsLocked(provider);
|
cancelBroadcastsLocked(provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void sendEnableAndUpdateIntentLocked(@NonNull Provider p, int[] appWidgetIds) {
|
||||||
|
final boolean canSendCombinedBroadcast = mIsCombinedBroadcastEnabled && p.info != null
|
||||||
|
&& p.info.isExtendedFromAppWidgetProvider;
|
||||||
|
if (!canSendCombinedBroadcast) {
|
||||||
|
// If this function is called by mistake, send two separate broadcasts instead
|
||||||
|
sendEnableIntentLocked(p);
|
||||||
|
sendUpdateIntentLocked(p, appWidgetIds);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLE_AND_UPDATE);
|
||||||
|
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
|
||||||
|
intent.setComponent(p.id.componentName);
|
||||||
|
sendBroadcastAsUser(intent, p.id.getProfile());
|
||||||
|
}
|
||||||
|
|
||||||
private void sendEnableIntentLocked(Provider p) {
|
private void sendEnableIntentLocked(Provider p) {
|
||||||
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLED);
|
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLED);
|
||||||
intent.setComponent(p.id.componentName);
|
intent.setComponent(p.id.componentName);
|
||||||
@@ -2852,7 +2873,6 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
|
|||||||
if (provider.widgets.size() > 0) {
|
if (provider.widgets.size() > 0) {
|
||||||
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
|
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
|
||||||
"appwidget init " + provider.id.componentName.getPackageName());
|
"appwidget init " + provider.id.componentName.getPackageName());
|
||||||
sendEnableIntentLocked(provider);
|
|
||||||
provider.widgets.forEach(widget -> {
|
provider.widgets.forEach(widget -> {
|
||||||
widget.trackingUpdate = true;
|
widget.trackingUpdate = true;
|
||||||
Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
|
Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
|
||||||
@@ -2861,7 +2881,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
|
|||||||
Log.i(TAG, "Widget update scheduled on unlock " + widget.toString());
|
Log.i(TAG, "Widget update scheduled on unlock " + widget.toString());
|
||||||
});
|
});
|
||||||
int[] appWidgetIds = getWidgetIds(provider.widgets);
|
int[] appWidgetIds = getWidgetIds(provider.widgets);
|
||||||
sendUpdateIntentLocked(provider, appWidgetIds);
|
sendEnableAndUpdateIntentLocked(provider, appWidgetIds);
|
||||||
registerForBroadcastsLocked(provider, appWidgetIds);
|
registerForBroadcastsLocked(provider, appWidgetIds);
|
||||||
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
|
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ public class AppWidgetServiceImplTest extends InstrumentationTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void testRequestPinAppWidget() {
|
public void testRequestPinAppWidget() {
|
||||||
ComponentName provider = new ComponentName(mTestContext, DummyAppWidget.class);
|
ComponentName provider = new ComponentName(mTestContext, TestAppWidgetProvider.class);
|
||||||
// Set up users.
|
// Set up users.
|
||||||
when(mMockShortcutService.requestPinAppWidget(anyString(),
|
when(mMockShortcutService.requestPinAppWidget(anyString(),
|
||||||
any(AppWidgetProviderInfo.class), eq(null), eq(null), anyInt()))
|
any(AppWidgetProviderInfo.class), eq(null), eq(null), anyInt()))
|
||||||
@@ -289,6 +289,16 @@ public class AppWidgetServiceImplTest extends InstrumentationTestCase {
|
|||||||
assertEquals(4, updates.size());
|
assertEquals(4, updates.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testReceiveBroadcastBehavior_enableAndUpdate() {
|
||||||
|
TestAppWidgetProvider testAppWidgetProvider = new TestAppWidgetProvider();
|
||||||
|
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLE_AND_UPDATE);
|
||||||
|
|
||||||
|
testAppWidgetProvider.onReceive(mTestContext, intent);
|
||||||
|
|
||||||
|
assertTrue(testAppWidgetProvider.isBehaviorSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public void testUpdatesReceived_queueNotEmpty_multipleWidgetIdProvided() {
|
public void testUpdatesReceived_queueNotEmpty_multipleWidgetIdProvided() {
|
||||||
int widgetId = setupHostAndWidget();
|
int widgetId = setupHostAndWidget();
|
||||||
int widgetId2 = bindNewWidget();
|
int widgetId2 = bindNewWidget();
|
||||||
@@ -385,7 +395,7 @@ public class AppWidgetServiceImplTest extends InstrumentationTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private int bindNewWidget() {
|
private int bindNewWidget() {
|
||||||
ComponentName provider = new ComponentName(mTestContext, DummyAppWidget.class);
|
ComponentName provider = new ComponentName(mTestContext, TestAppWidgetProvider.class);
|
||||||
int widgetId = mService.allocateAppWidgetId(mPkgName, HOST_ID);
|
int widgetId = mService.allocateAppWidgetId(mPkgName, HOST_ID);
|
||||||
assertTrue(mManager.bindAppWidgetIdIfAllowed(widgetId, provider));
|
assertTrue(mManager.bindAppWidgetIdIfAllowed(widgetId, provider));
|
||||||
assertEquals(provider, mManager.getAppWidgetInfo(widgetId).provider);
|
assertEquals(provider, mManager.getAppWidgetInfo(widgetId).provider);
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2017 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.android.server.appwidget;
|
|
||||||
|
|
||||||
import android.content.BroadcastReceiver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Placeholder widget for testing
|
|
||||||
*/
|
|
||||||
public class DummyAppWidget extends BroadcastReceiver {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context context, Intent intent) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.server.appwidget;
|
||||||
|
|
||||||
|
import android.appwidget.AppWidgetManager;
|
||||||
|
import android.appwidget.AppWidgetProvider;
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Placeholder widget for testing
|
||||||
|
*/
|
||||||
|
public class TestAppWidgetProvider extends AppWidgetProvider {
|
||||||
|
private boolean mEnabled;
|
||||||
|
private boolean mUpdated;
|
||||||
|
|
||||||
|
TestAppWidgetProvider() {
|
||||||
|
super();
|
||||||
|
mEnabled = false;
|
||||||
|
mUpdated = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isBehaviorSuccess() {
|
||||||
|
return mEnabled && mUpdated;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
|
||||||
|
int[] appWidgetids) {
|
||||||
|
mUpdated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnabled(Context context) {
|
||||||
|
mEnabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user