package com.abewy.android.apps.klyph.messenger.fragment; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import org.json.JSONArray; import org.json.JSONException; import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; import android.text.Editable; import android.text.TextWatcher; import android.util.Log; import android.view.View; import android.widget.AbsListView; import android.widget.EditText; import android.widget.ListView; import com.abewy.android.apps.klyph.core.fql.Friend; import com.abewy.android.apps.klyph.core.fql.serializer.FriendDeserializer; import com.abewy.android.apps.klyph.core.fql.serializer.FriendSerializer; import com.abewy.android.apps.klyph.core.graph.GraphObject; import com.abewy.android.apps.klyph.messenger.MessengerPreferences; import com.abewy.android.apps.klyph.messenger.R; import com.abewy.android.apps.klyph.messenger.adapter.MultiObjectAdapter; import com.abewy.android.apps.klyph.messenger.request.AsyncRequest.Query; import com.abewy.android.apps.klyph.messenger.service.IMessengerCallback; import com.abewy.android.apps.klyph.messenger.service.IMessengerService; import com.abewy.android.apps.klyph.messenger.service.MessengerService; import com.abewy.android.apps.klyph.messenger.service.PPresence; import com.abewy.android.apps.klyph.messenger.service.PRosterEntry; import com.abewy.klyph.items.Header; import com.crashlytics.android.Crashlytics; import android.widget.SearchView; public class SelectionFragment extends KlyphFragment implements SearchView.OnQueryTextListener { public static interface SelectionCallback { public void onFriendSelected(Friend friend); } private SelectionCallback listener; private List<PRosterEntry> roster; private List<GraphObject> friends; private List<GraphObject> avList; private List<GraphObject> unavList; private EditText editText; private ReadDataTask readTask; private boolean isStoreData = false; public SelectionFragment() { setRequestType(Query.FRIENDS); } @Override public void onViewCreated(View view, Bundle savedInstanceState) { setListAdapter(new MultiObjectAdapter(getListView())); defineEmptyText(R.string.empty_list_no_friend); getListView().setPadding(16, 0, 16, 0); getListView().setScrollBarStyle(AbsListView.SCROLLBARS_OUTSIDE_OVERLAY); getListView().setClipChildren(false); setListVisible(false); avList = new ArrayList<GraphObject>(); unavList = new ArrayList<GraphObject>(); roster = new ArrayList<PRosterEntry>(); friends = new ArrayList<GraphObject>(); editText = (EditText) view.findViewById(R.id.search_text_edit); editText.addTextChangedListener(new TextWatcher() { @Override public void onTextChanged(CharSequence s, int start, int before, int count) { populate(); } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void afterTextChanged(Editable s) { } }); setRequestType(Query.FRIENDS); super.onViewCreated(view, savedInstanceState); } @Override protected int getCustomLayout() { return R.layout.fragment_selection; } @Override protected void populate(List<GraphObject> data) { friends = data; if (!isStoreData) new StoreDataTask().execute(); refreshPresenceLists(); populate(); } @Override public boolean onQueryTextSubmit(String query) { return false; } @Override public boolean onQueryTextChange(String newText) { if (newText.length() > 0) { getAdapter().getFilter().filter(newText); } else { populate(); } return true; } @Override public void onListItemClick(ListView l, View v, int position, long id) { super.onListItemClick(l, v, position, id); PRosterEntry p = (PRosterEntry) l.getItemAtPosition(position); Friend friend = new Friend(); friend.setUid(p.getId()); friend.setName(p.getName()); listener.onFriendSelected(friend); } @Override public void load() { Log.d("SelectionFragment", "load"); if (getView() != null) { if (isFirstLoad()) { connectToService(); readTask = new ReadDataTask(); readTask.execute(); } else { setIsFirstLoad(true); super.load(); } } } public void disconnectFromService() { doUnbindService(); } private void refreshPresenceLists() { avList = new ArrayList<GraphObject>(); unavList = new ArrayList<GraphObject>(); if (mIRemoteService != null) { try { roster = mIRemoteService.getRoster(); } catch (RemoteException e) { e.printStackTrace(); } } //Log.d("SelectionFragment", "roster " + roster.size() + " friends " + friends.size()); if (roster.size() > 0 && friends.size() > 0) { Collections.sort(roster, new Comparator<PRosterEntry>() { @Override public int compare(PRosterEntry lhs, PRosterEntry rhs) { return lhs.name.compareTo(rhs.name); } }); final int n = roster.size(); for (int i = 0; i < n; i++) { PRosterEntry p = roster.get(i); String id = p.getId(); int m = friends.size(); for (int j = 0; j < m; j++) { Friend friend = (Friend) friends.get(j); if (id.equals(friend.getUid())) { p.setPic(friend.getPic()); m--; j--; break; } } if (p.isAvailable()) { avList.add(p); } else { unavList.add(p); } } } else if (friends.size() > 0) { for (GraphObject f : friends) { Friend friend = (Friend) f; PRosterEntry p = new PRosterEntry(); p.name = friend.getName(); p.setId(friend.getUid()); p.setPic(friend.getPic()); p.presence = PRosterEntry.UNAVAILABLE; unavList.add(p); } } } private void populate() { //Log.d("SelectionFragment", "avList " + avList.size()); List<GraphObject> avList = this.avList; List<GraphObject> unavList = this.unavList; if (editText.getText().length() > 0) { String key = editText.getText().toString().toLowerCase(); List<GraphObject> filteredAvList = new ArrayList<GraphObject>(); List<GraphObject> filteredUnavList = new ArrayList<GraphObject>(); int n = avList.size(); for (int i = 0; i < n; i++) { PRosterEntry friend = (PRosterEntry) avList.get(i); if (friend.getName().toLowerCase().startsWith(key.toString())) { filteredAvList.add(friend); } } n = unavList.size(); for (int i = 0; i < n; i++) { PRosterEntry friend = (PRosterEntry) unavList.get(i); if (friend.getName().toLowerCase().startsWith(key.toString())) { filteredUnavList.add(friend); } } avList = filteredAvList; unavList = filteredUnavList; } List<GraphObject> allList = new ArrayList<GraphObject>(); if (avList.size() > 0) { Header h = new Header(); h.setName(getString(R.string.online).toUpperCase()); allList.add(h); allList.addAll(avList); } if (unavList.size() > 0) { Header h = new Header(); h.setName(getString(R.string.offline).toUpperCase()); allList.add(h); allList.addAll(unavList); } getAdapter().clear(); super.populate(allList); getActivity().invalidateOptionsMenu(); } @Override public void onAttach(Activity activity) { super.onAttach(activity); if (activity instanceof SelectionCallback) listener = (SelectionCallback) activity; } @Override public void onDetach() { super.onDetach(); listener = null; } @Override public void onDestroy() { Log.d("SelectionFragment", "onDestroy"); doUnbindService(); if (readTask != null) { readTask.cancel(true); readTask = null; } mConnection = null; listener = null; super.onDestroy(); friends = null; avList = null; unavList = null; editText = null; } // ___ Service communication /** Messenger for communicating with service. */ Messenger mService = null; /** Flag indicating whether we have called bind on the service. */ boolean mIsBound; /** * Handler of incoming messages from service. */ class IncomingHandler extends Handler { @Override public void handleMessage(Message msg) { Log.d("SelectionFragment", "handleMessage " + msg.what); switch (msg.what) { case MessengerService.REPORT_PRESENCE_CHANGED: Log.d("SelectionFragment", "REPORT_PRESENCE_CHANGED"); refreshPresenceLists(); populate(); break; case MessengerService.REPORT_ROSTER_UPDATED: case MessengerService.REPORT_ROSTER_ENTRIES_ADDED: case MessengerService.REPORT_ROSTER_ENTRIES_DELETED: Log.d("SelectionFragment", "onRosterUpdate"); /* * try * { * roster = mIRemoteService.getRoster(); * } * catch (RemoteException e) * { * e.printStackTrace(); * } */ refreshPresenceLists(); populate(); break; default: super.handleMessage(msg); } } } /** * Target we publish for clients to send messages to IncomingHandler. */ final Messenger mMessenger = new Messenger(new IncomingHandler()); IMessengerService mIRemoteService; /** * Class for interacting with the main interface of the service. */ private ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { // This is called when the connection with the service has been // established, giving us the service object we can use to // interact with the service. We are communicating with our // service through an IDL interface, so get a client-side // representation of that from the raw service object. Log.d("SelectionFragment", "register connection 1"); mService = new Messenger(service); // We want to monitor the service for as long as we are // connected to it. try { Message msg = Message.obtain(null, MessengerService.REGISTER_CLIENT); msg.replyTo = mMessenger; mService.send(msg); } catch (RemoteException e) { // In this case the service has crashed before we could even // do anything with it; we can count on soon being // disconnected (and then reconnected if it can be restarted) // so there is no need to do anything here. } } public void onServiceDisconnected(ComponentName className) { // This is called when the connection with the service has been // unexpectedly disconnected -- that is, its process crashed. mService = null; } }; private IMessengerCallback mCallback = new IMessengerCallback.Stub() { @Override public void onPresenceChange(PPresence presence) throws RemoteException { if (getActivity() != null) { getActivity().runOnUiThread(new Runnable() { @Override public void run() { Log.d("SelectionFragment", "onPresenceChange"); refreshPresenceLists(); populate(); } }); } } @Override public void onRosterUpdated(List<PRosterEntry> roster) throws RemoteException { if (getActivity() != null) { getActivity().runOnUiThread(new Runnable() { @Override public void run() { Log.d("SelectionFragment", "onRosterUpdated"); refreshPresenceLists(); populate(); } }); } } }; private ServiceConnection mSecondConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { // This is called when the connection with the service has been // established, giving us the service object we can use to // interact with the service. We are communicating with our // service through an IDL interface, so get a client-side // representation of that from the raw service object. Log.d("SelectionFragment", "register connection 2"); mIRemoteService = IMessengerService.Stub.asInterface(service); try { mIRemoteService.registerCallback(mCallback); } catch (RemoteException e) { Log.d("SelectionFragment", "registerCallback error"); Crashlytics.log(Log.DEBUG, "SelectionFragment", "Can't connect to service " + e.getMessage()); } try { roster = mIRemoteService.getRoster(); } catch (RemoteException e) { Log.d("SelectionFragment", "getRoster error"); } } public void onServiceDisconnected(ComponentName className) { try { mIRemoteService.unregisterCallback(mCallback); } catch (RemoteException e) { Log.d("SelectionFragment", "unregisterCallback error"); } // This is called when the connection with the service has been // unexpectedly disconnected -- that is, its process crashed. mIRemoteService = null; } }; public void connectToService() { // Establish a connection with the service. We use an explicit // class name because there is no reason to be able to let other // applications replace our component. Log.d("SelectionFragment", "bind connection 1"); getActivity().getApplicationContext().bindService(new Intent(getActivity(), MessengerService.class), mConnection, Context.BIND_AUTO_CREATE); Log.d("SelectionFragment", "bind connection 2"); getActivity().getApplicationContext().bindService(new Intent(IMessengerService.class.getName()), mSecondConnection, Context.BIND_AUTO_CREATE); mIsBound = true; } void doUnbindService() { if (mIsBound) { // If we have received the service, and hence registered with // it, then now is the time to unregister. if (mService != null) { try { Message msg = Message.obtain(null, MessengerService.UNREGISTER_CLIENT); msg.replyTo = mMessenger; mService.send(msg); } catch (RemoteException e) { // There is nothing special we need to do if the service // has crashed. } } // Detach our existing connection. try { getActivity().getApplicationContext().unbindService(mConnection); } catch (IllegalArgumentException e) { } try { getActivity().getApplicationContext().unbindService(mSecondConnection); } catch (IllegalArgumentException e) { } mIsBound = false; } } // ___ Async tasks private class ReadDataTask extends AsyncTask<Void, Void, List<GraphObject>> { @Override protected List<GraphObject> doInBackground(Void... params) { Log.d("SelectionFragment", "begin"); String json = MessengerPreferences.getFriends(); if (json == null) return null; JSONArray data; try { data = new JSONArray(json); } catch (JSONException e) { Log.d("SelectionFragment", e.getMessage()); return null; } FriendDeserializer fd = new FriendDeserializer(); List<GraphObject> list = fd.deserializeArray(data); Log.d("SelectionFragment", "end " + list.size()); return list; } @Override protected void onPostExecute(List<GraphObject> result) { onStoredDataLoaded(result); } } private void onStoredDataLoaded(final List<GraphObject> data) { if (data != null && getView() != null) { if (getActivity() != null) { getActivity().runOnUiThread(new Runnable() { @Override public void run() { if (data.size() > 0) { isStoreData = true; populate(data); isStoreData = false; setNoMoreData(false); } setIsFirstLoad(false); load(); } }); } } else { if (getActivity() != null) { getActivity().runOnUiThread(new Runnable() { @Override public void run() { SelectionFragment.super.load(); } }); } } } private class StoreDataTask extends AsyncTask<Void, Void, Void> { @Override protected Void doInBackground(Void... params) { FriendSerializer fs = new FriendSerializer(); JSONArray json = fs.serializeArray(friends); String jsonString = json.toString(); MessengerPreferences.setFriends(jsonString); return null; } @Override protected void onPostExecute(Void params) { } } }