Merge "Revert^4 "Enable smartspace"" into sc-dev

This commit is contained in:
Matt Pietal
2021-04-11 11:10:01 +00:00
committed by Android (Google) Code Review
7 changed files with 202 additions and 8 deletions

View File

@@ -16,7 +16,9 @@
package com.android.systemui.plugins; package com.android.systemui.plugins;
import android.app.smartspace.SmartspaceTarget;
import android.os.Parcelable; import android.os.Parcelable;
import android.view.ViewGroup;
import com.android.systemui.plugins.annotations.ProvidesInterface; import com.android.systemui.plugins.annotations.ProvidesInterface;
@@ -36,9 +38,25 @@ public interface BcSmartspaceDataPlugin extends Plugin {
/** Unregister a listener. */ /** Unregister a listener. */
void unregisterListener(SmartspaceTargetListener listener); void unregisterListener(SmartspaceTargetListener listener);
/**
* Create a view to be shown within the parent. Do not add the view, as the parent
* will be responsible for correctly setting the LayoutParams
*/
default SmartspaceView getView(ViewGroup parent) {
return null;
}
/** Updates Smartspace data and propagates it to any listeners. */
void onTargetsAvailable(List<SmartspaceTarget> targets);
/** Provides Smartspace data to registered listeners. */ /** Provides Smartspace data to registered listeners. */
interface SmartspaceTargetListener { interface SmartspaceTargetListener {
/** Each Parcelable is a SmartspaceTarget that represents a card. */ /** Each Parcelable is a SmartspaceTarget that represents a card. */
void onSmartspaceTargetsUpdated(List<? extends Parcelable> targets); void onSmartspaceTargetsUpdated(List<? extends Parcelable> targets);
} }
/** View to which this plugin can be registered, in order to get updates. */
interface SmartspaceView {
void registerDataProvider(BcSmartspaceDataPlugin plugin);
}
} }

View File

@@ -50,4 +50,6 @@
<bool name="flag_charging_ripple">false</bool> <bool name="flag_charging_ripple">false</bool>
<bool name="flag_ongoing_call_status_bar_chip">true</bool> <bool name="flag_ongoing_call_status_bar_chip">true</bool>
<bool name="flag_smartspace">false</bool>
</resources> </resources>

View File

@@ -16,8 +16,15 @@
package com.android.keyguard; package com.android.keyguard;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import android.app.WallpaperManager; import android.app.WallpaperManager;
import android.app.smartspace.SmartspaceConfig;
import android.app.smartspace.SmartspaceManager;
import android.app.smartspace.SmartspaceSession;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources; import android.content.res.Resources;
import android.provider.Settings; import android.provider.Settings;
import android.text.TextUtils; import android.text.TextUtils;
@@ -25,6 +32,7 @@ import android.text.format.DateFormat;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.RelativeLayout;
import com.android.internal.colorextraction.ColorExtractor; import com.android.internal.colorextraction.ColorExtractor;
import com.android.keyguard.clock.ClockManager; import com.android.keyguard.clock.ClockManager;
@@ -32,8 +40,12 @@ import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.BcSmartspaceDataPlugin;
import com.android.systemui.plugins.ClockPlugin; import com.android.systemui.plugins.ClockPlugin;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator; import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.notification.stack.AnimationProperties; import com.android.systemui.statusbar.notification.stack.AnimationProperties;
@@ -43,6 +55,7 @@ import com.android.systemui.util.ViewController;
import java.util.Locale; import java.util.Locale;
import java.util.TimeZone; import java.util.TimeZone;
import java.util.concurrent.Executor;
import javax.inject.Inject; import javax.inject.Inject;
@@ -68,6 +81,13 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
private AnimatableClockController mNewLockScreenLargeClockViewController; private AnimatableClockController mNewLockScreenLargeClockViewController;
private FrameLayout mNewLockScreenLargeClockFrame; private FrameLayout mNewLockScreenLargeClockFrame;
private PluginManager mPluginManager;
private boolean mIsSmartspaceEnabled;
PluginListener mPluginListener;
private Executor mUiExecutor;
private SmartspaceSession mSmartspaceSession;
private SmartspaceSession.Callback mSmartspaceCallback;
private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL; private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
private final StatusBarStateController.StateListener mStateListener = private final StatusBarStateController.StateListener mStateListener =
@@ -96,6 +116,9 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
private ClockManager.ClockChangedListener mClockChangedListener = this::setClockPlugin; private ClockManager.ClockChangedListener mClockChangedListener = this::setClockPlugin;
private String mTimeFormat; private String mTimeFormat;
// If set, will replace keyguard_status_area
private BcSmartspaceDataPlugin.SmartspaceView mSmartspaceView;
@Inject @Inject
public KeyguardClockSwitchController( public KeyguardClockSwitchController(
KeyguardClockSwitch keyguardClockSwitch, KeyguardClockSwitch keyguardClockSwitch,
@@ -105,7 +128,10 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
KeyguardSliceViewController keyguardSliceViewController, KeyguardSliceViewController keyguardSliceViewController,
NotificationIconAreaController notificationIconAreaController, NotificationIconAreaController notificationIconAreaController,
ContentResolver contentResolver, ContentResolver contentResolver,
BroadcastDispatcher broadcastDispatcher) { BroadcastDispatcher broadcastDispatcher,
PluginManager pluginManager,
FeatureFlags featureFlags,
@Main Executor uiExecutor) {
super(keyguardClockSwitch); super(keyguardClockSwitch);
mResources = resources; mResources = resources;
mStatusBarStateController = statusBarStateController; mStatusBarStateController = statusBarStateController;
@@ -115,6 +141,9 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
mNotificationIconAreaController = notificationIconAreaController; mNotificationIconAreaController = notificationIconAreaController;
mBroadcastDispatcher = broadcastDispatcher; mBroadcastDispatcher = broadcastDispatcher;
mTimeFormat = Settings.System.getString(contentResolver, Settings.System.TIME_12_24); mTimeFormat = Settings.System.getString(contentResolver, Settings.System.TIME_12_24);
mPluginManager = pluginManager;
mIsSmartspaceEnabled = featureFlags.isSmartspaceEnabled();
mUiExecutor = uiExecutor;
} }
/** /**
@@ -137,6 +166,63 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
updateAodIcons(); updateAodIcons();
mNewLockScreenClockFrame = mView.findViewById(R.id.new_lockscreen_clock_view); mNewLockScreenClockFrame = mView.findViewById(R.id.new_lockscreen_clock_view);
mNewLockScreenLargeClockFrame = mView.findViewById(R.id.new_lockscreen_clock_view_large); mNewLockScreenLargeClockFrame = mView.findViewById(R.id.new_lockscreen_clock_view_large);
// If a smartspace plugin is detected, replace the existing smartspace
// (keyguard_status_area), and initialize a new session
mPluginListener = new PluginListener<BcSmartspaceDataPlugin>() {
@Override
public void onPluginConnected(BcSmartspaceDataPlugin plugin, Context pluginContext) {
if (!mIsSmartspaceEnabled) return;
View ksa = mView.findViewById(R.id.keyguard_status_area);
int ksaIndex = mView.indexOfChild(ksa);
ksa.setVisibility(View.GONE);
mSmartspaceView = plugin.getView(mView);
mSmartspaceView.registerDataProvider(plugin);
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(
MATCH_PARENT, WRAP_CONTENT);
lp.addRule(RelativeLayout.BELOW, R.id.new_lockscreen_clock_view);
mView.addView((View) mSmartspaceView, ksaIndex, lp);
View nic = mView.findViewById(
com.android.systemui.R.id.left_aligned_notification_icon_container);
lp = (RelativeLayout.LayoutParams) nic.getLayoutParams();
lp.addRule(RelativeLayout.BELOW, ((View) mSmartspaceView).getId());
nic.setLayoutParams(lp);
createSmartspaceSession(plugin);
}
@Override
public void onPluginDisconnected(BcSmartspaceDataPlugin plugin) {
if (!mIsSmartspaceEnabled) return;
mView.removeView((View) mSmartspaceView);
mView.findViewById(R.id.keyguard_status_area).setVisibility(View.VISIBLE);
View nic = mView.findViewById(
com.android.systemui.R.id.left_aligned_notification_icon_container);
RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams)
nic.getLayoutParams();
lp.addRule(RelativeLayout.BELOW, R.id.keyguard_status_area);
nic.setLayoutParams(lp);
mSmartspaceView = null;
}
private void createSmartspaceSession(BcSmartspaceDataPlugin plugin) {
mSmartspaceSession = getContext().getSystemService(SmartspaceManager.class)
.createSmartspaceSession(
new SmartspaceConfig.Builder(getContext(), "lockscreen").build());
mSmartspaceCallback = targets -> plugin.onTargetsAvailable(targets);
mSmartspaceSession.registerSmartspaceUpdates(mUiExecutor, mSmartspaceCallback);
mSmartspaceSession.requestSmartspaceUpdate();
}
};
mPluginManager.addPluginListener(mPluginListener, BcSmartspaceDataPlugin.class, false);
} }
@Override @Override
@@ -147,6 +233,13 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
mStatusBarStateController.removeCallback(mStateListener); mStatusBarStateController.removeCallback(mStateListener);
mColorExtractor.removeOnColorsChangedListener(mColorsListener); mColorExtractor.removeOnColorsChangedListener(mColorsListener);
mView.setClockPlugin(null, mStatusBarStateController.getState()); mView.setClockPlugin(null, mStatusBarStateController.getState());
if (mSmartspaceSession != null) {
mSmartspaceSession.unregisterSmartspaceUpdates(mSmartspaceCallback);
mSmartspaceSession.destroy();
mSmartspaceSession = null;
}
mPluginManager.removePluginListener(mPluginListener);
} }
/** /**
@@ -222,6 +315,11 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
PropertyAnimator.setProperty(mNewLockScreenLargeClockFrame, AnimatableProperty.SCALE_Y, PropertyAnimator.setProperty(mNewLockScreenLargeClockFrame, AnimatableProperty.SCALE_Y,
scale, props, animate); scale, props, animate);
} }
if (mSmartspaceView != null) {
PropertyAnimator.setProperty((View) mSmartspaceView, AnimatableProperty.TRANSLATION_X,
x, props, animate);
}
mKeyguardSliceViewController.updatePosition(x, props, animate); mKeyguardSliceViewController.updatePosition(x, props, animate);
mNotificationIconAreaController.updatePosition(x, props, animate); mNotificationIconAreaController.updatePosition(x, props, animate);
} }

View File

@@ -46,6 +46,7 @@ public class SystemUIFactory {
private GlobalRootComponent mRootComponent; private GlobalRootComponent mRootComponent;
private WMComponent mWMComponent; private WMComponent mWMComponent;
private SysUIComponent mSysUIComponent; private SysUIComponent mSysUIComponent;
private boolean mInitializeComponents;
public static <T extends SystemUIFactory> T getInstance() { public static <T extends SystemUIFactory> T getInstance() {
return (T) mFactory; return (T) mFactory;
@@ -88,13 +89,13 @@ public class SystemUIFactory {
public void init(Context context, boolean fromTest) public void init(Context context, boolean fromTest)
throws ExecutionException, InterruptedException { throws ExecutionException, InterruptedException {
// Only initialize components for the main system ui process running as the primary user // Only initialize components for the main system ui process running as the primary user
final boolean initializeComponents = !fromTest mInitializeComponents = !fromTest
&& android.os.Process.myUserHandle().isSystem() && android.os.Process.myUserHandle().isSystem()
&& ActivityThread.currentProcessName().equals(ActivityThread.currentPackageName()); && ActivityThread.currentProcessName().equals(ActivityThread.currentPackageName());
mRootComponent = buildGlobalRootComponent(context); mRootComponent = buildGlobalRootComponent(context);
// Stand up WMComponent // Stand up WMComponent
mWMComponent = mRootComponent.getWMComponentBuilder().build(); mWMComponent = mRootComponent.getWMComponentBuilder().build();
if (initializeComponents) { if (mInitializeComponents) {
// Only initialize when not starting from tests since this currently initializes some // Only initialize when not starting from tests since this currently initializes some
// components that shouldn't be run in the test environment // components that shouldn't be run in the test environment
mWMComponent.init(); mWMComponent.init();
@@ -102,7 +103,7 @@ public class SystemUIFactory {
// And finally, retrieve whatever SysUI needs from WMShell and build SysUI. // And finally, retrieve whatever SysUI needs from WMShell and build SysUI.
SysUIComponent.Builder builder = mRootComponent.getSysUIComponent(); SysUIComponent.Builder builder = mRootComponent.getSysUIComponent();
if (initializeComponents) { if (mInitializeComponents) {
// Only initialize when not starting from tests since this currently initializes some // Only initialize when not starting from tests since this currently initializes some
// components that shouldn't be run in the test environment // components that shouldn't be run in the test environment
builder = prepareSysUIComponentBuilder(builder, mWMComponent) builder = prepareSysUIComponentBuilder(builder, mWMComponent)
@@ -134,7 +135,7 @@ public class SystemUIFactory {
.setStartingSurface(Optional.ofNullable(null)); .setStartingSurface(Optional.ofNullable(null));
} }
mSysUIComponent = builder.build(); mSysUIComponent = builder.build();
if (initializeComponents) { if (mInitializeComponents) {
mSysUIComponent.init(); mSysUIComponent.init();
} }
@@ -160,6 +161,9 @@ public class SystemUIFactory {
.build(); .build();
} }
protected boolean shouldInitializeComponents() {
return mInitializeComponents;
}
public GlobalRootComponent getRootComponent() { public GlobalRootComponent getRootComponent() {
return mRootComponent; return mRootComponent;

View File

@@ -21,7 +21,7 @@ class SmartspaceMediaDataProvider @Inject constructor() : BcSmartspaceDataPlugin
} }
/** Updates Smartspace data and propagates it to any listeners. */ /** Updates Smartspace data and propagates it to any listeners. */
fun onTargetsAvailable(targets: List<SmartspaceTarget>) { override fun onTargetsAvailable(targets: List<SmartspaceTarget>) {
// Filter out non-media targets. // Filter out non-media targets.
val mediaTargets = mutableListOf<SmartspaceTarget>() val mediaTargets = mutableListOf<SmartspaceTarget>()
for (target in targets) { for (target in targets) {

View File

@@ -97,4 +97,8 @@ public class FeatureFlags {
public boolean isOngoingCallStatusBarChipEnabled() { public boolean isOngoingCallStatusBarChipEnabled() {
return mFlagReader.isEnabled(R.bool.flag_ongoing_call_status_bar_chip); return mFlagReader.isEnabled(R.bool.flag_ongoing_call_status_bar_chip);
} }
public boolean isSmartspaceEnabled() {
return mFlagReader.isEnabled(R.bool.flag_smartspace);
}
} }

View File

@@ -18,26 +18,34 @@ package com.android.keyguard;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources; import android.content.res.Resources;
import android.test.suitebuilder.annotation.SmallTest; import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner; import android.testing.AndroidTestingRunner;
import android.util.AttributeSet;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.RelativeLayout;
import com.android.internal.colorextraction.ColorExtractor; import com.android.internal.colorextraction.ColorExtractor;
import com.android.keyguard.clock.ClockManager; import com.android.keyguard.clock.ClockManager;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase; import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.plugins.BcSmartspaceDataPlugin;
import com.android.systemui.plugins.ClockPlugin; import com.android.systemui.plugins.ClockPlugin;
import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.NotificationIconAreaController; import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.NotificationIconContainer; import com.android.systemui.statusbar.phone.NotificationIconContainer;
@@ -50,6 +58,8 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.mockito.verification.VerificationMode; import org.mockito.verification.VerificationMode;
import java.util.concurrent.Executor;
@SmallTest @SmallTest
@RunWith(AndroidTestingRunner.class) @RunWith(AndroidTestingRunner.class)
public class KeyguardClockSwitchControllerTest extends SysuiTestCase { public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
@@ -78,6 +88,12 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
ContentResolver mContentResolver; ContentResolver mContentResolver;
@Mock @Mock
BroadcastDispatcher mBroadcastDispatcher; BroadcastDispatcher mBroadcastDispatcher;
@Mock
private PluginManager mPluginManager;
@Mock
private FeatureFlags mFeatureFlags;
@Mock
private Executor mExecutor;
private KeyguardClockSwitchController mController; private KeyguardClockSwitchController mController;
@@ -87,6 +103,8 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
when(mView.findViewById(com.android.systemui.R.id.left_aligned_notification_icon_container)) when(mView.findViewById(com.android.systemui.R.id.left_aligned_notification_icon_container))
.thenReturn(mNotificationIcons); .thenReturn(mNotificationIcons);
when(mView.getContext()).thenReturn(getContext());
when(mFeatureFlags.isSmartspaceEnabled()).thenReturn(true);
when(mView.isAttachedToWindow()).thenReturn(true); when(mView.isAttachedToWindow()).thenReturn(true);
when(mResources.getString(anyInt())).thenReturn("h:mm"); when(mResources.getString(anyInt())).thenReturn("h:mm");
mController = new KeyguardClockSwitchController( mController = new KeyguardClockSwitchController(
@@ -98,7 +116,10 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
mKeyguardSliceViewController, mKeyguardSliceViewController,
mNotificationIconAreaController, mNotificationIconAreaController,
mContentResolver, mContentResolver,
mBroadcastDispatcher); mBroadcastDispatcher,
mPluginManager,
mFeatureFlags,
mExecutor);
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE); when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
when(mColorExtractor.getColors(anyInt())).thenReturn(mGradientColors); when(mColorExtractor.getColors(anyInt())).thenReturn(mGradientColors);
@@ -182,6 +203,45 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
verify(mView).setClockPlugin(mClockPlugin, StatusBarState.SHADE); verify(mView).setClockPlugin(mClockPlugin, StatusBarState.SHADE);
} }
@Test
public void testSmartspacePluginConnectedRemovesKeyguardStatusArea() {
mController.init();
View statusArea = mock(View.class);
when(mView.findViewById(R.id.keyguard_status_area)).thenReturn(statusArea);
View nic = mock(View.class);
when(mView.findViewById(R.id.left_aligned_notification_icon_container)).thenReturn(nic);
when(nic.getLayoutParams()).thenReturn(mock(RelativeLayout.LayoutParams.class));
BcSmartspaceDataPlugin plugin = mock(BcSmartspaceDataPlugin.class);
TestView view = mock(TestView.class);
when(plugin.getView(any())).thenReturn(view);
mController.mPluginListener.onPluginConnected(plugin, mContext);
verify(statusArea).setVisibility(View.GONE);
}
@Test
public void testSmartspacePluginDisconnectedShowsKeyguardStatusArea() {
mController.init();
View statusArea = mock(View.class);
when(mView.findViewById(R.id.keyguard_status_area)).thenReturn(statusArea);
View nic = mock(View.class);
when(mView.findViewById(R.id.left_aligned_notification_icon_container)).thenReturn(nic);
when(nic.getLayoutParams()).thenReturn(mock(RelativeLayout.LayoutParams.class));
BcSmartspaceDataPlugin plugin = mock(BcSmartspaceDataPlugin.class);
TestView view = mock(TestView.class);
when(plugin.getView(any())).thenReturn(view);
mController.mPluginListener.onPluginConnected(plugin, mContext);
mController.mPluginListener.onPluginDisconnected(plugin);
verify(statusArea).setVisibility(View.VISIBLE);
}
private void verifyAttachment(VerificationMode times) { private void verifyAttachment(VerificationMode times) {
verify(mClockManager, times).addOnClockChangedListener( verify(mClockManager, times).addOnClockChangedListener(
any(ClockManager.ClockChangedListener.class)); any(ClockManager.ClockChangedListener.class));
@@ -191,4 +251,12 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
any(ColorExtractor.OnColorsChangedListener.class)); any(ColorExtractor.OnColorsChangedListener.class));
verify(mView, times).updateColors(mGradientColors); verify(mView, times).updateColors(mGradientColors);
} }
private static class TestView extends View implements BcSmartspaceDataPlugin.SmartspaceView {
TestView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void registerDataProvider(BcSmartspaceDataPlugin plugin) { }
}
} }