From 497b3529dc3b76ff5c2f8377986548de8ffd74b5 Mon Sep 17 00:00:00 2001 From: Mill Chen Date: Wed, 19 Dec 2018 00:07:17 +0800 Subject: [PATCH] Refactor slice renderer to handle different card width - Refactor SliceContextualCardRenderer to support for displaying slice in half/full width card. - Add two helper classes to separately deal with different card width. Only the skeleton of the half card helper is put in this CL, the implementation hasn't been filled in yet. Will implement the detail in next CL. Bug: 119655434 Test: visual, robotests Change-Id: Iacdc90c23bf41cfa7ccae3c0c70a3b663e89307d --- .../ContextualCardLookupTable.java | 6 +- .../slices/SliceContextualCardRenderer.java | 120 ++++++-------- .../slices/SliceFullCardRendererHelper.java | 99 ++++++++++++ .../slices/SliceHalfCardRendererHelper.java | 46 ++++++ .../SliceContextualCardRendererTest.java | 13 +- .../SliceFullCardRendererHelperTest.java | 151 ++++++++++++++++++ 6 files changed, 350 insertions(+), 85 deletions(-) create mode 100644 src/com/android/settings/homepage/contextualcards/slices/SliceFullCardRendererHelper.java create mode 100644 src/com/android/settings/homepage/contextualcards/slices/SliceHalfCardRendererHelper.java create mode 100644 tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceFullCardRendererHelperTest.java diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardLookupTable.java b/src/com/android/settings/homepage/contextualcards/ContextualCardLookupTable.java index a4a84199c98..90974f651bb 100644 --- a/src/com/android/settings/homepage/contextualcards/ContextualCardLookupTable.java +++ b/src/com/android/settings/homepage/contextualcards/ContextualCardLookupTable.java @@ -84,7 +84,11 @@ public class ContextualCardLookupTable { LegacySuggestionContextualCardController.class, LegacySuggestionContextualCardRenderer.class)); add(new ControllerRendererMapping(CardType.SLICE, - SliceContextualCardRenderer.VIEW_TYPE, + SliceContextualCardRenderer.VIEW_TYPE_FULL_WIDTH, + SliceContextualCardController.class, + SliceContextualCardRenderer.class)); + add(new ControllerRendererMapping(CardType.SLICE, + SliceContextualCardRenderer.VIEW_TYPE_HALF_WIDTH, SliceContextualCardController.class, SliceContextualCardRenderer.class)); add(new ControllerRendererMapping(CardType.CONDITIONAL_FOOTER, diff --git a/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRenderer.java b/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRenderer.java index 7ca6e9aad84..5a43f66241b 100644 --- a/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRenderer.java +++ b/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRenderer.java @@ -26,7 +26,6 @@ import android.view.View; import android.widget.Button; import android.widget.ViewFlipper; -import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import androidx.lifecycle.Lifecycle; import androidx.lifecycle.LifecycleObserver; @@ -35,40 +34,40 @@ import androidx.lifecycle.LiveData; import androidx.lifecycle.OnLifecycleEvent; import androidx.recyclerview.widget.RecyclerView; import androidx.slice.Slice; -import androidx.slice.SliceItem; -import androidx.slice.widget.EventInfo; import androidx.slice.widget.SliceLiveData; -import androidx.slice.widget.SliceView; import com.android.settings.R; import com.android.settings.homepage.contextualcards.CardContentProvider; import com.android.settings.homepage.contextualcards.ContextualCard; -import com.android.settings.homepage.contextualcards.ContextualCardFeatureProvider; import com.android.settings.homepage.contextualcards.ContextualCardRenderer; import com.android.settings.homepage.contextualcards.ControllerRendererPool; -import com.android.settings.overlay.FeatureFactory; import java.util.Map; import java.util.Set; /** - * Card renderer for {@link ContextualCard} built as slices. + * Card renderer for {@link ContextualCard} built as slice full card or slice half card. */ -public class SliceContextualCardRenderer implements ContextualCardRenderer, - SliceView.OnSliceActionListener, LifecycleObserver { - public static final int VIEW_TYPE = R.layout.homepage_slice_tile; +public class SliceContextualCardRenderer implements ContextualCardRenderer, LifecycleObserver { + public static final int VIEW_TYPE_FULL_WIDTH = R.layout.homepage_slice_tile; + public static final int VIEW_TYPE_HALF_WIDTH = R.layout.homepage_slice_half_tile; private static final String TAG = "SliceCardRenderer"; @VisibleForTesting final Map> mSliceLiveDataMap; @VisibleForTesting - final Set mFlippedCardSet; + final Set mFlippedCardSet; private final Context mContext; private final LifecycleOwner mLifecycleOwner; private final ControllerRendererPool mControllerRendererPool; private final Set mCardSet; + private final SliceFullCardRendererHelper mFullCardHelper; + private final SliceHalfCardRendererHelper mHalfCardHelper; + + //TODO(b/121303357): Remove isHalfWidth field from SliceContextualCardRenderer class. + private boolean mIsHalfWidth; public SliceContextualCardRenderer(Context context, LifecycleOwner lifecycleOwner, ControllerRendererPool controllerRendererPool) { @@ -79,21 +78,26 @@ public class SliceContextualCardRenderer implements ContextualCardRenderer, mCardSet = new ArraySet<>(); mFlippedCardSet = new ArraySet<>(); mLifecycleOwner.getLifecycle().addObserver(this); + mFullCardHelper = new SliceFullCardRendererHelper(context); + mHalfCardHelper = new SliceHalfCardRendererHelper(context); } @Override public int getViewType(boolean isHalfWidth) { - return VIEW_TYPE; + mIsHalfWidth = isHalfWidth; + return isHalfWidth? VIEW_TYPE_HALF_WIDTH : VIEW_TYPE_FULL_WIDTH; } @Override public RecyclerView.ViewHolder createViewHolder(View view) { - return new SliceViewHolder(view); + if (mIsHalfWidth) { + return mHalfCardHelper.createViewHolder(view); + } + return mFullCardHelper.createViewHolder(view); } @Override public void bindView(RecyclerView.ViewHolder holder, ContextualCard card) { - final SliceViewHolder cardHolder = (SliceViewHolder) holder; final Uri uri = card.getSliceUri(); //TODO(b/120629936): Take this out once blank card issue is fixed. Log.d(TAG, "bindView - uri = " + uri); @@ -103,10 +107,6 @@ public class SliceContextualCardRenderer implements ContextualCardRenderer, return; } - cardHolder.sliceView.setScrollable(false); - cardHolder.sliceView.setTag(uri); - //TODO(b/114009676): We will soon have a field to decide what slice mode we should set. - cardHolder.sliceView.setMode(SliceView.MODE_LARGE); LiveData sliceLiveData = mSliceLiveDataMap.get(uri); if (sliceLiveData == null) { @@ -125,82 +125,58 @@ public class SliceContextualCardRenderer implements ContextualCardRenderer, //TODO(b/120629936): Take this out once blank card issue is fixed. Log.d(TAG, "Slice callback - uri = " + slice.getUri()); } - cardHolder.sliceView.setSlice(slice); + if (holder.getItemViewType() == VIEW_TYPE_HALF_WIDTH) { + mHalfCardHelper.bindView(holder, card, slice); + } else { + mFullCardHelper.bindView(holder, card, slice, mCardSet); + } }); - // Set this listener so we can log the interaction users make on the slice - cardHolder.sliceView.setOnSliceActionListener(this); - - // Customize slice view for Settings - cardHolder.sliceView.showTitleItems(true); - if (card.isLargeCard()) { - cardHolder.sliceView.showHeaderDivider(true); - cardHolder.sliceView.showActionDividers(true); + if (holder.getItemViewType() == VIEW_TYPE_HALF_WIDTH) { + initDismissalActions(holder, card, R.id.content); + } else { + initDismissalActions(holder, card, R.id.slice_view); } - - initDismissalActions(cardHolder, card); } - private void initDismissalActions(SliceViewHolder cardHolder, ContextualCard card) { - cardHolder.sliceView.setOnLongClickListener(v -> { - cardHolder.viewFlipper.showNext(); - mFlippedCardSet.add(cardHolder); + private void initDismissalActions(RecyclerView.ViewHolder holder, ContextualCard card, + int initialViewId) { + // initialView is the first view in the ViewFlipper. + final View initialView = holder.itemView.findViewById(initialViewId); + initialView.setOnLongClickListener(v -> { + flipCardToDismissalView(holder); + mFlippedCardSet.add(holder); return true; }); - final Button btnKeep = cardHolder.itemView.findViewById(R.id.keep); + final Button btnKeep = holder.itemView.findViewById(R.id.keep); btnKeep.setOnClickListener(v -> { - cardHolder.resetCard(); - mFlippedCardSet.remove(cardHolder); + mFlippedCardSet.remove(holder); + resetCardView(holder); }); - final Button btnRemove = cardHolder.itemView.findViewById(R.id.remove); + final Button btnRemove = holder.itemView.findViewById(R.id.remove); btnRemove.setOnClickListener(v -> { mControllerRendererPool.getController(mContext, card.getCardType()).onDismissed(card); - cardHolder.resetCard(); - mFlippedCardSet.remove(cardHolder); + mFlippedCardSet.remove(holder); + resetCardView(holder); mSliceLiveDataMap.get(card.getSliceUri()).removeObservers(mLifecycleOwner); }); } - @Override - public void onSliceAction(@NonNull EventInfo eventInfo, @NonNull SliceItem sliceItem) { - //TODO(b/79698338): Log user interaction - - // sliceItem.getSlice().getUri() is like - // content://android.settings.slices/action/wifi/_gen/0/_gen/0 - // contextualCard.getSliceUri() is prefix of sliceItem.getSlice().getUri() - for (ContextualCard card : mCardSet) { - if (sliceItem.getSlice().getUri().toString().startsWith( - card.getSliceUri().toString())) { - ContextualCardFeatureProvider contexualCardFeatureProvider = - FeatureFactory.getFactory(mContext) - .getContextualCardFeatureProvider(mContext); - contexualCardFeatureProvider.logContextualCardClick(card, - eventInfo.rowIndex, eventInfo.actionType); - break; - } - } - } - @OnLifecycleEvent(Lifecycle.Event.ON_STOP) public void onStop() { - mFlippedCardSet.stream().forEach(holder -> holder.resetCard()); + mFlippedCardSet.stream().forEach(holder -> resetCardView(holder)); mFlippedCardSet.clear(); } - public static class SliceViewHolder extends RecyclerView.ViewHolder { - public final SliceView sliceView; - public final ViewFlipper viewFlipper; + private void resetCardView(RecyclerView.ViewHolder holder) { + final ViewFlipper viewFlipper = holder.itemView.findViewById(R.id.view_flipper); + viewFlipper.setDisplayedChild(0 /* whichChild */); + } - public SliceViewHolder(View view) { - super(view); - sliceView = view.findViewById(R.id.slice_view); - viewFlipper = view.findViewById(R.id.view_flipper); - } - - public void resetCard() { - viewFlipper.setDisplayedChild(0); - } + private void flipCardToDismissalView(RecyclerView.ViewHolder holder) { + final ViewFlipper viewFlipper = holder.itemView.findViewById(R.id.view_flipper); + viewFlipper.showNext(); } } diff --git a/src/com/android/settings/homepage/contextualcards/slices/SliceFullCardRendererHelper.java b/src/com/android/settings/homepage/contextualcards/slices/SliceFullCardRendererHelper.java new file mode 100644 index 00000000000..ef0a67d0e20 --- /dev/null +++ b/src/com/android/settings/homepage/contextualcards/slices/SliceFullCardRendererHelper.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2019 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.settings.homepage.contextualcards.slices; + +import android.content.Context; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; +import androidx.slice.Slice; +import androidx.slice.SliceItem; +import androidx.slice.widget.EventInfo; +import androidx.slice.widget.SliceView; + +import com.android.settings.R; +import com.android.settings.homepage.contextualcards.ContextualCard; +import com.android.settings.homepage.contextualcards.ContextualCardFeatureProvider; +import com.android.settings.overlay.FeatureFactory; + +import java.util.Set; + +/** + * Card renderer helper for {@link ContextualCard} built as slice full card. + */ +class SliceFullCardRendererHelper implements SliceView.OnSliceActionListener { + private static final String TAG = "SliceFCRendererHelper"; + + private final Context mContext; + + private Set mCardSet; + + SliceFullCardRendererHelper(Context context) { + mContext = context; + } + + RecyclerView.ViewHolder createViewHolder(View view) { + return new SliceViewHolder(view); + } + + void bindView(RecyclerView.ViewHolder holder, ContextualCard card, Slice slice, + Set cardSet) { + final SliceViewHolder cardHolder = (SliceViewHolder) holder; + cardHolder.sliceView.setScrollable(false); + cardHolder.sliceView.setTag(card.getSliceUri()); + //TODO(b/114009676): We will soon have a field to decide what slice mode we should set. + cardHolder.sliceView.setMode(SliceView.MODE_LARGE); + cardHolder.sliceView.setSlice(slice); + mCardSet = cardSet; + // Set this listener so we can log the interaction users make on the slice + cardHolder.sliceView.setOnSliceActionListener(this); + + // Customize slice view for Settings + cardHolder.sliceView.showTitleItems(true); + if (card.isLargeCard()) { + cardHolder.sliceView.showHeaderDivider(true); + cardHolder.sliceView.showActionDividers(true); + } + } + + @Override + public void onSliceAction(@NonNull EventInfo eventInfo, @NonNull SliceItem sliceItem) { + // sliceItem.getSlice().getUri() is like + // content://android.settings.slices/action/wifi/_gen/0/_gen/0 + // contextualCard.getSliceUri() is prefix of sliceItem.getSlice().getUri() + final ContextualCardFeatureProvider contextualCardFeatureProvider = + FeatureFactory.getFactory(mContext).getContextualCardFeatureProvider(mContext); + for (ContextualCard card : mCardSet) { + if (sliceItem.getSlice().getUri().toString().startsWith( + card.getSliceUri().toString())) { + contextualCardFeatureProvider.logContextualCardClick(card, eventInfo.rowIndex, + eventInfo.actionType); + break; + } + } + } + + static class SliceViewHolder extends RecyclerView.ViewHolder { + public final SliceView sliceView; + + public SliceViewHolder(View view) { + super(view); + sliceView = view.findViewById(R.id.slice_view); + } + } +} diff --git a/src/com/android/settings/homepage/contextualcards/slices/SliceHalfCardRendererHelper.java b/src/com/android/settings/homepage/contextualcards/slices/SliceHalfCardRendererHelper.java new file mode 100644 index 00000000000..2986dbc8fe7 --- /dev/null +++ b/src/com/android/settings/homepage/contextualcards/slices/SliceHalfCardRendererHelper.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2019 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.settings.homepage.contextualcards.slices; + +import android.content.Context; +import android.view.View; + +import androidx.recyclerview.widget.RecyclerView; +import androidx.slice.Slice; + +import com.android.settings.homepage.contextualcards.ContextualCard; + +/** + * Card renderer helper for {@link ContextualCard} built as slice half card. + */ +class SliceHalfCardRendererHelper { + private static final String TAG = "SliceHCRendererHelper"; + + private final Context mContext; + + SliceHalfCardRendererHelper(Context context) { + mContext = context; + } + + RecyclerView.ViewHolder createViewHolder(View view) { + return null; + } + + void bindView(RecyclerView.ViewHolder holder, ContextualCard card, Slice slice) { + + } +} diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRendererTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRendererTest.java index 0b87525b6bd..4d9a21db9a6 100644 --- a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRendererTest.java +++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRendererTest.java @@ -78,17 +78,6 @@ public class SliceContextualCardRendererTest { mControllerRendererPool); } - @Test - public void bindView_shouldSetScrollableToFalse() { - RecyclerView.ViewHolder viewHolder = getSliceViewHolder(); - - mRenderer.bindView(viewHolder, buildContextualCard(TEST_SLICE_URI)); - - assertThat( - ((SliceContextualCardRenderer.SliceViewHolder) viewHolder).sliceView.isScrollable - ()).isFalse(); - } - @Test public void bindView_invalidScheme_sliceShouldBeNull() { final Uri sliceUri = Uri.parse("contet://com.android.settings.slices/action/flashlight"); @@ -97,7 +86,7 @@ public class SliceContextualCardRendererTest { mRenderer.bindView(viewHolder, buildContextualCard(sliceUri)); assertThat( - ((SliceContextualCardRenderer.SliceViewHolder) viewHolder).sliceView.getSlice()) + ((SliceFullCardRendererHelper.SliceViewHolder) viewHolder).sliceView.getSlice()) .isNull(); } diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceFullCardRendererHelperTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceFullCardRendererHelperTest.java new file mode 100644 index 00000000000..9172300df44 --- /dev/null +++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceFullCardRendererHelperTest.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2019 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.settings.homepage.contextualcards.slices; + +import static com.android.settings.homepage.contextualcards.slices.SliceContextualCardRenderer.VIEW_TYPE_FULL_WIDTH; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.Activity; +import android.app.PendingIntent; +import android.content.Intent; +import android.net.Uri; +import android.view.LayoutInflater; +import android.view.View; + +import androidx.core.graphics.drawable.IconCompat; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.slice.Slice; +import androidx.slice.SliceProvider; +import androidx.slice.builders.ListBuilder; +import androidx.slice.builders.SliceAction; +import androidx.slice.widget.SliceLiveData; +import androidx.slice.widget.SliceView; + +import com.android.settings.R; +import com.android.settings.homepage.contextualcards.ContextualCard; +import com.android.settings.homepage.contextualcards.slices.SliceFullCardRendererHelper.SliceViewHolder; +import com.android.settings.intelligence.ContextualCardProto; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; + +import java.util.Collections; + +@RunWith(RobolectricTestRunner.class) +public class SliceFullCardRendererHelperTest { + + private static final Uri TEST_SLICE_URI = Uri.parse("content://test/test"); + + private Activity mActivity; + private SliceFullCardRendererHelper mHelper; + + @Before + public void setUp() { + // Set-up specs for SliceMetadata. + SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS); + mActivity = Robolectric.buildActivity(Activity.class).create().get(); + mActivity.setTheme(R.style.Theme_Settings_Home); + mHelper = new SliceFullCardRendererHelper(mActivity); + } + + @Test + public void createViewHolder_shouldAlwaysReturnSliceViewHolder() { + final RecyclerView.ViewHolder viewHolder = getSliceViewHolder(); + + assertThat(viewHolder).isInstanceOf(SliceViewHolder.class); + } + + @Test + public void bindView_shouldSetScrollableToFalse() { + final RecyclerView.ViewHolder viewHolder = getSliceViewHolder(); + + mHelper.bindView(viewHolder, buildContextualCard(), buildSlice(), Collections.emptySet()); + + assertThat(((SliceViewHolder) viewHolder).sliceView.isScrollable()).isFalse(); + } + + @Test + public void bindView_shouldSetTagToSliceUri() { + final RecyclerView.ViewHolder viewHolder = getSliceViewHolder(); + final ContextualCard card = buildContextualCard(); + + mHelper.bindView(viewHolder, card, buildSlice(), Collections.emptySet()); + + assertThat(((SliceViewHolder) viewHolder).sliceView.getTag()).isEqualTo(card.getSliceUri()); + } + + @Test + public void bindView_shouldSetModeToLarge() { + final RecyclerView.ViewHolder viewHolder = getSliceViewHolder(); + + mHelper.bindView(viewHolder, buildContextualCard(), buildSlice(), Collections.emptySet()); + + assertThat(((SliceViewHolder) viewHolder).sliceView.getMode()).isEqualTo( + SliceView.MODE_LARGE); + } + + @Test + public void bindView_shouldSetSlice() { + final RecyclerView.ViewHolder viewHolder = getSliceViewHolder(); + + mHelper.bindView(viewHolder, buildContextualCard(), buildSlice(), Collections.emptySet()); + + assertThat(((SliceViewHolder) viewHolder).sliceView.getSlice().getUri()).isEqualTo( + TEST_SLICE_URI); + } + + private RecyclerView.ViewHolder getSliceViewHolder() { + final RecyclerView recyclerView = new RecyclerView(mActivity); + recyclerView.setLayoutManager(new LinearLayoutManager(mActivity)); + final View view = LayoutInflater.from(mActivity).inflate(VIEW_TYPE_FULL_WIDTH, recyclerView, + false); + return mHelper.createViewHolder(view); + } + + private ContextualCard buildContextualCard() { + return new ContextualCard.Builder() + .setName("test_name") + .setCategory(ContextualCardProto.ContextualCard.Category.SUGGESTION_VALUE) + .setCardType(ContextualCard.CardType.SLICE) + .setSliceUri(TEST_SLICE_URI) + .setIsHalfWidth(false /* isHalfWidth */) + .build(); + } + + private Slice buildSlice() { + final String title = "test_title"; + final IconCompat icon = IconCompat.createWithResource(mActivity, R.drawable.empty_icon); + final PendingIntent pendingIntent = PendingIntent.getActivity( + mActivity, + title.hashCode() /* requestCode */, + new Intent("test action"), + 0 /* flags */); + final SliceAction action + = SliceAction.createDeeplink(pendingIntent, icon, ListBuilder.SMALL_IMAGE, title); + return new ListBuilder(mActivity, TEST_SLICE_URI, ListBuilder.INFINITY) + .addRow(new ListBuilder.RowBuilder() + .addEndItem(icon, ListBuilder.ICON_IMAGE) + .setTitle(title) + .setPrimaryAction(action)) + .build(); + } +}