From a5b620e73815834f138f8af0591ca42f45c0abd7 Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Wed, 22 Feb 2017 16:41:38 -0800 Subject: [PATCH] Swipe to dismiss suggestions - Move dismiss suggestion logic into feature provider - In DashboardData, use hashcode as suggestion's stable id. This is much more likely to provide a truely stable id for each suggestion card. Eventually I want to use hash for all tiles to provide stable id. - Add a SuggestionDismissionController to handle swipe to dismiss callbacks Change-Id: If3770f07a90c5469a0b86fc28f3eb5e4c17227cd Fix: 35159816 Test: make RunSettingsRoboTests --- res/layout/suggestion_tile.xml | 7 -- .../applications/AppStorageSettings.java | 4 - .../settings/dashboard/DashboardAdapter.java | 78 ++++-------- .../settings/dashboard/DashboardData.java | 37 ++++-- .../settings/dashboard/DashboardSummary.java | 16 ++- .../conditional/ConditionAdapterUtils.java | 11 -- .../SuggestionDismissController.java | 84 +++++++++++++ .../SuggestionFeatureProvider.java | 11 +- .../SuggestionFeatureProviderImpl.java | 29 +++++ .../SuggestionDismissControllerTest.java | 107 ++++++++++++++++ .../SuggestionFeatureProviderImplTest.java | 114 ++++++++++++++++++ 11 files changed, 398 insertions(+), 100 deletions(-) create mode 100644 src/com/android/settings/dashboard/suggestions/SuggestionDismissController.java create mode 100644 tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionDismissControllerTest.java create mode 100644 tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImplTest.java diff --git a/res/layout/suggestion_tile.xml b/res/layout/suggestion_tile.xml index 80de8ff4937..9e17e38be5a 100644 --- a/res/layout/suggestion_tile.xml +++ b/res/layout/suggestion_tile.xml @@ -55,11 +55,4 @@ - - \ No newline at end of file diff --git a/src/com/android/settings/applications/AppStorageSettings.java b/src/com/android/settings/applications/AppStorageSettings.java index 0ea9515ed16..aa35328c030 100644 --- a/src/com/android/settings/applications/AppStorageSettings.java +++ b/src/com/android/settings/applications/AppStorageSettings.java @@ -30,7 +30,6 @@ import android.content.pm.IPackageDataObserver; import android.content.pm.PackageManager; import android.content.pm.ProviderInfo; import android.os.Bundle; -import android.os.Environment; import android.os.Handler; import android.os.Message; import android.os.RemoteException; @@ -39,7 +38,6 @@ import android.os.storage.StorageManager; import android.os.storage.VolumeInfo; import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceCategory; -import android.text.format.Formatter; import android.util.Log; import android.util.MutableInt; import android.view.View; @@ -51,8 +49,6 @@ import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.deviceinfo.StorageWizardMoveConfirm; import com.android.settingslib.RestrictedLockUtils; -import com.android.settingslib.applications.ApplicationsState; -import com.android.settingslib.applications.ApplicationsState.AppEntry; import com.android.settingslib.applications.ApplicationsState.Callbacks; import com.android.settingslib.applications.StorageStatsSource; import com.android.settingslib.applications.StorageStatsSource.AppStorageStats; diff --git a/src/com/android/settings/dashboard/DashboardAdapter.java b/src/com/android/settings/dashboard/DashboardAdapter.java index 07d48062955..f968cd0f040 100644 --- a/src/com/android/settings/dashboard/DashboardAdapter.java +++ b/src/com/android/settings/dashboard/DashboardAdapter.java @@ -17,21 +17,17 @@ package com.android.settings.dashboard; import android.app.Activity; import android.content.Context; -import android.content.pm.PackageManager; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.os.Bundle; import android.support.annotation.VisibleForTesting; import android.support.v7.util.DiffUtil; -import android.support.v7.widget.PopupMenu; import android.support.v7.widget.RecyclerView; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; -import android.view.ContextThemeWrapper; import android.view.LayoutInflater; -import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; @@ -43,18 +39,17 @@ import com.android.settings.SettingsActivity; import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.dashboard.conditional.Condition; import com.android.settings.dashboard.conditional.ConditionAdapterUtils; +import com.android.settings.dashboard.suggestions.SuggestionDismissController; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.SuggestionParser; import com.android.settingslib.drawer.DashboardCategory; import com.android.settingslib.drawer.Tile; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; -import java.util.Set; public class DashboardAdapter extends RecyclerView.Adapter - implements SummaryLoader.SummaryConsumer { + implements SummaryLoader.SummaryConsumer, SuggestionDismissController.Callback { public static final String TAG = "DashboardAdapter"; private static final String STATE_SUGGESTION_LIST = "suggestion_list"; private static final String STATE_CATEGORY_LIST = "category_list"; @@ -66,7 +61,6 @@ public class DashboardAdapter extends RecyclerView.Adapter mSuggestionsShownLogged; - private SuggestionParser mSuggestionParser; private boolean mFirstFrameDrawn; @VisibleForTesting @@ -115,7 +109,6 @@ public class DashboardAdapter extends RecyclerView.Adapter suggestions = mDashboardData.getSuggestions(); - suggestions.remove(suggestion); + @Override + public Tile getSuggestionForPosition(int position) { + return (Tile) mDashboardData.getItemEntityByPosition(position); + } - DashboardData prevData = mDashboardData; - mDashboardData = new DashboardData.Builder(prevData) - .setSuggestions(suggestions) - .build(); - notifyDashboardDataChanged(prevData); + @Override + public void onSuggestionDismissed(Tile suggestion) { + final List suggestions = mDashboardData.getSuggestions(); + if (suggestions == null) { + return; + } + suggestions.remove(suggestion); - return true; - } - }); - popup.show(); + final DashboardData prevData = mDashboardData; + mDashboardData = new DashboardData.Builder(prevData) + .setSuggestions(suggestions) + .build(); + notifyDashboardDataChanged(prevData); } @VisibleForTesting @@ -513,9 +477,9 @@ public class DashboardAdapter extends RecyclerView.Adapter mConditions; private List mSuggestions; - public Builder() { } @@ -359,7 +370,7 @@ public class DashboardData { return "condition"; // return anything but null to mark the payload } return null; - } + } } /** @@ -433,11 +444,11 @@ public class DashboardData { switch (type) { case TYPE_DASHBOARD_CATEGORY: // Only check title for dashboard category - return TextUtils.equals(((DashboardCategory)entity).title, + return TextUtils.equals(((DashboardCategory) entity).title, ((DashboardCategory) targetItem.entity).title); case TYPE_DASHBOARD_TILE: - final Tile localTile = (Tile)entity; - final Tile targetTile = (Tile)targetItem.entity; + final Tile localTile = (Tile) entity; + final Tile targetTile = (Tile) targetItem.entity; // Only check title and summary for dashboard tile return TextUtils.equals(localTile.title, targetTile.title) diff --git a/src/com/android/settings/dashboard/DashboardSummary.java b/src/com/android/settings/dashboard/DashboardSummary.java index 6482672e3d9..4e2baa9d1ff 100644 --- a/src/com/android/settings/dashboard/DashboardSummary.java +++ b/src/com/android/settings/dashboard/DashboardSummary.java @@ -35,6 +35,7 @@ import com.android.settings.dashboard.conditional.Condition; import com.android.settings.dashboard.conditional.ConditionAdapterUtils; import com.android.settings.dashboard.conditional.ConditionManager; import com.android.settings.dashboard.conditional.FocusRecyclerView; +import com.android.settings.dashboard.suggestions.SuggestionDismissController; import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.SuggestionParser; @@ -70,6 +71,7 @@ public class DashboardSummary extends InstrumentedFragment private DashboardFeatureProvider mDashboardFeatureProvider; private SuggestionFeatureProvider mSuggestionFeatureProvider; private boolean isOnCategoriesChangedCalled; + private SuggestionDismissController mSuggestionDismissHandler; @Override public int getMetricsCategory() { @@ -119,8 +121,7 @@ public class DashboardSummary extends InstrumentedFragment } } if (DEBUG_TIMING) { - Log.d(TAG, "onResume took " + (System.currentTimeMillis() - startTime) - + " ms"); + Log.d(TAG, "onResume took " + (System.currentTimeMillis() - startTime) + " ms"); } } @@ -177,7 +178,7 @@ public class DashboardSummary extends InstrumentedFragment @Override public void onViewCreated(View view, Bundle bundle) { long startTime = System.currentTimeMillis(); - mDashboard = (FocusRecyclerView) view.findViewById(R.id.dashboard_container); + mDashboard = view.findViewById(R.id.dashboard_container); mLayoutManager = new LinearLayoutManager(getContext()); mLayoutManager.setOrientation(LinearLayoutManager.VERTICAL); if (bundle != null) { @@ -192,6 +193,8 @@ public class DashboardSummary extends InstrumentedFragment mAdapter = new DashboardAdapter(getContext(), mSuggestionParser, mMetricsFeatureProvider, bundle, mConditionManager.getConditions()); mDashboard.setAdapter(mAdapter); + mSuggestionDismissHandler = new SuggestionDismissController( + getContext(), mDashboard, mSuggestionParser, mAdapter); mDashboard.setItemAnimator(new DashboardItemAnimator()); mSummaryLoader.setSummaryConsumer(mAdapter); ConditionAdapterUtils.addDismiss(mDashboard); @@ -243,7 +246,7 @@ public class DashboardSummary extends InstrumentedFragment List suggestionIds = new ArrayList<>(suggestions.size()); for (Tile suggestion : suggestions) { suggestionIds.add( - DashboardAdapter.getSuggestionIdentifier(context, suggestion)); + DashboardAdapter.getSuggestionIdentifier(context, suggestion)); } // TODO: create a Suggestion class to maintain the id and other info mSuggestionFeatureProvider.rankSuggestions(suggestions, suggestionIds); @@ -251,7 +254,8 @@ public class DashboardSummary extends InstrumentedFragment for (int i = 0; i < suggestions.size(); i++) { Tile suggestion = suggestions.get(i); if (mSuggestionsChecks.isSuggestionComplete(suggestion)) { - mAdapter.disableSuggestion(suggestion); + mSuggestionFeatureProvider.dismissSuggestion( + context, mSuggestionParser, suggestion); suggestions.remove(i--); } } @@ -277,7 +281,7 @@ public class DashboardSummary extends InstrumentedFragment // API that takes a single category. List categories = new ArrayList<>(); categories.add(mDashboardFeatureProvider.getTilesForCategory( - CategoryKey.CATEGORY_HOMEPAGE)); + CategoryKey.CATEGORY_HOMEPAGE)); if (suggestions != null) { mAdapter.setCategoriesAndSuggestions(categories, suggestions); } else { diff --git a/src/com/android/settings/dashboard/conditional/ConditionAdapterUtils.java b/src/com/android/settings/dashboard/conditional/ConditionAdapterUtils.java index b58ac39b9fe..aebbf931c81 100644 --- a/src/com/android/settings/dashboard/conditional/ConditionAdapterUtils.java +++ b/src/com/android/settings/dashboard/conditional/ConditionAdapterUtils.java @@ -15,16 +15,11 @@ */ package com.android.settings.dashboard.conditional; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ObjectAnimator; import android.content.Context; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.helper.ItemTouchHelper; import android.util.Log; import android.view.View; -import android.view.View.OnLayoutChangeListener; -import android.view.ViewGroup.LayoutParams; import android.widget.Button; import android.widget.ImageView; @@ -126,12 +121,6 @@ public class ConditionAdapterUtils { } } - private static void setHeight(View detailGroup, int height) { - final LayoutParams params = detailGroup.getLayoutParams(); - params.height = height; - detailGroup.setLayoutParams(params); - } - private static void setViewVisibility(View containerView, int viewId, boolean visible) { View view = containerView.findViewById(viewId); if (view != null) { diff --git a/src/com/android/settings/dashboard/suggestions/SuggestionDismissController.java b/src/com/android/settings/dashboard/suggestions/SuggestionDismissController.java new file mode 100644 index 00000000000..708aadb8e7a --- /dev/null +++ b/src/com/android/settings/dashboard/suggestions/SuggestionDismissController.java @@ -0,0 +1,84 @@ +/* + * 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.settings.dashboard.suggestions; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.helper.ItemTouchHelper; + +import com.android.settings.R; +import com.android.settings.overlay.FeatureFactory; +import com.android.settingslib.SuggestionParser; +import com.android.settingslib.drawer.Tile; + +public class SuggestionDismissController extends ItemTouchHelper.SimpleCallback { + + public interface Callback { + + /** + * Returns suggestion tile data from the callback + */ + Tile getSuggestionForPosition(int position); + + /** + * Called when a suggestion is dismissed. + */ + void onSuggestionDismissed(Tile suggestion); + } + + private final Context mContext; + private final SuggestionFeatureProvider mSuggestionFeatureProvider; + private final SuggestionParser mSuggestionParser; + private final Callback mCallback; + + public SuggestionDismissController(Context context, RecyclerView recyclerView, + SuggestionParser parser, Callback callback) { + super(0, ItemTouchHelper.START | ItemTouchHelper.END); + mContext = context; + mSuggestionParser = parser; + mSuggestionFeatureProvider = FeatureFactory.getFactory(context) + .getSuggestionFeatureProvider(context); + mCallback = callback; + final ItemTouchHelper itemTouchHelper = new ItemTouchHelper(this); + itemTouchHelper.attachToRecyclerView(recyclerView); + } + + @Override + public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, + RecyclerView.ViewHolder target) { + return true; + } + + @Override + public int getSwipeDirs(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { + if (viewHolder.getItemViewType() == R.layout.suggestion_tile) { + // Only return swipe direction for suggestion tiles. All other types are not swipeable. + return super.getSwipeDirs(recyclerView, viewHolder); + } + return 0; + } + + @Override + public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { + if (mCallback == null) { + return; + } + final Tile suggestion = mCallback.getSuggestionForPosition(viewHolder.getAdapterPosition()); + mSuggestionFeatureProvider.dismissSuggestion(mContext, mSuggestionParser, suggestion); + mCallback.onSuggestionDismissed(suggestion); + } +} diff --git a/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProvider.java b/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProvider.java index 18fbd90815e..806d6f880f5 100644 --- a/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProvider.java +++ b/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProvider.java @@ -18,6 +18,7 @@ package com.android.settings.dashboard.suggestions; import android.content.Context; +import com.android.settingslib.SuggestionParser; import com.android.settingslib.drawer.Tile; import java.util.List; @@ -38,8 +39,14 @@ public interface SuggestionFeatureProvider { /** * Ranks the list of suggestions in place. - * @param suggestions: List of suggestion Tiles - * @param suggestionIds: List of suggestion ids corresponding to the suggestion tiles. + * + * @param suggestions List of suggestion Tiles + * @param suggestionIds List of suggestion ids corresponding to the suggestion tiles. */ void rankSuggestions(final List suggestions, List suggestionIds); + + /** + * Dismisses a suggestion. + */ + void dismissSuggestion(Context context, SuggestionParser parser, Tile suggestion); } diff --git a/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImpl.java b/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImpl.java index 3a4d5456bf7..9231037e2f4 100644 --- a/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImpl.java +++ b/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImpl.java @@ -17,7 +17,13 @@ package com.android.settings.dashboard.suggestions; import android.content.Context; +import android.content.pm.PackageManager; +import com.android.internal.logging.nano.MetricsProto; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; +import com.android.settings.dashboard.DashboardAdapter; +import com.android.settings.overlay.FeatureFactory; +import com.android.settingslib.SuggestionParser; import com.android.settingslib.drawer.Tile; import java.util.List; @@ -25,6 +31,7 @@ import java.util.List; public class SuggestionFeatureProviderImpl implements SuggestionFeatureProvider { private final SuggestionRanker mSuggestionRanker; + private final MetricsFeatureProvider mMetricsFeatureProvider; @Override public boolean isSmartSuggestionEnabled(Context context) { @@ -45,6 +52,8 @@ public class SuggestionFeatureProviderImpl implements SuggestionFeatureProvider public SuggestionFeatureProviderImpl(Context context) { mSuggestionRanker = new SuggestionRanker( new SuggestionFeaturizer(new EventStore(context.getApplicationContext()))); + mMetricsFeatureProvider = FeatureFactory.getFactory(context) + .getMetricsFeatureProvider(); } @Override @@ -52,4 +61,24 @@ public class SuggestionFeatureProviderImpl implements SuggestionFeatureProvider mSuggestionRanker.rankSuggestions(suggestions, suggestionIds); } + @Override + public void dismissSuggestion(Context context, SuggestionParser parser, Tile suggestion) { + if (parser == null || suggestion == null) { + return; + } + mMetricsFeatureProvider.action( + context, MetricsProto.MetricsEvent.ACTION_SETTINGS_DISMISS_SUGGESTION, + DashboardAdapter.getSuggestionIdentifier(context, suggestion)); + + final boolean isSmartSuggestionEnabled = isSmartSuggestionEnabled(context); + if (!parser.dismissSuggestion(suggestion, isSmartSuggestionEnabled)) { + return; + } + context.getPackageManager().setComponentEnabledSetting( + suggestion.intent.getComponent(), + PackageManager.COMPONENT_ENABLED_STATE_DISABLED, + PackageManager.DONT_KILL_APP); + parser.markCategoryDone(suggestion.category); + } + } diff --git a/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionDismissControllerTest.java b/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionDismissControllerTest.java new file mode 100644 index 00000000000..4a41ede188c --- /dev/null +++ b/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionDismissControllerTest.java @@ -0,0 +1,107 @@ +/* + * 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.settings.dashboard.suggestions; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.helper.ItemTouchHelper; + +import com.android.settings.R; +import com.android.settings.SettingsRobolectricTestRunner; +import com.android.settings.TestConfig; +import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settingslib.SuggestionParser; +import com.android.settingslib.drawer.Tile; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.annotation.Config; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class SuggestionDismissControllerTest { + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private Context mContext; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private RecyclerView mRecyclerView; + @Mock + private SuggestionParser mSuggestionParser; + @Mock + private SuggestionDismissController.Callback mCallback; + + private FakeFeatureFactory mFactory; + private SuggestionDismissController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + FakeFeatureFactory.setupForTest(mContext); + mFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext); + + when(mRecyclerView.getResources().getDimension(anyInt())).thenReturn(50F); + + mController = new SuggestionDismissController(mContext, mRecyclerView, + mSuggestionParser, mCallback); + } + + @Test + public void onMove_alwaysReturnTrue() { + assertThat(mController.onMove(null, null, null)).isTrue(); + } + + @Test + public void getSwipeDirs_isSuggestionTile_shouldReturnDirection() { + final RecyclerView.ViewHolder vh = mock(RecyclerView.ViewHolder.class); + when(vh.getItemViewType()).thenReturn(R.layout.suggestion_tile); + + assertThat(mController.getSwipeDirs(mRecyclerView, vh)) + .isEqualTo(ItemTouchHelper.START | ItemTouchHelper.END); + } + + @Test + public void getSwipeDirs_isNotSuggestionTile_shouldReturn0() { + final RecyclerView.ViewHolder vh = mock(RecyclerView.ViewHolder.class); + when(vh.getItemViewType()).thenReturn(R.layout.condition_card); + + assertThat(mController.getSwipeDirs(mRecyclerView, vh)) + .isEqualTo(0); + } + + @Test + public void onSwiped_shouldTriggerDismissSuggestion() { + final RecyclerView.ViewHolder vh = mock(RecyclerView.ViewHolder.class); + + mController.onSwiped(vh, ItemTouchHelper.START); + + verify(mFactory.suggestionsFeatureProvider).dismissSuggestion( + eq(mContext), eq(mSuggestionParser), any(Tile.class)); + verify(mCallback).onSuggestionDismissed(any(Tile.class)); + } +} diff --git a/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImplTest.java new file mode 100644 index 00000000000..912947ab3d8 --- /dev/null +++ b/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImplTest.java @@ -0,0 +1,114 @@ +/* + * 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.settings.dashboard.suggestions; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; + +import com.android.internal.logging.nano.MetricsProto; +import com.android.settings.SettingsRobolectricTestRunner; +import com.android.settings.TestConfig; +import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settingslib.SuggestionParser; +import com.android.settingslib.drawer.Tile; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class SuggestionFeatureProviderImplTest { + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private Context mContext; + @Mock + private SuggestionParser mSuggestionParser; + @Mock + private Tile mSuggestion; + + private FakeFeatureFactory mFactory; + private SuggestionFeatureProviderImpl mProvider; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + FakeFeatureFactory.setupForTest(mContext); + mFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext); + when(mContext.getApplicationContext()).thenReturn(RuntimeEnvironment.application); + mSuggestion.intent = new Intent().setClassName("pkg", "cls"); + mSuggestion.category = "category"; + + mProvider = new SuggestionFeatureProviderImpl(mContext); + } + + @Test + public void dismissSuggestion_noParserOrSuggestion_noop() { + mProvider.dismissSuggestion(mContext, null, null); + mProvider.dismissSuggestion(mContext, mSuggestionParser, null); + mProvider.dismissSuggestion(mContext, null, mSuggestion); + + verifyZeroInteractions(mFactory.metricsFeatureProvider); + } + + @Test + public void dismissSuggestion_hasMoreDismissCount_shouldNotDisableComponent() { + when(mSuggestionParser.dismissSuggestion(any(Tile.class), anyBoolean())) + .thenReturn(false); + mProvider.dismissSuggestion(mContext, mSuggestionParser, mSuggestion); + + verify(mFactory.metricsFeatureProvider).action( + eq(mContext), + eq(MetricsProto.MetricsEvent.ACTION_SETTINGS_DISMISS_SUGGESTION), + anyString()); + verify(mContext, never()).getPackageManager(); + } + + @Test + public void dismissSuggestion_hasNoMoreDismissCount_shouldDisableComponent() { + when(mSuggestionParser.dismissSuggestion(any(Tile.class), anyBoolean())) + .thenReturn(true); + + mProvider.dismissSuggestion(mContext, mSuggestionParser, mSuggestion); + + verify(mFactory.metricsFeatureProvider).action( + eq(mContext), + eq(MetricsProto.MetricsEvent.ACTION_SETTINGS_DISMISS_SUGGESTION), + anyString()); + + verify(mContext.getPackageManager()) + .setComponentEnabledSetting(mSuggestion.intent.getComponent(), + PackageManager.COMPONENT_ENABLED_STATE_DISABLED, + PackageManager.DONT_KILL_APP); + verify(mSuggestionParser).markCategoryDone(mSuggestion.category); + } +}