com.hangulo.powercontact.ContactsListFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.hangulo.powercontact.ContactsListFragment.java

Source

/*
 * Copyright (C) 2013 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.hangulo.powercontact;

import android.app.Activity;
import android.app.SearchManager;
import android.content.Context;

import android.net.Uri;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.support.v4.app.ListFragment;

import android.support.v4.app.LoaderManager;
import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.content.Loader;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.style.TextAppearanceSpan;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;

import android.widget.Filter;
import android.widget.Filterable;
import android.widget.ImageView;
import android.widget.ListView;
import android.support.v7.widget.SearchView;
import android.widget.QuickContactBadge;
import android.widget.Spinner;
import android.widget.TextView;

import com.google.android.gms.maps.model.LatLng;
import com.hangulo.powercontact.model.PowerContactAddress;
import com.hangulo.powercontact.model.PowerContactSettings;
import com.hangulo.powercontact.util.CircleTransform;
import com.hangulo.powercontact.util.Utils;
import com.squareup.picasso.Picasso;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;

/*
*   ================================================
*        Android Devlelopment Nanodegree
*        Project 8: Capstone, Stage 2 - Build
*        PowerContact by Kwanghyun Jung (ihangulo@gmail.com)
*   ================================================
*
*   date : Apr. 4th 2016
*
*    ContactsListFragment.java
*    -------------
*      List of Contacts (listview)
*
*
*/

/*
Based on
// http://developer.android.com/training/contacts-provider/retrieve-names.html
// http://developer.android.com/intl/ko/training/contacts-provider/display-contact-badge.html
 */

public class ContactsListFragment extends ListFragment
        implements AdapterView.OnItemClickListener, LoaderManager.LoaderCallbacks<ArrayList<PowerContactAddress>> {

    final String LOG_TAG = ContactsListFragment.class.getSimpleName();

    private static final String SEARCH_KEYWORD_KEY = "SEARCH_KEYWORD";
    //private static final String DISTANCE_KEY = "DISTANCE";
    private static final String POSITION_KEY = "POSITION";
    private static final String SPINNER_KEY = "SPINNER";

    private boolean mTwoPane;

    private ContactsAdapter mAdapter; // The main query adapter
    private ArrayList<PowerContactAddress> mContactList;
    private View mRootView;
    public android.support.v7.widget.SearchView mSearchView;
    private String mSearchKeyword = null; // searchString
    //    private TextView textViewZipperIcon; //    ??
    //private Space mSpaceTopBlank; //  ?

    //private float mDistance; // ...
    private int mPosition = ListView.INVALID_POSITION;
    public Spinner mSpinnerDistance;
    ImageView mBtnHomeUp; // ?   ( )

    // callbacks --------------------------------
    ContactsListCallback mCallback; // callback

    public interface ContactsListCallback {

        void makeMapClusterItemClick(PowerContactAddress item); // mapview? ? ?  (listfragment? ).

        ArrayList<PowerContactAddress> getContactAddress();

        int getRealDistanceUnits(); // distance unit? .. ?... (1=meter 2=mile) ?.. 0? auto? ? locale?  ? .

        PowerContactSettings getPowerContactSettings(); //  ? .

        void restartMainLoader(double distance); // ? ? .

        void setDistance(double distance); // main settings? distance .

        void launchSearchFragment(boolean onoff); // search view   ?..

        boolean isExpandedListViewFragment(); //    

    }

    public ContactsListFragment() {
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mTwoPane = getResources().getBoolean(R.bool.two_pane);

        if (mContactList == null)
            mContactList = new ArrayList<>(0); // prevent null exception error

        // Create the main contacts adapter
        mAdapter = new ContactsAdapter(getActivity(), R.layout.contact_list_item, mContactList); // 
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        // Inflate the list fragment layout
        mRootView = inflater.inflate(R.layout.fragment_contact_list, container, false);
        mSearchView = (android.support.v7.widget.SearchView) mRootView.findViewById(R.id.widget_searchview);
        setupSearchView();

        // select distance spinner
        mSpinnerDistance = (Spinner) mRootView.findViewById(R.id.spinner_distance); //  ?
        mBtnHomeUp = (ImageView) mRootView.findViewById(R.id.btn_home_up); // ?   ( )
        mBtnHomeUp.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mCallback.launchSearchFragment(false); // set serchview small   .

                mSearchView.clearFocus();// clear focus of searchview ?? ?

            }
        });

        Log.v(LOG_TAG, "setDistanceSpinnerAdapter():");

        setDistanceSpinnerAdapter(); //   ? 
        Log.v(LOG_TAG, "setDistanceSpinnerAdapter() after initLoader:");
        getActivity().getSupportLoaderManager().initLoader(Constants.CONTACTLIST_LOADER, null, this).forceLoad();

        if (savedInstanceState != null) {

            Log.v(LOG_TAG, "restore position:" + savedInstanceState.getInt(SPINNER_KEY, 0));

            mSpinnerDistance.setOnItemSelectedListener(null);
            mSpinnerDistance.setSelection(savedInstanceState.getInt(SPINNER_KEY, 0)); // restore spinner

            mSearchKeyword = savedInstanceState.getString(SEARCH_KEYWORD_KEY);
            mPosition = savedInstanceState.getInt(POSITION_KEY, ListView.INVALID_POSITION);

        }
        return mRootView;
    }

    // get distance from MainActivity
    double getDistance() {
        return (mCallback.getPowerContactSettings()).getDistance();
    }

    void setupSearchView() {

        // Retrieves the system search manager service
        final SearchManager searchManager = (SearchManager) getActivity().getSystemService(Context.SEARCH_SERVICE);

        // Retrieves the SearchView from the search menu item

        if (mSearchView == null)
            return;

        // Assign searchable info to SearchView

        mSearchView.setSearchableInfo(searchManager.getSearchableInfo(getActivity().getComponentName()));

        //      mSearchView.setSubmitButtonEnabled(true); // enable subit button
        mSearchView.setIconifiedByDefault(false);
        mSearchView.setIconified(false);
        mSearchView.setFocusable(false);
        mSearchView.clearFocus();

        // text focus ...
        mSearchView.setOnQueryTextFocusChangeListener(new View.OnFocusChangeListener() {
            @Override
            public void onFocusChange(View v, boolean hasFocus) {
                if (hasFocus) {
                    if (!mTwoPane && !mCallback.isExpandedListViewFragment())
                        mCallback.launchSearchFragment(true); // ?  . ? ? .
                } else {
                    if (!mTwoPane & mCallback.isExpandedListViewFragment()) {
                        mCallback.launchSearchFragment(false); // ? ?
                    }
                }
            }
        });

        // Set listeners for SearchView
        mSearchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
            @Override
            public boolean onQueryTextSubmit(String queryText) {
                mSearchView.clearFocus();

                if (!mTwoPane & mCallback.isExpandedListViewFragment()) {
                    mCallback.launchSearchFragment(false); // ? ?
                }
                return true;
            }

            @Override
            public boolean onQueryTextChange(String newText) {
                // Called when the action bar search text has changed.  Updates
                // the search filter, and restarts the loader to do a new query
                // using the new search string.
                //    isSearchMode=true;

                // if first String starts with "@@Search:" then wait until input complete
                //  ? @@Search: ...  ? .
                // @@Search:198.20123232,-111.20232323  ? .
                //
                String newFilter = !TextUtils.isEmpty(newText) ? newText : null;

                // Don't do anything if the filter is empty
                if (mSearchKeyword == null && newFilter == null) {
                    return true;
                }
                // Don't do anything if the new filter is the same as the current filter
                if (mSearchKeyword != null && mSearchKeyword.equals(newFilter)) {
                    return true;
                }

                // Updates current filter to new filter
                //                mPreviousSearchKeyword = mSearchKeyword; // ? ...... ?  ? ??   ?  .

                if (mSearchKeyword != null && mSearchKeyword.startsWith("@@")) //  @@ , @@ ??...
                    if (!mSearchKeyword.endsWith("@@"))
                        return true;

                // ?  . @@  ?  .  ?  String search .
                mSearchKeyword = newFilter; //  ...? ?.
                getActivity().getSupportLoaderManager().restartLoader(Constants.CONTACTLIST_LOADER, null,
                        ContactsListFragment.this);

                return true;
            }
        });

    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        // Set up ListView, assign adapter and set some listeners. The adapter was previously
        // created in onCreate().
        setListAdapter(mAdapter);
        getListView().setOnItemClickListener(this);
        getListView().setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
    }

    // distance spinner
    void setDistanceSpinnerAdapter() {
        // title must be changed by locale / ?? ? ?? ? .
        final int num_array_pref_range_distance_titles;
        final int num_array_pref_range_distance_values;

        Log.v(LOG_TAG, "setDistanceSpinnerAdapter distance");

        // set mile or meter by locale ?? ? ? ? .
        if (mCallback.getPowerContactSettings().getRealDistanceUnits() == Constants.DISTANCE_UNITS_METER) {
            num_array_pref_range_distance_titles = R.array.pref_range_distance_titles_meter;
            num_array_pref_range_distance_values = R.array.pref_range_distance_values_meter;
        } else {
            num_array_pref_range_distance_titles = R.array.pref_range_distance_titles_mile;
            num_array_pref_range_distance_values = R.array.pref_range_distance_values_mile;
        }

        ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(getActivity(),
                num_array_pref_range_distance_titles, android.R.layout.simple_spinner_item); // R.layout.spinner_item);

        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);

        mSpinnerDistance.setAdapter(adapter);
        //  
        mSpinnerDistance.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {

                double distance = setSpinnerDistanceChanged(position, num_array_pref_range_distance_values); // change distance                 // ?.. ?? ..

                Log.v(LOG_TAG, "mSpinnerDistance/listener : loader restart distace:" + distance + "time"
                        + System.currentTimeMillis());

                mCallback.restartMainLoader(distance); // restart main loader  // ? .. ? ?   ?.    ? ?  ?.
            }

            @Override
            public void onNothingSelected(AdapterView<?> parent) {
            }
        });

        String[] strArray = getResources().getStringArray(num_array_pref_range_distance_values);

        Double distance = getDistance(); // ?  .

        Log.v(LOG_TAG, "setDistanceSpinnerAdapter distance setSection" + distance);
        int i;
        for (i = 0; i < strArray.length; i++)
            if (Double.parseDouble(strArray[i]) == distance) {
                mSpinnerDistance.setSelection(i); // 2016.3.31 .
                // setSpinnerDistanceChanged(i, num_array_pref_range_distance_values); // change distance
                break;
            }

        adapter.notifyDataSetChanged(); // http://stackoverflow.com/questions/9443370/how-to-update-an-spinner-dynamically-in-android-correctly

    }

    // Change spinner and return distance
    double setSpinnerDistanceChanged(int position, int num_array_pref_range_distance_values) {
        double distance;
        try {
            distance = Double
                    .parseDouble(getResources().getStringArray(num_array_pref_range_distance_values)[position]); // ?  
            Log.v(LOG_TAG, "setSpinnerDistanceChanged mCallback.setDistance(distance);" + distance);
            mCallback.setDistance(distance);

            return distance;
        } catch (NumberFormatException ex) {

            // if error, then set to default distance
            distance = (mCallback.getPowerContactSettings()
                    .getRealDistanceUnits() == Constants.DISTANCE_UNITS_METER) ? Constants.DEFAULT_DISTANCE_METER
                            : Constants.DEFAULT_DISTANCE_MILE;
            mCallback.setDistance(distance);

            return distance;

        }

    }

    void setSpinnerDistanceChanged(int position) {
        int num_array_pref_range_distance_values;
        if (mCallback.getPowerContactSettings().getRealDistanceUnits() == Constants.DISTANCE_UNITS_METER) {
            num_array_pref_range_distance_values = R.array.pref_range_distance_values_meter;
        } else { // if (mPowerContactSettings.getRealDistanceUnits() == Constants.DISTANCE_UNITS_MILE)
            num_array_pref_range_distance_values = R.array.pref_range_distance_values_mile;
        }

        setSpinnerDistanceChanged(position, num_array_pref_range_distance_values);

        //   .
        mSpinnerDistance
                .setSelection(Arrays.asList(getResources().getStringArray(num_array_pref_range_distance_values))
                        .indexOf((String.valueOf(mCallback.getPowerContactSettings().getDistance())))); // set default distance

    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        // http://stackoverflow.com/questions/32083053/android-fragment-onattach-deprecated
        if (context instanceof Activity) {
            try {
                mCallback = (ContactsListCallback) context;
            } catch (ClassCastException e) {
                throw new ClassCastException(
                        getActivity().toString() + " must implement OnHeadlineSelectedListener");
            }
        }
    }

    void restartLoader() {
        getActivity().getSupportLoaderManager().restartLoader(Constants.CONTACTLIST_LOADER, null, this);
    }

    @Override
    public void onItemClick(AdapterView<?> parent, View v, int position, long id) {

        PowerContactAddress item = mContactList.get(position); // ? ? ? 
        mPosition = position; //  ?? ?
        mCallback.makeMapClusterItemClick(item); // .
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        // save mSearchkeyword

        outState.putString(SEARCH_KEYWORD_KEY, mSearchKeyword);
        //outState.putFloat(DISTANCE_KEY, mDistance);
        outState.putInt(POSITION_KEY, mPosition); // ?..? ?  ??
        outState.putInt(SPINNER_KEY, mSpinnerDistance.getSelectedItemPosition()); // spinner save -->  ?

        //Log.v("SPINNER","SAVE POSTION:"+mSpinnerDistance.getSelectedItemPosition() );
        super.onSaveInstanceState(outState);

    }

    // ?? ?  ? ? 
    void makeSelectItem(PowerContactAddress item) {
        for (int i = 0; i < mContactList.size(); i++) {
            if (item.equals(mContactList.get(i))) { // ok found!
                //getListView().setSelection(i);
                //getListView().smoothScrollToPosition(i);
                getListView().setItemChecked(i, true);
                getListView().setSelection(i);
                mPosition = i; // set current position
                break;
            }
        }
    }

    // when touch same position from MapView --> using search mode with @@ parameter
    // ?? ?   ? ??,   .
    // 2016.2.2.1
    void searchItemByCoord(LatLng data) {
        String query = "@@" + data.latitude + "," + data.longitude + "@@";
        mCallback.launchSearchFragment(true); //  . ? ? .
        mSearchView.setQuery(query, false);
    }

    // ? ? 
    // ??  .
    public ArrayList<PowerContactAddress> getFilteredData() {
        if (mAdapter == null)
            return null;
        return mAdapter.getFilteredData();
    }

    // contact adapter -------------------------------
    private class ContactsAdapter extends ArrayAdapter<PowerContactAddress> implements Filterable {
        //            implements SectionIndexer {
        private TextAppearanceSpan highlightTextSpan; // Stores the highlight text appearance style
        private Context mContext;
        private ArrayList<PowerContactAddress> originalData = null;
        private ArrayList<PowerContactAddress> filteredData = null;
        private ItemFilter mFilter = new ItemFilter();

        public ContactsAdapter(Context context, int resource, ArrayList<PowerContactAddress> objects) {
            super(context, resource, objects);

            mContext = context;

            //   final String alphabet = context.getString(R.string.alphabet);
            //            mAlphabetIndexer = new AlphabetIndexer(null, ContactsQuery.SORT_KEY, alphabet);
            highlightTextSpan = new TextAppearanceSpan(getActivity(), R.style.searchTextHiglight);
            // search?
            this.originalData = objects;
            this.filteredData = null;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder;
            PowerContactAddress contactAddressItem = getItem(position);

            LayoutInflater mInflater = (LayoutInflater) mContext.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);

            if (convertView == null) { // make new one

                convertView = mInflater.inflate(R.layout.contact_list_item, parent, false);

                holder = new ViewHolder();

                holder.name = (TextView) convertView.findViewById(R.id.name);
                holder.address = (TextView) convertView.findViewById(R.id.address);
                holder.distance = (TextView) convertView.findViewById(R.id.distance);
                holder.photo_thumbnail = (QuickContactBadge) convertView.findViewById(R.id.photo_thumbnail);
                convertView.setTag(holder);

            } else {
                holder = (ViewHolder) convertView.getTag();
            }

            final String displayName = contactAddressItem.getName();
            final String displayAddr = contactAddressItem.getAddr();
            String photo = contactAddressItem.getPhoto();
            Uri photoUri;
            if (photo == null || photo.length() == 0) {
                photoUri = null;
            } else
                photoUri = Uri.parse(photo);

            int startIndex = indexOfSearchQuery(mSearchKeyword, displayName); // search for name

            // ? 
            if (startIndex != -1) { // if found -- hilghlight name

                // Wraps the display name in the SpannableString
                final SpannableString highlightedName = new SpannableString(displayName);
                // Sets the span to start at the starting point of the match and end at "length"
                // characters beyond the starting point
                highlightedName.setSpan(highlightTextSpan, startIndex, startIndex + mSearchKeyword.length(), 0);
                // Binds the SpannableString to the display name View object
                holder.name.setText(highlightedName);
            } else
                holder.name.setText(displayName);

            // now address
            startIndex = indexOfSearchQuery(mSearchKeyword, displayAddr); // search for name

            if (startIndex != -1) { // if found -- hilghlight name

                // Wraps the display name in the SpannableString
                final SpannableString highlightedAddr = new SpannableString(displayAddr);

                // Sets the span to start at the starting point of the match and end at "length"
                // characters beyond the starting point
                highlightedAddr.setSpan(highlightTextSpan, startIndex, startIndex + mSearchKeyword.length(), 0);

                // Binds the SpannableString to the display name View object
                holder.address.setText(highlightedAddr); // highlited text
            } else
                holder.address.setText(displayAddr); // plain text

            // show distance  .
            holder.distance.setText(Utils.getDistanceValueStringWithUnits(contactAddressItem.getDistance(),
                    mCallback.getRealDistanceUnits()));

            // photo
            final Uri contactUri = ContactsContract.Contacts.getLookupUri(contactAddressItem.getContact_id(),
                    contactAddressItem.getLookup_key());

            // Binds the contact's lookup Uri to the QuickContactBadge
            holder.photo_thumbnail.assignContactUri(contactUri);
            //holder.photo_thumbnail.setMode(ContactsContract.QuickContact.MODE_MEDIUM);

            if (photoUri != null) { // defalut

                // http://stackoverflow.com/questions/26112150/android-create-circular-image-with-picasso
                Picasso.with(getActivity()).load(photoUri).transform(new CircleTransform())
                        .into(holder.photo_thumbnail);
            } else {
                Picasso.with(getActivity()).load(R.drawable.ic_person_40dp).transform(new CircleTransform())
                        .into(holder.photo_thumbnail);
            }
            return convertView;
        }

        private class ViewHolder {
            TextView name;
            TextView address;
            TextView distance;
            QuickContactBadge photo_thumbnail;

        }

        // get from ContactList (android studio example)
        // ContactsListFragment.java
        private int indexOfSearchQuery(String searchTerm, String displayName) {
            if (!TextUtils.isEmpty(searchTerm)) {
                return displayName.toLowerCase(Locale.getDefault())
                        .indexOf(searchTerm.toLowerCase(Locale.getDefault()));
            }
            return -1;
        }

        // filtered counter

        public int getCount() {

            if (filteredData != null) // not yet filtered
                return filteredData.size();
            else
                return originalData.size();
        }

        public PowerContactAddress getItem(int position) {

            if (filteredData != null)
                return filteredData.get(position);
            else
                return originalData.get(position);
        }

        // ? ? 
        // ??  .
        public ArrayList<PowerContactAddress> getFilteredData() {
            return filteredData;
        }

        //  ??  ???
        //ttp://stackoverflow.com/questions/24769257/custom-listview-adapter-with-filter-android

        @Override
        public Filter getFilter() {
            return mFilter;
        }

        //     
        // http://stackoverflow.com/questions/2519317/how-to-write-a-custom-filter-for-listview-with-arrayadapter
        // mlock?   , ?? 
        private class ItemFilter extends Filter {
            @Override
            protected FilterResults performFiltering(CharSequence constraint) {

                boolean coordMode = false; //   ?, ?  ?.
                LatLng filterLatLng = null;
                FilterResults results = new FilterResults();

                // No prefix is sent to filter by so we're going to send back the original array

                // back  ? ?? ? ????,  ? ??
                if (constraint == null || constraint.length() == 0) {
                    results.values = originalData;
                    results.count = originalData.size();

                    return results;
                }

                String filterString = constraint.toString().toLowerCase(); // search string

                if (filterString.startsWith("@@"))
                    if (!filterString.endsWith("@@")) {
                        // ? ?  ? ? .. . ? ?.
                        results.values = new ArrayList<PowerContactAddress>();
                        results.count = 0;
                        return results;
                    } else {
                        // ? . @@ ,  @@
                        coordMode = true;

                        String temp = filterString.substring(2); // remove start @@
                        temp = temp.substring(0, temp.length() - 2); // remove end @@
                        String coord[] = temp.split(","); // comma   .
                        if (coord.length != 2) { // ?... ? .
                            results.values = originalData;
                            results.count = originalData.size();

                            return results;
                        }

                        try {
                            filterLatLng = new LatLng(Double.valueOf(coord[0]), Double.valueOf(coord[1]));

                        } catch (NumberFormatException e) {
                            Log.e(LOG_TAG, "invalid latlng search mode");
                        }
                    }

                final List<PowerContactAddress> originalList = originalData;

                int count = originalList.size();
                final List<PowerContactAddress> filteredList = new ArrayList<>();

                if (coordMode) { // coordinate mode
                    for (int i = 0; i < count; i++) {
                        LatLng filterableLatLng = originalList.get(i).getLatLng();
                        if (filterableLatLng.equals(filterLatLng)) { //   ?.
                            filteredList.add(originalList.get(i));
                        }
                    }
                } else { // keyword mode
                    String filterableStringName;
                    String filterableStringAddr;

                    for (int i = 0; i < count; i++) {
                        filterableStringName = originalList.get(i).getName(); // ? ?.
                        filterableStringAddr = originalList.get(i).getAddr(); // ? ?.

                        if (filterableStringName.toLowerCase().contains(filterString)) { // ?? 
                            filteredList.add(originalList.get(i));

                        } else if (filterableStringAddr.toLowerCase().contains(filterString)) { //  ..
                            filteredList.add(originalList.get(i));
                        }
                    }
                }

                results.values = filteredList;
                results.count = filteredList.size();

                return results;
            }

            @SuppressWarnings("unchecked")
            @Override
            protected void publishResults(CharSequence constraint, FilterResults results) {
                filteredData = (ArrayList<PowerContactAddress>) results.values;

                if (results.count > 0) {
                    notifyDataSetChanged();
                } else {
                    notifyDataSetInvalidated();
                }
            }
        }
    }

    public void setContactsList(ArrayList<PowerContactAddress> powerContactArrayList, String searchKeyword) {

        if (powerContactArrayList == null) {
            if (mAdapter != null) {
                mAdapter.clear(); //   ??
            }
            return;
        }
        Log.v(LOG_TAG, "LOADER:List/onLoadFinished/setContactlist:" + powerContactArrayList.size());

        if (mAdapter != null) {
            mAdapter.clear();
            // setListAdapter(mAdapter);

            mAdapter.addAll(powerContactArrayList);
            // ? ? ?   ???????????????????????????????????
            if (searchKeyword == null || searchKeyword.trim().length() == 0) { // null ? ?  ... // ""? ?   .
                //   ??...  ?
                mSearchKeyword = ""; // ? ? . hangulo
                mAdapter.getFilter().filter(null);
            } else {
                mSearchKeyword = searchKeyword.trim();
                mAdapter.getFilter().filter(searchKeyword);
            }

            mAdapter.notifyDataSetChanged();

            Log.v(LOG_TAG, "LOADER:List/setContactsList/mAdapter:" + mAdapter.getCount());

        }
    }

    @Override
    public Loader<ArrayList<PowerContactAddress>> onCreateLoader(int id, Bundle args) {
        // ? ?  ?  ?   ?.
        return new ContactsListLoader(getActivity(), mCallback.getContactAddress());
    }

    @Override
    public void onLoadFinished(Loader<ArrayList<PowerContactAddress>> loader, ArrayList<PowerContactAddress> data) {
        // ?? ...

        Log.v(LOG_TAG, "onLoadFinished keyword:" + mSearchKeyword);
        mContactList = data;
        setContactsList(data, mSearchKeyword);

        //  . ()

        if (mPosition != ListView.INVALID_POSITION) {
            getListView().smoothScrollToPosition(mPosition); // restore listview
            getListView().setSelection(mPosition);
            getListView().setItemChecked(mPosition, true);

            //            mPosition = position; //  ?? ?
            // ? ? ? . -->  mContactList ?  .
            if (mContactList != null && mContactList.size() > mPosition) {
                Log.v(LOG_TAG, "now select map position = " + mPosition);
                PowerContactAddress item = mContactList.get(mPosition); // ? ? ? 
                mCallback.makeMapClusterItemClick(item); // .
            }

        }

    }

    @Override
    public void onLoaderReset(Loader<ArrayList<PowerContactAddress>> loader) {
        if (mAdapter != null) {
            mAdapter.clear();
            mAdapter.notifyDataSetChanged();
        }

        Log.v(LOG_TAG, "ContactList/ LOADER:List/onLoaderReset  ");
    }

    // for mainactivity
    public void setLoaderReset() {

        if (mAdapter != null) {
            mAdapter.clear();
            mAdapter.notifyDataSetChanged();
        }

    }

    // impements AsyncTaskLoader
    // http://www.androiddesignpatterns.com/2012/08/implementing-loaders.html
    public static class ContactsListLoader extends AsyncTaskLoader<ArrayList<PowerContactAddress>> {

        private ArrayList<PowerContactAddress> mData;

        public ContactsListLoader(Context context, ArrayList<PowerContactAddress> data) {
            // Loaders may be used across multiple Activitys (assuming they aren't
            // bound to the LoaderManager), so NEVER hold a reference to the context
            // directly. Doing so will cause you to leak an entire Activity's context.
            // The superclass constructor will store a reference to the Application
            // Context instead, and can be retrieved with a call to getContext().
            super(context);

            mData = data;

        }

        @Override
        public ArrayList<PowerContactAddress> loadInBackground() {
            return mData; // --> ?  ?? ?.
        }

        // (2) Deliver the results to the registered listener **/
        @Override
        public void deliverResult(ArrayList<PowerContactAddress> data) {
            if (isReset()) {
                // The Loader has been reset; ignore the result and invalidate the data.
                releaseResources(data);
                return;
            }

            // Hold a reference to the old data so it doesn't get garbage collected.
            // We must protect it until the new data has been delivered.
            List<PowerContactAddress> oldData = mData;
            mData = data;

            if (isStarted()) {
                // If the Loader is in a started state, deliver the results to the
                // client. The superclass method does this for us.
                super.deliverResult(data);
            }

            // Invalidate the old data as we don't need it any more.
            if (oldData != null && oldData != data) {
                releaseResources(oldData);
            }
        }

        //(3) Implement the Loaders state-dependent behavior

        @Override
        protected void onStartLoading() {
            if (mData != null) {
                // Deliver any previously loaded data immediately.
                deliverResult(mData);
            }

            //            // Begin monitoring the underlying data source.
            //            if (mObserver == null) {
            //                mObserver = new SampleObserver();
            //                // TODO: register the observer
            //            }

            if (takeContentChanged() || mData == null) {
                // When the observer detects a change, it should call onContentChanged()
                // on the Loader, which will cause the next call to takeContentChanged()
                // to return true. If this is ever the case (or if the current data is
                // null), we force a new load.
                forceLoad();
            }
        }

        @Override
        protected void onStopLoading() {
            // The Loader is in a stopped state, so we should attempt to cancel the
            // current load (if there is one).
            cancelLoad();

            // Note that we leave the observer as is. Loaders in a stopped state
            // should still monitor the data source for changes so that the Loader
            // will know to force a new load if it is ever started again.
        }

        @Override
        protected void onReset() {
            // Ensure the loader has been stopped.
            onStopLoading();

            // At this point we can release the resources associated with 'mData'.
            if (mData != null) {
                releaseResources(mData);
                mData = null;
            }

            //            // The Loader is being reset, so we should stop monitoring for changes.
            //            if (mObserver != null) {
            //                // TODO: unregister the observer
            //                mObserver = null;
            //            }
        }

        @Override
        public void onCanceled(ArrayList<PowerContactAddress> data) {
            // Attempt to cancel the current asynchronous load.
            super.onCanceled(data);

            // The load has been canceled, so we should release the resources
            // associated with 'data'.
            releaseResources(data);
        }

        private void releaseResources(List<PowerContactAddress> data) {
            // For a simple List, there is nothing to do. For something like a Cursor, we
            // would close it in this method. All resources associated with the Loader
            // should be released here.
        }

    }
}
/*
http://kindlybugs.com/281
// ?? ,,,...?.. ?  ?
function checkLastWord(text) {
    var last = text.substring(text.length - 1);
    var check = ["", "", "", "", "?", "", "", "", "", "", "", "", "?", "",
                    "", "", "", "", "", "?", "", "", "", "", "", "", "", "", "", "?", "", "", ""];
    for (var i = check.length - 1; i >= 0; i--) {
        if (last == check[i])
            return false;
    }
    return true;
}
    
    
//            // ? ...
//            if(photoUri != null) {
//                // make rounded image
//
//                try {
//                    // ?  ? ?  . ? ??
//                    Bitmap bm = MediaStore.Images.Media.getBitmap(getContext().getContentResolver(), photoUri);
//                    RoundedBitmapDrawable circularBitmapDrawable =
//                            RoundedBitmapDrawableFactory.create(getContext().getResources(), bm);
//                    circularBitmapDrawable.setCircular(true);
//                    holder.photo_thumbnail.setImageDrawable(circularBitmapDrawable); // ?  ?  ???...
//
//
//                } catch (IOException e) {
//
//                }
//            }
//            else
//                holder.photo_thumbnail.setImageBitmap(null);
    
 */