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.content.SharedPreferences; 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.preference.PreferenceManager; import android.util.Log; import android.view.View; import android.widget.ListView; import com.abewy.android.apps.klyph.core.KlyphSession; import com.abewy.android.apps.klyph.core.fql.MessageThread; import com.abewy.android.apps.klyph.core.graph.GraphObject; import com.abewy.android.apps.klyph.core.request.BaseAsyncRequest; import com.abewy.android.apps.klyph.core.request.RequestError; import com.abewy.android.apps.klyph.core.request.Response; import com.abewy.android.apps.klyph.core.fql.serializer.MessageThreadDeserializer; import com.abewy.android.apps.klyph.core.fql.serializer.MessageThreadSerializer; import com.abewy.android.apps.klyph.messenger.MessengerApplication; 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; 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.crashlytics.android.Crashlytics; public class ConversationListFragment extends KlyphFragment { public static interface ConversationListCallback { public void onConversationSelected(MessageThread thread); } private ConversationListCallback listener; private ReadDataTask readTask; private boolean isStoredData; private List<PRosterEntry> roster; private boolean hasLoadedOnce = false; private String selectedConversationId; private String selectedThreadId; public ConversationListFragment() { setRequestType(Query.THREADS); } @Override public void onViewCreated(View view, Bundle savedInstanceState) { setListAdapter(new MultiObjectAdapter(getListView())); defineEmptyText(R.string.empty_list_no_message); PreferenceManager.getDefaultSharedPreferences(MessengerApplication.getInstance()).registerOnSharedPreferenceChangeListener( sharedPreferencesListener); getListView().setDrawSelectorOnTop(false); getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE); setListVisible(false); setRequestType(Query.THREADS); super.onViewCreated(view, savedInstanceState); } private void sendConversationToService(String id) { if (mIRemoteService != null) { try { mIRemoteService.setSelectedRecipient(id); } catch (RemoteException e) { e.printStackTrace(); } } } private void sendNoConversationToService() { if (mIRemoteService != null) { try { mIRemoteService.setNoSelectedRecipient(); } catch (RemoteException e) { e.printStackTrace(); } } } private void refreshRoster() { if (mIRemoteService != null) { try { roster = mIRemoteService.getRoster(); } catch (RemoteException e) { e.printStackTrace(); } } } private boolean refreshPresenceLists() { Log.d("ConversationListFragment", "refreshPresenceLists"); if (roster != null && roster.size() > 0 && getAdapter().getCount() > 0) { Log.d("ConversationListFragment", "refreshPresenceLists 2"); List<GraphObject> data = getAdapter().getItems(); boolean presenceSet = bindPresenceToConversation(data); if (presenceSet) getAdapter().notifyDataSetChanged(); return presenceSet; } return false; } private boolean bindPresenceToConversation(List<GraphObject> data) { boolean presenceSet = false; for (GraphObject graphObject : data) { if (graphObject instanceof MessageThread) { MessageThread thread = (MessageThread) graphObject; thread.setFriend_is_online(false); if (thread.getRecipients().size() == 2) { for (String id : thread.getRecipients()) { if (!id.equals(KlyphSession.getSessionUserId())) { PRosterEntry entry = getRosterEntry(id); if (entry != null) { thread.setFriend_is_online(entry.isAvailable()); presenceSet = true; } break; } } } } } return presenceSet; } private PRosterEntry getRosterEntry(String id) { if (roster != null) { for (PRosterEntry entry : roster) { if (entry.getId().equals(id)) { return entry; } } } return null; } @Override protected void populate(List<GraphObject> data) { if (isFirstLoad()) getAdapter().clear(); final int size = getAdapter().getCount(); bindPresenceToConversation(data); super.populate(data); if (size == 0 && !isStoredData) { new StoreDataTask().execute(); } if (data.size() > 0) setOffset(((MessageThread) data.get(data.size() - 1)).getUpdated_time()); if (selectedThreadId != null) selectThread(selectedThreadId); if (selectedConversationId != null) setSelectedConversation(selectedConversationId); } @Override public void onListItemClick(ListView l, View v, int position, long id) { super.onListItemClick(l, v, position, id); // getListView().setItemChecked(position, true); for (GraphObject object : getAdapter().getItems()) { object.setSelected(false); } MessageThread thread = (MessageThread) l.getItemAtPosition(position); thread.setUnread(0); thread.setSelected(true); selectedThreadId = thread.getThread_id(); if (thread.isSingleUserConversation()) { for (String uid : thread.getRecipients()) { if (!uid.equals(KlyphSession.getSessionUserId())) { sendConversationToService(uid); break; } } } else { sendNoConversationToService(); } listener.onConversationSelected(thread); getAdapter().notifyDataSetChanged(); } public void deselect() { selectedThreadId = null; selectedConversationId = null; getListView().clearChoices(); for (GraphObject object : getAdapter().getItems()) { object.setSelected(false); } getAdapter().notifyDataSetChanged(); sendNoConversationToService(); // Hack to deselect the list /*getListView().post(new Runnable() { @Override public void run() { getAdapter().notifyDataSetChanged(); } });*/ } public void setSelectedConversation(String id) { selectedConversationId = id; selectedThreadId = null; Log.d("ConversationListFragment", "setSelectedConversation " + id); int n = getAdapter().getCount(); for (int i = 0; i < n; i++) { GraphObject object = getAdapter().getItem(i); if (object instanceof MessageThread) { MessageThread thread = (MessageThread) object; thread.setSelected(false); if (thread.isSingleUserConversation()) { for (String uid : thread.getRecipients()) { if (uid.equals(id)) { Log.d("ConversationListFragmen0t", "setSelectedConversation found"); thread.setUnread(0); thread.setSelected(true); selectedThreadId = thread.getThread_id(); } } } } } getAdapter().notifyDataSetChanged(); sendConversationToService(id); } private void selectThread(String threadId) { selectedThreadId = threadId; selectedConversationId = null; List<GraphObject> items = getAdapter().getItems(); int n = items.size(); for (int i = 0; i < n; i++) { GraphObject object = items.get(i); if (object instanceof MessageThread) { MessageThread thread = (MessageThread) object; thread.setSelected(false); if (thread.getThread_id().equals(threadId)) { Log.d("ConversationListFragment", "setSelectedThread found"); thread.setUnread(0); thread.setSelected(true); } } } getAdapter().notifyDataSetChanged(); } private void onMessageReceived(Bundle data) { String uid = data.getString("participant"); String msg = data.getString("body"); String date = data.getString("date"); date = date.substring(0, date.length() - 3); int selectedIndex = getListView().getCheckedItemPosition(); int n = getAdapter().getCount(); boolean found = false; for (int i = 0; i < n; i++) { GraphObject object = getAdapter().getItem(i); if (object instanceof MessageThread) { MessageThread thread = (MessageThread) object; if (thread.isMultiUserConversation()) continue; for (String id : thread.getRecipients()) { if (id.equals(uid)) { if (i != selectedIndex) { thread.setUnread(thread.getUnread() + 1); } thread.setSnippet(msg); thread.setUpdated_time(date); found = true; break; } } } if (found) break; } if (found) getAdapter().notifyDataSetChanged(); else loadNewest(); /* * if (uid.equals(recipientId)) * { * String date = data.getString("date"); * date = date.substring(0, date.length() - 3); * * Message msg = new Message(); * msg.setAuthor_id(uid); * msg.setAuthor_name(data.getString("from")); * msg.setCreated_time(date); * msg.setAuthor_pic(recipientPicUrl != null && recipientPicUrl.length() > 0 ? recipientPicUrl : FacebookUtil.getProfilePictureURLForId(uid)); * msg.setBody(data.getString("body")); * * getListView().setTranscriptMode(AbsListView.TRANSCRIPT_MODE_ALWAYS_SCROLL); * getAdapter().add(msg); * getAdapter().notifyDataSetChanged(); * getListView().setTranscriptMode(AbsListView.TRANSCRIPT_MODE_NORMAL); * } */ } public void userSentMessage(String threadId, String message) { List<GraphObject> items = getAdapter().getItems(); int n = items.size(); for (int i = 0; i < n; i++) { GraphObject object = items.get(i); if (object instanceof MessageThread) { MessageThread thread = (MessageThread) object; thread.setSelected(false); if (thread.getThread_id().equals(threadId)) { Log.d("ConversationFragment", "userSentMessage found"); thread.setSnippet(message); thread.setSnippet_author(KlyphSession.getSessionUserId()); } } } getAdapter().notifyDataSetChanged(); } @Override public void onAttach(Activity activity) { super.onAttach(activity); if (activity instanceof ConversationListCallback) listener = (ConversationListCallback) activity; } @Override public void onDetach() { super.onDetach(); listener = null; } @Override public void onResume() { super.onResume(); if (hasLoadedOnce) loadNewest(); } @Override protected void loadNewest() { String offset = null; if (getAdapter().getCount() > 0) { for (int i = 0; i < getAdapter().getCount(); i++) { GraphObject object = getAdapter().getItem(i); if (object.getItemViewType() == GraphObject.MESSAGE_THREAD) { offset = ((MessageThread) object).getUpdated_time(); break; } } } new AsyncRequest(Query.THREADS_NEWEST, "", offset, new BaseAsyncRequest.Callback() { @Override public void onComplete(Response response) { onRequestComplete(response); } }).execute(); } private void onRequestComplete(final Response response) { if (getActivity() != null) { getActivity().runOnUiThread(new Runnable() { @Override public void run() { if (response.getError() == null) { onRequestSuccess(response.getGraphObjectList()); } else { onRequestError(response.getError()); } //setActionBarRefreshItemLoading(false); } }); } } private void onRequestSuccess(List<GraphObject> result) { Log.d("ConversationListFragment", "onRequestSuccess"); if (getView() == null || getActivity() == null) return; List<GraphObject> items = getAdapter().getItems(); List<GraphObject> data = new ArrayList<GraphObject>(); for (int i = 0; i < result.size(); i++) { MessageThread thread = (MessageThread) result.get(i); for (int j = 0; j < items.size(); j++) { GraphObject graphObject = items.get(j); if (graphObject.getItemViewType() == GraphObject.MESSAGE_THREAD) { if (((MessageThread) graphObject).getThread_id().equals(thread.getThread_id())) { items.remove(j); j--; data.add(thread); result.remove(thread); i--; } } } } result.addAll(data); for (GraphObject graphObject : items) { if (graphObject.getItemViewType() == GraphObject.MESSAGE_THREAD) { result.add(graphObject); } } Collections.sort(result, new Comparator<GraphObject>() { @Override public int compare(GraphObject lhs, GraphObject rhs) { String time1 = ((MessageThread) lhs).getUpdated_time(); String time2 = ((MessageThread) rhs).getUpdated_time(); return time2.compareTo(time1); }}); getAdapter().setData(result); if (selectedThreadId != null) selectThread(selectedThreadId); if (selectedConversationId != null) setSelectedConversation(selectedConversationId); refreshPresenceLists(); } private void onRequestError(RequestError error) { //do nothing } public void disconnectFromService() { doUnbindService(); } @Override public void onDestroy() { super.onDestroy(); PreferenceManager.getDefaultSharedPreferences(MessengerApplication.getInstance()).unregisterOnSharedPreferenceChangeListener( sharedPreferencesListener); doUnbindService(); listener = null; mCallback = null; mConnection = null; mIsBound = false; mIRemoteService = null; } @Override public void load() { if (getView() != null) { if (isFirstLoad()) { connectToService(); hasLoadedOnce = true; readTask = new ReadDataTask(); readTask.execute(); } else { setIsFirstLoad(true); super.load(); } } } private class ReadDataTask extends AsyncTask<Void, Void, List<GraphObject>> { @Override protected List<GraphObject> doInBackground(Void... params) { String json = MessengerPreferences.getLastConversations(); if (json == null) return null; JSONArray data; try { data = new JSONArray(json); } catch (JSONException e) { return null; } MessageThreadDeserializer mtd = new MessageThreadDeserializer(); List<GraphObject> list = mtd.deserializeArray(data); 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() { isStoredData = true; populate(data); isStoredData = false; setNoMoreData(false); load(); } }); } } else { if (getActivity() != null) { getActivity().runOnUiThread(new Runnable() { @Override public void run() { ConversationListFragment.super.load(); } }); } } } private class StoreDataTask extends AsyncTask<Void, Void, Void> { @Override protected Void doInBackground(Void... params) { List<MessageThread> conversations = new ArrayList<MessageThread>(); List<GraphObject> objects = getAdapter().getItems(); for (GraphObject object : objects) { if (object instanceof MessageThread) { conversations.add((MessageThread) object); } } MessageThreadSerializer mts = new MessageThreadSerializer(); JSONArray json = mts.serializeArray(conversations); String jsonString = json.toString(); MessengerPreferences.setLastConversations(jsonString); return null; } @Override protected void onPostExecute(Void params) { } } // Service /** Messenger for communicating with service. */ Messenger mService = null; private boolean mIsBound; /** * Handler of incoming messages from service. */ class IncomingHandler extends Handler { @Override public void handleMessage(android.os.Message msg) { switch (msg.what) { case MessengerService.REPORT_MESSAGE_RECEIVED: Log.d("ConvearsationFragment", "REPORT_MESSAGE_RECEIVED"); onMessageReceived(msg.getData()); break; default: super.handleMessage(msg); } } } /** * Target we publish for clients to send messages to IncomingHandler. */ final Messenger mMessenger = new Messenger(new IncomingHandler()); private 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("ConversationListFragment", "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) { Crashlytics.log(Log.DEBUG, "ConversationListFragment", "Can't connect to service " + e.getMessage()); // 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("ConversationListFragment", "onPresenceChange"); refreshRoster(); refreshPresenceLists(); } }); } } @Override public void onRosterUpdated(List<PRosterEntry> roster) throws RemoteException { if (getActivity() != null) { getActivity().runOnUiThread(new Runnable() { @Override public void run() { Log.d("ConversationListFragment", "onRosterUpdated"); refreshRoster(); refreshPresenceLists(); } }); } } }; 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("ConversationListFragment", "register connection 2"); mIRemoteService = IMessengerService.Stub.asInterface(service); try { mIRemoteService.registerCallback(mCallback); } catch (RemoteException e) { Log.d("ConversationListFragment", "registerCallback error"); } try { roster = mIRemoteService.getRoster(); } catch (RemoteException e) { Log.d("ConversationListFragment", "getRoster error"); } } public void onServiceDisconnected(ComponentName className) { try { mIRemoteService.unregisterCallback(mCallback); } catch (RemoteException e) { Log.d("ConversationListFragment", "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. getActivity().getApplicationContext().bindService(new Intent(getActivity(), MessengerService.class), mConnection, Context.BIND_AUTO_CREATE); 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; } } private SharedPreferences.OnSharedPreferenceChangeListener sharedPreferencesListener = new SharedPreferences.OnSharedPreferenceChangeListener() { @Override public void onSharedPreferenceChanged( SharedPreferences sharedPreferences, String key) { if (key.equals(MessengerPreferences.PREFERENCE_NOTIFICATIONS)) { try { mIRemoteService .setNotificationsEnabled(MessengerPreferences .areNotificationsEnabled()); } catch (RemoteException e) { e.printStackTrace(); } } else if (key .equals(MessengerPreferences.PREFERENCE_NOTIFICATIONS_RINGTONE)) { Log.d("ConversationListFragment", "onSharedPreferenceChanged ringtone " + MessengerPreferences .getNotificationRingtone()); try { mIRemoteService .setRingtone(MessengerPreferences .getNotificationRingtone()); } catch (RemoteException e) { e.printStackTrace(); } } else if (key .equals(MessengerPreferences.PREFERENCE_NOTIFICATIONS_RINGTONE_URI)) { Log.d("ConversationListFragment", "onSharedPreferenceChanged ringtoneUri " + MessengerPreferences .getNotificationRingtoneUri()); try { mIRemoteService .setRingtoneUri(MessengerPreferences .getNotificationRingtoneUri()); } catch (RemoteException e) { e.printStackTrace(); } } else if (key .equals(MessengerPreferences.PREFERENCE_NOTIFICATIONS_VIBRATE)) { try { mIRemoteService .setVibrateEnabled(MessengerPreferences .isNotificationVibrationEnabled()); } catch (RemoteException e) { e.printStackTrace(); } } } }; }