Fix crash in time zone picker due to race condition on view updates

- Can't reproduce the race condition with manual test, probably the view
  updates are fast enough that only monkey test can reproduce the issue.
- Reproduced a similar stacktrace and IndexOutOfBoundsException with
  Robolectric test by assuming that the race condition happens after
  text filtering and view updates. Try to fix the bug with this assumption
- The fix is to bind the data (data position in adapter) with ViewHolder.

Bug: 75322108
Test: m RunSettingsRoboTests ROBOTEST_FILTER=com.android.settings.datetime.timezone
Change-Id: Ie5d932bce30590b8067e042c3380911c9608872f
This commit is contained in:
Victor Chang
2018-03-24 18:07:50 +00:00
parent d7ea524e81
commit 201c629fcc
5 changed files with 138 additions and 43 deletions

View File

@@ -18,6 +18,7 @@ package com.android.settings.datetime.timezone;
import android.icu.text.BreakIterator;
import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
import android.support.annotation.WorkerThread;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
@@ -42,14 +43,14 @@ public class BaseTimeZoneAdapter<T extends BaseTimeZoneAdapter.AdapterItem>
extends RecyclerView.Adapter<BaseTimeZoneAdapter.ItemViewHolder> {
private final List<T> mOriginalItems;
private final OnListItemClickListener mOnListItemClickListener;
private final OnListItemClickListener<T> mOnListItemClickListener;
private final Locale mLocale;
private final boolean mShowItemSummary;
private List<T> mItems;
private ArrayFilter mFilter;
public BaseTimeZoneAdapter(List<T> items, OnListItemClickListener
public BaseTimeZoneAdapter(List<T> items, OnListItemClickListener<T>
onListItemClickListener, Locale locale, boolean showItemSummary) {
mOriginalItems = items;
mItems = items;
@@ -69,14 +70,8 @@ public class BaseTimeZoneAdapter<T extends BaseTimeZoneAdapter.AdapterItem>
@Override
public void onBindViewHolder(@NonNull ItemViewHolder holder, int position) {
final AdapterItem item = mItems.get(position);
holder.mSummaryFrame.setVisibility(
mShowItemSummary ? View.VISIBLE : View.GONE);
holder.mTitleView.setText(item.getTitle());
holder.mIconTextView.setText(item.getIconText());
holder.mSummaryView.setText(item.getSummary());
holder.mTimeView.setText(item.getCurrentTime());
holder.setPosition(position);
holder.setAdapterItem(mItems.get(position));
holder.mSummaryFrame.setVisibility(mShowItemSummary ? View.VISIBLE : View.GONE);
}
@Override
@@ -89,8 +84,7 @@ public class BaseTimeZoneAdapter<T extends BaseTimeZoneAdapter.AdapterItem>
return mItems.size();
}
public @NonNull
Filter getFilter() {
public @NonNull ArrayFilter getFilter() {
if (mFilter == null) {
mFilter = new ArrayFilter();
}
@@ -110,18 +104,18 @@ public class BaseTimeZoneAdapter<T extends BaseTimeZoneAdapter.AdapterItem>
String[] getSearchKeys();
}
public static class ItemViewHolder extends RecyclerView.ViewHolder
implements View.OnClickListener{
public static class ItemViewHolder<T extends BaseTimeZoneAdapter.AdapterItem>
extends RecyclerView.ViewHolder implements View.OnClickListener {
final OnListItemClickListener mOnListItemClickListener;
final OnListItemClickListener<T> mOnListItemClickListener;
final View mSummaryFrame;
final TextView mTitleView;
final TextView mIconTextView;
final TextView mSummaryView;
final TextView mTimeView;
private int mPosition;
private T mItem;
public ItemViewHolder(View itemView, OnListItemClickListener onListItemClickListener) {
public ItemViewHolder(View itemView, OnListItemClickListener<T> onListItemClickListener) {
super(itemView);
itemView.setOnClickListener(this);
mSummaryFrame = itemView.findViewById(R.id.summary_frame);
@@ -132,13 +126,17 @@ public class BaseTimeZoneAdapter<T extends BaseTimeZoneAdapter.AdapterItem>
mOnListItemClickListener = onListItemClickListener;
}
public void setPosition(int position) {
mPosition = position;
public void setAdapterItem(T item) {
mItem = item;
mTitleView.setText(item.getTitle());
mIconTextView.setText(item.getIconText());
mSummaryView.setText(item.getSummary());
mTimeView.setText(item.getCurrentTime());
}
@Override
public void onClick(View v) {
mOnListItemClickListener.onListItemClick(mPosition);
mOnListItemClickListener.onListItemClick(mItem);
}
}
@@ -151,7 +149,8 @@ public class BaseTimeZoneAdapter<T extends BaseTimeZoneAdapter.AdapterItem>
* require additional pre-processing. Potentially, a trie structure can be used to match
* prefixes of the search keys.
*/
private class ArrayFilter extends Filter {
@VisibleForTesting
public class ArrayFilter extends Filter {
private BreakIterator mBreakIterator = BreakIterator.getWordInstance(mLocale);
@@ -197,8 +196,9 @@ public class BaseTimeZoneAdapter<T extends BaseTimeZoneAdapter.AdapterItem>
return results;
}
@VisibleForTesting
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
public void publishResults(CharSequence constraint, FilterResults results) {
mItems = (List<T>) results.values;
notifyDataSetChanged();
}