com.github.yuukis.businessmap.app.ContactsTaskFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.github.yuukis.businessmap.app.ContactsTaskFragment.java

Source

/*
 * ContactsTaskFragment.java
 *
 * Copyright 2014 Yuuki Shimizu.
 *
 * 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.github.yuukis.businessmap.app;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.json.JSONException;

import com.github.yuukis.businessmap.R;
import com.github.yuukis.businessmap.app.ProgressDialogFragment;
import com.github.yuukis.businessmap.data.GeocodingCacheDatabase;
import com.github.yuukis.businessmap.model.ContactsGroup;
import com.github.yuukis.businessmap.model.ContactsItem;
import com.github.yuukis.businessmap.util.CacheUtils;
import com.github.yuukis.businessmap.util.ContactsItemComparator;
import com.github.yuukis.businessmap.util.CursorJoinerWithIntKey;
import com.github.yuukis.businessmap.util.GeocoderUtils;
import com.github.yuukis.businessmap.util.SerializationException;
import com.github.yuukis.businessmap.util.SerializationUtils;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.database.Cursor;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.provider.ContactsContract.CommonDataKinds.Organization;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
import android.provider.ContactsContract.CommonDataKinds.Note;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class ContactsTaskFragment extends Fragment {

    public interface TaskCallback {
        void onContactsLoaded(List<ContactsItem> contactsList);
    }

    private static final String FILENAME_CACHE_CONTACTS_LIST = "contacts_list.cache";

    private TaskCallback mCallback;
    private ContactsAsyncTask mContactsTask;
    private boolean mRunning = false;
    private List<ContactsItem> mContactsList;
    private Map<String, Double[]> mGeocodingResultCache;

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        if (!(activity instanceof TaskCallback)) {
            throw new IllegalStateException("Activity must implement the TaskCallback interface.");
        }
        mCallback = (TaskCallback) activity;
    }

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

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_contacts_task, null);
    }

    @Override
    public void onStart() {
        super.onStart();
        setProgressBarVisibie(mRunning);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        cancel();
    }

    public void start() {
        if (!mRunning) {
            mContactsTask = new ContactsAsyncTask();
            mContactsTask.execute();
            setRunning(true);
        }
    }

    public void cancel() {
        if (mRunning) {
            mContactsTask.cancel(false);
            mContactsTask = null;
            setRunning(false);
        }
    }

    public boolean isRunning() {
        return mRunning;
    }

    private void setRunning(boolean running) {
        mRunning = running;
        setProgressBarVisibie(mRunning);
    }

    private void setProgressBarVisibie(boolean visible) {
        View view = getView();
        if (view != null) {
            View progressBar = view.findViewById(R.id.contacts_progressbar);
            if (visible) {
                progressBar.setVisibility(View.VISIBLE);
            } else {
                progressBar.setVisibility(View.GONE);
            }
        }
    }

    private void showProgress() {
        String title = getString(R.string.title_geocoding);
        String message = getString(R.string.message_geocoding);
        int max = mGeocodingResultCache.size();

        Bundle args = new Bundle();
        args.putString(ProgressDialogFragment.TITLE, title);
        args.putString(ProgressDialogFragment.MESSAGE, message);
        args.putBoolean(ProgressDialogFragment.CANCELABLE, true);
        args.putInt(ProgressDialogFragment.MAX, max);
        final DialogFragment dialog = ProgressDialogFragment.newInstance();
        dialog.setArguments(args);
        if (getActivity() != null) {
            new Handler(Looper.getMainLooper()).post(new Runnable() {
                @Override
                public void run() {
                    dialog.show(getActivity().getSupportFragmentManager(), ProgressDialogFragment.TAG);
                }
            });
        }
    }

    private void updateProgress(Integer... values) {
        ProgressDialogFragment progress = getProgressDialogFragment();
        if (progress == null) {
            return;
        }
        progress.updateProgress(values[0]);
    }

    private void hideProgress() {
        ProgressDialogFragment progress = getProgressDialogFragment();
        if (progress == null) {
            return;
        }
        progress.dismissAllowingStateLoss();
    }

    private ProgressDialogFragment getProgressDialogFragment() {
        Fragment fragment = getActivity().getSupportFragmentManager().findFragmentByTag(ProgressDialogFragment.TAG);
        return (ProgressDialogFragment) fragment;
    }

    private void showError(final String title, final String message) {
        if (getActivity() != null) {
            new Handler(Looper.getMainLooper()).post(new Runnable() {
                @Override
                public void run() {
                    new AlertDialog.Builder(getActivity()).setTitle(title).setMessage(message)
                            .setPositiveButton(android.R.string.ok, null).show();
                }
            });
        }
    }

    private class ContactsAsyncTask extends AsyncTask<Void, Integer, Void>
            implements DialogInterface.OnCancelListener {

        @Override
        public void onCancel(DialogInterface dialog) {
            cancel(true);
        }

        @Override
        protected void onPreExecute() {
            super.onPreExecute();

            mContactsList = readContactsListFromCache();
            if (mContactsList == null) {
                mContactsList = new ArrayList<ContactsItem>();
            }
            mGeocodingResultCache = new HashMap<String, Double[]>();
            if (mCallback != null) {
                mCallback.onContactsLoaded(mContactsList);
            }
            setRunning(true);
        }

        @Override
        protected Void doInBackground(Void... params) {
            loadAllContacts();
            if (!mGeocodingResultCache.isEmpty()) {
                geocoding();
            }
            return null;
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);
            updateProgress(values);
        }

        @Override
        protected void onCancelled() {
            super.onCancelled();
            setRunning(false);
        }

        @Override
        protected void onPostExecute(Void result) {
            super.onPostExecute(result);

            writeContactsListInCache(mContactsList);
            if (mCallback != null) {
                mCallback.onContactsLoaded(mContactsList);
            }
            setRunning(false);
        }

        private void loadAllContacts() {
            Context context = getActivity();
            mGeocodingResultCache.clear();

            List<ContactsItem> contactsList = new ArrayList<ContactsItem>();
            Cursor groupCursor = null;
            Cursor postalCursor = null;
            Cursor noteCursor = null;
            Cursor companyCursor = null;
            GeocodingCacheDatabase db = null;

            try {
                groupCursor = context.getContentResolver().query(Data.CONTENT_URI,
                        new String[] { GroupMembership.RAW_CONTACT_ID, GroupMembership.CONTACT_ID,
                                GroupMembership.DISPLAY_NAME, GroupMembership.PHONETIC_NAME,
                                GroupMembership.GROUP_ROW_ID },
                        Data.MIMETYPE + "=?", new String[] { GroupMembership.CONTENT_ITEM_TYPE },
                        Data.RAW_CONTACT_ID);

                postalCursor = context.getContentResolver().query(StructuredPostal.CONTENT_URI,
                        new String[] { StructuredPostal.RAW_CONTACT_ID, StructuredPostal.CONTACT_ID,
                                StructuredPostal.DISPLAY_NAME, StructuredPostal.PHONETIC_NAME,
                                StructuredPostal.FORMATTED_ADDRESS },
                        null, null, StructuredPostal.RAW_CONTACT_ID);

                noteCursor = context.getContentResolver().query(Data.CONTENT_URI,
                        new String[] { Note.RAW_CONTACT_ID, Note.NOTE }, Data.MIMETYPE + "=?",
                        new String[] { Note.CONTENT_ITEM_TYPE }, Data.RAW_CONTACT_ID);

                companyCursor = context.getContentResolver().query(Data.CONTENT_URI,
                        new String[] { Organization.RAW_CONTACT_ID, Organization.COMPANY }, Data.MIMETYPE + "=?",
                        new String[] { Organization.CONTENT_ITEM_TYPE }, Data.RAW_CONTACT_ID);

                HashMap<Long, String> noteMap = new HashMap<Long, String>();
                while (noteCursor.moveToNext()) {
                    long rowId = noteCursor.getLong(0);
                    String note = noteCursor.getString(1);
                    noteMap.put(rowId, note);
                }

                HashMap<Long, String> companyMap = new HashMap<Long, String>();
                while (companyCursor.moveToNext()) {
                    long rowId = companyCursor.getLong(0);
                    String company = companyCursor.getString(1);
                    companyMap.put(rowId, company);
                }

                CursorJoinerWithIntKey joiner = new CursorJoinerWithIntKey(groupCursor,
                        new String[] { Data.RAW_CONTACT_ID }, postalCursor, new String[] { Data.RAW_CONTACT_ID });

                db = new GeocodingCacheDatabase(context);
                long _rowId = -1, _cid = -1;
                String _name = null, _phonetic = null, _note = null, _companyName = null;
                List<Long> _groupIds = new ArrayList<Long>();
                List<String> _address = new ArrayList<String>();

                for (CursorJoinerWithIntKey.Result result : joiner) {
                    long rowId, cid, groupId;
                    String name, phonetic, address, note, companyName;

                    switch (result) {
                    case LEFT:
                        rowId = groupCursor.getLong(0);
                        cid = groupCursor.getLong(1);
                        name = groupCursor.getString(2);
                        phonetic = groupCursor.getString(3);
                        groupId = groupCursor.getLong(4);
                        address = null;
                        note = noteMap.get(rowId);
                        companyName = companyMap.get(rowId);
                        break;

                    case RIGHT:
                        rowId = postalCursor.getLong(0);
                        cid = postalCursor.getLong(1);
                        name = postalCursor.getString(2);
                        phonetic = postalCursor.getString(3);
                        groupId = ContactsGroup.ID_GROUP_ALL_CONTACTS;
                        address = postalCursor.getString(4);
                        note = noteMap.get(rowId);
                        companyName = companyMap.get(rowId);
                        break;

                    case BOTH:
                        rowId = groupCursor.getLong(0);
                        cid = groupCursor.getLong(1);
                        name = groupCursor.getString(2);
                        phonetic = groupCursor.getString(3);
                        groupId = groupCursor.getLong(4);
                        address = postalCursor.getString(4);
                        note = noteMap.get(rowId);
                        companyName = companyMap.get(rowId);
                        break;

                    default:
                        continue;
                    }

                    if (_rowId != rowId) {
                        for (long gid : _groupIds) {
                            if (_address.isEmpty()) {
                                contactsList.add(
                                        new ContactsItem(_cid, _name, _phonetic, gid, null, _note, _companyName));
                                continue;
                            }
                            for (String addr : _address) {
                                ContactsItem contact = new ContactsItem(_cid, _name, _phonetic, gid, addr, _note,
                                        _companyName);
                                double[] latlng = db.get(addr);
                                if (latlng != null && latlng.length == 2) {
                                    contact.setLat(latlng[0]);
                                    contact.setLng(latlng[1]);
                                } else {
                                    if (!mGeocodingResultCache.containsKey(addr)) {
                                        mGeocodingResultCache.put(addr, null);
                                    }
                                }
                                contactsList.add(contact);
                            }
                        }
                        _rowId = rowId;
                        _cid = cid;
                        _name = name;
                        _phonetic = phonetic;
                        _groupIds.clear();
                        _groupIds.add(ContactsGroup.ID_GROUP_ALL_CONTACTS);
                        _address.clear();
                        _note = note;
                        _companyName = companyName;
                    }

                    if (_groupIds.indexOf(groupId) < 0) {
                        _groupIds.add(groupId);
                    }
                    if (address != null && _address.indexOf(address) < 0) {
                        _address.add(address);
                    }
                }
                // FIXME: 
                for (long gid : _groupIds) {
                    if (_address.isEmpty()) {
                        contactsList.add(new ContactsItem(_cid, _name, _phonetic, gid, null, _note, _companyName));
                        continue;
                    }
                    for (String addr : _address) {
                        ContactsItem contact = new ContactsItem(_cid, _name, _phonetic, gid, addr, _note,
                                _companyName);
                        double[] latlng = db.get(addr);
                        if (latlng != null && latlng.length == 2) {
                            contact.setLat(latlng[0]);
                            contact.setLng(latlng[1]);
                        } else {
                            if (!mGeocodingResultCache.containsKey(addr)) {
                                mGeocodingResultCache.put(addr, null);
                            }
                        }
                        contactsList.add(contact);
                    }
                }
            } finally {
                if (groupCursor != null) {
                    groupCursor.close();
                }
                if (postalCursor != null) {
                    postalCursor.close();
                }
                if (noteCursor != null) {
                    noteCursor.close();
                }
                if (db != null) {
                    db.close();
                }
            }
            Collections.sort(contactsList, new ContactsItemComparator());
            mContactsList = contactsList;
        }

        private void geocoding() {
            Context context = getActivity();
            showProgress();

            final GeocodingCacheDatabase db = new GeocodingCacheDatabase(context);
            int count = 0;
            try {
                for (Iterator<Entry<String, Double[]>> it = mGeocodingResultCache.entrySet().iterator(); it
                        .hasNext();) {
                    Entry<String, Double[]> entry = it.next();
                    String address = entry.getKey();

                    Double[] latlng = GeocoderUtils.getFromLocationName(context, address);
                    db.put(address, latlng);
                    entry.setValue(latlng);

                    count++;
                    final int progress = count;
                    publishProgress(progress);

                    if (isCancelled()) {
                        return;
                    }
                }
            } catch (IOException e) {
                hideProgress();
                String title = getString(R.string.title_geocoding_ioerror);
                String message = getString(R.string.message_geocoding_ioerror);
                showError(title, message);
                return;
            } catch (JSONException e) {
                hideProgress();
                String title = getString(R.string.title_geocoding_jsonerror);
                String message = getString(R.string.message_geocoding_jsonerror);
                showError(title, message);
                return;
            } finally {
                db.close();
            }

            for (int j = 0; j < mContactsList.size(); j++) {
                ContactsItem contact = mContactsList.get(j);
                String address = contact.getAddress();
                if (address == null) {
                    continue;
                }
                if (contact.getLat() != null && contact.getLng() != null) {
                    continue;
                }
                if (!mGeocodingResultCache.containsKey(address)) {
                    continue;
                }

                Double[] latlng = mGeocodingResultCache.get(address);
                if (latlng != null && latlng.length == 2) {
                    contact.setLat(latlng[0]);
                    contact.setLng(latlng[1]);
                    mContactsList.set(j, contact);
                }
            }
            hideProgress();
        }

        @SuppressWarnings("unchecked")
        private List<ContactsItem> readContactsListFromCache() {
            Context context = getActivity();
            Object object = null;
            try {
                byte[] bytes = CacheUtils.read(context, FILENAME_CACHE_CONTACTS_LIST);
                object = SerializationUtils.deserialize(bytes);
            } catch (SerializationException e) {
                // ?
                object = null;
            } catch (FileNotFoundException e) {
                // Nothing to do.
            } catch (IOException e) {
                e.printStackTrace();
            }
            return (List<ContactsItem>) object;
        }

        private void writeContactsListInCache(List<ContactsItem> contactsList) {
            Context context = getActivity();
            if (contactsList == null) {
                return;
            }

            byte[] bytes = SerializationUtils.serialize((Serializable) contactsList);
            try {
                CacheUtils.write(context, bytes, FILENAME_CACHE_CONTACTS_LIST);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}