Elicit ItemsData class and add DiffUtil.Callback
In the previous version, when there is a minor change in data, we will refresh the whole screen(and data) by invoking "recountItems" and "notifyDataSetChanged", which did lots of unnecessary works. In this new cl, I elicit ItemsData class, which encapsulates the list data used in adapter. When data changed, I build another ItemsData and use the DiffUtil.Callback to calculate diffs between ItemsDatas. In this way we can only refresh the items that changed in adapter. Since I cannot find usage of see_all.xml anymore, I delete the relevant code as well as the resource files. Bug: 30319913 Test: make RunSettingsRoboTests Change-Id: I4f753a26f624affea6c6c35d49cfb9c43fb74fe6
This commit is contained in:
@@ -1,65 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- Copyright (C) 2015 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.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="?android:attr/selectableItemBackground"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:minHeight="@dimen/dashboard_tile_minimum_height"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true">
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:layout_width="@dimen/dashboard_tile_image_size"
|
|
||||||
android:layout_height="@dimen/dashboard_tile_image_size"
|
|
||||||
android:layout_marginStart="@dimen/dashboard_tile_image_margin_start"
|
|
||||||
android:layout_marginEnd="@dimen/dashboard_tile_image_margin_end"
|
|
||||||
android:visibility="invisible" />
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0dip"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:layout_weight="1">
|
|
||||||
|
|
||||||
<RelativeLayout
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<TextView android:id="@android:id/title"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:singleLine="true"
|
|
||||||
android:textAppearance="@android:style/TextAppearance.Material.Widget.Button.Inverse"
|
|
||||||
android:textColor="?android:attr/colorAccent"
|
|
||||||
android:ellipsize="marquee"
|
|
||||||
android:fadingEdge="horizontal" />
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
@@ -7281,11 +7281,6 @@
|
|||||||
<!-- Conversation message timestamp of the messaging app preview screen. [CHAR LIMIT=20] -->
|
<!-- Conversation message timestamp of the messaging app preview screen. [CHAR LIMIT=20] -->
|
||||||
<string name="screen_zoom_conversation_timestamp_4">Tue 6:03PM</string>
|
<string name="screen_zoom_conversation_timestamp_4">Tue 6:03PM</string>
|
||||||
|
|
||||||
<!-- Button to show all top-level settings items [CHAR LIMIT=20] -->
|
|
||||||
<string name="see_all">See all</string>
|
|
||||||
<!-- Button to show less top-level settings items [CHAR LIMIT=20] -->
|
|
||||||
<string name="see_less">See less</string>
|
|
||||||
|
|
||||||
<!-- Wi-Fi state - Disconnected [CHAR LIMIT=NONE] -->
|
<!-- Wi-Fi state - Disconnected [CHAR LIMIT=NONE] -->
|
||||||
<string name="disconnected">Disconnected</string>
|
<string name="disconnected">Disconnected</string>
|
||||||
|
|
||||||
|
@@ -20,6 +20,7 @@ import android.content.pm.PackageManager;
|
|||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.graphics.drawable.Icon;
|
import android.graphics.drawable.Icon;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.support.v7.util.DiffUtil;
|
||||||
import android.support.v7.widget.PopupMenu;
|
import android.support.v7.widget.PopupMenu;
|
||||||
import android.support.v7.widget.RecyclerView;
|
import android.support.v7.widget.RecyclerView;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
@@ -34,7 +35,6 @@ import android.widget.ImageView;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.android.internal.logging.MetricsProto.MetricsEvent;
|
import com.android.internal.logging.MetricsProto.MetricsEvent;
|
||||||
import com.android.internal.util.ArrayUtils;
|
|
||||||
import com.android.settings.R;
|
import com.android.settings.R;
|
||||||
import com.android.settings.SettingsActivity;
|
import com.android.settings.SettingsActivity;
|
||||||
import com.android.settings.core.instrumentation.MetricsFeatureProvider;
|
import com.android.settings.core.instrumentation.MetricsFeatureProvider;
|
||||||
@@ -48,82 +48,92 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class DashboardAdapter extends RecyclerView.Adapter<DashboardAdapter.DashboardItemHolder>
|
public class DashboardAdapter extends RecyclerView.Adapter<DashboardAdapter.DashboardItemHolder>
|
||||||
implements View.OnClickListener, SummaryLoader.SummaryConsumer {
|
implements SummaryLoader.SummaryConsumer {
|
||||||
public static final String TAG = "DashboardAdapter";
|
public static final String TAG = "DashboardAdapter";
|
||||||
private static final String STATE_SUGGESTION_LIST = "suggestion_list";
|
private static final String STATE_SUGGESTION_LIST = "suggestion_list";
|
||||||
private static final String STATE_CATEGORY_LIST = "category_list";
|
private static final String STATE_CATEGORY_LIST = "category_list";
|
||||||
private static final String STATE_IS_SHOWING_ALL = "is_showing_all";
|
|
||||||
private static final String STATE_SUGGESTION_MODE = "suggestion_mode";
|
private static final String STATE_SUGGESTION_MODE = "suggestion_mode";
|
||||||
private static final int NS_SPACER = 0;
|
|
||||||
private static final int NS_SUGGESTION = 1000;
|
|
||||||
private static final int NS_ITEMS = 2000;
|
|
||||||
private static final int NS_CONDITION = 3000;
|
|
||||||
|
|
||||||
private static int SUGGESTION_MODE_DEFAULT = 0;
|
|
||||||
private static int SUGGESTION_MODE_COLLAPSED = 1;
|
|
||||||
private static int SUGGESTION_MODE_EXPANDED = 2;
|
|
||||||
|
|
||||||
private static final int DEFAULT_SUGGESTION_COUNT = 2;
|
|
||||||
|
|
||||||
private final List<Object> mItems = new ArrayList<>();
|
|
||||||
private final List<Integer> mTypes = new ArrayList<>();
|
|
||||||
private final List<Integer> mIds = new ArrayList<>();
|
|
||||||
private final IconCache mCache;
|
private final IconCache mCache;
|
||||||
|
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
private final MetricsFeatureProvider mMetricsFeatureProvider;
|
private final MetricsFeatureProvider mMetricsFeatureProvider;
|
||||||
|
private DashboardData mDashboardData;
|
||||||
private List<DashboardCategory> mCategories;
|
|
||||||
private List<Condition> mConditions;
|
|
||||||
private List<Tile> mSuggestions;
|
|
||||||
|
|
||||||
private boolean mIsShowingAll;
|
|
||||||
// Used for counting items;
|
|
||||||
private int mId;
|
|
||||||
|
|
||||||
private int mSuggestionMode = SUGGESTION_MODE_DEFAULT;
|
|
||||||
|
|
||||||
private Condition mExpandedCondition = null;
|
|
||||||
private SuggestionParser mSuggestionParser;
|
private SuggestionParser mSuggestionParser;
|
||||||
|
|
||||||
|
private View.OnClickListener mTileClickListener = new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
//TODO: get rid of setTag/getTag
|
||||||
|
final Tile tile = (Tile) mDashboardData.getItemEntityByPosition((int) v.getTag());
|
||||||
|
((SettingsActivity) mContext).openTile(tile);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private View.OnClickListener mConditionClickListener = new View.OnClickListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
Condition expandedCondition = mDashboardData.getExpandedCondition();
|
||||||
|
|
||||||
|
//TODO: get rid of setTag/getTag
|
||||||
|
if (v.getTag() == expandedCondition) {
|
||||||
|
mMetricsFeatureProvider.action(mContext,
|
||||||
|
MetricsEvent.ACTION_SETTINGS_CONDITION_CLICK,
|
||||||
|
expandedCondition.getMetricsConstant());
|
||||||
|
expandedCondition.onPrimaryClick();
|
||||||
|
} else {
|
||||||
|
expandedCondition = (Condition) v.getTag();
|
||||||
|
mMetricsFeatureProvider.action(mContext,
|
||||||
|
MetricsEvent.ACTION_SETTINGS_CONDITION_EXPAND,
|
||||||
|
expandedCondition.getMetricsConstant());
|
||||||
|
|
||||||
|
updateExpandedCondition(expandedCondition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public DashboardAdapter(Context context, SuggestionParser parser,
|
public DashboardAdapter(Context context, SuggestionParser parser,
|
||||||
MetricsFeatureProvider metricsFeatureProvider, Bundle savedInstanceState,
|
MetricsFeatureProvider metricsFeatureProvider, Bundle savedInstanceState,
|
||||||
List<Condition> conditions) {
|
List<Condition> conditions) {
|
||||||
|
List<Tile> suggestions = null;
|
||||||
|
List<DashboardCategory> categories = null;
|
||||||
|
int suggestionMode = DashboardData.SUGGESTION_MODE_DEFAULT;
|
||||||
|
|
||||||
mContext = context;
|
mContext = context;
|
||||||
mMetricsFeatureProvider = metricsFeatureProvider;
|
mMetricsFeatureProvider = metricsFeatureProvider;
|
||||||
mCache = new IconCache(context);
|
mCache = new IconCache(context);
|
||||||
mSuggestionParser = parser;
|
mSuggestionParser = parser;
|
||||||
mConditions = conditions;
|
|
||||||
|
|
||||||
setHasStableIds(true);
|
setHasStableIds(true);
|
||||||
|
|
||||||
boolean showAll = true;
|
|
||||||
if (savedInstanceState != null) {
|
if (savedInstanceState != null) {
|
||||||
mSuggestions = savedInstanceState.getParcelableArrayList(STATE_SUGGESTION_LIST);
|
suggestions = savedInstanceState.getParcelableArrayList(STATE_SUGGESTION_LIST);
|
||||||
mCategories = savedInstanceState.getParcelableArrayList(STATE_CATEGORY_LIST);
|
categories = savedInstanceState.getParcelableArrayList(STATE_CATEGORY_LIST);
|
||||||
showAll = savedInstanceState.getBoolean(STATE_IS_SHOWING_ALL, true);
|
suggestionMode = savedInstanceState.getInt(
|
||||||
mSuggestionMode = savedInstanceState.getInt(
|
STATE_SUGGESTION_MODE, DashboardData.SUGGESTION_MODE_DEFAULT);
|
||||||
STATE_SUGGESTION_MODE, SUGGESTION_MODE_DEFAULT);
|
|
||||||
}
|
}
|
||||||
setShowingAll(showAll);
|
|
||||||
|
mDashboardData = new DashboardData.Builder()
|
||||||
|
.setConditions(conditions)
|
||||||
|
.setSuggestions(suggestions)
|
||||||
|
.setCategories(categories)
|
||||||
|
.setSuggestionMode(suggestionMode)
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Tile> getSuggestions() {
|
public List<Tile> getSuggestions() {
|
||||||
return mSuggestions;
|
return mDashboardData.getSuggestions();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCategoriesAndSuggestions(List<DashboardCategory> categories,
|
public void setCategoriesAndSuggestions(List<DashboardCategory> categories,
|
||||||
List<Tile> suggestions) {
|
List<Tile> suggestions) {
|
||||||
mSuggestions = suggestions;
|
|
||||||
mCategories = categories;
|
|
||||||
|
|
||||||
// TODO: Better place for tinting?
|
// TODO: Better place for tinting?
|
||||||
TypedValue tintColor = new TypedValue();
|
TypedValue tintColor = new TypedValue();
|
||||||
mContext.getTheme().resolveAttribute(com.android.internal.R.attr.colorAccent,
|
mContext.getTheme().resolveAttribute(com.android.internal.R.attr.colorAccent,
|
||||||
tintColor, true);
|
tintColor, true);
|
||||||
for (int i = 0; i < categories.size(); i++) {
|
for (int i = 0; i < categories.size(); i++) {
|
||||||
for (int j = 0; j < categories.get(i).tiles.size(); j++) {
|
for (int j = 0; j < categories.get(i).tiles.size(); j++) {
|
||||||
Tile tile = categories.get(i).tiles.get(j);
|
final Tile tile = categories.get(i).tiles.get(j);
|
||||||
|
|
||||||
if (!mContext.getPackageName().equals(
|
if (!mContext.getPackageName().equals(
|
||||||
tile.intent.getComponent().getPackageName())) {
|
tile.intent.getComponent().getPackageName())) {
|
||||||
@@ -133,85 +143,49 @@ public class DashboardAdapter extends RecyclerView.Adapter<DashboardAdapter.Dash
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
recountItems();
|
|
||||||
|
final DashboardData prevData = mDashboardData;
|
||||||
|
mDashboardData = new DashboardData.Builder(prevData)
|
||||||
|
.setSuggestions(suggestions)
|
||||||
|
.setCategories(categories)
|
||||||
|
.build();
|
||||||
|
notifyDashboardDataChanged(prevData);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setConditions(List<Condition> conditions) {
|
public void setConditions(List<Condition> conditions) {
|
||||||
mConditions = conditions;
|
final DashboardData prevData = mDashboardData;
|
||||||
recountItems();
|
mDashboardData = new DashboardData.Builder(prevData)
|
||||||
|
.setConditions(conditions)
|
||||||
|
.build();
|
||||||
|
notifyDashboardDataChanged(prevData);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void notifySummaryChanged(Tile tile) {
|
public void notifySummaryChanged(Tile tile) {
|
||||||
notifyDataSetChanged();
|
final int position = mDashboardData.getPositionByTile(tile);
|
||||||
|
if (position != DashboardData.POSITION_NOT_FOUND) {
|
||||||
|
final Tile targetTile = (Tile) mDashboardData.getItemEntityByPosition(position);
|
||||||
|
if (!TextUtils.equals(tile.summary, targetTile.summary)) {
|
||||||
|
|
||||||
|
// Since usually tile in parameter and tile in mCategories are same instance,
|
||||||
|
// which is hard to be detected by DiffUtil, so we notifyItemChanged directly.
|
||||||
|
notifyItemChanged(position);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setShowingAll(boolean showingAll) {
|
// TODO: move this method to SuggestionParser or some other util class
|
||||||
mIsShowingAll = showingAll;
|
public void disableSuggestion(Tile suggestion) {
|
||||||
recountItems();
|
if (mSuggestionParser == null) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
if (mSuggestionParser.dismissSuggestion(suggestion)) {
|
||||||
private void recountItems() {
|
mContext.getPackageManager().setComponentEnabledSetting(
|
||||||
reset();
|
suggestion.intent.getComponent(),
|
||||||
boolean hasConditions = false;
|
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
||||||
for (int i = 0; mConditions != null && i < mConditions.size(); i++) {
|
PackageManager.DONT_KILL_APP);
|
||||||
boolean shouldShow = mConditions.get(i).shouldShow();
|
mSuggestionParser.markCategoryDone(suggestion.category);
|
||||||
hasConditions |= shouldShow;
|
|
||||||
countItem(mConditions.get(i), R.layout.condition_card, shouldShow, NS_CONDITION);
|
|
||||||
}
|
}
|
||||||
boolean hasSuggestions = mSuggestions != null && mSuggestions.size() != 0;
|
|
||||||
countItem(null, R.layout.dashboard_spacer, hasConditions && hasSuggestions, NS_SPACER);
|
|
||||||
countItem(null, R.layout.suggestion_header, hasSuggestions, NS_SPACER);
|
|
||||||
resetCount();
|
|
||||||
if (mSuggestions != null) {
|
|
||||||
int maxSuggestions = getDisplayableSuggestionCount();
|
|
||||||
for (int i = 0; i < mSuggestions.size(); i++) {
|
|
||||||
countItem(mSuggestions.get(i), R.layout.suggestion_tile, i < maxSuggestions,
|
|
||||||
NS_SUGGESTION);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resetCount();
|
|
||||||
for (int i = 0; mCategories != null && i < mCategories.size(); i++) {
|
|
||||||
DashboardCategory category = mCategories.get(i);
|
|
||||||
countItem(category, R.layout.dashboard_category, mIsShowingAll
|
|
||||||
&& !TextUtils.isEmpty(category.title), NS_ITEMS);
|
|
||||||
for (int j = 0; j < category.tiles.size(); j++) {
|
|
||||||
Tile tile = category.tiles.get(j);
|
|
||||||
countItem(tile, R.layout.dashboard_tile, mIsShowingAll
|
|
||||||
|| ArrayUtils.contains(DashboardSummary.INITIAL_ITEMS,
|
|
||||||
tile.intent.getComponent().getClassName()), NS_ITEMS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void resetCount() {
|
|
||||||
mId = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void reset() {
|
|
||||||
mItems.clear();
|
|
||||||
mTypes.clear();
|
|
||||||
mIds.clear();
|
|
||||||
mId = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void countItem(Object object, int type, boolean add, int nameSpace) {
|
|
||||||
if (add) {
|
|
||||||
mItems.add(object);
|
|
||||||
mTypes.add(type);
|
|
||||||
// TODO: Counting namespaces for handling of suggestions/conds appearing/disappearing.
|
|
||||||
mIds.add(mId + nameSpace);
|
|
||||||
}
|
|
||||||
mId++;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getDisplayableSuggestionCount() {
|
|
||||||
final int suggestionSize = mSuggestions.size();
|
|
||||||
return mSuggestionMode == SUGGESTION_MODE_DEFAULT
|
|
||||||
? Math.min(DEFAULT_SUGGESTION_COUNT, suggestionSize)
|
|
||||||
: mSuggestionMode == SUGGESTION_MODE_EXPANDED
|
|
||||||
? suggestionSize : 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -222,21 +196,24 @@ public class DashboardAdapter extends RecyclerView.Adapter<DashboardAdapter.Dash
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(DashboardItemHolder holder, int position) {
|
public void onBindViewHolder(DashboardItemHolder holder, int position) {
|
||||||
switch (mTypes.get(position)) {
|
final int type = mDashboardData.getItemTypeByPosition(position);
|
||||||
|
switch (type) {
|
||||||
case R.layout.dashboard_category:
|
case R.layout.dashboard_category:
|
||||||
onBindCategory(holder, (DashboardCategory) mItems.get(position));
|
onBindCategory(holder,
|
||||||
|
(DashboardCategory) mDashboardData.getItemEntityByPosition(position));
|
||||||
break;
|
break;
|
||||||
case R.layout.dashboard_tile:
|
case R.layout.dashboard_tile:
|
||||||
final Tile tile = (Tile) mItems.get(position);
|
final Tile tile = (Tile) mDashboardData.getItemEntityByPosition(position);
|
||||||
onBindTile(holder, tile);
|
onBindTile(holder, tile);
|
||||||
holder.itemView.setTag(tile);
|
holder.itemView.setTag(position);
|
||||||
holder.itemView.setOnClickListener(this);
|
holder.itemView.setOnClickListener(mTileClickListener);
|
||||||
break;
|
break;
|
||||||
case R.layout.suggestion_header:
|
case R.layout.suggestion_header:
|
||||||
onBindSuggestionHeader(holder);
|
onBindSuggestionHeader(holder, (DashboardData.SuggestionHeaderData)
|
||||||
|
mDashboardData.getItemEntityByPosition(position));
|
||||||
break;
|
break;
|
||||||
case R.layout.suggestion_tile:
|
case R.layout.suggestion_tile:
|
||||||
final Tile suggestion = (Tile) mItems.get(position);
|
final Tile suggestion = (Tile) mDashboardData.getItemEntityByPosition(position);
|
||||||
onBindTile(holder, suggestion);
|
onBindTile(holder, suggestion);
|
||||||
holder.itemView.setOnClickListener(new View.OnClickListener() {
|
holder.itemView.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
@@ -255,12 +232,12 @@ public class DashboardAdapter extends RecyclerView.Adapter<DashboardAdapter.Dash
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case R.layout.see_all:
|
|
||||||
onBindSeeAll(holder);
|
|
||||||
break;
|
|
||||||
case R.layout.condition_card:
|
case R.layout.condition_card:
|
||||||
ConditionAdapterUtils.bindViews((Condition) mItems.get(position), holder,
|
final boolean isExpanded = mDashboardData.getItemEntityByPosition(position)
|
||||||
mItems.get(position) == mExpandedCondition, this,
|
== mDashboardData.getExpandedCondition();
|
||||||
|
ConditionAdapterUtils.bindViews(
|
||||||
|
(Condition) mDashboardData.getItemEntityByPosition(position),
|
||||||
|
holder, isExpanded, mConditionClickListener,
|
||||||
new View.OnClickListener() {
|
new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
@@ -271,8 +248,71 @@ public class DashboardAdapter extends RecyclerView.Adapter<DashboardAdapter.Dash
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getItemId(int position) {
|
||||||
|
return mDashboardData.getItemIdByPosition(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemViewType(int position) {
|
||||||
|
return mDashboardData.getItemTypeByPosition(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return mDashboardData.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onExpandClick(View v) {
|
||||||
|
Condition expandedCondition = mDashboardData.getExpandedCondition();
|
||||||
|
if (v.getTag() == expandedCondition) {
|
||||||
|
mMetricsFeatureProvider.action(mContext,
|
||||||
|
MetricsEvent.ACTION_SETTINGS_CONDITION_COLLAPSE,
|
||||||
|
expandedCondition.getMetricsConstant());
|
||||||
|
expandedCondition = null;
|
||||||
|
} else {
|
||||||
|
expandedCondition = (Condition) v.getTag();
|
||||||
|
mMetricsFeatureProvider.action(mContext, MetricsEvent.ACTION_SETTINGS_CONDITION_EXPAND,
|
||||||
|
expandedCondition.getMetricsConstant());
|
||||||
|
}
|
||||||
|
|
||||||
|
updateExpandedCondition(expandedCondition);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getItem(long itemId) {
|
||||||
|
return mDashboardData.getItemEntityById(itemId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getSuggestionIdentifier(Context context, Tile suggestion) {
|
||||||
|
String packageName = suggestion.intent.getComponent().getPackageName();
|
||||||
|
if (packageName.equals(context.getPackageName())) {
|
||||||
|
// Since Settings provides several suggestions, fill in the class instead of the
|
||||||
|
// package for these.
|
||||||
|
packageName = suggestion.intent.getComponent().getClassName();
|
||||||
|
}
|
||||||
|
return packageName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyDashboardDataChanged(DashboardData prevData) {
|
||||||
|
if (prevData != null) {
|
||||||
|
final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DashboardData
|
||||||
|
.ItemsDataDiffCallback(prevData.getItemList(), mDashboardData.getItemList()));
|
||||||
|
diffResult.dispatchUpdatesTo(this);
|
||||||
|
} else {
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateExpandedCondition(Condition condition) {
|
||||||
|
final DashboardData prevData = mDashboardData;
|
||||||
|
mDashboardData = new DashboardData.Builder(prevData)
|
||||||
|
.setExpandedCondition(condition)
|
||||||
|
.build();
|
||||||
|
notifyDashboardDataChanged(prevData);
|
||||||
|
}
|
||||||
|
|
||||||
private void showRemoveOption(View v, final Tile suggestion) {
|
private void showRemoveOption(View v, final Tile suggestion) {
|
||||||
PopupMenu popup = new PopupMenu(
|
final PopupMenu popup = new PopupMenu(
|
||||||
new ContextThemeWrapper(mContext, R.style.Theme_AppCompat_DayNight), v);
|
new ContextThemeWrapper(mContext, R.style.Theme_AppCompat_DayNight), v);
|
||||||
popup.getMenu().add(R.string.suggestion_remove).setOnMenuItemClickListener(
|
popup.getMenu().add(R.string.suggestion_remove).setOnMenuItemClickListener(
|
||||||
new MenuItem.OnMenuItemClickListener() {
|
new MenuItem.OnMenuItemClickListener() {
|
||||||
@@ -282,34 +322,29 @@ public class DashboardAdapter extends RecyclerView.Adapter<DashboardAdapter.Dash
|
|||||||
mContext, MetricsEvent.ACTION_SETTINGS_DISMISS_SUGGESTION,
|
mContext, MetricsEvent.ACTION_SETTINGS_DISMISS_SUGGESTION,
|
||||||
DashboardAdapter.getSuggestionIdentifier(mContext, suggestion));
|
DashboardAdapter.getSuggestionIdentifier(mContext, suggestion));
|
||||||
disableSuggestion(suggestion);
|
disableSuggestion(suggestion);
|
||||||
mSuggestions.remove(suggestion);
|
List<Tile> suggestions = mDashboardData.getSuggestions();
|
||||||
recountItems();
|
suggestions.remove(suggestion);
|
||||||
|
|
||||||
|
DashboardData prevData = mDashboardData;
|
||||||
|
mDashboardData = new DashboardData.Builder(prevData)
|
||||||
|
.setSuggestions(suggestions)
|
||||||
|
.build();
|
||||||
|
notifyDashboardDataChanged(prevData);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
popup.show();
|
popup.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void disableSuggestion(Tile suggestion) {
|
private void onBindSuggestionHeader(final DashboardItemHolder holder, DashboardData
|
||||||
if (mSuggestionParser == null) {
|
.SuggestionHeaderData data) {
|
||||||
return;
|
final boolean moreSuggestions = data.hasMoreSuggestions;
|
||||||
}
|
final int undisplayedSuggestionCount = data.undisplayedSuggestionCount;
|
||||||
if (mSuggestionParser.dismissSuggestion(suggestion)) {
|
|
||||||
mContext.getPackageManager().setComponentEnabledSetting(
|
|
||||||
suggestion.intent.getComponent(),
|
|
||||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
|
||||||
PackageManager.DONT_KILL_APP);
|
|
||||||
mSuggestionParser.markCategoryDone(suggestion.category);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onBindSuggestionHeader(final DashboardItemHolder holder) {
|
|
||||||
final boolean moreSuggestions = hasMoreSuggestions();
|
|
||||||
final int undisplayedSuggestionCount =
|
|
||||||
mSuggestions.size() - getDisplayableSuggestionCount();
|
|
||||||
holder.icon.setImageResource(moreSuggestions ? R.drawable.ic_expand_more
|
holder.icon.setImageResource(moreSuggestions ? R.drawable.ic_expand_more
|
||||||
: R.drawable.ic_expand_less);
|
: R.drawable.ic_expand_less);
|
||||||
holder.title.setText(mContext.getString(R.string.suggestions_title, mSuggestions.size()));
|
holder.title.setText(mContext.getString(R.string.suggestions_title, data.suggestionSize));
|
||||||
String summaryContentDescription;
|
String summaryContentDescription;
|
||||||
if (moreSuggestions) {
|
if (moreSuggestions) {
|
||||||
summaryContentDescription = mContext.getResources().getQuantityString(
|
summaryContentDescription = mContext.getResources().getQuantityString(
|
||||||
@@ -329,20 +364,20 @@ public class DashboardAdapter extends RecyclerView.Adapter<DashboardAdapter.Dash
|
|||||||
holder.itemView.setOnClickListener(new View.OnClickListener() {
|
holder.itemView.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
if (hasMoreSuggestions()) {
|
final int suggestionMode;
|
||||||
mSuggestionMode = SUGGESTION_MODE_EXPANDED;
|
if (moreSuggestions) {
|
||||||
|
suggestionMode = DashboardData.SUGGESTION_MODE_EXPANDED;
|
||||||
} else {
|
} else {
|
||||||
mSuggestionMode = SUGGESTION_MODE_COLLAPSED;
|
suggestionMode = DashboardData.SUGGESTION_MODE_COLLAPSED;
|
||||||
}
|
|
||||||
recountItems();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean hasMoreSuggestions() {
|
DashboardData prevData = mDashboardData;
|
||||||
return mSuggestionMode == SUGGESTION_MODE_COLLAPSED
|
mDashboardData = new DashboardData.Builder(prevData)
|
||||||
|| (mSuggestionMode == SUGGESTION_MODE_DEFAULT
|
.setSuggestionMode(suggestionMode)
|
||||||
&& mSuggestions.size() > DEFAULT_SUGGESTION_COUNT);
|
.build();
|
||||||
|
notifyDashboardDataChanged(prevData);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onBindTile(DashboardItemHolder holder, Tile tile) {
|
private void onBindTile(DashboardItemHolder holder, Tile tile) {
|
||||||
@@ -360,98 +395,21 @@ public class DashboardAdapter extends RecyclerView.Adapter<DashboardAdapter.Dash
|
|||||||
holder.title.setText(category.title);
|
holder.title.setText(category.title);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onBindSeeAll(DashboardItemHolder holder) {
|
|
||||||
holder.title.setText(mIsShowingAll ? R.string.see_less
|
|
||||||
: R.string.see_all);
|
|
||||||
holder.itemView.setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
setShowingAll(!mIsShowingAll);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getItemId(int position) {
|
|
||||||
return mIds.get(position);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemViewType(int position) {
|
|
||||||
return mTypes.get(position);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemCount() {
|
|
||||||
return mIds.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
if (v.getId() == R.id.dashboard_tile) {
|
|
||||||
((SettingsActivity) mContext).openTile((Tile) v.getTag());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (v.getTag() == mExpandedCondition) {
|
|
||||||
mMetricsFeatureProvider.action(mContext, MetricsEvent.ACTION_SETTINGS_CONDITION_CLICK,
|
|
||||||
mExpandedCondition.getMetricsConstant());
|
|
||||||
mExpandedCondition.onPrimaryClick();
|
|
||||||
} else {
|
|
||||||
mExpandedCondition = (Condition) v.getTag();
|
|
||||||
mMetricsFeatureProvider.action(mContext, MetricsEvent.ACTION_SETTINGS_CONDITION_EXPAND,
|
|
||||||
mExpandedCondition.getMetricsConstant());
|
|
||||||
notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onExpandClick(View v) {
|
|
||||||
if (v.getTag() == mExpandedCondition) {
|
|
||||||
mMetricsFeatureProvider.action(mContext,
|
|
||||||
MetricsEvent.ACTION_SETTINGS_CONDITION_COLLAPSE,
|
|
||||||
mExpandedCondition.getMetricsConstant());
|
|
||||||
mExpandedCondition = null;
|
|
||||||
} else {
|
|
||||||
mExpandedCondition = (Condition) v.getTag();
|
|
||||||
mMetricsFeatureProvider.action(mContext, MetricsEvent.ACTION_SETTINGS_CONDITION_EXPAND,
|
|
||||||
mExpandedCondition.getMetricsConstant());
|
|
||||||
}
|
|
||||||
notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object getItem(long itemId) {
|
|
||||||
for (int i = 0; i < mIds.size(); i++) {
|
|
||||||
if (mIds.get(i) == itemId) {
|
|
||||||
return mItems.get(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getSuggestionIdentifier(Context context, Tile suggestion) {
|
|
||||||
String packageName = suggestion.intent.getComponent().getPackageName();
|
|
||||||
if (packageName.equals(context.getPackageName())) {
|
|
||||||
// Since Settings provides several suggestions, fill in the class instead of the
|
|
||||||
// package for these.
|
|
||||||
packageName = suggestion.intent.getComponent().getClassName();
|
|
||||||
}
|
|
||||||
return packageName;
|
|
||||||
}
|
|
||||||
|
|
||||||
void onSaveInstanceState(Bundle outState) {
|
void onSaveInstanceState(Bundle outState) {
|
||||||
if (mSuggestions != null) {
|
final List<Tile> suggestions = mDashboardData.getSuggestions();
|
||||||
|
final List<DashboardCategory> categories = mDashboardData.getCategories();
|
||||||
|
if (suggestions != null) {
|
||||||
outState.putParcelableArrayList(STATE_SUGGESTION_LIST,
|
outState.putParcelableArrayList(STATE_SUGGESTION_LIST,
|
||||||
new ArrayList<Tile>(mSuggestions));
|
new ArrayList<Tile>(suggestions));
|
||||||
}
|
}
|
||||||
if (mCategories != null) {
|
if (categories != null) {
|
||||||
outState.putParcelableArrayList(STATE_CATEGORY_LIST,
|
outState.putParcelableArrayList(STATE_CATEGORY_LIST,
|
||||||
new ArrayList<DashboardCategory>(mCategories));
|
new ArrayList<DashboardCategory>(categories));
|
||||||
}
|
}
|
||||||
outState.putBoolean(STATE_IS_SHOWING_ALL, mIsShowingAll);
|
outState.putInt(STATE_SUGGESTION_MODE, mDashboardData.getSuggestionMode());
|
||||||
outState.putInt(STATE_SUGGESTION_MODE, mSuggestionMode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class IconCache {
|
private static class IconCache {
|
||||||
|
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
private final ArrayMap<Icon, Drawable> mMap = new ArrayMap<>();
|
private final ArrayMap<Icon, Drawable> mMap = new ArrayMap<>();
|
||||||
|
|
||||||
|
488
src/com/android/settings/dashboard/DashboardData.java
Normal file
488
src/com/android/settings/dashboard/DashboardData.java
Normal file
@@ -0,0 +1,488 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 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;
|
||||||
|
|
||||||
|
import android.annotation.IntDef;
|
||||||
|
import android.support.v7.util.DiffUtil;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import com.android.settings.dashboard.conditional.Condition;
|
||||||
|
import com.android.settingslib.drawer.DashboardCategory;
|
||||||
|
import com.android.settingslib.drawer.Tile;
|
||||||
|
import com.android.settings.R;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Description about data list used in the DashboardAdapter. In the data list each item can be
|
||||||
|
* Condition, suggestion or category tile.
|
||||||
|
* <p>
|
||||||
|
* ItemsData has inner class Item, which represents the Item in data list.
|
||||||
|
*/
|
||||||
|
public class DashboardData {
|
||||||
|
public static final int SUGGESTION_MODE_DEFAULT = 0;
|
||||||
|
public static final int SUGGESTION_MODE_COLLAPSED = 1;
|
||||||
|
public static final int SUGGESTION_MODE_EXPANDED = 2;
|
||||||
|
public static final int POSITION_NOT_FOUND = -1;
|
||||||
|
public static final int DEFAULT_SUGGESTION_COUNT = 2;
|
||||||
|
|
||||||
|
// id namespace for different type of items.
|
||||||
|
private static final int NS_SPACER = 0;
|
||||||
|
private static final int NS_SUGGESTION = 1000;
|
||||||
|
private static final int NS_ITEMS = 2000;
|
||||||
|
private static final int NS_CONDITION = 3000;
|
||||||
|
|
||||||
|
private final List<Item> mItems;
|
||||||
|
private final List<DashboardCategory> mCategories;
|
||||||
|
private final List<Condition> mConditions;
|
||||||
|
private final List<Tile> mSuggestions;
|
||||||
|
private final int mSuggestionMode;
|
||||||
|
private final Condition mExpandedCondition;
|
||||||
|
private int mId;
|
||||||
|
|
||||||
|
private DashboardData(Builder builder) {
|
||||||
|
mCategories = builder.mCategories;
|
||||||
|
mConditions = builder.mConditions;
|
||||||
|
mSuggestions = builder.mSuggestions;
|
||||||
|
mSuggestionMode = builder.mSuggestionMode;
|
||||||
|
mExpandedCondition = builder.mExpandedCondition;
|
||||||
|
|
||||||
|
mItems = new ArrayList<>();
|
||||||
|
mId = 0;
|
||||||
|
|
||||||
|
buildItemsData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getItemIdByPosition(int position) {
|
||||||
|
return mItems.get(position).id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getItemTypeByPosition(int position) {
|
||||||
|
return mItems.get(position).type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getItemEntityByPosition(int position) {
|
||||||
|
return mItems.get(position).entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Item> getItemList() {
|
||||||
|
return mItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int size() {
|
||||||
|
return mItems.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getItemEntityById(long id) {
|
||||||
|
for (final Item item : mItems) {
|
||||||
|
if (item.id == id) {
|
||||||
|
return item.entity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<DashboardCategory> getCategories() {
|
||||||
|
return mCategories;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Condition> getConditions() {
|
||||||
|
return mConditions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Tile> getSuggestions() {
|
||||||
|
return mSuggestions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSuggestionMode() {
|
||||||
|
return mSuggestionMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Condition getExpandedCondition() {
|
||||||
|
return mExpandedCondition;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the position of the object in mItems list, using the equals method to compare
|
||||||
|
*
|
||||||
|
* @param entity the object that need to be found in list
|
||||||
|
* @return position of the object, return POSITION_NOT_FOUND if object isn't in the list
|
||||||
|
*/
|
||||||
|
public int getPositionByEntity(Object entity) {
|
||||||
|
if (entity == null) return POSITION_NOT_FOUND;
|
||||||
|
|
||||||
|
final int size = mItems.size();
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
final Object item = mItems.get(i).entity;
|
||||||
|
if (entity.equals(item)) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return POSITION_NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the position of the Tile object.
|
||||||
|
* <p>
|
||||||
|
* First, try to find the exact identical instance of the tile object, if not found,
|
||||||
|
* then try to find a tile has the same title.
|
||||||
|
*
|
||||||
|
* @param tile tile that need to be found
|
||||||
|
* @return position of the object, return INDEX_NOT_FOUND if object isn't in the list
|
||||||
|
*/
|
||||||
|
public int getPositionByTile(Tile tile) {
|
||||||
|
final int size = mItems.size();
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
final Object entity = mItems.get(i).entity;
|
||||||
|
if (entity == tile) {
|
||||||
|
return i;
|
||||||
|
} else if (entity instanceof Tile && tile.title.equals(((Tile) entity).title)) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return POSITION_NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the count of suggestions to display
|
||||||
|
*
|
||||||
|
* The displayable count mainly depends on the {@link #mSuggestionMode}
|
||||||
|
* and the size of suggestions list.
|
||||||
|
*
|
||||||
|
* When in default mode, displayable count couldn't larger than
|
||||||
|
* {@link #DEFAULT_SUGGESTION_COUNT}.
|
||||||
|
*
|
||||||
|
* When in expanded mode, display all the suggestions.
|
||||||
|
* @return the count of suggestions to display
|
||||||
|
*/
|
||||||
|
public int getDisplayableSuggestionCount() {
|
||||||
|
final int suggestionSize = mSuggestions.size();
|
||||||
|
return mSuggestionMode == SUGGESTION_MODE_DEFAULT
|
||||||
|
? Math.min(DEFAULT_SUGGESTION_COUNT, suggestionSize)
|
||||||
|
: mSuggestionMode == SUGGESTION_MODE_EXPANDED
|
||||||
|
? suggestionSize : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasMoreSuggestions() {
|
||||||
|
return mSuggestionMode == SUGGESTION_MODE_COLLAPSED
|
||||||
|
|| (mSuggestionMode == SUGGESTION_MODE_DEFAULT
|
||||||
|
&& mSuggestions.size() > DEFAULT_SUGGESTION_COUNT);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetCount() {
|
||||||
|
mId = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count the item and add it into list when {@paramref add} is true.
|
||||||
|
*
|
||||||
|
* Note that {@link #mId} will increment automatically and the real
|
||||||
|
* id stored in {@link Item} is shifted by {@paramref nameSpace}. This is a
|
||||||
|
* simple way to keep the id stable.
|
||||||
|
*
|
||||||
|
* @param object maybe {@link Condition}, {@link Tile}, {@link DashboardCategory} or null
|
||||||
|
* @param type type of the item, and value is the layout id
|
||||||
|
* @param add flag about whether to add item into list
|
||||||
|
* @param nameSpace namespace based on the type
|
||||||
|
*/
|
||||||
|
private void countItem(Object object, int type, boolean add, int nameSpace) {
|
||||||
|
if (add) {
|
||||||
|
mItems.add(new Item(object, type, mId + nameSpace, object == mExpandedCondition));
|
||||||
|
}
|
||||||
|
mId++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the mItems list using mConditions, mSuggestions, mCategories data
|
||||||
|
* and mIsShowingAll, mSuggestionMode flag.
|
||||||
|
*/
|
||||||
|
private void buildItemsData() {
|
||||||
|
boolean hasConditions = false;
|
||||||
|
for (int i = 0; mConditions != null && i < mConditions.size(); i++) {
|
||||||
|
boolean shouldShow = mConditions.get(i).shouldShow();
|
||||||
|
hasConditions |= shouldShow;
|
||||||
|
countItem(mConditions.get(i), R.layout.condition_card, shouldShow, NS_CONDITION);
|
||||||
|
}
|
||||||
|
|
||||||
|
resetCount();
|
||||||
|
final boolean hasSuggestions = mSuggestions != null && mSuggestions.size() != 0;
|
||||||
|
countItem(null, R.layout.dashboard_spacer, hasConditions && hasSuggestions, NS_SPACER);
|
||||||
|
countItem(buildSuggestionHeaderData(), R.layout.suggestion_header, hasSuggestions,
|
||||||
|
NS_SPACER);
|
||||||
|
|
||||||
|
resetCount();
|
||||||
|
if (mSuggestions != null) {
|
||||||
|
int maxSuggestions = getDisplayableSuggestionCount();
|
||||||
|
for (int i = 0; i < mSuggestions.size(); i++) {
|
||||||
|
countItem(mSuggestions.get(i), R.layout.suggestion_tile, i < maxSuggestions,
|
||||||
|
NS_SUGGESTION);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resetCount();
|
||||||
|
for (int i = 0; mCategories != null && i < mCategories.size(); i++) {
|
||||||
|
DashboardCategory category = mCategories.get(i);
|
||||||
|
countItem(category, R.layout.dashboard_category,
|
||||||
|
!TextUtils.isEmpty(category.title), NS_ITEMS);
|
||||||
|
for (int j = 0; j < category.tiles.size(); j++) {
|
||||||
|
Tile tile = category.tiles.get(j);
|
||||||
|
countItem(tile, R.layout.dashboard_tile, true, NS_ITEMS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private SuggestionHeaderData buildSuggestionHeaderData() {
|
||||||
|
SuggestionHeaderData data;
|
||||||
|
if (mSuggestions == null) {
|
||||||
|
data = new SuggestionHeaderData();
|
||||||
|
} else {
|
||||||
|
final boolean hasMoreSuggestions = hasMoreSuggestions();
|
||||||
|
final int suggestionSize = mSuggestions.size();
|
||||||
|
final int undisplayedSuggestionCount = suggestionSize - getDisplayableSuggestionCount();
|
||||||
|
data = new SuggestionHeaderData(hasMoreSuggestions, suggestionSize,
|
||||||
|
undisplayedSuggestionCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder used to build the ItemsData
|
||||||
|
* <p>
|
||||||
|
* {@link #mExpandedCondition} and {@link #mSuggestionMode} have default value
|
||||||
|
* while others are not.
|
||||||
|
*/
|
||||||
|
public static class Builder {
|
||||||
|
private int mSuggestionMode = SUGGESTION_MODE_DEFAULT;
|
||||||
|
private Condition mExpandedCondition = null;
|
||||||
|
|
||||||
|
private List<DashboardCategory> mCategories;
|
||||||
|
private List<Condition> mConditions;
|
||||||
|
private List<Tile> mSuggestions;
|
||||||
|
|
||||||
|
|
||||||
|
public Builder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder(DashboardData dashboardData) {
|
||||||
|
mCategories = dashboardData.mCategories;
|
||||||
|
mConditions = dashboardData.mConditions;
|
||||||
|
mSuggestions = dashboardData.mSuggestions;
|
||||||
|
mSuggestionMode = dashboardData.mSuggestionMode;
|
||||||
|
mExpandedCondition = dashboardData.mExpandedCondition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setCategories(List<DashboardCategory> categories) {
|
||||||
|
this.mCategories = categories;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setConditions(List<Condition> conditions) {
|
||||||
|
this.mConditions = conditions;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setSuggestions(List<Tile> suggestions) {
|
||||||
|
this.mSuggestions = suggestions;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setSuggestionMode(int suggestionMode) {
|
||||||
|
this.mSuggestionMode = suggestionMode;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setExpandedCondition(Condition expandedCondition) {
|
||||||
|
this.mExpandedCondition = expandedCondition;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DashboardData build() {
|
||||||
|
return new DashboardData(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A DiffCallback to calculate the difference between old and new Item
|
||||||
|
* List in DashboardData
|
||||||
|
*/
|
||||||
|
public static class ItemsDataDiffCallback extends DiffUtil.Callback {
|
||||||
|
final private List<Item> mOldItems;
|
||||||
|
final private List<Item> mNewItems;
|
||||||
|
|
||||||
|
public ItemsDataDiffCallback(List<Item> oldItems, List<Item> newItems) {
|
||||||
|
mOldItems = oldItems;
|
||||||
|
mNewItems = newItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOldListSize() {
|
||||||
|
return mOldItems.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getNewListSize() {
|
||||||
|
return mNewItems.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
|
||||||
|
return mOldItems.get(oldItemPosition).id == mNewItems.get(newItemPosition).id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
|
||||||
|
return mOldItems.get(oldItemPosition).equals(mNewItems.get(newItemPosition));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An item contains the data needed in the DashboardData.
|
||||||
|
*/
|
||||||
|
private static class Item {
|
||||||
|
// valid types in field type
|
||||||
|
private static final int TYPE_DASHBOARD_CATEGORY = R.layout.dashboard_category;
|
||||||
|
private static final int TYPE_DASHBOARD_TILE = R.layout.dashboard_tile;
|
||||||
|
private static final int TYPE_SUGGESTION_HEADER = R.layout.suggestion_header;
|
||||||
|
private static final int TYPE_SUGGESTION_TILE = R.layout.suggestion_tile;
|
||||||
|
private static final int TYPE_CONDITION_CARD = R.layout.condition_card;
|
||||||
|
private static final int TYPE_DASHBOARD_SPACER = R.layout.dashboard_spacer;
|
||||||
|
|
||||||
|
@IntDef({TYPE_DASHBOARD_CATEGORY, TYPE_DASHBOARD_TILE, TYPE_SUGGESTION_HEADER,
|
||||||
|
TYPE_SUGGESTION_TILE, TYPE_CONDITION_CARD, TYPE_DASHBOARD_SPACER})
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
public @interface ItemTypes{}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The main data object in item, usually is a {@link Tile}, {@link Condition} or
|
||||||
|
* {@link DashboardCategory} object. This object can also be null when the
|
||||||
|
* item is an divider line. Please refer to {@link #buildItemsData()} for
|
||||||
|
* detail usage of the Item.
|
||||||
|
*/
|
||||||
|
public final Object entity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of item, value inside is the layout id(e.g. R.layout.dashboard_tile)
|
||||||
|
*/
|
||||||
|
public final @ItemTypes int type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Id of this item, used in the {@link ItemsDataDiffCallback} to identify the same item.
|
||||||
|
*/
|
||||||
|
public final int id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To store whether the condition is expanded, useless when {@link #type} is not
|
||||||
|
* {@link #TYPE_CONDITION_CARD}
|
||||||
|
*/
|
||||||
|
public final boolean conditionExpanded;
|
||||||
|
|
||||||
|
public Item(Object entity, @ItemTypes int type, int id, boolean conditionExpanded) {
|
||||||
|
this.entity = entity;
|
||||||
|
this.type = type;
|
||||||
|
this.id = id;
|
||||||
|
this.conditionExpanded = conditionExpanded;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override it to make comparision in the {@link ItemsDataDiffCallback}
|
||||||
|
* @param obj object to compared with
|
||||||
|
* @return true if the same object or has equal value.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(obj instanceof Item)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Item targetItem = (Item) obj;
|
||||||
|
if (type != targetItem.type || id != targetItem.id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case TYPE_DASHBOARD_CATEGORY:
|
||||||
|
// Only check title for dashboard category
|
||||||
|
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;
|
||||||
|
|
||||||
|
// Only check title and summary for dashboard tile
|
||||||
|
return TextUtils.equals(localTile.title, targetTile.title)
|
||||||
|
&& TextUtils.equals(localTile.summary, targetTile.summary);
|
||||||
|
case TYPE_CONDITION_CARD:
|
||||||
|
// First check conditionExpanded for quick return
|
||||||
|
if (conditionExpanded != targetItem.conditionExpanded) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// After that, go to default to do final check
|
||||||
|
default:
|
||||||
|
return entity == null ? targetItem.entity == null
|
||||||
|
: entity.equals(targetItem.entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class contains the data needed to build the header. The data can also be
|
||||||
|
* used to check the diff in DiffUtil.Callback
|
||||||
|
*/
|
||||||
|
public static class SuggestionHeaderData {
|
||||||
|
public final boolean hasMoreSuggestions;
|
||||||
|
public final int suggestionSize;
|
||||||
|
public final int undisplayedSuggestionCount;
|
||||||
|
|
||||||
|
public SuggestionHeaderData(boolean moreSuggestions, int suggestionSize, int
|
||||||
|
undisplayedSuggestionCount) {
|
||||||
|
this.hasMoreSuggestions = moreSuggestions;
|
||||||
|
this.suggestionSize = suggestionSize;
|
||||||
|
this.undisplayedSuggestionCount = undisplayedSuggestionCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SuggestionHeaderData() {
|
||||||
|
hasMoreSuggestions = false;
|
||||||
|
suggestionSize = 0;
|
||||||
|
undisplayedSuggestionCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(obj instanceof SuggestionHeaderData)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SuggestionHeaderData targetData = (SuggestionHeaderData) obj;
|
||||||
|
|
||||||
|
return hasMoreSuggestions == targetData.hasMoreSuggestions
|
||||||
|
&& suggestionSize == targetData.suggestionSize
|
||||||
|
&& undisplayedSuggestionCount == targetData.undisplayedSuggestionCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,336 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 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;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.v7.util.DiffUtil;
|
||||||
|
import android.support.v7.util.ListUpdateCallback;
|
||||||
|
import com.android.settings.TestConfig;
|
||||||
|
import com.android.settings.dashboard.conditional.AirplaneModeCondition;
|
||||||
|
import com.android.settings.dashboard.conditional.Condition;
|
||||||
|
import com.android.settingslib.drawer.DashboardCategory;
|
||||||
|
import com.android.settingslib.drawer.Tile;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.MockitoAnnotations;
|
||||||
|
import org.robolectric.RobolectricTestRunner;
|
||||||
|
import org.robolectric.annotation.Config;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@RunWith(RobolectricTestRunner.class)
|
||||||
|
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
|
||||||
|
public class DashboardDataTest {
|
||||||
|
private static final String TEST_SUGGESTION_TITLE = "Use fingerprint";
|
||||||
|
private static final String TEST_CATEGORY_TILE_TITLE = "Display";
|
||||||
|
|
||||||
|
private DashboardData mDashboardDataWithOneConditions;
|
||||||
|
private DashboardData mDashboardDataWithTwoConditions;
|
||||||
|
private DashboardData mDashboardDataWithNoItems;
|
||||||
|
@Mock
|
||||||
|
private Tile mTestCategoryTile;
|
||||||
|
@Mock
|
||||||
|
private Tile mTestSuggestion;
|
||||||
|
@Mock
|
||||||
|
private DashboardCategory mDashboardCategory;
|
||||||
|
@Mock
|
||||||
|
private Condition mTestCondition;
|
||||||
|
@Mock
|
||||||
|
private Condition mSecondCondition; // condition used to test insert in DiffUtil
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void SetUp() {
|
||||||
|
MockitoAnnotations.initMocks(this);
|
||||||
|
|
||||||
|
// Build suggestions
|
||||||
|
final List<Tile> suggestions = new ArrayList<>();
|
||||||
|
mTestSuggestion.title = TEST_SUGGESTION_TITLE;
|
||||||
|
suggestions.add(mTestSuggestion);
|
||||||
|
|
||||||
|
// Build oneItemConditions
|
||||||
|
final List<Condition> oneItemConditions = new ArrayList<>();
|
||||||
|
when(mTestCondition.shouldShow()).thenReturn(true);
|
||||||
|
oneItemConditions.add(mTestCondition);
|
||||||
|
|
||||||
|
// Build twoItemConditions
|
||||||
|
final List<Condition> twoItemsConditions = new ArrayList<>();
|
||||||
|
when(mSecondCondition.shouldShow()).thenReturn(true);
|
||||||
|
twoItemsConditions.add(mTestCondition);
|
||||||
|
twoItemsConditions.add(mSecondCondition);
|
||||||
|
|
||||||
|
// Build categories
|
||||||
|
final List<DashboardCategory> categories = new ArrayList<>();
|
||||||
|
mTestCategoryTile.title = TEST_CATEGORY_TILE_TITLE;
|
||||||
|
mDashboardCategory.title = "test";
|
||||||
|
mDashboardCategory.tiles = new ArrayList<>();
|
||||||
|
mDashboardCategory.tiles.add(mTestCategoryTile);
|
||||||
|
categories.add(mDashboardCategory);
|
||||||
|
|
||||||
|
// Build DashboardData
|
||||||
|
mDashboardDataWithOneConditions = new DashboardData.Builder()
|
||||||
|
.setConditions(oneItemConditions)
|
||||||
|
.setCategories(categories)
|
||||||
|
.setSuggestions(suggestions)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
mDashboardDataWithTwoConditions = new DashboardData.Builder()
|
||||||
|
.setConditions(twoItemsConditions)
|
||||||
|
.setCategories(categories)
|
||||||
|
.setSuggestions(suggestions)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
mDashboardDataWithNoItems = new DashboardData.Builder()
|
||||||
|
.setConditions(null)
|
||||||
|
.setCategories(null)
|
||||||
|
.setSuggestions(null)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBuildItemsData_containsAllData() {
|
||||||
|
final DashboardData.SuggestionHeaderData data =
|
||||||
|
new DashboardData.SuggestionHeaderData(false, 1, 0);
|
||||||
|
final Object[] expectedObjects = {mTestCondition, null, data, mTestSuggestion,
|
||||||
|
mDashboardCategory, mTestCategoryTile};
|
||||||
|
final int expectedSize = expectedObjects.length;
|
||||||
|
|
||||||
|
assertThat(mDashboardDataWithOneConditions.getItemList().size())
|
||||||
|
.isEqualTo(expectedSize);
|
||||||
|
for (int i = 0; i < expectedSize; i++) {
|
||||||
|
if (mDashboardDataWithOneConditions.getItemEntityByPosition(i)
|
||||||
|
instanceof DashboardData.SuggestionHeaderData) {
|
||||||
|
// SuggestionHeaderData is created inside when build, we can only use isEqualTo
|
||||||
|
assertThat(mDashboardDataWithOneConditions.getItemEntityByPosition(i))
|
||||||
|
.isEqualTo(expectedObjects[i]);
|
||||||
|
} else {
|
||||||
|
assertThat(mDashboardDataWithOneConditions.getItemEntityByPosition(i))
|
||||||
|
.isSameAs(expectedObjects[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetPositionByEntity_selfInstance_returnPositionFound() {
|
||||||
|
final int position = mDashboardDataWithOneConditions
|
||||||
|
.getPositionByEntity(mTestCondition);
|
||||||
|
assertThat(position).isNotEqualTo(DashboardData.POSITION_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetPositionByEntity_notExisted_returnNotFound() {
|
||||||
|
final Condition condition = mock(AirplaneModeCondition.class);
|
||||||
|
final int position = mDashboardDataWithOneConditions.getPositionByEntity(condition);
|
||||||
|
assertThat(position).isEqualTo(DashboardData.POSITION_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetPositionByTile_selfInstance_returnPositionFound() {
|
||||||
|
final int position = mDashboardDataWithOneConditions
|
||||||
|
.getPositionByTile(mTestCategoryTile);
|
||||||
|
assertThat(position).isNotEqualTo(DashboardData.POSITION_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetPositionByTile_equalTitle_returnPositionFound() {
|
||||||
|
final Tile tile = mock(Tile.class);
|
||||||
|
tile.title = TEST_CATEGORY_TILE_TITLE;
|
||||||
|
final int position = mDashboardDataWithOneConditions.getPositionByTile(tile);
|
||||||
|
assertThat(position).isNotEqualTo(DashboardData.POSITION_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetPositionByTile_notExisted_returnNotFound() {
|
||||||
|
final Tile tile = mock(Tile.class);
|
||||||
|
tile.title = "";
|
||||||
|
final int position = mDashboardDataWithOneConditions.getPositionByTile(tile);
|
||||||
|
assertThat(position).isEqualTo(DashboardData.POSITION_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDiffUtil_DataEqual_noResultData() {
|
||||||
|
List<ListUpdateResult.ResultData> testResultData = new ArrayList<>();
|
||||||
|
testDiffUtil(mDashboardDataWithOneConditions,
|
||||||
|
mDashboardDataWithOneConditions, testResultData);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDiffUtil_InsertOneCondition_ResultDataOneInserted() {
|
||||||
|
//Build testResultData
|
||||||
|
final List<ListUpdateResult.ResultData> testResultData = new ArrayList<>();
|
||||||
|
testResultData.add(new ListUpdateResult.ResultData(
|
||||||
|
ListUpdateResult.ResultData.TYPE_OPERATION_INSERT, 1, 1));
|
||||||
|
|
||||||
|
testDiffUtil(mDashboardDataWithOneConditions,
|
||||||
|
mDashboardDataWithTwoConditions, testResultData);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDiffUtil_DeleteAllData_ResultDataOneDeleted() {
|
||||||
|
//Build testResultData
|
||||||
|
final List<ListUpdateResult.ResultData> testResultData = new ArrayList<>();
|
||||||
|
testResultData.add(new ListUpdateResult.ResultData(
|
||||||
|
ListUpdateResult.ResultData.TYPE_OPERATION_REMOVE, 0, 6));
|
||||||
|
|
||||||
|
testDiffUtil(mDashboardDataWithOneConditions, mDashboardDataWithNoItems, testResultData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test when using the
|
||||||
|
* {@link com.android.settings.dashboard.DashboardData.ItemsDataDiffCallback}
|
||||||
|
* to transfer List from {@paramref baseDashboardData} to {@paramref diffDashboardData}, whether
|
||||||
|
* the transform data result is equals to {@paramref testResultData}
|
||||||
|
* <p>
|
||||||
|
* The steps are described below:
|
||||||
|
* 1. Calculate a {@link android.support.v7.util.DiffUtil.DiffResult} from
|
||||||
|
* {@paramref baseDashboardData} to {@paramref diffDashboardData}
|
||||||
|
* <p>
|
||||||
|
* 2. Dispatch the {@link android.support.v7.util.DiffUtil.DiffResult} calculated from step 1
|
||||||
|
* into {@link ListUpdateResult}
|
||||||
|
* <p>
|
||||||
|
* 3. Get result data(a.k.a. baseResultData) from {@link ListUpdateResult} and compare it to
|
||||||
|
* {@paramref testResultData}
|
||||||
|
* <p>
|
||||||
|
* Because baseResultData and {@paramref testResultData} don't have sequence. When do the
|
||||||
|
* comparison, we will sort them first and then compare the inside data from them one by one.
|
||||||
|
*
|
||||||
|
* @param baseDashboardData
|
||||||
|
* @param diffDashboardData
|
||||||
|
* @param testResultData
|
||||||
|
*/
|
||||||
|
private void testDiffUtil(DashboardData baseDashboardData, DashboardData diffDashboardData,
|
||||||
|
List<ListUpdateResult.ResultData> testResultData) {
|
||||||
|
final DiffUtil.DiffResult diffUtilResult = DiffUtil.calculateDiff(
|
||||||
|
new DashboardData.ItemsDataDiffCallback(
|
||||||
|
baseDashboardData.getItemList(), diffDashboardData.getItemList()));
|
||||||
|
|
||||||
|
// Dispatch to listUpdateResult, then listUpdateResult will have result data
|
||||||
|
final ListUpdateResult listUpdateResult = new ListUpdateResult();
|
||||||
|
diffUtilResult.dispatchUpdatesTo(listUpdateResult);
|
||||||
|
|
||||||
|
final List<ListUpdateResult.ResultData> baseResultData = listUpdateResult.getResultData();
|
||||||
|
assertThat(testResultData.size()).isEqualTo(baseResultData.size());
|
||||||
|
|
||||||
|
// Sort them so we can compare them one by one using a for loop
|
||||||
|
Collections.sort(baseResultData);
|
||||||
|
Collections.sort(testResultData);
|
||||||
|
final int size = baseResultData.size();
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
// Refer to equals method in ResultData
|
||||||
|
assertThat(baseResultData.get(i)).isEqualTo(testResultData.get(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class contains the result about how the changes made to convert one
|
||||||
|
* list to another list. It implements ListUpdateCallback to record the result data.
|
||||||
|
*/
|
||||||
|
private static class ListUpdateResult implements ListUpdateCallback {
|
||||||
|
final private List<ResultData> mResultData;
|
||||||
|
|
||||||
|
public ListUpdateResult() {
|
||||||
|
mResultData = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ResultData> getResultData() {
|
||||||
|
return mResultData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInserted(int position, int count) {
|
||||||
|
mResultData.add(new ResultData(ResultData.TYPE_OPERATION_INSERT, position, count));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRemoved(int position, int count) {
|
||||||
|
mResultData.add(new ResultData(ResultData.TYPE_OPERATION_REMOVE, position, count));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMoved(int fromPosition, int toPosition) {
|
||||||
|
mResultData.add(
|
||||||
|
new ResultData(ResultData.TYPE_OPERATION_MOVE, fromPosition, toPosition));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onChanged(int position, int count, Object payload) {
|
||||||
|
mResultData.add(new ResultData(ResultData.TYPE_OPERATION_CHANGE, position, count));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class contains general type and field to record the operation data generated
|
||||||
|
* in {@link ListUpdateCallback}. Please refer to {@link ListUpdateCallback} for more info.
|
||||||
|
* <p>
|
||||||
|
* The following are examples about the data stored in this class:
|
||||||
|
* <p>
|
||||||
|
* "The data starts from position(arg1) with count number(arg2) is changed(operation)"
|
||||||
|
* or "The data is moved(operation) from position1(arg1) to position2(arg2)"
|
||||||
|
*/
|
||||||
|
private static class ResultData implements Comparable<ResultData> {
|
||||||
|
public static final int TYPE_OPERATION_INSERT = 0;
|
||||||
|
public static final int TYPE_OPERATION_REMOVE = 1;
|
||||||
|
public static final int TYPE_OPERATION_MOVE = 2;
|
||||||
|
public static final int TYPE_OPERATION_CHANGE = 3;
|
||||||
|
|
||||||
|
public final int operation;
|
||||||
|
public final int arg1;
|
||||||
|
public final int arg2;
|
||||||
|
|
||||||
|
public ResultData(int operation, int arg1, int arg2) {
|
||||||
|
this.operation = operation;
|
||||||
|
this.arg1 = arg1;
|
||||||
|
this.arg2 = arg2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(obj instanceof ResultData)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultData targetData = (ResultData) obj;
|
||||||
|
|
||||||
|
return operation == targetData.operation && arg1 == targetData.arg1
|
||||||
|
&& arg2 == targetData.arg2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(@NonNull ResultData resultData) {
|
||||||
|
if (this.operation != resultData.operation) {
|
||||||
|
return operation - resultData.operation;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg1 != resultData.arg1) {
|
||||||
|
return arg1 - resultData.arg1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return arg2 - resultData.arg2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user