Commit eb04b682 authored by Katie Dektar's avatar Katie Dektar Committed by Commit Bot

Revert "Refactor DisplayCutoutController to prepare for componentization."

This reverts commit eb6596e7.

Reason for revert: Suspected to have broken compile on Android:
https://ci.chromium.org/p/chromium/builders/ci/android-archive-rel/13404?

Original change's description:
> Refactor DisplayCutoutController to prepare for componentization.
> 
> DisplayCutoutController is split into DisplayCutoutController
> (destined for //components) and DisplayCutoutTabHelper (which stays
> in //chrome). Relocation/reuse will occur in a follow up CL.
> 
> TBR=dtrainor@chromium.org
> Bug: 1095714
> 
> Change-Id: Ie119069ae4756baf73acc23d655665fce1d4c035
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2296888
> Commit-Queue: Evan Stade <estade@chromium.org>
> Reviewed-by: Becca Hughes <beccahughes@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#790402}

TBR=dtrainor@chromium.org,estade@chromium.org,beccahughes@chromium.org

Change-Id: I9821d88e9e06163aae93800ae84ed21fb32ac180
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Bug: 1095714
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2310131Reviewed-by: default avatarKatie Dektar <katie@chromium.org>
Commit-Queue: Katie Dektar <katie@chromium.org>
Cr-Commit-Position: refs/heads/master@{#790440}
parent 2847ae48
......@@ -513,7 +513,6 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/directactions/MenuDirectActionHandler.java",
"java/src/org/chromium/chrome/browser/directactions/SimpleDirectActionHandler.java",
"java/src/org/chromium/chrome/browser/display_cutout/DisplayCutoutController.java",
"java/src/org/chromium/chrome/browser/display_cutout/DisplayCutoutTabHelper.java",
"java/src/org/chromium/chrome/browser/document/ChromeIntentUtil.java",
"java/src/org/chromium/chrome/browser/document/ChromeLauncherActivity.java",
"java/src/org/chromium/chrome/browser/document/DocumentWebContentsDelegate.java",
......
......@@ -14,21 +14,29 @@ import android.view.WindowManager.LayoutParams;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import org.chromium.base.UserData;
import org.chromium.base.UserDataHost;
import org.chromium.blink.mojom.ViewportFit;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.tab.EmptyTabObserver;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabObserver;
import org.chromium.chrome.browser.tab.TabSelectionType;
import org.chromium.components.browser_ui.widget.InsetObserverView;
import org.chromium.content_public.browser.WebContents;
import org.chromium.content_public.browser.WebContentsObserver;
import org.chromium.ui.base.WindowAndroid;
/**
* Controls the display safe area for a {@link WebContents} and the cutout mode for an {@link
* Activity} window.
*
* The WebContents is updated with the safe area continuously, as long as {@link
* Delegate#getAttachedActivity()} returns a non-null value. The cutout mode is set on the
* Activity's window only in P+, and only when the associated WebContents is fullscreen.
* Controls the display cutout state for the tab.
*/
public class DisplayCutoutController implements InsetObserverView.WindowInsetObserver {
public class DisplayCutoutController implements InsetObserverView.WindowInsetObserver, UserData {
private static final Class<DisplayCutoutController> USER_DATA_KEY =
DisplayCutoutController.class;
/** The tab that this controller belongs to. */
private Tab mTab;
/** {@link Window} of the current {@link Activity}. */
private Window mWindow;
......@@ -41,47 +49,73 @@ public class DisplayCutoutController implements InsetObserverView.WindowInsetObs
*/
private @Nullable InsetObserverView mInsetObserverView;
/** An interface for providing embedder-specific behavior to the controller. */
interface Delegate {
/** Returns the activity this controller is associated with, if there is one. */
@Nullable
Activity getAttachedActivity();
/**
* Returns the {@link WebContents} this controller should update the safe area for, if
* there is one.
*/
@Nullable
WebContents getWebContents();
/** Returns the view this controller uses for safe area updates, if there is one. */
@Nullable
InsetObserverView getInsetObserverView();
/** Returns whether the user can interact with the associated WebContents/UI element. */
boolean isInteractable();
/** Listens to various Tab events. */
private final TabObserver mTabObserver = new EmptyTabObserver() {
@Override
public void onShown(Tab tab, @TabSelectionType int type) {
assert tab == mTab;
// Force a layout update if we are now being shown.
maybeUpdateLayout();
}
@Override
public void onInteractabilityChanged(Tab tab, boolean interactable) {
// Force a layout update if the tab is now in the foreground.
maybeUpdateLayout();
}
@Override
public void onActivityAttachmentChanged(Tab tab, @Nullable WindowAndroid window) {
assert tab == mTab;
if (window != null) {
maybeAddInsetObserver(tab.getWindowAndroid().getActivity().get());
} else {
maybeRemoveInsetObserver();
}
}
};
public static DisplayCutoutController from(Tab tab) {
UserDataHost host = tab.getUserDataHost();
DisplayCutoutController controller = host.getUserData(USER_DATA_KEY);
return controller == null
? host.setUserData(USER_DATA_KEY, new DisplayCutoutController(tab))
: controller;
}
private final Delegate mDelegate;
public DisplayCutoutController(Delegate delegate) {
mDelegate = delegate;
maybeAddInsetObserver();
/**
* Constructs a new DisplayCutoutController for a specific tab.
* @param tab The tab that this controller belongs to.
*/
@VisibleForTesting
DisplayCutoutController(Tab tab) {
mTab = tab;
tab.addObserver(mTabObserver);
maybeAddInsetObserver(tab.getWindowAndroid().getActivity().get());
}
/** Add an observer to {@link InsetObserverView} if we have not already added one. */
void maybeAddInsetObserver() {
Activity activity = mDelegate.getAttachedActivity();
/**
* Add an observer to {@link InsetObserverView} if we have not already added
* one.
*/
private void maybeAddInsetObserver(Activity activity) {
if (mInsetObserverView != null || activity == null) return;
mInsetObserverView = mDelegate.getInsetObserverView();
mInsetObserverView = ((ChromeActivity) activity).getInsetObserverView();
if (mInsetObserverView == null) return;
mInsetObserverView.addObserver(this);
mWindow = activity.getWindow();
}
/** Remove the observer added to {@link InsetObserverView} if we have added one. */
void maybeRemoveInsetObserver() {
/**
* Remove the observer added to {@link InsetObserverView} if we have added
* one.
*/
private void maybeRemoveInsetObserver() {
if (mInsetObserverView == null) return;
mInsetObserverView.removeObserver(this);
......@@ -89,7 +123,9 @@ public class DisplayCutoutController implements InsetObserverView.WindowInsetObs
mWindow = null;
}
@Override
public void destroy() {
mTab.removeObserver(mTabObserver);
maybeRemoveInsetObserver();
}
......@@ -98,10 +134,6 @@ public class DisplayCutoutController implements InsetObserverView.WindowInsetObs
* @param value The new viewport fit value.
*/
public void setViewportFit(@WebContentsObserver.ViewportFitType int value) {
if (value != ViewportFit.AUTO) {
assert mDelegate.getWebContents().isFullscreenForCurrentTab();
}
if (value == mViewportFit) return;
mViewportFit = value;
......@@ -111,7 +143,7 @@ public class DisplayCutoutController implements InsetObserverView.WindowInsetObs
/** Implements {@link WindowInsetsObserver}. */
@Override
public void onSafeAreaChanged(Rect area) {
WebContents webContents = mDelegate.getWebContents();
WebContents webContents = mTab.getWebContents();
if (webContents == null) return;
float dipScale = getDipScale();
......@@ -131,13 +163,18 @@ public class DisplayCutoutController implements InsetObserverView.WindowInsetObs
* @param dipScale The devices dip scale as an integer.
* @return The CSS pixel value adjusted for scale.
*/
private static int adjustInsetForScale(int inset, float dipScale) {
private int adjustInsetForScale(int inset, float dipScale) {
return (int) Math.ceil(inset / dipScale);
}
@VisibleForTesting
static void initForTesting(UserDataHost host, DisplayCutoutController controller) {
host.setUserData(USER_DATA_KEY, controller);
}
@VisibleForTesting
protected float getDipScale() {
return mDelegate.getWebContents().getTopLevelNativeWindow().getDisplay().getDipScale();
return mTab.getWindowAndroid().getDisplay().getDipScale();
}
/**
......@@ -149,7 +186,7 @@ public class DisplayCutoutController implements InsetObserverView.WindowInsetObs
@TargetApi(Build.VERSION_CODES.P)
protected int getDisplayCutoutMode() {
// If we are not interactable then force the default mode.
if (!mDelegate.isInteractable()) {
if (!mTab.isUserInteractable()) {
return LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
}
......@@ -186,12 +223,4 @@ public class DisplayCutoutController implements InsetObserverView.WindowInsetObs
attributes.layoutInDisplayCutoutMode = getDisplayCutoutMode();
setWindowAttributes(attributes);
}
void onActivityAttachmentChanged(@Nullable WindowAndroid window) {
if (window == null) {
maybeRemoveInsetObserver();
} else {
maybeAddInsetObserver();
}
}
}
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.display_cutout;
import android.app.Activity;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import org.chromium.base.UserData;
import org.chromium.base.UserDataHost;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.tab.EmptyTabObserver;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabObserver;
import org.chromium.chrome.browser.tab.TabSelectionType;
import org.chromium.components.browser_ui.widget.InsetObserverView;
import org.chromium.content_public.browser.WebContents;
import org.chromium.content_public.browser.WebContentsObserver;
import org.chromium.ui.base.WindowAndroid;
/**
* Wraps a {@link DisplayCutoutController} for a Chrome {@link Tab}.
*
* This will only be created once the tab sets a non-default viewport fit.
*/
public class DisplayCutoutTabHelper implements UserData {
private static final Class<DisplayCutoutTabHelper> USER_DATA_KEY = DisplayCutoutTabHelper.class;
/** The tab that this object belongs to. */
private Tab mTab;
@VisibleForTesting
DisplayCutoutController mCutoutController;
/** Listens to various Tab events. */
private final TabObserver mTabObserver = new EmptyTabObserver() {
@Override
public void onShown(Tab tab, @TabSelectionType int type) {
assert tab == mTab;
// Force a layout update if we are now being shown.
mCutoutController.maybeUpdateLayout();
}
@Override
public void onInteractabilityChanged(Tab tab, boolean interactable) {
// Force a layout update if the tab is now in the foreground.
mCutoutController.maybeUpdateLayout();
}
@Override
public void onActivityAttachmentChanged(Tab tab, @Nullable WindowAndroid window) {
assert tab == mTab;
mCutoutController.onActivityAttachmentChanged(window);
}
};
public static DisplayCutoutTabHelper from(Tab tab) {
UserDataHost host = tab.getUserDataHost();
DisplayCutoutTabHelper tabHelper = host.getUserData(USER_DATA_KEY);
return tabHelper == null ? host.setUserData(USER_DATA_KEY, new DisplayCutoutTabHelper(tab))
: tabHelper;
}
@VisibleForTesting
static class ChromeDisplayCutoutDelegate implements DisplayCutoutController.Delegate {
private Tab mTab;
ChromeDisplayCutoutDelegate(Tab tab) {
mTab = tab;
}
@Override
public Activity getAttachedActivity() {
return mTab.getWindowAndroid().getActivity().get();
}
@Override
public WebContents getWebContents() {
return mTab.getWebContents();
}
@Override
public InsetObserverView getInsetObserverView() {
Activity activity = getAttachedActivity();
return activity == null ? null : ((ChromeActivity) activity).getInsetObserverView();
}
@Override
public boolean isInteractable() {
return mTab.isUserInteractable();
}
}
/**
* Constructs a new DisplayCutoutTabHelper for a specific tab.
* @param tab The tab that this object belongs to.
*/
@VisibleForTesting
DisplayCutoutTabHelper(Tab tab) {
mTab = tab;
tab.addObserver(mTabObserver);
mCutoutController = new DisplayCutoutController(new ChromeDisplayCutoutDelegate(mTab));
}
/**
* Set the viewport fit value for the tab.
* @param value The new viewport fit value.
*/
public void setViewportFit(@WebContentsObserver.ViewportFitType int value) {
mCutoutController.setViewportFit(value);
}
@Override
public void destroy() {
mTab.removeObserver(mTabObserver);
mCutoutController.destroy();
}
@VisibleForTesting
static void initForTesting(Tab tab, DisplayCutoutController controller) {
DisplayCutoutTabHelper tabHelper = new DisplayCutoutTabHelper(tab);
tabHelper.mCutoutController = controller;
tab.getUserDataHost().setUserData(USER_DATA_KEY, tabHelper);
}
}
......@@ -76,8 +76,5 @@ specific_include_rules = {
],
'TabViewManagerImpl\.java': [
"+chrome/browser/browser_controls/android/java/src/org/chromium/chrome/browser/browser_controls/BrowserControlsMarginSupplier.java",
],
'TabWebContentsObserver\.java': [
"+chrome/android/java/src/org/chromium/chrome/browser/display_cutout/DisplayCutoutTabHelper.java",
]
}
......@@ -20,7 +20,7 @@ import org.chromium.base.ObserverList.RewindableIterator;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.chrome.browser.AppHooks;
import org.chromium.chrome.browser.SwipeRefreshHandler;
import org.chromium.chrome.browser.display_cutout.DisplayCutoutTabHelper;
import org.chromium.chrome.browser.display_cutout.DisplayCutoutController;
import org.chromium.chrome.browser.media.MediaCaptureNotificationService;
import org.chromium.chrome.browser.policy.PolicyAuditor;
import org.chromium.chrome.browser.policy.PolicyAuditor.AuditEvent;
......@@ -347,7 +347,7 @@ public class TabWebContentsObserver extends TabWebContentsUserData {
@Override
public void viewportFitChanged(@WebContentsObserver.ViewportFitType int value) {
DisplayCutoutTabHelper.from(mTab).setViewportFit(value);
DisplayCutoutController.from(mTab).setViewportFit(value);
}
@Override
......
......@@ -59,8 +59,8 @@ public class DisplayCutoutTestRule<T extends ChromeActivity> extends ChromeActiv
private boolean mDeviceHasCutout = true;
private float mDipScale = 1;
TestDisplayCutoutController(DisplayCutoutController.Delegate delegate) {
super(delegate);
TestDisplayCutoutController(Tab tab) {
super(tab);
}
@Override
......@@ -159,11 +159,10 @@ public class DisplayCutoutTestRule<T extends ChromeActivity> extends ChromeActiv
protected void setUp() {
mTab = getActivity().getActivityTab();
mTestController = new TestDisplayCutoutController(
new DisplayCutoutTabHelper.ChromeDisplayCutoutDelegate(mTab));
mTestController = new TestDisplayCutoutController(mTab);
TestThreadUtils.runOnUiThreadBlocking(
() -> DisplayCutoutTabHelper.initForTesting(mTab, mTestController));
() -> DisplayCutoutController.initForTesting(
mTab.getUserDataHost(), mTestController));
mListener = new FullscreenToggleObserver();
getActivity().getFullscreenManager().addObserver(mListener);
......
......@@ -31,7 +31,6 @@ import org.chromium.chrome.browser.tab.TabImpl;
import org.chromium.chrome.browser.tab.TabObserver;
import org.chromium.chrome.browser.tab.TabSelectionType;
import org.chromium.components.browser_ui.widget.InsetObserverView;
import org.chromium.content_public.browser.WebContents;
import org.chromium.testing.local.LocalRobolectricTestRunner;
import org.chromium.ui.base.WindowAndroid;
......@@ -46,9 +45,6 @@ public class DisplayCutoutControllerTest {
@Mock
private TabImpl mTab;
@Mock
private WebContents mWebContents;
@Mock
private WindowAndroid mWindowAndroid;
......@@ -64,8 +60,7 @@ public class DisplayCutoutControllerTest {
@Mock
private InsetObserverView mInsetObserver;
private DisplayCutoutTabHelper mDisplayCutoutTabHelper;
private DisplayCutoutController mController;
private DisplayCutoutController mDisplayCutoutController;
private WeakReference<Activity> mActivityRef;
......@@ -80,32 +75,28 @@ public class DisplayCutoutControllerTest {
when(mChromeActivity.getWindow()).thenReturn(mWindow);
when(mWindow.getAttributes()).thenReturn(new LayoutParams());
when(mTab.getWindowAndroid()).thenReturn(mWindowAndroid);
when(mTab.getWebContents()).thenReturn(mWebContents);
when(mWebContents.isFullscreenForCurrentTab()).thenReturn(true);
when(mWindowAndroid.getActivity()).thenReturn(mActivityRef);
when(mChromeActivity.getInsetObserverView()).thenReturn(mInsetObserver);
mDisplayCutoutTabHelper = spy(new DisplayCutoutTabHelper(mTab));
mController = spy(mDisplayCutoutTabHelper.mCutoutController);
mDisplayCutoutTabHelper.mCutoutController = mController;
mDisplayCutoutController = spy(new DisplayCutoutController(mTab));
}
@Test
@SmallTest
public void testViewportFitUpdate() {
verify(mController, never()).maybeUpdateLayout();
verify(mDisplayCutoutController, never()).maybeUpdateLayout();
mDisplayCutoutTabHelper.setViewportFit(ViewportFit.COVER);
verify(mController).maybeUpdateLayout();
mDisplayCutoutController.setViewportFit(ViewportFit.COVER);
verify(mDisplayCutoutController).maybeUpdateLayout();
}
@Test
@SmallTest
public void testViewportFitUpdateNotChanged() {
verify(mController, never()).maybeUpdateLayout();
verify(mDisplayCutoutController, never()).maybeUpdateLayout();
mDisplayCutoutTabHelper.setViewportFit(ViewportFit.AUTO);
verify(mController, never()).maybeUpdateLayout();
mDisplayCutoutController.setViewportFit(ViewportFit.AUTO);
verify(mDisplayCutoutController, never()).maybeUpdateLayout();
}
@Test
......@@ -113,9 +104,9 @@ public class DisplayCutoutControllerTest {
public void testCutoutModeWhenAutoAndInteractable() {
when(mTab.isUserInteractable()).thenReturn(true);
mDisplayCutoutTabHelper.setViewportFit(ViewportFit.AUTO);
mDisplayCutoutController.setViewportFit(ViewportFit.AUTO);
Assert.assertEquals(LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT,
mController.getDisplayCutoutMode());
mDisplayCutoutController.getDisplayCutoutMode());
}
@Test
......@@ -123,9 +114,9 @@ public class DisplayCutoutControllerTest {
public void testCutoutModeWhenCoverAndInteractable() {
when(mTab.isUserInteractable()).thenReturn(true);
mDisplayCutoutTabHelper.setViewportFit(ViewportFit.COVER);
mDisplayCutoutController.setViewportFit(ViewportFit.COVER);
Assert.assertEquals(LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES,
mController.getDisplayCutoutMode());
mDisplayCutoutController.getDisplayCutoutMode());
}
@Test
......@@ -133,9 +124,9 @@ public class DisplayCutoutControllerTest {
public void testCutoutModeWhenCoverForcedAndInteractable() {
when(mTab.isUserInteractable()).thenReturn(true);
mDisplayCutoutTabHelper.setViewportFit(ViewportFit.COVER_FORCED_BY_USER_AGENT);
mDisplayCutoutController.setViewportFit(ViewportFit.COVER_FORCED_BY_USER_AGENT);
Assert.assertEquals(LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES,
mController.getDisplayCutoutMode());
mDisplayCutoutController.getDisplayCutoutMode());
}
@Test
......@@ -143,41 +134,41 @@ public class DisplayCutoutControllerTest {
public void testCutoutModeWhenContainAndInteractable() {
when(mTab.isUserInteractable()).thenReturn(true);
mDisplayCutoutTabHelper.setViewportFit(ViewportFit.CONTAIN);
mDisplayCutoutController.setViewportFit(ViewportFit.CONTAIN);
Assert.assertEquals(LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER,
mController.getDisplayCutoutMode());
mDisplayCutoutController.getDisplayCutoutMode());
}
@Test
@SmallTest
public void testCutoutModeWhenAutoAndNotInteractable() {
mDisplayCutoutTabHelper.setViewportFit(ViewportFit.AUTO);
mDisplayCutoutController.setViewportFit(ViewportFit.AUTO);
Assert.assertEquals(LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT,
mController.getDisplayCutoutMode());
mDisplayCutoutController.getDisplayCutoutMode());
}
@Test
@SmallTest
public void testCutoutModeWhenCoverAndNotInteractable() {
mDisplayCutoutTabHelper.setViewportFit(ViewportFit.COVER);
mDisplayCutoutController.setViewportFit(ViewportFit.COVER);
Assert.assertEquals(LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT,
mController.getDisplayCutoutMode());
mDisplayCutoutController.getDisplayCutoutMode());
}
@Test
@SmallTest
public void testCutoutModeWhenCoverForcedAndNotInteractable() {
mDisplayCutoutTabHelper.setViewportFit(ViewportFit.COVER_FORCED_BY_USER_AGENT);
mDisplayCutoutController.setViewportFit(ViewportFit.COVER_FORCED_BY_USER_AGENT);
Assert.assertEquals(LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT,
mController.getDisplayCutoutMode());
mDisplayCutoutController.getDisplayCutoutMode());
}
@Test
@SmallTest
public void testCutoutModeWhenContainAndNotInteractable() {
mDisplayCutoutTabHelper.setViewportFit(ViewportFit.CONTAIN);
mDisplayCutoutController.setViewportFit(ViewportFit.CONTAIN);
Assert.assertEquals(LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT,
mController.getDisplayCutoutMode());
mDisplayCutoutController.getDisplayCutoutMode());
}
@Test
......@@ -185,7 +176,7 @@ public class DisplayCutoutControllerTest {
public void testLayoutOnInteractability_True() {
// In this test we are checking for a side effect of maybeUpdateLayout.
// This is because the tab observer holds a reference to the original
// mDisplayCutoutTabHelper and not the spied one.
// mDisplayCutoutController and not the spied one.
verify(mTab).addObserver(mTabObserverCaptor.capture());
reset(mTab);
......@@ -198,7 +189,7 @@ public class DisplayCutoutControllerTest {
public void testLayoutOnInteractability_False() {
// In this test we are checking for a side effect of maybeUpdateLayout.
// This is because the tab observer holds a reference to the original
// mDisplayCutoutTabHelper and not the spied one.
// mDisplayCutoutController and not the spied one.
verify(mTab).addObserver(mTabObserverCaptor.capture());
reset(mTab);
......@@ -223,7 +214,7 @@ public class DisplayCutoutControllerTest {
public void testLayoutOnShown() {
// In this test we are checking for a side effect of maybeUpdateLayout.
// This is because the tab observer holds a reference to the original
// mDisplayCutoutTabHelper and not the spied one.
// mDisplayCutoutController and not the spied one.
verify(mTab).addObserver(mTabObserverCaptor.capture());
reset(mTab);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment