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:
jackqdyulei
2016-10-05 18:05:52 -07:00
parent c33b78df9c
commit 0b818b79db
5 changed files with 1026 additions and 314 deletions

View File

@@ -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>

View File

@@ -7281,11 +7281,6 @@
<!-- Conversation message timestamp of the messaging app preview screen. [CHAR LIMIT=20] -->
<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] -->
<string name="disconnected">Disconnected</string>

View File

@@ -20,6 +20,7 @@ import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.support.v7.util.DiffUtil;
import android.support.v7.widget.PopupMenu;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
@@ -34,7 +35,6 @@ import android.widget.ImageView;
import android.widget.TextView;
import com.android.internal.logging.MetricsProto.MetricsEvent;
import com.android.internal.util.ArrayUtils;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.core.instrumentation.MetricsFeatureProvider;
@@ -48,82 +48,92 @@ import java.util.ArrayList;
import java.util.List;
public class DashboardAdapter extends RecyclerView.Adapter<DashboardAdapter.DashboardItemHolder>
implements View.OnClickListener, SummaryLoader.SummaryConsumer {
implements SummaryLoader.SummaryConsumer {
public static final String TAG = "DashboardAdapter";
private static final String STATE_SUGGESTION_LIST = "suggestion_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 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 Context mContext;
private final MetricsFeatureProvider mMetricsFeatureProvider;
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 DashboardData mDashboardData;
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,
MetricsFeatureProvider metricsFeatureProvider, Bundle savedInstanceState,
List<Condition> conditions) {
List<Tile> suggestions = null;
List<DashboardCategory> categories = null;
int suggestionMode = DashboardData.SUGGESTION_MODE_DEFAULT;
mContext = context;
mMetricsFeatureProvider = metricsFeatureProvider;
mCache = new IconCache(context);
mSuggestionParser = parser;
mConditions = conditions;
setHasStableIds(true);
boolean showAll = true;
if (savedInstanceState != null) {
mSuggestions = savedInstanceState.getParcelableArrayList(STATE_SUGGESTION_LIST);
mCategories = savedInstanceState.getParcelableArrayList(STATE_CATEGORY_LIST);
showAll = savedInstanceState.getBoolean(STATE_IS_SHOWING_ALL, true);
mSuggestionMode = savedInstanceState.getInt(
STATE_SUGGESTION_MODE, SUGGESTION_MODE_DEFAULT);
suggestions = savedInstanceState.getParcelableArrayList(STATE_SUGGESTION_LIST);
categories = savedInstanceState.getParcelableArrayList(STATE_CATEGORY_LIST);
suggestionMode = savedInstanceState.getInt(
STATE_SUGGESTION_MODE, DashboardData.SUGGESTION_MODE_DEFAULT);
}
setShowingAll(showAll);
mDashboardData = new DashboardData.Builder()
.setConditions(conditions)
.setSuggestions(suggestions)
.setCategories(categories)
.setSuggestionMode(suggestionMode)
.build();
}
public List<Tile> getSuggestions() {
return mSuggestions;
return mDashboardData.getSuggestions();
}
public void setCategoriesAndSuggestions(List<DashboardCategory> categories,
List<Tile> suggestions) {
mSuggestions = suggestions;
mCategories = categories;
// TODO: Better place for tinting?
TypedValue tintColor = new TypedValue();
mContext.getTheme().resolveAttribute(com.android.internal.R.attr.colorAccent,
tintColor, true);
for (int i = 0; i < categories.size(); i++) {
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(
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) {
mConditions = conditions;
recountItems();
final DashboardData prevData = mDashboardData;
mDashboardData = new DashboardData.Builder(prevData)
.setConditions(conditions)
.build();
notifyDashboardDataChanged(prevData);
}
@Override
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)) {
public void setShowingAll(boolean showingAll) {
mIsShowingAll = showingAll;
recountItems();
}
private void recountItems() {
reset();
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);
}
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);
// 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);
}
}
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);
}
}
// TODO: move this method to SuggestionParser or some other util class
public void disableSuggestion(Tile suggestion) {
if (mSuggestionParser == null) {
return;
}
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);
if (mSuggestionParser.dismissSuggestion(suggestion)) {
mContext.getPackageManager().setComponentEnabledSetting(
suggestion.intent.getComponent(),
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
mSuggestionParser.markCategoryDone(suggestion.category);
}
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
@@ -222,21 +196,24 @@ public class DashboardAdapter extends RecyclerView.Adapter<DashboardAdapter.Dash
@Override
public void onBindViewHolder(DashboardItemHolder holder, int position) {
switch (mTypes.get(position)) {
final int type = mDashboardData.getItemTypeByPosition(position);
switch (type) {
case R.layout.dashboard_category:
onBindCategory(holder, (DashboardCategory) mItems.get(position));
onBindCategory(holder,
(DashboardCategory) mDashboardData.getItemEntityByPosition(position));
break;
case R.layout.dashboard_tile:
final Tile tile = (Tile) mItems.get(position);
final Tile tile = (Tile) mDashboardData.getItemEntityByPosition(position);
onBindTile(holder, tile);
holder.itemView.setTag(tile);
holder.itemView.setOnClickListener(this);
holder.itemView.setTag(position);
holder.itemView.setOnClickListener(mTileClickListener);
break;
case R.layout.suggestion_header:
onBindSuggestionHeader(holder);
onBindSuggestionHeader(holder, (DashboardData.SuggestionHeaderData)
mDashboardData.getItemEntityByPosition(position));
break;
case R.layout.suggestion_tile:
final Tile suggestion = (Tile) mItems.get(position);
final Tile suggestion = (Tile) mDashboardData.getItemEntityByPosition(position);
onBindTile(holder, suggestion);
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
@@ -255,12 +232,12 @@ public class DashboardAdapter extends RecyclerView.Adapter<DashboardAdapter.Dash
}
});
break;
case R.layout.see_all:
onBindSeeAll(holder);
break;
case R.layout.condition_card:
ConditionAdapterUtils.bindViews((Condition) mItems.get(position), holder,
mItems.get(position) == mExpandedCondition, this,
final boolean isExpanded = mDashboardData.getItemEntityByPosition(position)
== mDashboardData.getExpandedCondition();
ConditionAdapterUtils.bindViews(
(Condition) mDashboardData.getItemEntityByPosition(position),
holder, isExpanded, mConditionClickListener,
new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -271,45 +248,103 @@ 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) {
PopupMenu popup = new PopupMenu(
final PopupMenu popup = new PopupMenu(
new ContextThemeWrapper(mContext, R.style.Theme_AppCompat_DayNight), v);
popup.getMenu().add(R.string.suggestion_remove).setOnMenuItemClickListener(
new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
mMetricsFeatureProvider.action(
mContext, MetricsEvent.ACTION_SETTINGS_DISMISS_SUGGESTION,
DashboardAdapter.getSuggestionIdentifier(mContext, suggestion));
disableSuggestion(suggestion);
mSuggestions.remove(suggestion);
recountItems();
return true;
}
});
@Override
public boolean onMenuItemClick(MenuItem item) {
mMetricsFeatureProvider.action(
mContext, MetricsEvent.ACTION_SETTINGS_DISMISS_SUGGESTION,
DashboardAdapter.getSuggestionIdentifier(mContext, suggestion));
disableSuggestion(suggestion);
List<Tile> suggestions = mDashboardData.getSuggestions();
suggestions.remove(suggestion);
DashboardData prevData = mDashboardData;
mDashboardData = new DashboardData.Builder(prevData)
.setSuggestions(suggestions)
.build();
notifyDashboardDataChanged(prevData);
return true;
}
});
popup.show();
}
public void disableSuggestion(Tile suggestion) {
if (mSuggestionParser == null) {
return;
}
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, DashboardData
.SuggestionHeaderData data) {
final boolean moreSuggestions = data.hasMoreSuggestions;
final int undisplayedSuggestionCount = data.undisplayedSuggestionCount;
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
: 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;
if (moreSuggestions) {
summaryContentDescription = mContext.getResources().getQuantityString(
@@ -329,22 +364,22 @@ public class DashboardAdapter extends RecyclerView.Adapter<DashboardAdapter.Dash
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (hasMoreSuggestions()) {
mSuggestionMode = SUGGESTION_MODE_EXPANDED;
final int suggestionMode;
if (moreSuggestions) {
suggestionMode = DashboardData.SUGGESTION_MODE_EXPANDED;
} else {
mSuggestionMode = SUGGESTION_MODE_COLLAPSED;
suggestionMode = DashboardData.SUGGESTION_MODE_COLLAPSED;
}
recountItems();
DashboardData prevData = mDashboardData;
mDashboardData = new DashboardData.Builder(prevData)
.setSuggestionMode(suggestionMode)
.build();
notifyDashboardDataChanged(prevData);
}
});
}
private boolean hasMoreSuggestions() {
return mSuggestionMode == SUGGESTION_MODE_COLLAPSED
|| (mSuggestionMode == SUGGESTION_MODE_DEFAULT
&& mSuggestions.size() > DEFAULT_SUGGESTION_COUNT);
}
private void onBindTile(DashboardItemHolder holder, Tile tile) {
holder.icon.setImageDrawable(mCache.getIcon(tile.icon));
holder.title.setText(tile.title);
@@ -360,98 +395,21 @@ public class DashboardAdapter extends RecyclerView.Adapter<DashboardAdapter.Dash
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) {
if (mSuggestions != null) {
final List<Tile> suggestions = mDashboardData.getSuggestions();
final List<DashboardCategory> categories = mDashboardData.getCategories();
if (suggestions != null) {
outState.putParcelableArrayList(STATE_SUGGESTION_LIST,
new ArrayList<Tile>(mSuggestions));
new ArrayList<Tile>(suggestions));
}
if (mCategories != null) {
if (categories != null) {
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, mSuggestionMode);
outState.putInt(STATE_SUGGESTION_MODE, mDashboardData.getSuggestionMode());
}
private static class IconCache {
private final Context mContext;
private final ArrayMap<Icon, Drawable> mMap = new ArrayMap<>();

View 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;
}
}
}

View File

@@ -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;
}
}
}
}