diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardLookupTable.java b/src/com/android/settings/homepage/contextualcards/ContextualCardLookupTable.java index 0268fb097c8..2ac0ad6a910 100644 --- a/src/com/android/settings/homepage/contextualcards/ContextualCardLookupTable.java +++ b/src/com/android/settings/homepage/contextualcards/ContextualCardLookupTable.java @@ -16,43 +16,57 @@ package com.android.settings.homepage.contextualcards; +import android.util.Log; + +import androidx.annotation.VisibleForTesting; + import com.android.settings.homepage.contextualcards.ContextualCard.CardType; import com.android.settings.homepage.contextualcards.conditional.ConditionContextualCardController; import com.android.settings.homepage.contextualcards.conditional.ConditionContextualCardRenderer; import com.android.settings.homepage.contextualcards.slices.SliceContextualCardController; import com.android.settings.homepage.contextualcards.slices.SliceContextualCardRenderer; +import java.util.Comparator; +import java.util.List; import java.util.Set; import java.util.TreeSet; +import java.util.stream.Collectors; public class ContextualCardLookupTable { - + private static final String TAG = "ContextualCardLookup"; static class ControllerRendererMapping implements Comparable { @CardType - private final int mCardType; - private final Class mControllerClass; - private final Class mRendererClass; + final int mCardType; + final int mViewType; + final Class mControllerClass; + final Class mRendererClass; - private ControllerRendererMapping(@CardType int cardType, + ControllerRendererMapping(@CardType int cardType, int viewType, Class controllerClass, Class rendererClass) { mCardType = cardType; + mViewType = viewType; mControllerClass = controllerClass; mRendererClass = rendererClass; } @Override public int compareTo(ControllerRendererMapping other) { - return Integer.compare(this.mCardType, other.mCardType); + return Comparator.comparingInt((ControllerRendererMapping mapping) -> mapping.mCardType) + .thenComparingInt(mapping -> mapping.mViewType) + .compare(this, other); } } - private static final Set LOOKUP_TABLE = + @VisibleForTesting + static final Set LOOKUP_TABLE = new TreeSet() {{ add(new ControllerRendererMapping(CardType.CONDITIONAL, + ConditionContextualCardRenderer.VIEW_TYPE, ConditionContextualCardController.class, ConditionContextualCardRenderer.class)); add(new ControllerRendererMapping(CardType.SLICE, + SliceContextualCardRenderer.VIEW_TYPE, SliceContextualCardController.class, SliceContextualCardRenderer.class)); }}; @@ -67,14 +81,27 @@ public class ContextualCardLookupTable { return null; } - //TODO(b/112578070): Implement multi renderer cases. - public static Class getCardRendererClasses( + public static Class getCardRendererClassByCardType( @CardType int cardType) { - for (ControllerRendererMapping mapping : LOOKUP_TABLE) { - if (mapping.mCardType == cardType) { - return mapping.mRendererClass; - } + return LOOKUP_TABLE.stream() + .filter(m -> m.mCardType == cardType) + .findFirst() + .map(mapping -> mapping.mRendererClass) + .orElse(null); + } + + public static Class getCardRendererClassByViewType( + int viewType) throws IllegalStateException { + List validMappings = LOOKUP_TABLE.stream() + .filter(m -> m.mViewType == viewType).collect(Collectors.toList()); + if (validMappings == null || validMappings.isEmpty()) { + Log.w(TAG, "No matching mapping"); + return null; } - return null; + if (validMappings.size() != 1) { + throw new IllegalStateException("Have duplicate VIEW_TYPE in lookup table."); + } + + return validMappings.get(0).mRendererClass; } } diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardRenderer.java b/src/com/android/settings/homepage/contextualcards/ContextualCardRenderer.java index 689b57232c2..bb85bd23916 100644 --- a/src/com/android/settings/homepage/contextualcards/ContextualCardRenderer.java +++ b/src/com/android/settings/homepage/contextualcards/ContextualCardRenderer.java @@ -26,9 +26,9 @@ import androidx.recyclerview.widget.RecyclerView; public interface ContextualCardRenderer { /** - * The layout type of the controller. + * The layout type of the renderer. */ - int getViewType(); + int getViewType(boolean isHalfWidth); /** * When {@link ContextualCardsAdapter} calls {@link ContextualCardsAdapter#onCreateViewHolder}, diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardsAdapter.java b/src/com/android/settings/homepage/contextualcards/ContextualCardsAdapter.java index 865d2421361..0a8749d37d4 100644 --- a/src/com/android/settings/homepage/contextualcards/ContextualCardsAdapter.java +++ b/src/com/android/settings/homepage/contextualcards/ContextualCardsAdapter.java @@ -61,26 +61,26 @@ public class ContextualCardsAdapter extends RecyclerView.Adapter getControllers() { + @VisibleForTesting + Set getControllers() { return mControllers; } - public ContextualCardRenderer getRenderer(Context context, LifecycleOwner lifecycleOwner, - @ContextualCard.CardType int cardType) { + @VisibleForTesting + Set getRenderers() { + return mRenderers; + } + + public ContextualCardRenderer getRendererByViewType(Context context, + LifecycleOwner lifecycleOwner, int viewType) { final Class clz = - ContextualCardLookupTable.getCardRendererClasses(cardType); + ContextualCardLookupTable.getCardRendererClassByViewType(viewType); + return getRenderer(context, lifecycleOwner, clz); + } + + public ContextualCardRenderer getRendererByCardType(Context context, + LifecycleOwner lifecycleOwner, @ContextualCard.CardType int cardType) { + final Class clz = + ContextualCardLookupTable.getCardRendererClassByCardType(cardType); + return getRenderer(context, lifecycleOwner, clz); + } + + private ContextualCardRenderer getRenderer(Context context, LifecycleOwner lifecycleOwner, + @NonNull Class clz) { for (ContextualCardRenderer renderer : mRenderers) { if (renderer.getClass() == clz) { Log.d(TAG, "Renderer is already there."); diff --git a/src/com/android/settings/homepage/contextualcards/conditional/ConditionContextualCardRenderer.java b/src/com/android/settings/homepage/contextualcards/conditional/ConditionContextualCardRenderer.java index 8a3635be443..40986363046 100644 --- a/src/com/android/settings/homepage/contextualcards/conditional/ConditionContextualCardRenderer.java +++ b/src/com/android/settings/homepage/contextualcards/conditional/ConditionContextualCardRenderer.java @@ -37,6 +37,7 @@ import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; * Card renderer for {@link ConditionalContextualCard}. */ public class ConditionContextualCardRenderer implements ContextualCardRenderer { + public static final int VIEW_TYPE = R.layout.homepage_condition_tile; private final Context mContext; private final ControllerRendererPool mControllerRendererPool; @@ -48,8 +49,8 @@ public class ConditionContextualCardRenderer implements ContextualCardRenderer { } @Override - public int getViewType() { - return R.layout.homepage_condition_tile; + public int getViewType(boolean isHalfWidth) { + return VIEW_TYPE; } @Override diff --git a/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRenderer.java b/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRenderer.java index c2bf5360132..5fc4473dd16 100644 --- a/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRenderer.java +++ b/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRenderer.java @@ -45,6 +45,7 @@ import java.util.Map; */ public class SliceContextualCardRenderer implements ContextualCardRenderer, SliceView.OnSliceActionListener { + public static final int VIEW_TYPE = R.layout.homepage_slice_tile; private static final String TAG = "SliceCardRenderer"; @@ -61,8 +62,8 @@ public class SliceContextualCardRenderer implements ContextualCardRenderer, } @Override - public int getViewType() { - return R.layout.homepage_slice_tile; + public int getViewType(boolean isHalfWidth) { + return VIEW_TYPE; } @Override diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardLookupTableTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardLookupTableTest.java new file mode 100644 index 00000000000..4724500a294 --- /dev/null +++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardLookupTableTest.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2018 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; + +import static com.google.common.truth.Truth.assertThat; + +import com.android.settings.homepage.contextualcards.ContextualCardLookupTable + .ControllerRendererMapping; +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.List; + +@RunWith(SettingsRobolectricTestRunner.class) +public class ContextualCardLookupTableTest { + + private static final int UNSUPPORTED_CARD_TYPE = -99999; + private static final int UNSUPPORTED_VIEW_TYPE = -99999; + + private List mOriginalLookupTable; + + @Before + public void setUp() { + mOriginalLookupTable = new ArrayList<>(); + ContextualCardLookupTable.LOOKUP_TABLE.stream() + .forEach(mapping -> mOriginalLookupTable.add(mapping)); + } + + @After + public void reset() { + ContextualCardLookupTable.LOOKUP_TABLE.clear(); + ContextualCardLookupTable.LOOKUP_TABLE.addAll(mOriginalLookupTable); + } + + @Test + public void getCardControllerClass_hasSupportedCardType_shouldGetCorrespondingController() { + for (ControllerRendererMapping mapping : ContextualCardLookupTable.LOOKUP_TABLE) { + assertThat(ContextualCardLookupTable.getCardControllerClass(mapping.mCardType)) + .isEqualTo(mapping.mControllerClass); + } + } + + @Test + public void getCardControllerClass_hasUnsupportedCardType_shouldAlwaysGetNull() { + assertThat(ContextualCardLookupTable.getCardControllerClass(UNSUPPORTED_CARD_TYPE)) + .isNull(); + } + + @Test + public void + getCardRendererClassByViewType_hasSupportedViewType_shouldGetCorrespondingRenderer() { + for (ControllerRendererMapping mapping : ContextualCardLookupTable.LOOKUP_TABLE) { + assertThat(ContextualCardLookupTable.getCardRendererClassByViewType(mapping.mViewType)) + .isEqualTo(mapping.mRendererClass); + } + } + + @Test + public void getCardRendererClassByViewType_hasUnsupportedViewType_shouldAlwaysGetNull() { + assertThat(ContextualCardLookupTable.getCardRendererClassByViewType( + UNSUPPORTED_VIEW_TYPE)).isNull(); + } + + @Test(expected = IllegalStateException.class) + public void + getCardRendererClassByViewType_hasDuplicateViewType_shouldThrowsIllegalStateException() { + final ControllerRendererMapping mapping1 = + new ControllerRendererMapping( + 1111 /* cardType */, UNSUPPORTED_VIEW_TYPE /* viewType */, + ContextualCardController.class, ContextualCardRenderer.class + ); + final ControllerRendererMapping mapping2 = + new ControllerRendererMapping( + 2222 /* cardType */, UNSUPPORTED_VIEW_TYPE /* viewType */, + ContextualCardController.class, ContextualCardRenderer.class + ); + ContextualCardLookupTable.LOOKUP_TABLE.add(mapping1); + ContextualCardLookupTable.LOOKUP_TABLE.add(mapping2); + + ContextualCardLookupTable.getCardRendererClassByViewType(UNSUPPORTED_VIEW_TYPE); + } + + @Test + public void getRendererClassByCardType_hasSupportedCardType_shouldGetCorrespondingRenderer() { + for (ControllerRendererMapping mapping : ContextualCardLookupTable.LOOKUP_TABLE) { + assertThat(ContextualCardLookupTable.getCardRendererClassByCardType(mapping.mCardType)) + .isEqualTo(mapping.mRendererClass); + } + } + + @Test + public void getCardRendererClassByCardType_hasUnsupportedCardType_shouldAlwaysGetNull() { + assertThat(ContextualCardLookupTable.getCardRendererClassByCardType(UNSUPPORTED_CARD_TYPE)) + .isNull(); + } +} diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/ControllerRendererPoolTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/ControllerRendererPoolTest.java new file mode 100644 index 00000000000..e51169e3972 --- /dev/null +++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/ControllerRendererPoolTest.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2018 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; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; + +import androidx.lifecycle.LifecycleOwner; + +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settingslib.core.lifecycle.Lifecycle; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RuntimeEnvironment; + +@RunWith(SettingsRobolectricTestRunner.class) +public class ControllerRendererPoolTest { + + private static final int UNSUPPORTED_CARD_TYPE = -99999; + private static final int UNSUPPORTED_VIEW_TYPE = -99999; + + private ControllerRendererPool mPool; + private Context mContext; + private Lifecycle mLifecycle; + private LifecycleOwner mLifecycleOwner; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + mLifecycleOwner = () -> mLifecycle; + mLifecycle = new Lifecycle(mLifecycleOwner); + + mPool = new ControllerRendererPool(); + } + + @Test + public void getController_hasSupportedCardType_shouldReturnCorrespondingController() { + ContextualCardLookupTable.LOOKUP_TABLE.stream().forEach(mapping -> assertThat( + mPool.getController(mContext, mapping.mCardType).getClass()).isEqualTo( + mapping.mControllerClass)); + } + + @Test + public void getController_hasSupportedCardType_shouldHaveTwoControllersInPool() { + final long count = ContextualCardLookupTable.LOOKUP_TABLE.stream().map( + mapping -> mapping.mControllerClass).distinct().count(); + + ContextualCardLookupTable.LOOKUP_TABLE.stream().forEach( + mapping -> mPool.getController(mContext, mapping.mCardType)); + + assertThat(mPool.getControllers()).hasSize((int) count); + } + + @Test + public void getController_hasUnsupportedCardType_shouldReturnNullAndPoolIsEmpty() { + final ContextualCardController controller = mPool.getController(mContext, + UNSUPPORTED_CARD_TYPE); + + assertThat(controller).isNull(); + assertThat(mPool.getControllers()).isEmpty(); + } + + @Test + public void getRenderer_hasSupportedViewType_shouldReturnCorrespondingRenderer() { + ContextualCardLookupTable.LOOKUP_TABLE.stream().forEach(mapping -> assertThat( + mPool.getRendererByViewType(mContext, mLifecycleOwner, + mapping.mViewType).getClass()).isEqualTo(mapping.mRendererClass)); + } + + @Test + public void getRenderer_hasSupportedViewType_shouldHaveDistinctRenderersInPool() { + final long count = ContextualCardLookupTable.LOOKUP_TABLE.stream().map( + mapping -> mapping.mRendererClass).distinct().count(); + + ContextualCardLookupTable.LOOKUP_TABLE.stream().forEach( + mapping -> mPool.getRendererByViewType(mContext, mLifecycleOwner, + mapping.mViewType)); + + assertThat(mPool.getRenderers()).hasSize((int) count); + } + + @Test + public void getRenderer_hasUnsupportedViewType_shouldReturnNullAndPoolIsEmpty() { + final ContextualCardRenderer renderer = mPool.getRendererByViewType(mContext, + mLifecycleOwner, + UNSUPPORTED_VIEW_TYPE); + + assertThat(renderer).isNull(); + assertThat(mPool.getRenderers()).isEmpty(); + } + + @Test + public void getRenderer_hasSupportedCardTypeAndWidth_shouldReturnCorrespondingRenderer() { + ContextualCardLookupTable.LOOKUP_TABLE.stream().forEach(mapping -> assertThat( + mPool.getRendererByCardType(mContext, mLifecycleOwner, + mapping.mCardType).getClass()).isEqualTo(mapping.mRendererClass)); + } + + @Test + public void getRenderer_hasSupportedCardTypeAndWidth_shouldHaveDistinctRenderersInPool() { + final long count = ContextualCardLookupTable.LOOKUP_TABLE.stream().map( + mapping -> mapping.mRendererClass).distinct().count(); + + ContextualCardLookupTable.LOOKUP_TABLE.stream().forEach( + mapping -> mPool.getRendererByCardType(mContext, mLifecycleOwner, + mapping.mCardType)); + + assertThat(mPool.getRenderers()).hasSize((int) count); + } + + @Test + public void getRenderer_hasUnsupportedCardType_shouldReturnNullAndPoolIsEmpty() { + final ContextualCardRenderer renderer = mPool.getRendererByCardType(mContext, + mLifecycleOwner, + UNSUPPORTED_CARD_TYPE); + + assertThat(renderer).isNull(); + assertThat(mPool.getRenderers()).isEmpty(); + } +} diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/conditional/ConditionContextualCardRendererTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/conditional/ConditionContextualCardRendererTest.java index ccfbbfb2fe8..376042d6588 100644 --- a/tests/robotests/src/com/android/settings/homepage/contextualcards/conditional/ConditionContextualCardRendererTest.java +++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/conditional/ConditionContextualCardRendererTest.java @@ -61,7 +61,7 @@ public class ConditionContextualCardRendererTest { @Test public void bindView_shouldSetListener() { - final int viewType = mRenderer.getViewType(); + final int viewType = mRenderer.getViewType(false /* isHalfWidth */); final RecyclerView recyclerView = new RecyclerView(mContext); recyclerView.setLayoutManager(new LinearLayoutManager(mContext)); final View view = LayoutInflater.from(mContext).inflate(viewType, recyclerView, false); @@ -78,7 +78,7 @@ public class ConditionContextualCardRendererTest { @Test public void viewClick_shouldInvokeControllerPrimaryClick() { - final int viewType = mRenderer.getViewType(); + final int viewType = mRenderer.getViewType(false /* isHalfWidth */); final RecyclerView recyclerView = new RecyclerView(mContext); recyclerView.setLayoutManager(new LinearLayoutManager(mContext)); final View view = LayoutInflater.from(mContext).inflate(viewType, recyclerView, false); 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 025f2623695..7a07d35dce3 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 @@ -113,7 +113,7 @@ public class SliceContextualCardRendererTest { } private RecyclerView.ViewHolder getSliceViewHolder() { - final int viewType = mRenderer.getViewType(); + final int viewType = mRenderer.getViewType(false /* isHalfWidth */); final RecyclerView recyclerView = new RecyclerView(mContext); recyclerView.setLayoutManager(new LinearLayoutManager(mContext)); final View view = LayoutInflater.from(mContext).inflate(viewType, recyclerView, false);