/*
* Copyright (C) 2008 Esmertec AG. Copyright (C) 2008 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 info.guardianproject.otr.app.im.app;
import info.guardianproject.otr.app.im.IImConnection;
import info.guardianproject.otr.app.im.R;
import info.guardianproject.otr.app.im.provider.Imps;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import android.app.Activity;
import android.content.AsyncQueryHandler;
import android.content.ContentQueryMap;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.database.Cursor;
import android.database.DataSetObserver;
import android.net.Uri;
import android.os.RemoteException;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.CursorTreeAdapter;
import android.widget.TextView;
import android.widget.ListAdapter;
public class ChatListAdapter implements ListAdapter, AbsListView.OnScrollListener {
private static final String[] CONTACT_LIST_PROJECTION = { Imps.ContactList._ID,
Imps.ContactList.NAME, };
private static final int COLUMN_CONTACT_LIST_ID = 0;
private static final int COLUMN_CONTACT_LIST_NAME = 1;
Activity mActivity;
SimpleAlertHandler mHandler;
private LayoutInflater mInflate;
private long mProviderId = -1;
long mAccountId = -1;
Cursor mOngoingConversations;
boolean mDataValid;
ListTreeAdapter mAdapter;
final MyContentObserver mContentObserver;
final MyDataSetObserver mDataSetObserver;
// private static final int TOKEN_CONTACT_LISTS = -1;
private static final int TOKEN_ONGOING_CONVERSATION = -2;
// private static final int TOKEN_SUBSCRIPTION = -3;
/*
private static final String NON_CHAT_AND_BLOCKED_CONTACTS = "("
+ Imps.Contacts.LAST_MESSAGE_DATE
+ " IS NULL) AND ("
+ Imps.Contacts.TYPE + "!="
+ Imps.Contacts.TYPE_BLOCKED + ")";
private static final String CONTACTS_SELECTION = Imps.Contacts.CONTACTLIST + "=? AND "
+ NON_CHAT_AND_BLOCKED_CONTACTS;
private static final String ONLINE_CONTACT_SELECTION = CONTACTS_SELECTION + " AND "
+ Imps.Contacts.PRESENCE_STATUS + " != "
+ Imps.Presence.OFFLINE;
*/
static final void log(String msg) {
Log.d(ImApp.LOG_TAG, "<ContactListAdapter>" + msg);
}
static final String[] CONTACT_COUNT_PROJECTION = { Imps.Contacts.CONTACTLIST,
Imps.Contacts._COUNT, };
ContentQueryMap mOnlineContactsCountMap;
// Async QueryHandler
private final class QueryHandler extends AsyncQueryHandler {
public QueryHandler(Context context) {
super(context.getContentResolver());
}
@Override
protected void onQueryComplete(int token, Object cookie, Cursor c) {
if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) {
log("onQueryComplete:token=" + token);
}
if (token == TOKEN_ONGOING_CONVERSATION) {
setOngoingConversations(c);
mAdapter.notifyDataSetChanged();
} else {
int count = mAdapter.getGroupCount();
for (int pos = 0; pos < count; pos++) {
long listId = mAdapter.getGroupId(pos);
if (listId == token) {
mAdapter.setChildrenCursor(pos, c);
break;
}
}
}
}
}
private QueryHandler mQueryHandler;
private int mScrollState;
private boolean mAutoRequery;
private boolean mRequeryPending;
public ChatListAdapter(Activity activity) {
mActivity = activity;
mInflate = activity.getLayoutInflater();
mHandler = new SimpleAlertHandler(activity);
mAdapter = new ListTreeAdapter(null);
mContentObserver = new MyContentObserver();
mDataSetObserver = new MyDataSetObserver();
mQueryHandler = new QueryHandler(activity);
}
public void changeConnection(IImConnection conn) {
mQueryHandler.cancelOperation(TOKEN_ONGOING_CONVERSATION);
synchronized (this) {
if (mOngoingConversations != null) {
mOngoingConversations.close();
mOngoingConversations = null;
}
if (mOnlineContactsCountMap != null) {
mOnlineContactsCountMap.close();
}
}
mAdapter.notifyDataSetChanged();
if (conn != null) {
try {
mProviderId = conn.getProviderId();
mAccountId = conn.getAccountId();
startQueryOngoingConversations();
} catch (RemoteException e) {
// Service died!
}
}
}
public void changeConnection() {
mQueryHandler.cancelOperation(TOKEN_ONGOING_CONVERSATION);
synchronized (this) {
if (mOngoingConversations != null) {
mOngoingConversations.close();
mOngoingConversations = null;
}
if (mOnlineContactsCountMap != null) {
mOnlineContactsCountMap.close();
}
}
mAdapter.notifyDataSetChanged();
mProviderId = -1;
mAccountId = -1;
startQueryOngoingConversations();
}
public void startAutoRequery() {
if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) {
log("startAutoRequery()");
}
mAutoRequery = true;
if (mRequeryPending) {
mRequeryPending = false;
startQueryOngoingConversations();
}
}
void startQueryOngoingConversations() {
if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) {
log("startQueryOngoingConversations()");
}
Uri uri = Imps.Contacts.CONTENT_URI_CHAT_CONTACTS_BY;
if (mProviderId != -1)
uri = ContentUris.withAppendedId(uri, mProviderId);
if (mAccountId != -1)
uri = ContentUris.withAppendedId(uri, mAccountId);
mQueryHandler.startQuery(TOKEN_ONGOING_CONVERSATION, null, uri,
ContactView.CONTACT_PROJECTION, null, null, Imps.Contacts.DEFAULT_SORT_ORDER);
}
/*
void startQuerySubscriptions() {
if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) {
log("startQuerySubscriptions()");
}
Uri uri = Imps.Contacts.CONTENT_URI_CONTACTS_BY;
uri = ContentUris.withAppendedId(uri, mProviderId);
uri = ContentUris.withAppendedId(uri, mAccountId);
}
*/
public long getChildId(int groupPosition, int childPosition) {
if (isPosForOngoingConversation(groupPosition)) {
// No cursor id exists for the "Empty" TextView item
if (getOngoingConversationCount() == 0)
return 0;
return getId(getOngoingConversations(), childPosition);
}
return -1;
}
public int getChildrenCount(int groupPosition) {
return 0;
}
public Object getGroup(int groupPosition) {
if (isPosForOngoingConversation(groupPosition)) {
return null;
} else {
return mAdapter.getGroup(getChildAdapterPosition(groupPosition));
}
}
public boolean isChildSelectable(int groupPosition, int childPosition) {
if (isPosForOngoingConversation(groupPosition)) {
// "Empty" TextView is not selectable
if (getOngoingConversationCount() == 0)
return false;
return true;
}
return mAdapter.isChildSelectable(getChildAdapterPosition(groupPosition), childPosition);
}
public boolean stableIds() {
return true;
}
View newChildView(ViewGroup parent) {
return mInflate.inflate(R.layout.contact_view, parent, false);
}
View newEmptyView(ViewGroup parent) {
return mInflate.inflate(R.layout.empty_conversation_group_view, parent, false);
}
View newGroupView(ViewGroup parent) {
return mInflate.inflate(R.layout.group_view, parent, false);
}
private synchronized Cursor getOngoingConversations() {
if (mOngoingConversations == null) {
startQueryOngoingConversations();
}
return mOngoingConversations;
}
synchronized void setOngoingConversations(Cursor c) {
if (mOngoingConversations != null) {
mOngoingConversations.unregisterContentObserver(mContentObserver);
mOngoingConversations.unregisterDataSetObserver(mDataSetObserver);
mOngoingConversations.close();
}
if (c != null) {
c.registerContentObserver(mContentObserver);
c.registerDataSetObserver(mDataSetObserver);
}
mOngoingConversations = c;
}
private int getOngoingConversationCount() {
Cursor c = getOngoingConversations();
return c == null ? 0 : c.getCount();
}
public boolean isPosForOngoingConversation(int groupPosition) {
return groupPosition == 0;
}
private int getChildAdapterPosition(int groupPosition) {
return groupPosition - 1;
}
private Cursor moveTo(Cursor cursor, int position) {
if (cursor.moveToPosition(position)) {
return cursor;
}
return null;
}
private long getId(Cursor cursor, int position) {
if (cursor.moveToPosition(position)) {
return cursor.getLong(ContactView.COLUMN_CONTACT_ID);
}
return 0;
}
class ListTreeAdapter extends CursorTreeAdapter {
public ListTreeAdapter(Cursor cursor) {
super(cursor, mActivity);
}
@Override
protected void bindChildView(View view, Context context, Cursor cursor, boolean isLastChild) {
// binding when child is text view for an empty group
if (view instanceof TextView) {
((TextView) view).setText(mActivity.getText(R.string.empty_contact_group));
} else {
((ContactView) view).bind(cursor, null, isScrolling());
}
}
@Override
protected void bindGroupView(View view, Context context, Cursor cursor, boolean isExpanded) {
TextView text1 = (TextView) view.findViewById(R.id.text1);
TextView text2 = (TextView) view.findViewById(R.id.text2);
Resources r = view.getResources();
text1.setText(cursor.getString(COLUMN_CONTACT_LIST_NAME));
text2.setVisibility(View.VISIBLE);
text2.setText(r.getString(R.string.online_count, getOnlineChildCount(cursor)));
}
View newEmptyView(ViewGroup parent) {
return mInflate.inflate(R.layout.empty_contact_group_view, parent, false);
}
// if the group is empty, provide a text view. The infrastructure provides a "convertView"
// as a possible suggestion to reuse an existing view's data. It may be null, it may be a
// TextView, or it may be a ContactView, so we need to test the possible cases.
@Override
public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
View convertView, ViewGroup parent) {
// Provide a TextView if the group is empty
if (super.getChildrenCount(groupPosition) == 0) {
if (convertView != null) {
if (convertView instanceof TextView) {
((TextView) convertView).setText(mActivity
.getText(R.string.empty_contact_group));
return convertView;
}
}
return newEmptyView(parent);
}
if (!(convertView instanceof ContactView)) {
convertView = null;
}
return super.getChildView(groupPosition, childPosition, isLastChild, convertView,
parent);
}
@Override
protected Cursor getChildrenCursor(Cursor groupCursor) {
return null;
}
// return a TextView for empty groups
@Override
protected View newChildView(Context context, Cursor cursor, boolean isLastChild,
ViewGroup parent) {
if (cursor.getCount() == 0) {
return newEmptyView(parent);
} else {
return ChatListAdapter.this.newChildView(parent);
}
}
@Override
protected View newGroupView(Context context, Cursor cursor, boolean isExpanded,
ViewGroup parent) {
return ChatListAdapter.this.newGroupView(parent);
}
private int getOnlineChildCount(Cursor groupCursor) {
long listId = groupCursor.getLong(COLUMN_CONTACT_LIST_ID);
if (mOnlineContactsCountMap == null) {
String where = Imps.Contacts.ACCOUNT + "=" + mAccountId;
ContentResolver cr = mActivity.getContentResolver();
Cursor c = cr.query(Imps.Contacts.CONTENT_URI_ONLINE_COUNT,
CONTACT_COUNT_PROJECTION, where, null, null);
mOnlineContactsCountMap = new ContentQueryMap(c, Imps.Contacts.CONTACTLIST, true,
mHandler);
mOnlineContactsCountMap.addObserver(new Observer() {
public void update(Observable observable, Object data) {
notifyDataSetChanged();
}
});
}
ContentValues value = mOnlineContactsCountMap.getValues(String.valueOf(listId));
return value == null ? 0 : value.getAsInteger(Imps.Contacts._COUNT);
}
@Override
public int getChildrenCount(int groupPosition) {
int children = super.getChildrenCount(groupPosition);
if (children == 0) {
// Count the empty group text item as a child
return 1;
}
return children;
}
// Don't allow the empty group text item to be selected
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return (super.getChildrenCount(groupPosition) > 0);
}
}
private class MyContentObserver extends ContentObserver {
public MyContentObserver() {
super(mHandler);
}
@Override
public void onChange(boolean selfChange) {
if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) {
log("MyContentObserver.onChange() autoRequery=" + mAutoRequery);
}
// Don't requery when fling. We will schedule a requery when the fling is complete.
if (isScrolling()) {
return;
}
if (mAutoRequery) {
startQueryOngoingConversations();
} else {
mRequeryPending = true;
}
}
}
private class MyDataSetObserver extends DataSetObserver {
public MyDataSetObserver() {
}
@Override
public void onChanged() {
if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) {
log("MyDataSetObserver.onChanged()");
}
mDataValid = true;
}
@Override
public void onInvalidated() {
if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) {
log("MyDataSetObserver.onInvalidated()");
}
mDataValid = false;
}
}
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
int totalItemCount) {
// no op
}
public void onScrollStateChanged(AbsListView view, int scrollState) {
int oldState = mScrollState;
mScrollState = scrollState;
// If we just finished a fling then some items may not have an icon
// So force a full redraw now that the fling is complete
if (oldState == OnScrollListener.SCROLL_STATE_FLING) {
}
}
public boolean isScrolling() {
return mScrollState == OnScrollListener.SCROLL_STATE_FLING;
}
@Override
public int getCount() {
return this.getOngoingConversationCount();
}
@Override
public Object getItem(int position) {
if (getOngoingConversationCount() == 0)
return null;
return moveTo(getOngoingConversations(), position);
}
@Override
public long getItemId(int position) {
// No cursor id exists for the "Empty" TextView item
if (getOngoingConversationCount() == 0)
return 0;
return getId(getOngoingConversations(), position);
}
@Override
public int getItemViewType(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// boolean isOngoingConversation = isPosForOngoingConversation(0);
boolean displayEmpty = (getOngoingConversationCount() == 0);
View view = null;
if (convertView != null) {
// use the convert view if it matches the type required by displayEmpty
if (displayEmpty && (convertView instanceof TextView)) {
view = convertView;
((TextView) view).setText(mActivity.getText(R.string.empty_conversation_group));
} else if (!displayEmpty && (convertView instanceof ContactView)) {
view = convertView;
}
}
if (view == null) {
if (displayEmpty) {
view = newEmptyView(parent);
} else {
view = newChildView(parent);
}
}
if (!displayEmpty) {
Cursor cursor = getOngoingConversations();
cursor.moveToPosition(position);
String[] myColumnString = cursor.getColumnNames();
for (int i = 0; i < myColumnString.length; i++) {
}
((ContactView) view).bind(cursor, null, isScrolling());
}
return view;
}
@Override
public int getViewTypeCount() {
return 1;
}
@Override
public boolean hasStableIds() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isEmpty() {
return (this.getOngoingConversationCount() == 0);
}
@Override
public void registerDataSetObserver(DataSetObserver observer) {
mAdapter.registerDataSetObserver(observer);
}
@Override
public void unregisterDataSetObserver(DataSetObserver observer) {
mAdapter.unregisterDataSetObserver(observer);
}
@Override
public boolean areAllItemsEnabled() {
return true;
}
@Override
public boolean isEnabled(int position) {
return true;
}
}