package org.yaxim.androidclient.chat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashSet;
import com.actionbarsherlock.app.SherlockFragmentActivity;
import com.actionbarsherlock.view.MenuInflater;
import org.yaxim.androidclient.MainWindow;
import org.yaxim.androidclient.R;
import org.yaxim.androidclient.YaximApplication;
import org.yaxim.androidclient.data.ChatHelper;
import org.yaxim.androidclient.data.ChatProvider;
import org.yaxim.androidclient.data.ChatProvider.ChatConstants;
import org.yaxim.androidclient.data.RosterProvider;
import org.yaxim.androidclient.data.YaximConfiguration;
import org.yaxim.androidclient.service.IXMPPChatService;
import org.yaxim.androidclient.service.XMPPService;
import org.yaxim.androidclient.util.StatusMode;
import org.yaxim.androidclient.util.XMPPHelper;
import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.view.Window;
import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.database.ContentObserver;
import android.database.Cursor;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.TransitionDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Handler;
import android.os.HandlerThread;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.text.ClipboardManager;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.util.TypedValue;
import android.view.ContextMenu;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnKeyListener;
import android.view.inputmethod.InputMethodManager;
import android.view.WindowManager;
import android.widget.*;
import android.widget.AdapterView.AdapterContextMenuInfo;
@SuppressWarnings("deprecation") /* recent ClipboardManager only available since API 11 */
public class ChatWindow extends SherlockFragmentActivity implements OnKeyListener,
TextWatcher, LoaderManager.LoaderCallbacks<Cursor>, AbsListView.OnScrollListener {
public static final String INTENT_EXTRA_USERNAME = ChatWindow.class.getName() + ".username";
public static final String INTENT_EXTRA_MESSAGE = ChatWindow.class.getName() + ".message";
private static final String TAG = "yaxim.ChatWindow";
private static final String[] PROJECTION_FROM = new String[] {
ChatProvider.ChatConstants._ID, ChatProvider.ChatConstants.DATE,
ChatProvider.ChatConstants.DIRECTION, ChatProvider.ChatConstants.JID,
ChatProvider.ChatConstants.RESOURCE, ChatProvider.ChatConstants.MESSAGE,
ChatProvider.ChatConstants.DELIVERY_STATUS };
private static final int[] PROJECTION_TO = new int[] { R.id.chat_date,
R.id.chat_from, R.id.chat_message };
private static final int DELAY_NEWMSG = 2000;
private static final int CHAT_MSG_LOADER = 0;
private int lastlog_size = 200;
private int lastlog_index = -1;
protected YaximConfiguration mConfig;
private ContentObserver mContactObserver = new ContactObserver();
private ImageView mStatusMode;
private TextView mTitle;
private TextView mSubTitle;
private Button mSendButton = null;
private ProgressBar mLoadingProgress;
protected EditText mChatInput = null;
protected String mWithJabberID = null;
protected String mUserScreenName = null;
private Intent mChatServiceIntent;
private ServiceConnection mChatServiceConnection;
private XMPPChatServiceAdapter mChatServiceAdapter;
private int mChatFontSize;
private ActionBar actionBar;
private ListView mListView;
protected ChatWindowAdapter mChatAdapter;
volatile boolean mMarkRunnableQuit = false;
private Runnable mMarkRunnable = new Runnable() {
@Override
public void run() {
Log.d(TAG, "mMarkRunnable: running...");
markReadMessagesInDb();
Log.d(TAG, "mMarkRunnable: done...");
if (mMarkRunnableQuit)
mMarkThread.quit();
}
};
private HandlerThread mMarkThread;
private Handler mMarkHandler;
private final HashSet<Integer> mReadMessages = new HashSet<Integer>();
private boolean mShowOrHide = true;
@Override
public void onCreate(Bundle savedInstanceState) {
mConfig = YaximApplication.getConfig(this);
setContactFromUri();
Log.d(TAG, "onCreate, registering XMPP service");
registerXMPPService();
setTheme(mConfig.getTheme());
super.onCreate(savedInstanceState);
XMPPHelper.setStaticNFC(this, "xmpp:" + mWithJabberID + "?roster;name=" + java.net.URLEncoder.encode(mUserScreenName));
mChatFontSize = Integer.valueOf(mConfig.chatFontSize);
requestWindowFeature(Window.FEATURE_ACTION_BAR);
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED);
setContentView(R.layout.mainchat);
getContentResolver().registerContentObserver(RosterProvider.CONTENT_URI,
true, mContactObserver);
actionBar = getSupportActionBar();
actionBar.setHomeButtonEnabled(true);
actionBar.setDisplayHomeAsUpEnabled(true);
// Setup the actual chat view
mListView = (ListView) findViewById(android.R.id.list);
mChatAdapter = new ChatWindowAdapter(null, PROJECTION_FROM, PROJECTION_TO,
mWithJabberID, null);
mListView.setAdapter(mChatAdapter);
mListView.setOnScrollListener(this);
Log.d(TAG, "registrs for contextmenu...");
registerForContextMenu(mListView);
setSendButton();
setUserInput();
String titleUserid;
if (mUserScreenName != null) {
titleUserid = mUserScreenName;
} else {
titleUserid = mWithJabberID;
}
setCustomTitle(titleUserid);
// Setup the loader
getSupportLoaderManager().initLoader(CHAT_MSG_LOADER, null, this);
// Loading progress
mLoadingProgress = (ProgressBar) findViewById(R.id.loading_progress);
mLoadingProgress.setVisibility(View.VISIBLE);
mMarkThread = new HandlerThread("MarkAsReadThread: " + mWithJabberID);
mMarkThread.start();
mMarkHandler = new Handler(mMarkThread.getLooper());
}
private void setCustomTitle(String title) {
LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
View layout = inflater.inflate(R.layout.chat_action_title, null);
mStatusMode = (ImageView)layout.findViewById(R.id.action_bar_status);
mTitle = (TextView)layout.findViewById(R.id.action_bar_title);
mSubTitle = (TextView)layout.findViewById(R.id.action_bar_subtitle);
mTitle.setText(title);
setTitle(null);
getSupportActionBar().setCustomView(layout);
getSupportActionBar().setDisplayShowCustomEnabled(true);
}
@Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
// There's only one Loader, so ...
if (i == CHAT_MSG_LOADER) {
String selection = null;
Uri lastlog = new Uri.Builder().scheme("content").authority(ChatProvider.AUTHORITY)
.appendPath("chats")
.appendPath(mWithJabberID).appendPath(String.valueOf(lastlog_size))
.build();
return new CursorLoader(this, lastlog, PROJECTION_FROM,
selection, null, "date");
} else {
Log.w(TAG, "Unknown loader id returned in LoaderCallbacks.onCreateLoader: " + i);
return null;
}
}
@Override
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
mLoadingProgress.setVisibility(View.GONE);
mChatAdapter.changeCursor(cursor);
// Only do this the first time (show or hide the keyboard)
if (mShowOrHide) {
if (cursor.getCount() == 0) {
showKeyboard();
}
mShowOrHide = false;
}
// correct position after loading more lastlog
if (lastlog_index >= 0) {
int delta = 1 + mChatAdapter.getCursor().getCount() - lastlog_index;
mListView.setSelection(delta);
lastlog_index = -1;
}
}
@Override
public void onLoaderReset(Loader<Cursor> cursorLoader) {
// Make sure we don't leak the (memory of the) cursor
mChatAdapter.changeCursor(null);
}
public void increaseLastLog() {
// only trigger this if we already have a cursor and that was LIMITed by lastlog_size
if (mChatAdapter.getCursor() != null && mChatAdapter.getCursor().getCount() == lastlog_size) {
Log.d(TAG, "increaseLastLog: " + mChatAdapter.getCursor().getCount());
lastlog_size += 200;
lastlog_index = mChatAdapter.getCursor().getCount();
getSupportLoaderManager().restartLoader(CHAT_MSG_LOADER, null, this /*LoaderCallbacks<Cursor>*/);
}
}
/* AbsListView.OnScrollListener */
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
// re-query the lastlog when reaching the first item
if (visibleItemCount > 0 && firstVisibleItem == 0)
increaseLastLog();
}
@Override
public void onScrollStateChanged (AbsListView view, int scrollState) {
// ignore, not needed for infinite scrolling
}
// onPause/onResume are not called on older Androids when the lockscreen is
// right in front of a chat window. onWindowFocusChanged is toggled
// when the MUC contacts are shown.
// We need to count both events to reliably bind/unbind our service. Sigh.
// We bind if at least one of them happens, and unbind when both are
// reversed.
protected int needs_to_bind_unbind = 0;
protected void changeBoundness(int direction) {
if (needs_to_bind_unbind == 0)
bindXMPPService();
needs_to_bind_unbind += direction;
if (needs_to_bind_unbind == 0)
unbindXMPPService();
}
@Override
protected void onResume() {
Log.d(TAG, "onResume");
super.onResume();
updateContactStatus();
changeBoundness(+1);
}
@Override
protected void onPause() {
Log.d(TAG, "onPause");
super.onPause();
changeBoundness(-1);
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
Log.d(TAG, "onWindowFocusChanged: " + hasFocus);
super.onWindowFocusChanged(hasFocus);
changeBoundness(hasFocus ? +1 : -1);
}
@Override
public void onDestroy() {
super.onDestroy();
getContentResolver().unregisterContentObserver(mContactObserver);
// XXX: quitSafely would be better, but needs API r18
mMarkRunnableQuit = true;
mMarkHandler.post(mMarkRunnable);
}
protected void registerXMPPService() {
Log.i(TAG, "called startXMPPService()");
mChatServiceIntent = new Intent(this, XMPPService.class);
Uri chatURI = Uri.parse(mWithJabberID);
mChatServiceIntent.setData(chatURI);
mChatServiceIntent.setAction("org.yaxim.androidclient.XMPPSERVICE");
mChatServiceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i(TAG, "called onServiceConnected() (for ChatService)");
mChatServiceAdapter = new XMPPChatServiceAdapter(
IXMPPChatService.Stub.asInterface(service),
mWithJabberID);
mChatServiceAdapter.clearNotifications(mWithJabberID);
updateContactStatus();
}
public void onServiceDisconnected(ComponentName name) {
Log.i(TAG, "called onServiceDisconnected() (for ChatService)");
}
};
}
protected void unbindXMPPService() {
try {
unbindService(mChatServiceConnection);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Service wasn't bound!");
}
}
protected void bindXMPPService() {
bindService(mChatServiceIntent, mChatServiceConnection, BIND_AUTO_CREATE);
}
private void setSendButton() {
mSendButton = (Button) findViewById(R.id.Chat_SendButton);
View.OnClickListener onSend = getOnSetListener();
mSendButton.setOnClickListener(onSend);
mSendButton.setEnabled(false);
}
private void setUserInput() {
Intent i = getIntent();
mChatInput = (EditText) findViewById(R.id.Chat_UserInput);
mChatInput.addTextChangedListener(this);
mChatInput.setOnKeyListener(this);
if (i.hasExtra(INTENT_EXTRA_MESSAGE)) {
mChatInput.setText(i.getExtras().getString(INTENT_EXTRA_MESSAGE));
}
}
private void setContactFromUri() {
Intent i = getIntent();
mWithJabberID = i.getDataString(); // TODO: lowercase bare-JID, stringprep-normalize
Log.d(TAG, "setting contact from URI: "+mWithJabberID);
if (i.hasExtra(INTENT_EXTRA_USERNAME)) {
mUserScreenName = i.getExtras().getString(INTENT_EXTRA_USERNAME);
} else {
mUserScreenName = mWithJabberID;
}
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
View target = ((AdapterContextMenuInfo)menuInfo).targetView;
TextView from = (TextView)target.findViewById(R.id.chat_from);
getMenuInflater().inflate(R.menu.chat_contextmenu, menu);
if (!from.getText().equals(getString(R.string.chat_from_me))) {
menu.findItem(R.id.chat_contextmenu_resend).setEnabled(false);
}
}
private String getMessageFromContextMenu(android.view.MenuItem item) {
AdapterContextMenuInfo info = (AdapterContextMenuInfo)item.getMenuInfo();
Cursor c = (Cursor)mListView.getItemAtPosition(info.position);
return c.getString(c.getColumnIndex(ChatProvider.ChatConstants.MESSAGE));
}
@Override
public boolean onContextItemSelected(android.view.MenuItem item) {
switch (item.getItemId()) {
case R.id.chat_contextmenu_copy_text:
ClipboardManager cm = (ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE);
cm.setText(getMessageFromContextMenu(item));
return true;
case R.id.chat_contextmenu_resend:
sendMessage(getMessageFromContextMenu(item).toString());
Log.d(TAG, "resend!");
return true;
default:
return super.onContextItemSelected((android.view.MenuItem) item);
}
}
public boolean onCreateOptionsMenu(com.actionbarsherlock.view.Menu menu) {
MenuInflater inflater = getSupportMenuInflater();
//inflater.inflate(R.menu.contact_options, menu);
inflater.inflate(R.menu.roster_item_contextmenu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(com.actionbarsherlock.view.MenuItem item) {
Log.d(TAG, "options item selected");
switch (item.getItemId()) {
case android.R.id.home:
Intent intent = new Intent(this, MainWindow.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
finish();
return true;
default:
return ChatHelper.handleJidOptions(this, item.getItemId(), mWithJabberID, mUserScreenName);
}
}
private View.OnClickListener getOnSetListener() {
return new View.OnClickListener() {
public void onClick(View v) {
sendMessageIfNotNull();
}
};
}
private void sendMessageIfNotNull() {
if (mChatInput.getText().length() >= 1) {
sendMessage(mChatInput.getText().toString());
}
}
private void sendMessage(String message) {
mChatInput.setText(null);
mSendButton.setEnabled(false);
mChatServiceAdapter.sendMessage(mWithJabberID, message);
if (!mChatServiceAdapter.isServiceAuthenticated())
showToastNotification(R.string.toast_stored_offline);
}
private boolean markAsReadDelayed(final int id, final int delay) {
if (mReadMessages.contains(id)) {
return false;
}
mMarkHandler.removeCallbacks(mMarkRunnable);
mReadMessages.add(id);
mMarkHandler.postDelayed(mMarkRunnable, delay);
return true;
}
private void markReadMessagesInDb() {
if (mReadMessages.size() == 0)
return;
HashSet<Integer> hs = (HashSet)mReadMessages.clone();
Uri rowuri = Uri.parse("content://" + ChatProvider.AUTHORITY
+ "/" + ChatProvider.TABLE_NAME);
// create custom WHERE statement instead of relying on ContentResolvers whereArgs
StringBuilder where = new StringBuilder();
where.append("_id IN (");
for (int id : hs) {
where.append(id);
where.append(",");
}
// ',' --> ')'
where.setCharAt(where.length()-1, ')');
Log.d(TAG, "markAsRead: " + where);
ContentValues values = new ContentValues();
values.put(ChatConstants.DELIVERY_STATUS, ChatConstants.DS_SENT_OR_READ);
getContentResolver().update(rowuri, values, where.toString(), null);
// XXX: is this the right place?
mReadMessages.removeAll(hs);
}
public String jid2nickname(String jid, String resource) {
String from = jid;
if (jid.equals(mWithJabberID))
from = mUserScreenName;
return from;
}
class ChatWindowAdapter extends SimpleCursorAdapter {
String mScreenName, mJID;
ChatWindowAdapter(Cursor cursor, String[] from, int[] to,
String JID, String screenName) {
super(ChatWindow.this, android.R.layout.simple_list_item_1, cursor,
from, to);
mScreenName = screenName;
mJID = JID;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
ChatItemWrapper wrapper = null;
Cursor cursor = this.getCursor();
cursor.moveToPosition(position);
long dateMilliseconds = cursor.getLong(cursor
.getColumnIndex(ChatProvider.ChatConstants.DATE));
int _id = cursor.getInt(cursor
.getColumnIndex(ChatProvider.ChatConstants._ID));
String date = getDateString(dateMilliseconds);
String message = cursor.getString(cursor
.getColumnIndex(ChatProvider.ChatConstants.MESSAGE));
boolean from_me = (cursor.getInt(cursor
.getColumnIndex(ChatProvider.ChatConstants.DIRECTION)) ==
ChatConstants.OUTGOING);
String jid = cursor.getString(cursor
.getColumnIndex(ChatProvider.ChatConstants.JID));
String resource = cursor.getString(
cursor.getColumnIndex(ChatProvider.ChatConstants.RESOURCE)
);
int delivery_status = cursor.getInt(cursor
.getColumnIndex(ChatProvider.ChatConstants.DELIVERY_STATUS));
if (row == null) {
LayoutInflater inflater = getLayoutInflater();
row = inflater.inflate(R.layout.chatrow, null);
wrapper = new ChatItemWrapper(row, ChatWindow.this);
row.setTag(wrapper);
} else {
wrapper = (ChatItemWrapper) row.getTag();
}
if (!from_me && delivery_status == ChatConstants.DS_NEW) {
if (!markAsReadDelayed(_id, DELAY_NEWMSG))
delivery_status = ChatConstants.DS_SENT_OR_READ;
}
wrapper.populateFrom(date, from_me, jid2nickname(jid, resource), message, delivery_status, mScreenName);
return row;
}
}
private String getDateString(long milliSeconds) {
SimpleDateFormat dateFormater = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = new Date(milliSeconds);
return dateFormater.format(date);
}
public class ChatItemWrapper {
private TextView mDateView = null;
private TextView mFromView = null;
private TextView mMessageView = null;
private ImageView mIconView = null;
private final View mRowView;
private ChatWindow chatWindow;
ChatItemWrapper(View row, ChatWindow chatWindow) {
this.mRowView = row;
this.chatWindow = chatWindow;
}
void populateFrom(String date, boolean from_me, String from, String message,
int delivery_status, String highlight_text) {
getDateView().setText(date);
TypedValue tv = new TypedValue();
if (from_me) {
getTheme().resolveAttribute(R.attr.ChatMsgHeaderMeColor, tv, true);
getFromView().setText(getString(R.string.chat_from_me));
getFromView().setTextColor(tv.data);
from = mConfig.userName;
} else {
nick2Color(from, tv);
getFromView().setText(from + ":");
getFromView().setTextColor(tv.data);
}
switch (delivery_status) {
case ChatConstants.DS_NEW:
ColorDrawable layers[] = new ColorDrawable[2];
getTheme().resolveAttribute(R.attr.ChatNewMessageColor, tv, true);
layers[0] = new ColorDrawable(tv.data);
if (from_me) {
// message stored for later transmission
getTheme().resolveAttribute(R.attr.ChatStoredMessageColor, tv, true);
layers[1] = new ColorDrawable(tv.data);
} else {
layers[1] = new ColorDrawable(0x00000000);
}
TransitionDrawable backgroundColorAnimation = new
TransitionDrawable(layers);
int l = mRowView.getPaddingLeft();
int t = mRowView.getPaddingTop();
int r = mRowView.getPaddingRight();
int b = mRowView.getPaddingBottom();
mRowView.setBackgroundDrawable(backgroundColorAnimation);
mRowView.setPadding(l, t, r, b);
backgroundColorAnimation.setCrossFadeEnabled(true);
backgroundColorAnimation.startTransition(DELAY_NEWMSG);
getIconView().setImageResource(R.drawable.ic_chat_msg_status_queued);
break;
case ChatConstants.DS_SENT_OR_READ:
getIconView().setImageResource(R.drawable.ic_chat_msg_status_unread);
mRowView.setBackgroundColor(0x00000000); // default is transparent
break;
case ChatConstants.DS_ACKED:
getIconView().setImageResource(R.drawable.ic_chat_msg_status_ok);
mRowView.setBackgroundColor(0x00000000); // default is transparent
break;
case ChatConstants.DS_FAILED:
getIconView().setImageResource(R.drawable.ic_chat_msg_status_failed);
mRowView.setBackgroundColor(0x30ff0000); // default is transparent
break;
}
int style = 0;
if (highlight_text != null && message.toLowerCase().contains(highlight_text.toLowerCase()))
style |= android.graphics.Typeface.BOLD;
boolean slash_me = message.startsWith("/me ");
if (slash_me) {
message = String.format("\u25CF %s %s", from, message.substring(4));
style |= android.graphics.Typeface.ITALIC;
}
getMessageView().setText(message);
getMessageView().setTypeface(null, style);
int fontsize = (int)(chatWindow.mChatFontSize * XMPPHelper.getEmojiScalingFactor(message, 12));
getMessageView().setTextSize(TypedValue.COMPLEX_UNIT_SP, fontsize);
getDateView().setTextSize(TypedValue.COMPLEX_UNIT_SP, chatWindow.mChatFontSize*2/3);
getFromView().setTextSize(TypedValue.COMPLEX_UNIT_SP, chatWindow.mChatFontSize*2/3);
}
TextView getDateView() {
if (mDateView == null) {
mDateView = (TextView) mRowView.findViewById(R.id.chat_date);
}
return mDateView;
}
TextView getFromView() {
if (mFromView == null) {
mFromView = (TextView) mRowView.findViewById(R.id.chat_from);
}
return mFromView;
}
TextView getMessageView() {
if (mMessageView == null) {
mMessageView = (TextView) mRowView
.findViewById(R.id.chat_message);
}
return mMessageView;
}
ImageView getIconView() {
if (mIconView == null) {
mIconView = (ImageView) mRowView
.findViewById(R.id.iconView);
}
return mIconView;
}
}
// OnKeyListener
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN
&& keyCode == KeyEvent.KEYCODE_ENTER) {
sendMessageIfNotNull();
return true;
}
return false;
}
// TextWatcher
@Override
public void afterTextChanged(Editable s) {
mSendButton.setEnabled(mChatInput.getText().length() >= 1);
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
private void showToastNotification(int message) {
Toast toastNotification = Toast.makeText(this, message,
Toast.LENGTH_SHORT);
toastNotification.show();
}
private static final String[] STATUS_QUERY = new String[] {
RosterProvider.RosterConstants.ALIAS,
RosterProvider.RosterConstants.STATUS_MODE,
RosterProvider.RosterConstants.STATUS_MESSAGE,
};
private void updateContactStatus() {
Cursor cursor = getContentResolver().query(RosterProvider.CONTENT_URI, STATUS_QUERY,
RosterProvider.RosterConstants.JID + " = ?", new String[] { mWithJabberID }, null);
int ALIAS_IDX = cursor.getColumnIndex(RosterProvider.RosterConstants.ALIAS);
int MODE_IDX = cursor.getColumnIndex(RosterProvider.RosterConstants.STATUS_MODE);
int MSG_IDX = cursor.getColumnIndex(RosterProvider.RosterConstants.STATUS_MESSAGE);
if (cursor.getCount() == 1) {
cursor.moveToFirst();
int status_mode = cursor.getInt(MODE_IDX);
String status_message = cursor.getString(MSG_IDX);
Log.d(TAG, "contact status changed: " + status_mode + " " + status_message);
mTitle.setText(cursor.getString(ALIAS_IDX));
mSubTitle.setVisibility((status_message != null && status_message.length() != 0)?
View.VISIBLE : View.GONE);
mSubTitle.setText(status_message);
if (mChatServiceAdapter == null || !mChatServiceAdapter.isServiceAuthenticated())
status_mode = 0; // override icon if we are offline
mStatusMode.setImageResource(StatusMode.values()[status_mode].getDrawableId());
}
cursor.close();
}
// this method is a "virtual" placeholder for the MUC activity
public void nick2Color(String nick, TypedValue tv) {
getTheme().resolveAttribute(R.attr.ChatMsgHeaderYouColor, tv, true);
}
public ListView getListView() {
return mListView;
}
private void showKeyboard() {
mChatInput.requestFocus();
new Handler(getMainLooper()).postDelayed(new Runnable() {
@Override
public void run() {
InputMethodManager keyboard = (InputMethodManager)
getSystemService(Context.INPUT_METHOD_SERVICE);
keyboard.showSoftInput(mChatInput, InputMethodManager.SHOW_IMPLICIT);
}
}, 200);
}
private class ContactObserver extends ContentObserver {
public ContactObserver() {
super(new Handler());
}
public void onChange(boolean selfChange) {
Log.d(TAG, "ContactObserver.onChange: " + selfChange);
updateContactStatus();
}
}
}