Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit be2c4f92 authored by Gilles Debunne's avatar Gilles Debunne
Browse files

Race condition patched in Email autocompletion.

Bug 3347962

Root cause of this problem: if the adapter's content gets updated by a backgroung
thread, the PopupDataSetObserver will call showDropDown which will popup the
list.

Added a flag to make this call show the popup iif it is already visible.
This relayout is needed to clear the mDataChanged flag set when the content was
modified and which otherwise prevents touch events on the result list.

ArrayAdapter didn't use its lock to protect access to mObject.

-------------------------------------------------

However, the study of the this race conditions revealed an other bug:

Updated adapter's content is not displayed in filtered AutoCompleteTextView
Bug 3369097

Change-Id: Icd90d452f98231866f4d8a1f6994c1492febecb9
parent 9240f16d
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -817,7 +817,7 @@ public interface WindowManager extends ViewManager {
        public static final int SOFT_INPUT_IS_FORWARD_NAVIGATION = 0x100;

        /**
         * Desired operating mode for any soft input area.  May any combination
         * Desired operating mode for any soft input area.  May be any combination
         * of:
         * 
         * <ul>
+42 −37
Original line number Diff line number Diff line
@@ -25,9 +25,9 @@ import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Comparator;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * A concrete BaseAdapter that is backed by an array of arbitrary
@@ -86,6 +86,8 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable {

    private Context mContext;

    // A copy of the original mObjects array, initialized from and then used instead as soon as
    // the mFilter ArrayFilter is used. mObjects will then only contain the filtered values.
    private ArrayList<T> mOriginalValues;
    private ArrayFilter mFilter;

@@ -170,16 +172,15 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
     * @param object The object to add at the end of the array.
     */
    public void add(T object) {
        if (mOriginalValues != null) {
        synchronized (mLock) {
            if (mOriginalValues != null) {
                mOriginalValues.add(object);
                if (mNotifyOnChange) notifyDataSetChanged();
            }
            } else {
                mObjects.add(object);
            if (mNotifyOnChange) notifyDataSetChanged();
            }
        }
        if (mNotifyOnChange) notifyDataSetChanged();
    }

    /**
     * Adds the specified Collection at the end of the array.
@@ -187,16 +188,15 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
     * @param collection The Collection to add at the end of the array.
     */
    public void addAll(Collection<? extends T> collection) {
        if (mOriginalValues != null) {
        synchronized (mLock) {
            if (mOriginalValues != null) {
                mOriginalValues.addAll(collection);
                if (mNotifyOnChange) notifyDataSetChanged();
            }
            } else {
                mObjects.addAll(collection);
            if (mNotifyOnChange) notifyDataSetChanged();
            }
        }
        if (mNotifyOnChange) notifyDataSetChanged();
    }

    /**
     * Adds the specified items at the end of the array.
@@ -204,20 +204,19 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
     * @param items The items to add at the end of the array.
     */
    public void addAll(T ... items) {
        if (mOriginalValues != null) {
        synchronized (mLock) {
            if (mOriginalValues != null) {
                for (T item : items) {
                    mOriginalValues.add(item);
                }
                if (mNotifyOnChange) notifyDataSetChanged();
            }
            } else {
                for (T item : items) {
                    mObjects.add(item);
                }
            if (mNotifyOnChange) notifyDataSetChanged();
            }
        }
        if (mNotifyOnChange) notifyDataSetChanged();
    }

    /**
     * Inserts the specified object at the specified index in the array.
@@ -226,16 +225,15 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
     * @param index The index at which the object must be inserted.
     */
    public void insert(T object, int index) {
        if (mOriginalValues != null) {
        synchronized (mLock) {
            if (mOriginalValues != null) {
                mOriginalValues.add(index, object);
                if (mNotifyOnChange) notifyDataSetChanged();
            }
            } else {
                mObjects.add(index, object);
            if (mNotifyOnChange) notifyDataSetChanged();
            }
        }
        if (mNotifyOnChange) notifyDataSetChanged();
    }

    /**
     * Removes the specified object from the array.
@@ -243,13 +241,13 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
     * @param object The object to remove.
     */
    public void remove(T object) {
        if (mOriginalValues != null) {
        synchronized (mLock) {
            if (mOriginalValues != null) {
                mOriginalValues.remove(object);
            }
            } else {
                mObjects.remove(object);
            }
        }
        if (mNotifyOnChange) notifyDataSetChanged();
    }

@@ -257,13 +255,13 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
     * Remove all elements from the list.
     */
    public void clear() {
        if (mOriginalValues != null) {
        synchronized (mLock) {
            if (mOriginalValues != null) {
                mOriginalValues.clear();
            }
            } else {
                mObjects.clear();
            }
        }
        if (mNotifyOnChange) notifyDataSetChanged();
    }

@@ -274,7 +272,13 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
     *        in this adapter.
     */
    public void sort(Comparator<? super T> comparator) {
        synchronized (mLock) {
            if (mOriginalValues != null) {
                Collections.sort(mOriginalValues, comparator);
            } else {
                Collections.sort(mObjects, comparator);
            }
        }
        if (mNotifyOnChange) notifyDataSetChanged();
    }

@@ -482,6 +486,7 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
                        final String[] words = valueText.split(" ");
                        final int wordCount = words.length;

                        // Start at index 0, in case valueText starts with space(s)
                        for (int k = 0; k < wordCount; k++) {
                            if (words[k].startsWith(prefixString)) {
                                newValues.add(value);
+15 −8
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package android.widget;

import com.android.internal.R;

import android.content.Context;
import android.content.res.TypedArray;
import android.database.DataSetObserver;
@@ -36,8 +38,6 @@ import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;

import com.android.internal.R;


/**
 * <p>An editable text view that shows completion suggestions automatically
@@ -113,6 +113,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe

    private Validator mValidator = null;

    // Set to true when text is set directly and no filtering shall be performed
    private boolean mBlockCompletion;

    private PassThroughClickListener mPassThroughClickListener;
@@ -721,6 +722,10 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
            return;
        }

        updateList();
    }

    private void updateList() {
        // the drop down is shown only when a minimum number of characters
        // was typed in the text view
        if (enoughToFilter()) {
@@ -903,10 +908,10 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe

    /** {@inheritDoc} */
    public void onFilterComplete(int count) {
        updateDropDownForFilter(count);
        updateDropDownForFilter(count, true);
    }

    private void updateDropDownForFilter(int count) {
    private void updateDropDownForFilter(int count, boolean forceShow) {
        // Not attached to window, don't update drop-down
        if (getWindowVisibility() == View.GONE) return;

@@ -919,7 +924,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe

        final boolean dropDownAlwaysVisible = mPopup.isDropDownAlwaysVisible();
        if ((count > 0 || dropDownAlwaysVisible) && enoughToFilter()) {
            if (hasFocus() && hasWindowFocus()) {
            if (hasFocus() && hasWindowFocus() && (forceShow || isPopupShowing())) {
                showDropDown();
            }
        } else if (!dropDownAlwaysVisible) {
@@ -1187,7 +1192,9 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
                    public void run() {
                        final ListAdapter adapter = mAdapter;
                        if (adapter != null) {
                            updateDropDownForFilter(adapter.getCount());
                            // This will re-layout, thus resetting mDataChanged, so that the
                            // listView click listener stays responsive
                            updateDropDownForFilter(adapter.getCount(), false);
                        }
                    }
                });