diff --git a/src/com/android/settings/dashboard/DashboardAdapter.java b/src/com/android/settings/dashboard/DashboardAdapter.java index 741067ac92c..5df66f1dcb3 100644 --- a/src/com/android/settings/dashboard/DashboardAdapter.java +++ b/src/com/android/settings/dashboard/DashboardAdapter.java @@ -23,6 +23,7 @@ import android.graphics.Color; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.os.Bundle; +import android.service.settings.suggestions.Suggestion; import android.support.annotation.VisibleForTesting; import android.support.v7.util.DiffUtil; import android.support.v7.widget.LinearLayoutManager; @@ -62,6 +63,7 @@ public class DashboardAdapter extends RecyclerView.Adapter conditions, SuggestionParser suggestionParser, SuggestionDismissController.Callback callback) { + + // @deprecated In favor of suggestionsV2 below. List suggestions = null; + List suggestionsV2 = null; DashboardCategory category = null; int suggestionConditionMode = DashboardData.HEADER_MODE_DEFAULT; @@ -116,6 +121,7 @@ public class DashboardAdapter extends RecyclerView.Adapter suggestions) { + /** + * @deprecated in favor of {@link #setCategory(DashboardCategory)} and + * {@link #setSuggestionsV2(List)}. + */ + @Deprecated + public void setCategoriesAndSuggestions(DashboardCategory category, List suggestions) { tintIcons(category, suggestions); final DashboardData prevData = mDashboardData; @@ -168,6 +179,16 @@ public class DashboardAdapter extends RecyclerView.Adapter data) { + // TODO: Tint icon + final DashboardData prevData = mDashboardData; + mDashboardData = new DashboardData.Builder(prevData) + .setSuggestionsV2(data) + .build(); + notifyDashboardDataChanged(prevData); + // TODO: Replicate the metrics logging from setCategoriesAndSuggestions() + } + public void setCategory(DashboardCategory category) { tintIcons(category, null); final DashboardData prevData = mDashboardData; @@ -187,6 +208,10 @@ public class DashboardAdapter extends RecyclerView.Adapter suggestions = mDashboardData.getSuggestions(); if (suggestions == null || suggestions.isEmpty()) { @@ -205,6 +230,24 @@ public class DashboardAdapter extends RecyclerView.Adapter list = mDashboardData.getSuggestionsV2(); + if (list == null || list.size() == 0) { + return; + } + if (list.size() == 1) { + // The only suggestion is dismissed, and the the empty suggestion container will + // remain as the dashboard item. Need to refresh the dashboard list. + final DashboardData prevData = mDashboardData; + mDashboardData = new DashboardData.Builder(prevData) + .setSuggestionsV2(null) + .build(); + notifyDashboardDataChanged(prevData); + } else { + mSuggestionAdapter.removeSuggestion(suggestion); + } + } + @Override public void notifySummaryChanged(Tile tile) { final int position = mDashboardData.getPositionByTile(tile); @@ -303,10 +346,18 @@ public class DashboardAdapter extends RecyclerView.Adapter suggestionsV2 = mDashboardData.getSuggestionsV2(); final List suggestions = mDashboardData.getSuggestions(); - if (position == SUGGESTION_CONDITION_HEADER_POSITION - && suggestions != null && suggestions.size() > 0) { - mSuggestionAdapter = new SuggestionAdapter(mContext, (List) - mDashboardData.getItemEntityByPosition(position), mSuggestionsShownLogged); - mSuggestionDismissHandler = new SuggestionDismissController(mContext, - holder.data, mSuggestionParser, mCallback); - holder.data.setAdapter(mSuggestionAdapter); - } else { + + boolean conditionOnly = true; + if (position == SUGGESTION_CONDITION_HEADER_POSITION) { + if (suggestions != null && suggestions.size() > 0) { + conditionOnly = false; + mSuggestionAdapter = new SuggestionAdapter(mContext, (List) + mDashboardData.getItemEntityByPosition(position), + null, mSuggestionsShownLogged); + mSuggestionDismissHandler = new SuggestionDismissController(mContext, + holder.data, mSuggestionParser, mCallback); + holder.data.setAdapter(mSuggestionAdapter); + } else if (suggestionsV2 != null && suggestionsV2.size() > 0) { + conditionOnly = false; + mSuggestionAdapter = new SuggestionAdapter(mContext, null, + (List) mDashboardData.getItemEntityByPosition(position), + mSuggestionsShownLogged); + mSuggestionDismissHandler = new SuggestionDismissController(mContext, + holder.data, null /* parser */, mCallback); + holder.data.setAdapter(mSuggestionAdapter); + } + } + if (conditionOnly) { ConditionAdapter adapter = new ConditionAdapter(mContext, (List) mDashboardData.getItemEntityByPosition(position), mDashboardData.getSuggestionConditionMode()); @@ -482,11 +548,16 @@ public class DashboardAdapter extends RecyclerView.Adapter suggestions = mDashboardData.getSuggestions(); final DashboardCategory category = mDashboardData.getCategory(); + final List suggestions = mDashboardData.getSuggestions(); + final List suggestionV2 = mDashboardData.getSuggestionsV2(); if (suggestions != null) { outState.putParcelableArrayList(STATE_SUGGESTION_LIST, new ArrayList<>(suggestions)); } + if (suggestionV2 != null) { + outState.putParcelableArrayList(STATE_SUGGESTION_LIST_V2, + new ArrayList<>(suggestionV2)); + } if (category != null) { outState.putParcelable(STATE_CATEGORY_LIST, category); } diff --git a/src/com/android/settings/dashboard/DashboardData.java b/src/com/android/settings/dashboard/DashboardData.java index 414757e48dd..a14b8c24125 100644 --- a/src/com/android/settings/dashboard/DashboardData.java +++ b/src/com/android/settings/dashboard/DashboardData.java @@ -17,6 +17,7 @@ package com.android.settings.dashboard; import android.annotation.IntDef; import android.graphics.drawable.Icon; +import android.service.settings.suggestions.Suggestion; import android.support.annotation.VisibleForTesting; import android.support.v7.util.DiffUtil; import android.text.TextUtils; @@ -69,6 +70,7 @@ public class DashboardData { private final DashboardCategory mCategory; private final List mConditions; private final List mSuggestions; + private final List mSuggestionsV2; @HeaderMode private final int mSuggestionConditionMode; @@ -76,6 +78,7 @@ public class DashboardData { mCategory = builder.mCategory; mConditions = builder.mConditions; mSuggestions = builder.mSuggestions; + mSuggestionsV2 = builder.mSuggestionsV2; mSuggestionConditionMode = builder.mSuggestionConditionMode; mItems = new ArrayList<>(); @@ -124,6 +127,10 @@ public class DashboardData { return mSuggestions; } + public List getSuggestionsV2() { + return mSuggestionsV2; + } + public int getSuggestionConditionMode() { return mSuggestionConditionMode; } @@ -190,13 +197,22 @@ public class DashboardData { * and mIsShowingAll, mSuggestionConditionMode flag. */ private void buildItemsData() { - final boolean hasSuggestions = sizeOf(mSuggestions) > 0; + final boolean useSuggestionV2 = mSuggestionsV2 != null; + final boolean hasSuggestions = useSuggestionV2 + ? sizeOf(mSuggestionsV2) > 0 + : sizeOf(mSuggestions) > 0; final List conditions = getConditionsToShow(mConditions); final boolean hasConditions = sizeOf(conditions) > 0; final List suggestions = getSuggestionsToShow(mSuggestions); - final int hiddenSuggestion = - hasSuggestions ? sizeOf(mSuggestions) - sizeOf(suggestions) : 0; + final List suggestionsV2 = getSuggestionsV2ToShow(mSuggestionsV2); + + final int hiddenSuggestion; + if (useSuggestionV2) { + hiddenSuggestion = hasSuggestions ? sizeOf(mSuggestionsV2) - sizeOf(suggestionsV2) : 0; + } else { + hiddenSuggestion = hasSuggestions ? sizeOf(mSuggestions) - sizeOf(suggestions) : 0; + } final boolean hasSuggestionAndCollapsed = hasSuggestions && mSuggestionConditionMode == HEADER_MODE_COLLAPSED; @@ -215,10 +231,15 @@ public class DashboardData { R.layout.suggestion_condition_header, STABLE_ID_SUGGESTION_CONDITION_MIDDLE_HEADER, onlyHasConditionAndCollapsed); - /* Suggestion container. This is the card view that contains the list of suggestions. - * This will be added whenever the suggestion list is not empty */ - addToItemList(suggestions, R.layout.suggestion_condition_container, - STABLE_ID_SUGGESTION_CONTAINER, sizeOf(suggestions) > 0); + if (useSuggestionV2) { + addToItemList(suggestionsV2, R.layout.suggestion_condition_container, + STABLE_ID_SUGGESTION_CONTAINER, sizeOf(suggestionsV2) > 0); + } else { + /* Suggestion container. This is the card view that contains the list of suggestions. + * This will be added whenever the suggestion list is not empty */ + addToItemList(suggestions, R.layout.suggestion_condition_container, + STABLE_ID_SUGGESTION_CONTAINER, sizeOf(suggestions) > 0); + } /* Second suggestion/condition header. This will be added when there is at least one * suggestion or condition that is not currently displayed, and the user can expand the @@ -246,7 +267,7 @@ public class DashboardData { && !hasConditions && hiddenSuggestion == 0); - if(mCategory != null) { + if (mCategory != null) { for (int j = 0; j < mCategory.tiles.size(); j++) { final Tile tile = mCategory.tiles.get(j); addToItemList(tile, R.layout.dashboard_tile, Objects.hash(tile.title), @@ -274,6 +295,10 @@ public class DashboardData { return result; } + /** + * @deprecated in favor of {@link #getSuggestionsV2ToShow}. + */ + @Deprecated private List getSuggestionsToShow(List suggestions) { if (suggestions == null || mSuggestionConditionMode == HEADER_MODE_COLLAPSED) { return null; @@ -285,6 +310,17 @@ public class DashboardData { return suggestions.subList(0, DEFAULT_SUGGESTION_COUNT); } + private List getSuggestionsV2ToShow(List suggestions) { + if (suggestions == null || mSuggestionConditionMode == HEADER_MODE_COLLAPSED) { + return null; + } + if (mSuggestionConditionMode != HEADER_MODE_DEFAULT + || suggestions.size() <= DEFAULT_SUGGESTION_COUNT) { + return suggestions; + } + return suggestions.subList(0, DEFAULT_SUGGESTION_COUNT); + } + /** * Builder used to build the ItemsData *

@@ -296,7 +332,12 @@ public class DashboardData { private DashboardCategory mCategory; private List mConditions; + /** + * @deprecated in favor of SuggestionList + */ + @Deprecated private List mSuggestions; + private List mSuggestionsV2; public Builder() { } @@ -305,6 +346,7 @@ public class DashboardData { mCategory = dashboardData.mCategory; mConditions = dashboardData.mConditions; mSuggestions = dashboardData.mSuggestions; + mSuggestionsV2 = dashboardData.mSuggestionsV2; mSuggestionConditionMode = dashboardData.mSuggestionConditionMode; } @@ -318,11 +360,20 @@ public class DashboardData { return this; } + /** + * @deprecated in favor of {@link #setSuggestionsV2(List)})} + */ + @Deprecated public Builder setSuggestions(List suggestions) { this.mSuggestions = suggestions; return this; } + public Builder setSuggestionsV2(List suggestions) { + this.mSuggestionsV2 = suggestions; + return this; + } + public Builder setSuggestionConditionMode(@HeaderMode int mode) { this.mSuggestionConditionMode = mode; return this; diff --git a/src/com/android/settings/dashboard/DashboardSummary.java b/src/com/android/settings/dashboard/DashboardSummary.java index 6caf052bc60..769a3d70c86 100644 --- a/src/com/android/settings/dashboard/DashboardSummary.java +++ b/src/com/android/settings/dashboard/DashboardSummary.java @@ -21,6 +21,7 @@ import android.content.Context; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; +import android.service.settings.suggestions.Suggestion; import android.support.annotation.VisibleForTesting; import android.support.v7.widget.LinearLayoutManager; import android.util.Log; @@ -36,9 +37,9 @@ import com.android.settings.dashboard.conditional.ConditionManager; import com.android.settings.dashboard.conditional.ConditionManager.ConditionListener; import com.android.settings.dashboard.conditional.FocusRecyclerView; import com.android.settings.dashboard.conditional.FocusRecyclerView.FocusListener; +import com.android.settings.dashboard.suggestions.SuggestionControllerMixin; import com.android.settings.dashboard.suggestions.SuggestionDismissController; import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider; -import com.android.settings.dashboard.suggestions.SuggestionControllerMixin; import com.android.settings.dashboard.suggestions.SuggestionsChecks; import com.android.settings.overlay.FeatureFactory; import com.android.settings.widget.ActionBarShadowController; @@ -55,7 +56,8 @@ import java.util.List; public class DashboardSummary extends InstrumentedFragment implements CategoryListener, ConditionListener, - FocusListener, SuggestionDismissController.Callback { + FocusListener, SuggestionDismissController.Callback, + SuggestionControllerMixin.SuggestionControllerHost { public static final boolean DEBUG = false; private static final boolean DEBUG_TIMING = false; private static final int MAX_WAIT_MILLIS = 700; @@ -86,7 +88,12 @@ public class DashboardSummary extends InstrumentedFragment @Override public void onAttach(Context context) { super.onAttach(context); - mSuggestionControllerMixin = new SuggestionControllerMixin(context, getLifecycle()); + mSuggestionFeatureProvider = FeatureFactory.getFactory(context) + .getSuggestionFeatureProvider(context); + if (mSuggestionFeatureProvider.isSuggestionV2Enabled(context)) { + mSuggestionControllerMixin = new SuggestionControllerMixin(context, this /* host */, + getLifecycle()); + } } @Override @@ -96,8 +103,6 @@ public class DashboardSummary extends InstrumentedFragment final Activity activity = getActivity(); mDashboardFeatureProvider = FeatureFactory.getFactory(activity) .getDashboardFeatureProvider(activity); - mSuggestionFeatureProvider = FeatureFactory.getFactory(activity) - .getSuggestionFeatureProvider(activity); mSummaryLoader = new SummaryLoader(activity, CategoryKey.CATEGORY_HOMEPAGE); @@ -109,8 +114,7 @@ public class DashboardSummary extends InstrumentedFragment mSuggestionsChecks = new SuggestionsChecks(getContext()); } if (DEBUG_TIMING) { - Log.d(TAG, "onCreate took " + (System.currentTimeMillis() - startTime) - + " ms"); + Log.d(TAG, "onCreate took " + (System.currentTimeMillis() - startTime) + " ms"); } } @@ -221,12 +225,12 @@ public class DashboardSummary extends InstrumentedFragment void rebuildUI() { if (!mSuggestionFeatureProvider.isSuggestionEnabled(getContext())) { Log.d(TAG, "Suggestion feature is disabled, skipping suggestion entirely"); - updateCategoryAndSuggestion(null /* tiles */); + updateCategory(); } else { new SuggestionLoader().execute(); // Set categories on their own if loading suggestions takes too long. mHandler.postDelayed(() -> { - updateCategoryAndSuggestion(null /* tiles */); + updateCategory(); }, MAX_WAIT_MILLIS); } } @@ -263,6 +267,11 @@ public class DashboardSummary extends InstrumentedFragment } } + @Override + public Suggestion getSuggestionAt(int position) { + return mAdapter.getSuggestionV2(position); + } + @Override public Tile getSuggestionForPosition(int position) { return mAdapter.getSuggestion(position); @@ -273,6 +282,20 @@ public class DashboardSummary extends InstrumentedFragment mAdapter.onSuggestionDismissed(suggestion); } + @Override + public void onSuggestionDismissed(Suggestion suggestion) { + mAdapter.onSuggestionDismissed(suggestion); + } + + @Override + public void onSuggestionReady(List suggestions) { + mAdapter.setSuggestionsV2(suggestions); + } + + /** + * @deprecated in favor of the real SuggestionLoader. + */ + @Deprecated private class SuggestionLoader extends AsyncTask> { @Override protected List doInBackground(Void... params) { @@ -311,6 +334,17 @@ public class DashboardSummary extends InstrumentedFragment } } + void updateCategory() { + final DashboardCategory category = mDashboardFeatureProvider.getTilesForCategory( + CategoryKey.CATEGORY_HOMEPAGE); + mSummaryLoader.updateSummaryToCache(category); + mAdapter.setCategory(category); + } + + /** + * @deprecated in favor of SuggestionControllerMixin. + */ + @Deprecated @VisibleForTesting void updateCategoryAndSuggestion(List suggestions) { final Activity activity = getActivity(); diff --git a/src/com/android/settings/dashboard/suggestions/SuggestionAdapter.java b/src/com/android/settings/dashboard/suggestions/SuggestionAdapter.java index 3815211c475..4852ed9519a 100644 --- a/src/com/android/settings/dashboard/suggestions/SuggestionAdapter.java +++ b/src/com/android/settings/dashboard/suggestions/SuggestionAdapter.java @@ -15,13 +15,17 @@ */ package com.android.settings.dashboard.suggestions; +import android.app.PendingIntent; import android.content.Context; +import android.service.settings.suggestions.Suggestion; import android.support.v7.widget.RecyclerView; import android.text.TextUtils; +import android.util.Log; import android.util.Pair; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; + import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.R; import com.android.settings.SettingsActivity; @@ -41,14 +45,18 @@ public class SuggestionAdapter extends RecyclerView.Adapter private final Context mContext; private final MetricsFeatureProvider mMetricsFeatureProvider; private final SuggestionFeatureProvider mSuggestionFeatureProvider; - private List mSuggestions; + @Deprecated // in favor of mNewSuggestions + private final List mSuggestions; + private final List mSuggestionsV2; private final IconCache mCache; private final List mSuggestionsShownLogged; public SuggestionAdapter(Context context, List suggestions, + List suggestionsV2, List suggestionsShownLogged) { mContext = context; mSuggestions = suggestions; + mSuggestionsV2 = suggestionsV2; mSuggestionsShownLogged = suggestionsShownLogged; mCache = new IconCache(context); final FeatureFactory factory = FeatureFactory.getFactory(context); @@ -66,6 +74,68 @@ public class SuggestionAdapter extends RecyclerView.Adapter @Override public void onBindViewHolder(DashboardItemHolder holder, int position) { + if (mSuggestions != null) { + bindSuggestionTile(holder, position); + } else { + bindSuggestion(holder, position); + } + } + + private void bindSuggestion(DashboardItemHolder holder, int position) { + final Suggestion suggestion = mSuggestionsV2.get(position); + final String id = suggestion.getId(); + if (!mSuggestionsShownLogged.contains(id)) { + mMetricsFeatureProvider.action( + mContext, MetricsEvent.ACTION_SHOW_SETTINGS_SUGGESTION, id); + mSuggestionsShownLogged.add(id); + } + // TODO: Add remote view field in Suggestion, and enable this. + // if (suggestion.remoteViews != null) { + // final ViewGroup itemView = (ViewGroup) holder.itemView; + // itemView.removeAllViews(); + // itemView.addView(suggestion.remoteViews.apply(itemView.getContext(), + // itemView)); + // } else + { + // TODO: Add icon field in Suggestion, and enable this. + // holder.icon.setImageDrawable(mCache.getIcon(suggestion.icon)); + holder.title.setText(suggestion.getTitle()); + final CharSequence summary = suggestion.getSummary(); + if (!TextUtils.isEmpty(summary)) { + holder.summary.setText(summary); + holder.summary.setVisibility(View.VISIBLE); + } else { + holder.summary.setVisibility(View.GONE); + } + } + final View divider = holder.itemView.findViewById(R.id.divider); + if (divider != null) { + divider.setVisibility(position < mSuggestionsV2.size() - 1 ? View.VISIBLE : View.GONE); + } + View clickHandler = holder.itemView; + // If a view with @android:id/primary is defined, use that as the click handler + // instead. + final View primaryAction = holder.itemView.findViewById(android.R.id.primary); + if (primaryAction != null) { + clickHandler = primaryAction; + // set the item view to disabled to remove any touch effects + holder.itemView.setEnabled(false); + } + clickHandler.setOnClickListener(v -> { + mMetricsFeatureProvider.action(mContext, MetricsEvent.ACTION_SETTINGS_SUGGESTION, id); + try { + suggestion.getPendingIntent().send(); + } catch (PendingIntent.CanceledException e) { + Log.w(TAG, "Failed to start suggestion " + suggestion.getTitle()); + } + }); + } + + /** + * @deprecated in favor {@link #bindSuggestion(DashboardItemHolder, int)}. + */ + @Deprecated + private void bindSuggestionTile(DashboardItemHolder holder, int position) { final Tile suggestion = (Tile) mSuggestions.get(position); final String suggestionId = mSuggestionFeatureProvider.getSuggestionIdentifier( mContext, suggestion); @@ -115,20 +185,39 @@ public class SuggestionAdapter extends RecyclerView.Adapter @Override public long getItemId(int position) { - return Objects.hash(mSuggestions.get(position).title); + if (mSuggestions != null) { + return Objects.hash(mSuggestions.get(position).title); + } else { + return Objects.hash(mSuggestionsV2.get(position).getId()); + } } @Override public int getItemViewType(int position) { - Tile suggestion = getSuggestion(position); - return suggestion.remoteViews != null - ? R.layout.suggestion_tile_remote_container - : R.layout.suggestion_tile; + if (mSuggestions != null) { + Tile suggestion = getSuggestion(position); + + return suggestion.remoteViews != null + ? R.layout.suggestion_tile_remote_container + : R.layout.suggestion_tile; + } else { + + return R.layout.suggestion_tile; + // TODO: Add remote view field in Suggestion, and enable this. + // Suggestion suggestion = getSuggestionsV2(position); + // return suggestion.remoteViews != null + // ? R.layout.suggestion_tile_remote_container + // : R.layout.suggestion_tile; + } } @Override public int getItemCount() { - return mSuggestions.size(); + if (mSuggestions != null) { + return mSuggestions.size(); + } else { + return mSuggestionsV2.size(); + } } public Tile getSuggestion(int position) { @@ -141,6 +230,16 @@ public class SuggestionAdapter extends RecyclerView.Adapter return null; } + public Suggestion getSuggestionsV2(int position) { + final long itemId = getItemId(position); + for (Suggestion suggestion : mSuggestionsV2) { + if (Objects.hash(suggestion.getId()) == itemId) { + return suggestion; + } + } + return null; + } + public void removeSuggestion(Tile suggestion) { mSuggestions.remove(suggestion); notifyDataSetChanged(); @@ -151,4 +250,8 @@ public class SuggestionAdapter extends RecyclerView.Adapter mSuggestionFeatureProvider.isSmartSuggestionEnabled(mContext)); } + public void removeSuggestion(Suggestion suggestion) { + mSuggestionsV2.remove(suggestion); + notifyDataSetChanged(); + } } diff --git a/src/com/android/settings/dashboard/suggestions/SuggestionControllerMixin.java b/src/com/android/settings/dashboard/suggestions/SuggestionControllerMixin.java index 6f5c82eddff..21ae435dd69 100644 --- a/src/com/android/settings/dashboard/suggestions/SuggestionControllerMixin.java +++ b/src/com/android/settings/dashboard/suggestions/SuggestionControllerMixin.java @@ -16,11 +16,12 @@ package com.android.settings.dashboard.suggestions; +import android.app.LoaderManager; import android.content.ComponentName; import android.content.Context; +import android.content.Loader; +import android.os.Bundle; import android.service.settings.suggestions.Suggestion; -import android.support.annotation.VisibleForTesting; -import android.util.FeatureFlagUtils; import android.util.Log; import com.android.settingslib.core.lifecycle.Lifecycle; @@ -34,23 +35,32 @@ import java.util.List; * Manages IPC communication to SettingsIntelligence for suggestion related services. */ public class SuggestionControllerMixin implements SuggestionController.ServiceConnectionListener, - LifecycleObserver, OnStart, OnStop { + LifecycleObserver, OnStart, OnStop, LoaderManager.LoaderCallbacks> { + + public interface SuggestionControllerHost { + /** + * Called when suggestion data fetching is ready. + */ + void onSuggestionReady(List data); + + /** + * Returns {@link LoaderManager} associated with the host. + */ + LoaderManager getLoaderManager(); + } - @VisibleForTesting - static final String FEATURE_FLAG = "new_settings_suggestion"; private static final String TAG = "SuggestionCtrlMixin"; private static final boolean DEBUG = false; private final Context mContext; private final SuggestionController mSuggestionController; + private final SuggestionControllerHost mHost; - public static boolean isEnabled() { - return FeatureFlagUtils.isEnabled(FEATURE_FLAG); - } - - public SuggestionControllerMixin(Context context, Lifecycle lifecycle) { + public SuggestionControllerMixin(Context context, SuggestionControllerHost host, + Lifecycle lifecycle) { mContext = context.getApplicationContext(); - mSuggestionController = new SuggestionController(context, + mHost = host; + mSuggestionController = new SuggestionController(mContext, new ComponentName( "com.android.settings.intelligence", "com.android.settings.intelligence.suggestions.SuggestionService"), @@ -62,10 +72,6 @@ public class SuggestionControllerMixin implements SuggestionController.ServiceCo @Override public void onStart() { - if (!isEnabled()) { - Log.w(TAG, "Feature not enabled, skipping"); - return; - } mSuggestionController.start(); } @@ -76,11 +82,8 @@ public class SuggestionControllerMixin implements SuggestionController.ServiceCo @Override public void onServiceConnected() { - // TODO: Call API to get data from a loader instead of in current thread. - final List data = mSuggestionController.getSuggestions(); - if (DEBUG) { - Log.d(TAG, "data size " + (data == null ? 0 : data.size())); - } + mHost.getLoaderManager().restartLoader(SuggestionLoader.LOADER_ID_SUGGESTIONS, + null /* args */, this /* callback */); } @Override @@ -88,5 +91,24 @@ public class SuggestionControllerMixin implements SuggestionController.ServiceCo if (DEBUG) { Log.d(TAG, "SuggestionService disconnected"); } + mHost.getLoaderManager().destroyLoader(SuggestionLoader.LOADER_ID_SUGGESTIONS); + } + + @Override + public Loader> onCreateLoader(int id, Bundle args) { + if (id == SuggestionLoader.LOADER_ID_SUGGESTIONS) { + return new SuggestionLoader(mContext, mSuggestionController); + } + throw new IllegalArgumentException("This loader id is not supported " + id); + } + + @Override + public void onLoadFinished(Loader> loader, List data) { + mHost.onSuggestionReady(data); + } + + @Override + public void onLoaderReset(Loader> loader) { + } } diff --git a/src/com/android/settings/dashboard/suggestions/SuggestionDismissController.java b/src/com/android/settings/dashboard/suggestions/SuggestionDismissController.java index 100c955716b..6affa8cedf7 100644 --- a/src/com/android/settings/dashboard/suggestions/SuggestionDismissController.java +++ b/src/com/android/settings/dashboard/suggestions/SuggestionDismissController.java @@ -17,6 +17,7 @@ package com.android.settings.dashboard.suggestions; import android.content.Context; +import android.service.settings.suggestions.Suggestion; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.helper.ItemTouchHelper; @@ -30,18 +31,37 @@ public class SuggestionDismissController extends ItemTouchHelper.SimpleCallback public interface Callback { /** + * @deprecated in favor of {@link #getSuggestionAt(int)} * Returns suggestion tile data from the callback */ + @Deprecated Tile getSuggestionForPosition(int position); + /** + * @deprecated in favor of {@link #onSuggestionDismissed(Suggestion)} + * Called when a suggestion is dismissed. + */ + @Deprecated + void onSuggestionDismissed(Tile suggestion); + + /** + * Returns suggestion tile data from the callback + */ + Suggestion getSuggestionAt(int position); + /** * Called when a suggestion is dismissed. */ - void onSuggestionDismissed(Tile suggestion); + void onSuggestionDismissed(Suggestion suggestion); } private final Context mContext; private final SuggestionFeatureProvider mSuggestionFeatureProvider; + + /** + * @deprecated in favor of the new Suggestion backend. + */ + @Deprecated private final SuggestionParser mSuggestionParser; private final Callback mCallback; @@ -79,8 +99,15 @@ public class SuggestionDismissController extends ItemTouchHelper.SimpleCallback if (mCallback == null) { return; } - final Tile suggestion = mCallback.getSuggestionForPosition(viewHolder.getAdapterPosition()); - mSuggestionFeatureProvider.dismissSuggestion(mContext, mSuggestionParser, suggestion); - mCallback.onSuggestionDismissed(suggestion); + final int position = viewHolder.getAdapterPosition(); + final Suggestion suggestionV2 = mCallback.getSuggestionAt(position); + if (suggestionV2 != null) { + mSuggestionFeatureProvider.dismissSuggestion(mContext, suggestionV2); + mCallback.onSuggestionDismissed(suggestionV2); + } else { + final Tile suggestion = mCallback.getSuggestionForPosition(position); + 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 d19a7781bfe..b6495724e70 100644 --- a/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProvider.java +++ b/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProvider.java @@ -19,6 +19,7 @@ package com.android.settings.dashboard.suggestions; import android.content.ComponentName; import android.content.Context; import android.content.SharedPreferences; +import android.service.settings.suggestions.Suggestion; import android.support.annotation.NonNull; import com.android.settingslib.drawer.Tile; @@ -31,9 +32,16 @@ public interface SuggestionFeatureProvider { /** * Whether or not the whole suggestion feature is enabled. + * @deprecated in favor of {@link #isSuggestionV2Enabled(Context)} */ + @Deprecated boolean isSuggestionEnabled(Context context); + /** + * Whether or not the suggestion v2 feature is enabled. + */ + boolean isSuggestionV2Enabled(Context context); + /** * Returns true if smart suggestion should be used instead of xml based SuggestionParser. */ @@ -52,7 +60,9 @@ public interface SuggestionFeatureProvider { * * @param suggestions List of suggestion Tiles * @param suggestionIds List of suggestion ids corresponding to the suggestion tiles. + * @deprecated in favor of SettingsIntelligence */ + @Deprecated void rankSuggestions(final List suggestions, List suggestionIds); /** @@ -62,9 +72,17 @@ public interface SuggestionFeatureProvider { /** * Dismisses a suggestion. + * + * @deprecated in favor of {@link #dismissSuggestion(Context, Suggestion)} */ + @Deprecated void dismissSuggestion(Context context, SuggestionParser parser, Tile suggestion); + /** + * Dismisses a suggestion. + */ + void dismissSuggestion(Context context, Suggestion suggestion); + /** * Returns an identifier for the suggestion */ diff --git a/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImpl.java b/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImpl.java index 3d40d96d4e4..f85d95a57af 100644 --- a/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImpl.java +++ b/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImpl.java @@ -23,11 +23,14 @@ import android.content.Context; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.provider.Settings.Secure; +import android.service.settings.suggestions.Suggestion; import android.support.annotation.NonNull; import android.support.annotation.VisibleForTesting; +import android.util.FeatureFlagUtils; import android.util.Log; import android.util.Pair; +import com.android.internal.logging.nano.MetricsProto; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.Settings.AmbientDisplayPickupSuggestionActivity; import com.android.settings.Settings.AmbientDisplaySuggestionActivity; @@ -46,7 +49,6 @@ import com.android.settings.support.NewDeviceIntroSuggestionActivity; import com.android.settingslib.drawer.Tile; import com.android.settingslib.suggestions.SuggestionParser; -import java.util.ArrayList; import java.util.List; public class SuggestionFeatureProviderImpl implements SuggestionFeatureProvider { @@ -55,6 +57,8 @@ public class SuggestionFeatureProviderImpl implements SuggestionFeatureProvider private static final int EXCLUSIVE_SUGGESTION_MAX_COUNT = 3; private static final String SHARED_PREF_FILENAME = "suggestions"; + @VisibleForTesting + static final String FEATURE_FLAG_SUGGESTIONS_V2 = "new_settings_suggestion"; private final SuggestionRanker mSuggestionRanker; private final MetricsFeatureProvider mMetricsFeatureProvider; @@ -63,7 +67,20 @@ public class SuggestionFeatureProviderImpl implements SuggestionFeatureProvider public boolean isSuggestionEnabled(Context context) { final ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); - return !am.isLowRamDevice(); + boolean isLowRamDevice = am.isLowRamDevice(); + return !isLowRamDevice && !isV2Enabled(); + } + + @Override + public boolean isSuggestionV2Enabled(Context context) { + final ActivityManager am = + (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + boolean isLowRamDevice = am.isLowRamDevice(); + return !isLowRamDevice && isV2Enabled(); + } + + private static boolean isV2Enabled() { + return FeatureFlagUtils.isEnabled(FEATURE_FLAG_SUGGESTIONS_V2); } @Override @@ -148,6 +165,17 @@ public class SuggestionFeatureProviderImpl implements SuggestionFeatureProvider PackageManager.DONT_KILL_APP); } + @Override + public void dismissSuggestion(Context context, Suggestion suggestion) { + if (suggestion == null || context == null) { + return; + } + mMetricsFeatureProvider.action( + context, MetricsProto.MetricsEvent.ACTION_SETTINGS_DISMISS_SUGGESTION, + suggestion.getId()); + // TODO: Call SettingsIntelligence to dismiss suggestion. + } + @Override public String getSuggestionIdentifier(Context context, Tile suggestion) { if (suggestion.intent == null || suggestion.intent.getComponent() == null diff --git a/src/com/android/settings/dashboard/suggestions/SuggestionLoader.java b/src/com/android/settings/dashboard/suggestions/SuggestionLoader.java new file mode 100644 index 00000000000..b9d51ce04d6 --- /dev/null +++ b/src/com/android/settings/dashboard/suggestions/SuggestionLoader.java @@ -0,0 +1,54 @@ +/* + * 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.service.settings.suggestions.Suggestion; +import android.util.Log; + +import com.android.settings.utils.AsyncLoader; + +import java.util.List; + +public class SuggestionLoader extends AsyncLoader> { + + public static final int LOADER_ID_SUGGESTIONS = 42; + private static final String TAG = "SuggestionLoader"; + + private final SuggestionController mSuggestionController; + + public SuggestionLoader(Context context, SuggestionController controller) { + super(context); + mSuggestionController = controller; + } + + @Override + protected void onDiscardResult(List result) { + + } + + @Override + public List loadInBackground() { + final List data = mSuggestionController.getSuggestions(); + if (data == null) { + Log.d(TAG, "data is null"); + } else { + Log.d(TAG, "data size " + data.size()); + } + return data; + } +} diff --git a/tests/robotests/src/android/service/settings/suggestions/Suggestion.java b/tests/robotests/src/android/service/settings/suggestions/Suggestion.java index df7a8aed962..2bb6192c647 100644 --- a/tests/robotests/src/android/service/settings/suggestions/Suggestion.java +++ b/tests/robotests/src/android/service/settings/suggestions/Suggestion.java @@ -16,5 +16,104 @@ package android.service.settings.suggestions; +import android.app.PendingIntent; +import android.os.Parcel; +import android.text.TextUtils; + public class Suggestion { + private final String mId; + private final CharSequence mTitle; + private final CharSequence mSummary; + private final PendingIntent mPendingIntent; + + /** + * Gets the id for the suggestion object. + */ + public String getId() { + return mId; + } + + /** + * Title of the suggestion that is shown to the user. + */ + public CharSequence getTitle() { + return mTitle; + } + + /** + * Optional summary describing what this suggestion controls. + */ + public CharSequence getSummary() { + return mSummary; + } + + /** + * The Intent to launch when the suggestion is activated. + */ + public PendingIntent getPendingIntent() { + return mPendingIntent; + } + + private Suggestion(Builder builder) { + mTitle = builder.mTitle; + mSummary = builder.mSummary; + mPendingIntent = builder.mPendingIntent; + mId = builder.mId; + } + + private Suggestion(Parcel in) { + mId = in.readString(); + mTitle = in.readCharSequence(); + mSummary = in.readCharSequence(); + mPendingIntent = in.readParcelable(PendingIntent.class.getClassLoader()); + } + + /** + * Builder class for {@link Suggestion}. + */ + public static class Builder { + private final String mId; + private CharSequence mTitle; + private CharSequence mSummary; + private PendingIntent mPendingIntent; + + public Builder(String id) { + if (TextUtils.isEmpty(id)) { + throw new IllegalArgumentException("Suggestion id cannot be empty"); + } + mId = id; + } + + /** + * Sets suggestion title + */ + + public Builder setTitle(CharSequence title) { + mTitle = title; + return this; + } + + /** + * Sets suggestion summary + */ + public Builder setSummary(CharSequence summary) { + mSummary = summary; + return this; + } + + /** + * Sets suggestion intent + */ + public Builder setPendingIntent(PendingIntent pendingIntent) { + mPendingIntent = pendingIntent; + return this; + } + + /** + * Builds an immutable {@link Suggestion} object. + */ + public Suggestion build() { + return new Suggestion(this /* builder */); + } + } } diff --git a/tests/robotests/src/com/android/settings/dashboard/DashboardAdapterTest.java b/tests/robotests/src/com/android/settings/dashboard/DashboardAdapterTest.java index 4b345d072d7..b14a80ffe16 100644 --- a/tests/robotests/src/com/android/settings/dashboard/DashboardAdapterTest.java +++ b/tests/robotests/src/com/android/settings/dashboard/DashboardAdapterTest.java @@ -28,6 +28,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -35,6 +36,7 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.drawable.Icon; import android.os.Bundle; +import android.service.settings.suggestions.Suggestion; import android.support.v7.widget.RecyclerView; import android.util.DisplayMetrics; import android.util.Pair; @@ -557,6 +559,33 @@ public class DashboardAdapterTest { // should not crash } + @Test + public void testBindConditionAndSuggestion_v2_shouldSetSuggestionAdapterAndNoCrash() { + mDashboardAdapter = new DashboardAdapter(mContext, null, null, null, null); + final List suggestions = makeSuggestionsV2("pkg1"); + final DashboardCategory category = mock(DashboardCategory.class); + final List tiles = new ArrayList<>(); + tiles.add(mock(Tile.class)); + category.tiles = tiles; + + mDashboardAdapter.setSuggestionsV2(suggestions); + + final RecyclerView data = mock(RecyclerView.class); + when(data.getResources()).thenReturn(mResources); + when(data.getContext()).thenReturn(mContext); + when(mResources.getDisplayMetrics()).thenReturn(mock(DisplayMetrics.class)); + final View itemView = mock(View.class); + when(itemView.findViewById(R.id.data)).thenReturn(data); + final DashboardAdapter.SuggestionAndConditionContainerHolder holder = + new DashboardAdapter.SuggestionAndConditionContainerHolder(itemView); + + mDashboardAdapter.onBindConditionAndSuggestion( + holder, DashboardAdapter.SUGGESTION_CONDITION_HEADER_POSITION); + + verify(data).setAdapter(any(SuggestionAdapter.class)); + // should not crash + } + @Test public void testBindConditionAndSuggestion_emptySuggestion_shouldSetConditionAdpater() { final Bundle savedInstance = new Bundle(); @@ -598,6 +627,17 @@ public class DashboardAdapterTest { return suggestions; } + private List makeSuggestionsV2(String... pkgNames) { + final List suggestions = new ArrayList<>(); + for (String pkgName : pkgNames) { + final Suggestion suggestion = new Suggestion.Builder(pkgName) + .setPendingIntent(mock(PendingIntent.class)) + .build(); + suggestions.add(suggestion); + } + return suggestions; + } + private void setupSuggestions(List suggestions) { mDashboardAdapter.setCategoriesAndSuggestions(null /* category */, suggestions); final Context context = RuntimeEnvironment.application; @@ -605,4 +645,6 @@ public class DashboardAdapterTest { LayoutInflater.from(context).inflate( R.layout.suggestion_condition_header, new RelativeLayout(context), true)); } + + } diff --git a/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionAdapterTest.java b/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionAdapterTest.java index bd3650b74a7..1e4ad79afc5 100644 --- a/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionAdapterTest.java +++ b/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionAdapterTest.java @@ -15,13 +15,23 @@ */ package com.android.settings.dashboard.suggestions; +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.graphics.drawable.Icon; +import android.service.settings.suggestions.Suggestion; +import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; -import android.view.ContextThemeWrapper; import android.view.ViewGroup; import android.widget.Button; import android.widget.FrameLayout; @@ -31,15 +41,12 @@ import android.widget.TextView; import com.android.settings.R; import com.android.settings.SettingsActivity; -import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.TestConfig; import com.android.settings.dashboard.DashboardAdapter; import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settingslib.drawer.Tile; -import java.util.ArrayList; -import java.util.List; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -50,22 +57,13 @@ import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import org.robolectric.shadows.ShadowApplication; -import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; -import static org.mockito.Mockito.verify; +import java.util.ArrayList; +import java.util.List; @RunWith(SettingsRobolectricTestRunner.class) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) public class SuggestionAdapterTest { - @Mock - private Tile mSuggestion1; - @Mock - private Tile mSuggestion2; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) private SettingsActivity mActivity; @@ -74,6 +72,8 @@ public class SuggestionAdapterTest { private DashboardAdapter.DashboardItemHolder mSuggestionHolder; private List mOneSuggestion; private List mTwoSuggestions; + private List mOneSuggestionV2; + private List mTwoSuggestionsV2; @Before public void setUp() { @@ -81,39 +81,75 @@ public class SuggestionAdapterTest { mContext = RuntimeEnvironment.application; FakeFeatureFactory.setupForTest(mActivity); - mSuggestion1.title = "Test Suggestion 1"; - mSuggestion1.icon = mock(Icon.class); - mSuggestion2.title = "Test Suggestion 2"; - mSuggestion2.icon = mock(Icon.class); + final Tile suggestion1 = new Tile(); + final Tile suggestion2 = new Tile(); + final Suggestion suggestion1V2 = new Suggestion.Builder("id1") + .setTitle("Test suggestion 1") + .build(); + final Suggestion suggestion2V2 = new Suggestion.Builder("id2") + .setTitle("Test suggestion 2") + .build(); + suggestion1.title = "Test Suggestion 1"; + suggestion1.icon = mock(Icon.class); + suggestion2.title = "Test Suggestion 2"; + suggestion2.icon = mock(Icon.class); mOneSuggestion = new ArrayList<>(); - mOneSuggestion.add(mSuggestion1); + mOneSuggestion.add(suggestion1); mTwoSuggestions = new ArrayList<>(); - mTwoSuggestions.add(mSuggestion1); - mTwoSuggestions.add(mSuggestion2); + mTwoSuggestions.add(suggestion1); + mTwoSuggestions.add(suggestion2); + mOneSuggestionV2 = new ArrayList<>(); + mOneSuggestionV2.add(suggestion1V2); + mTwoSuggestionsV2 = new ArrayList<>(); + mTwoSuggestionsV2.add(suggestion1V2); + mTwoSuggestionsV2.add(suggestion2V2); } @Test public void getItemCount_shouldReturnListSize() { - mSuggestionAdapter = new SuggestionAdapter(mContext, mOneSuggestion, new ArrayList<>()); + mSuggestionAdapter = new SuggestionAdapter(mContext, mOneSuggestion, + null /* suggestionV2 */, new ArrayList<>()); assertThat(mSuggestionAdapter.getItemCount()).isEqualTo(1); - mSuggestionAdapter = new SuggestionAdapter(mContext, mTwoSuggestions, new ArrayList<>()); + mSuggestionAdapter = new SuggestionAdapter(mContext, mTwoSuggestions, + null /* suggestionV2 */, new ArrayList<>()); + assertThat(mSuggestionAdapter.getItemCount()).isEqualTo(2); + } + + @Test + public void getItemCount_v2_shouldReturnListSize() { + mSuggestionAdapter = new SuggestionAdapter(mContext, null /* suggestions */, + mOneSuggestionV2, new ArrayList<>()); + assertThat(mSuggestionAdapter.getItemCount()).isEqualTo(1); + + mSuggestionAdapter = new SuggestionAdapter(mContext, null /* suggestions */, + mTwoSuggestionsV2, new ArrayList<>()); assertThat(mSuggestionAdapter.getItemCount()).isEqualTo(2); } @Test public void getItemViewType_shouldReturnSuggestionTile() { - mSuggestionAdapter = new SuggestionAdapter(mContext, mOneSuggestion, new ArrayList<>()); + mSuggestionAdapter = new SuggestionAdapter(mContext, mOneSuggestion, + null /* suggestionV2 */, new ArrayList<>()); assertThat(mSuggestionAdapter.getItemViewType(0)) - .isEqualTo(R.layout.suggestion_tile); + .isEqualTo(R.layout.suggestion_tile); + } + + @Test + public void getItemViewType_v2_shouldReturnSuggestionTile() { + mSuggestionAdapter = new SuggestionAdapter(mContext, null /* suggestions */, + mOneSuggestionV2, new ArrayList<>()); + assertThat(mSuggestionAdapter.getItemViewType(0)) + .isEqualTo(R.layout.suggestion_tile); } @Test public void onBindViewHolder_shouldSetListener() { final View view = spy(LayoutInflater.from(mContext).inflate( - R.layout.suggestion_tile, new LinearLayout(mContext), true)); + R.layout.suggestion_tile, new LinearLayout(mContext), true)); mSuggestionHolder = new DashboardAdapter.DashboardItemHolder(view); - mSuggestionAdapter = new SuggestionAdapter(mContext, mOneSuggestion, new ArrayList<>()); + mSuggestionAdapter = new SuggestionAdapter(mContext, mOneSuggestion, + null /* suggestionV2 */, new ArrayList<>()); mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0); @@ -127,7 +163,7 @@ public class SuggestionAdapterTest { TextView textView = new TextView(RuntimeEnvironment.application); doReturn(textView).when(remoteViews).apply(any(Context.class), any(ViewGroup.class)); packages.get(0).remoteViews = remoteViews; - setupSuggestions(mActivity, packages); + setupSuggestions(mActivity, packages, null); mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0); @@ -150,7 +186,7 @@ public class SuggestionAdapterTest { layout.addView(primary); doReturn(layout).when(remoteViews).apply(any(Context.class), any(ViewGroup.class)); packages.get(0).remoteViews = remoteViews; - setupSuggestions(mActivity, packages); + setupSuggestions(mActivity, packages, null /* suggestionV2 */); mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0); mSuggestionHolder.itemView.performClick(); @@ -163,6 +199,18 @@ public class SuggestionAdapterTest { verify(mActivity).startSuggestion(any(Intent.class)); } + @Test + public void onBindViewHolder_v2_itemViewShouldHandleClick() + throws PendingIntent.CanceledException { + final List packages = makeSuggestionsV2("pkg1"); + setupSuggestions(mActivity, null /* suggestionV1 */ , packages); + + mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0); + mSuggestionHolder.itemView.performClick(); + + verify(packages.get(0).getPendingIntent()).send(); + } + @Test public void onBindViewHolder_viewsShouldClearOnRebind() { Context context = @@ -176,7 +224,7 @@ public class SuggestionAdapterTest { layout.addView(primary); doReturn(layout).when(remoteViews).apply(any(Context.class), any(ViewGroup.class)); packages.get(0).remoteViews = remoteViews; - setupSuggestions(mActivity, packages); + setupSuggestions(mActivity, packages, null /* suggestionV2 */); mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0); mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0); @@ -185,8 +233,10 @@ public class SuggestionAdapterTest { assertThat(itemView.getChildCount()).isEqualTo(1); } - private void setupSuggestions(Context context, List suggestions) { - mSuggestionAdapter = new SuggestionAdapter(context, suggestions, new ArrayList<>()); + private void setupSuggestions(Context context, List suggestions, + List suggestionsV2) { + mSuggestionAdapter = new SuggestionAdapter(context, suggestions, suggestionsV2, + new ArrayList<>()); mSuggestionHolder = mSuggestionAdapter.onCreateViewHolder( new FrameLayout(RuntimeEnvironment.application), mSuggestionAdapter.getItemViewType(0)); @@ -204,4 +254,14 @@ public class SuggestionAdapterTest { return suggestions; } + private List makeSuggestionsV2(String... pkgNames) { + final List suggestions = new ArrayList<>(); + for (String pkgName : pkgNames) { + final Suggestion suggestion = new Suggestion.Builder(pkgName) + .setPendingIntent(mock(PendingIntent.class)) + .build(); + suggestions.add(suggestion); + } + return suggestions; + } } diff --git a/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionControllerMixinTest.java b/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionControllerMixinTest.java index e1184783423..e42466cba24 100644 --- a/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionControllerMixinTest.java +++ b/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionControllerMixinTest.java @@ -17,14 +17,15 @@ package com.android.settings.dashboard.suggestions; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.LoaderManager; import android.content.Context; -import android.util.FeatureFlagUtils; import com.android.settings.TestConfig; import com.android.settings.testutils.SettingsRobolectricTestRunner; -import com.android.settings.testutils.shadow.SettingsShadowSystemProperties; import com.android.settingslib.core.lifecycle.Lifecycle; import org.junit.After; @@ -38,13 +39,14 @@ import org.robolectric.annotation.Config; @RunWith(SettingsRobolectricTestRunner.class) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION, shadows = { - SettingsShadowSystemProperties.class, ShadowSuggestionController.class }) public class SuggestionControllerMixinTest { @Mock private Context mContext; + @Mock + private SuggestionControllerMixin.SuggestionControllerHost mHost; private Lifecycle mLifecycle; private SuggestionControllerMixin mMixin; @@ -53,33 +55,16 @@ public class SuggestionControllerMixinTest { MockitoAnnotations.initMocks(this); mLifecycle = new Lifecycle(); when(mContext.getApplicationContext()).thenReturn(mContext); - SettingsShadowSystemProperties.set( - FeatureFlagUtils.FFLAG_PREFIX + SuggestionControllerMixin.FEATURE_FLAG, "true"); } @After public void tearDown() { ShadowSuggestionController.reset(); - SettingsShadowSystemProperties.clear(); } @Test - public void systemPropertySet_verifyIsEnabled() { - SettingsShadowSystemProperties.set( - FeatureFlagUtils.FFLAG_PREFIX + SuggestionControllerMixin.FEATURE_FLAG, "true"); - assertThat(SuggestionControllerMixin.isEnabled()).isTrue(); - } - - @Test - public void systemPropertyNotSet_verifyIsDisabled() { - SettingsShadowSystemProperties.set( - FeatureFlagUtils.FFLAG_PREFIX + SuggestionControllerMixin.FEATURE_FLAG, "false"); - assertThat(SuggestionControllerMixin.isEnabled()).isFalse(); - } - - @Test - public void goThroughLifecycle_onStartStop_shouldStartStopService() { - mMixin = new SuggestionControllerMixin(mContext, mLifecycle); + public void goThroughLifecycle_onStartStop_shouldStartStopController() { + mMixin = new SuggestionControllerMixin(mContext, mHost, mLifecycle); mLifecycle.onStart(); assertThat(ShadowSuggestionController.sStartCalled).isTrue(); @@ -90,10 +75,13 @@ public class SuggestionControllerMixinTest { @Test public void onServiceConnected_shouldGetSuggestion() { - mMixin = new SuggestionControllerMixin(mContext, mLifecycle); + final LoaderManager loaderManager = mock(LoaderManager.class); + when(mHost.getLoaderManager()).thenReturn(loaderManager); + + mMixin = new SuggestionControllerMixin(mContext, mHost, mLifecycle); mMixin.onServiceConnected(); - assertThat(ShadowSuggestionController.sGetSuggestionCalled).isTrue(); + verify(loaderManager).restartLoader(SuggestionLoader.LOADER_ID_SUGGESTIONS, + null /* args */, mMixin /* callback */); } - } diff --git a/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImplTest.java index c343f972bd8..f1568fe68fc 100644 --- a/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImplTest.java +++ b/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImplTest.java @@ -35,6 +35,7 @@ import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.hardware.fingerprint.FingerprintManager; import android.provider.Settings.Secure; +import android.util.FeatureFlagUtils; import android.util.Pair; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; @@ -54,10 +55,12 @@ import com.android.settings.gestures.SwipeToNotificationSettings; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.testutils.shadow.SettingsShadowResources; +import com.android.settings.testutils.shadow.SettingsShadowSystemProperties; import com.android.settings.testutils.shadow.ShadowSecureSettings; import com.android.settingslib.drawer.Tile; import com.android.settingslib.suggestions.SuggestionParser; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -76,7 +79,10 @@ import java.util.List; @Config( manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION, - shadows = {ShadowSecureSettings.class, SettingsShadowResources.class} + shadows = {ShadowSecureSettings.class, + SettingsShadowResources.class, + SettingsShadowSystemProperties.class + } ) public class SuggestionFeatureProviderImplTest { @@ -122,6 +128,11 @@ public class SuggestionFeatureProviderImplTest { mProvider = new SuggestionFeatureProviderImpl(mContext); } + @After + public void tearDown() { + SettingsShadowSystemProperties.clear(); + } + @Test public void isSuggestionCompleted_doubleTapPower_trueWhenNotAvailable() { SettingsShadowResources.overrideResource( @@ -288,6 +299,23 @@ public class SuggestionFeatureProviderImplTest { assertThat(mProvider.isSuggestionEnabled(mContext)).isTrue(); } + @Test + public void isSuggestionV2Enabled_isNotLowMemoryDevice_sysPropOn_shouldReturnTrue() { + when(mActivityManager.isLowRamDevice()).thenReturn(false); + SettingsShadowSystemProperties.set( + FeatureFlagUtils.FFLAG_PREFIX + mProvider.FEATURE_FLAG_SUGGESTIONS_V2, "true"); + assertThat(mProvider.isSuggestionV2Enabled(mContext)).isTrue(); + } + + @Test + public void isSuggestionV2Enabled_isNotLowMemoryDevice_sysPropOff_shouldReturnTrue() { + when(mActivityManager.isLowRamDevice()).thenReturn(false); + SettingsShadowSystemProperties.set( + FeatureFlagUtils.FFLAG_PREFIX + mProvider.FEATURE_FLAG_SUGGESTIONS_V2, "false"); + assertThat(mProvider.isSuggestionV2Enabled(mContext)).isFalse(); + } + + @Test public void dismissSuggestion_noParserOrSuggestion_noop() { mProvider.dismissSuggestion(mContext, null, null);