• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 Esmertec AG.
3  * Copyright (C) 2008 The Android Open Source Project
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 package com.android.im.app;
19 
20 import java.util.ArrayList;
21 import java.util.Date;
22 import java.util.Map;
23 
24 import android.app.Activity;
25 import android.app.AlertDialog;
26 import android.content.AsyncQueryHandler;
27 import android.content.ContentResolver;
28 import android.content.ContentUris;
29 import android.content.Context;
30 import android.content.DialogInterface;
31 import android.content.Intent;
32 import android.content.res.Resources;
33 import android.content.res.Configuration;
34 import android.database.ContentObserver;
35 import android.database.Cursor;
36 import android.database.CursorIndexOutOfBoundsException;
37 import android.database.DataSetObserver;
38 import android.database.CharArrayBuffer;
39 import android.graphics.Typeface;
40 import android.net.Uri;
41 import android.os.Bundle;
42 import android.os.Message;
43 import android.os.RemoteException;
44 import android.provider.Browser;
45 import android.text.Editable;
46 import android.text.TextUtils;
47 import android.text.TextWatcher;
48 import android.text.style.StyleSpan;
49 import android.text.style.URLSpan;
50 import android.util.AttributeSet;
51 import android.util.Log;
52 import android.view.KeyEvent;
53 import android.view.LayoutInflater;
54 import android.view.MotionEvent;
55 import android.view.View;
56 import android.view.ViewGroup;
57 import android.view.inputmethod.InputMethodManager;
58 import android.widget.AbsListView;
59 import android.widget.AdapterView;
60 import android.widget.ArrayAdapter;
61 import android.widget.Button;
62 import android.widget.CursorAdapter;
63 import android.widget.EditText;
64 import android.widget.ImageView;
65 import android.widget.LinearLayout;
66 import android.widget.ListView;
67 import android.widget.TextView;
68 import android.widget.AbsListView.OnScrollListener;
69 import android.widget.AdapterView.OnItemClickListener;
70 
71 import com.android.im.IChatListener;
72 import com.android.im.IChatSession;
73 import com.android.im.IChatSessionListener;
74 import com.android.im.IChatSessionManager;
75 import com.android.im.IContactList;
76 import com.android.im.IContactListListener;
77 import com.android.im.IContactListManager;
78 import com.android.im.IImConnection;
79 import com.android.im.R;
80 import com.android.im.app.adapter.ChatListenerAdapter;
81 import com.android.im.app.adapter.ChatSessionListenerAdapter;
82 import com.android.im.engine.Contact;
83 import com.android.im.engine.ImConnection;
84 import com.android.im.engine.ImErrorInfo;
85 import com.android.im.plugin.BrandingResourceIDs;
86 import com.android.im.provider.Imps;
87 
88 public class ChatView extends LinearLayout {
89     // This projection and index are set for the query of active chats
90     static final String[] CHAT_PROJECTION = {
91         Imps.Contacts._ID,
92         Imps.Contacts.ACCOUNT,
93         Imps.Contacts.PROVIDER,
94         Imps.Contacts.USERNAME,
95         Imps.Contacts.NICKNAME,
96         Imps.Contacts.TYPE,
97         Imps.Presence.PRESENCE_STATUS,
98         Imps.Chats.LAST_UNREAD_MESSAGE,
99     };
100     static final int CONTACT_ID_COLUMN             = 0;
101     static final int ACCOUNT_COLUMN                = 1;
102     static final int PROVIDER_COLUMN               = 2;
103     static final int USERNAME_COLUMN               = 3;
104     static final int NICKNAME_COLUMN               = 4;
105     static final int TYPE_COLUMN                   = 5;
106     static final int PRESENCE_STATUS_COLUMN        = 6;
107     static final int LAST_UNREAD_MESSAGE_COLUMN    = 7;
108 
109     static final String[] INVITATION_PROJECT = {
110         Imps.Invitation._ID,
111         Imps.Invitation.PROVIDER,
112         Imps.Invitation.SENDER,
113     };
114     static final int INVITATION_ID_COLUMN = 0;
115     static final int INVITATION_PROVIDER_COLUMN = 1;
116     static final int INVITATION_SENDER_COLUMN = 2;
117 
118     static final StyleSpan STYLE_BOLD = new StyleSpan(Typeface.BOLD);
119 
120     Markup mMarkup;
121 
122     Activity mScreen;
123     ImApp mApp;
124     SimpleAlertHandler mHandler;
125     Cursor mCursor;
126 
127     private ImageView   mStatusIcon;
128     private TextView    mTitle;
129     /*package*/ListView    mHistory;
130     EditText    mEdtInput;
131     private Button      mSendButton;
132     private View mStatusWarningView;
133     private ImageView mWarningIcon;
134     private TextView mWarningText;
135 
136     private MessageAdapter mMessageAdapter;
137     private IChatSessionManager mChatSessionMgr;
138     private IChatSessionListener mChatSessionListener;
139 
140     private IChatSession mChatSession;
141     private long mChatId;
142     int mType;
143     String mNickName;
144     String mUserName;
145     long mProviderId;
146     long mAccountId;
147     long mInvitationId;
148     private int mPresenceStatus;
149 
150     private int mViewType;
151 
152     private static final int VIEW_TYPE_CHAT = 1;
153     private static final int VIEW_TYPE_INVITATION = 2;
154     private static final int VIEW_TYPE_SUBSCRIPTION = 3;
155 
156     private static final long SHOW_TIME_STAMP_INTERVAL = 60 * 1000;     // 1 minute
157     private static final int QUERY_TOKEN = 10;
158 
159     // Async QueryHandler
160     private final class QueryHandler extends AsyncQueryHandler {
QueryHandler(Context context)161         public QueryHandler(Context context) {
162             super(context.getContentResolver());
163         }
164 
165         @Override
onQueryComplete(int token, Object cookie, Cursor c)166         protected void onQueryComplete(int token, Object cookie, Cursor c) {
167             Cursor cursor = new DeltaCursor(c);
168 
169             if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)){
170                 log("onQueryComplete: cursor.count=" + cursor.getCount());
171             }
172 
173             mMessageAdapter.changeCursor(cursor);
174         }
175     }
176     private QueryHandler mQueryHandler;
177 
178     private class RequeryCallback implements Runnable {
run()179         public void run() {
180             if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)){
181                 log("RequeryCallback");
182             }
183             requeryCursor();
184         }
185     }
186     private RequeryCallback mRequeryCallback = null;
187 
188     private OnItemClickListener mOnItemClickListener = new OnItemClickListener() {
189         public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
190             if (!(view instanceof MessageView)) {
191                 return;
192             }
193             URLSpan[] links = ((MessageView)view).getMessageLinks();
194             if (links.length == 0){
195                 return;
196             }
197 
198             final ArrayList<String> linkUrls = new ArrayList<String>(links.length);
199             for (URLSpan u : links) {
200                 linkUrls.add(u.getURL());
201             }
202             ArrayAdapter<String> a = new ArrayAdapter<String>(mScreen,
203                     android.R.layout.select_dialog_item, linkUrls);
204             AlertDialog.Builder b = new AlertDialog.Builder(mScreen);
205             b.setTitle(R.string.select_link_title);
206             b.setCancelable(true);
207             b.setAdapter(a, new DialogInterface.OnClickListener() {
208                 public void onClick(DialogInterface dialog, int which) {
209                     Uri uri = Uri.parse(linkUrls.get(which));
210                     Intent intent = new Intent(Intent.ACTION_VIEW, uri);
211                     intent.putExtra(Browser.EXTRA_APPLICATION_ID, mScreen.getPackageName());
212                     mScreen.startActivity(intent);
213                 }
214             });
215             b.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
216                 public void onClick(DialogInterface dialog, int which) {
217                     dialog.dismiss();
218                 }
219             });
220             b.show();
221         }
222     };
223 
224     private IChatListener mChatListener = new ChatListenerAdapter() {
225         @Override
226         public void onIncomingMessage(IChatSession ses,
227                 com.android.im.engine.Message msg) {
228             scheduleRequery(0);
229         }
230 
231         @Override
232         public void onContactJoined(IChatSession ses, Contact contact) {
233             scheduleRequery(0);
234         }
235 
236         @Override
237         public void onContactLeft(IChatSession ses, Contact contact) {
238             scheduleRequery(0);
239         }
240 
241         @Override
242         public void onSendMessageError(IChatSession ses,
243                 com.android.im.engine.Message msg, ImErrorInfo error) {
244             scheduleRequery(0);
245         }
246     };
247 
248     private Runnable mUpdateChatCallback = new Runnable() {
249         public void run() {
250             if (mCursor.requery() && mCursor.moveToFirst()) {
251                 updateChat();
252             }
253         }
254     };
255     private IContactListListener mContactListListener = new IContactListListener.Stub () {
256         public void onAllContactListsLoaded() {
257         }
258 
259         public void onContactChange(int type, IContactList list, Contact contact){
260         }
261 
262         public void onContactError(int errorType, ImErrorInfo error,
263                 String listName, Contact contact) {
264         }
265 
266         public void onContactsPresenceUpdate(Contact[] contacts) {
267             if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) {
268                 log("onContactsPresenceUpdate()");
269             }
270             for (Contact c : contacts) {
271                 if (c.getAddress().getFullName().equals(mUserName)) {
272                     mHandler.post(mUpdateChatCallback);
273                     scheduleRequery(0);
274                     break;
275                 }
276             }
277         }
278     };
279 
log(String msg)280     static final void log(String msg) {
281         Log.d(ImApp.LOG_TAG, "<ChatView> " +msg);
282     }
283 
ChatView(Context context, AttributeSet attrs)284     public ChatView(Context context, AttributeSet attrs) {
285         super(context, attrs);
286         mScreen = (Activity) context;
287         mApp = ImApp.getApplication(mScreen);
288         mHandler = new ChatViewHandler();
289     }
290 
registerForConnEvents()291     void registerForConnEvents() {
292         mApp.registerForConnEvents(mHandler);
293     }
294 
unregisterForConnEvents()295     void unregisterForConnEvents() {
296         mApp.unregisterForConnEvents(mHandler);
297     }
298 
299     @Override
onFinishInflate()300     protected void onFinishInflate() {
301         mStatusIcon     = (ImageView) findViewById(R.id.statusIcon);
302         mTitle          = (TextView) findViewById(R.id.title);
303         mHistory        = (ListView) findViewById(R.id.history);
304         mEdtInput       = (EditText) findViewById(R.id.edtInput);
305         mSendButton     = (Button)findViewById(R.id.btnSend);
306         mHistory.setOnItemClickListener(mOnItemClickListener);
307 
308         mStatusWarningView = findViewById(R.id.warning);
309         mWarningIcon = (ImageView)findViewById(R.id.warningIcon);
310         mWarningText = (TextView)findViewById(R.id.warningText);
311 
312         Button acceptInvitation = (Button)findViewById(R.id.btnAccept);
313         Button declineInvitation= (Button)findViewById(R.id.btnDecline);
314 
315         Button approveSubscription = (Button)findViewById(R.id.btnApproveSubscription);
316         Button declineSubscription = (Button)findViewById(R.id.btnDeclineSubscription);
317 
318         acceptInvitation.setOnClickListener(new OnClickListener() {
319             public void onClick(View v) {
320                 acceptInvitation();
321             }
322         });
323         declineInvitation.setOnClickListener(new OnClickListener() {
324             public void onClick(View v) {
325                 declineInvitation();
326             }
327         });
328 
329         approveSubscription.setOnClickListener(new OnClickListener(){
330             public void onClick(View v) {
331                 approveSubscription();
332             }
333         });
334         declineSubscription.setOnClickListener(new OnClickListener(){
335             public void onClick(View v) {
336                 declineSubscription();
337             }
338         });
339 
340         mEdtInput.setOnKeyListener(new OnKeyListener(){
341             public boolean onKey(View v, int keyCode, KeyEvent event) {
342                 if (event.getAction() == KeyEvent.ACTION_DOWN) {
343                     switch (keyCode) {
344                         case KeyEvent.KEYCODE_DPAD_CENTER:
345                             sendMessage();
346                             return true;
347 
348                         case KeyEvent.KEYCODE_ENTER:
349                             if (event.isAltPressed()) {
350                                 mEdtInput.append("\n");
351                                 return true;
352                             }
353                     }
354                 }
355                 return false;
356             }
357         });
358 
359         mEdtInput.setOnEditorActionListener(new TextView.OnEditorActionListener() {
360             public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
361                 if (event != null) {
362                     if (event.isAltPressed()) {
363                         return false;
364                     }
365                 }
366 
367                 sendMessage();
368                 return true;
369             }
370         });
371 
372         // TODO: this is a hack to implement BUG #1611278, when dispatchKeyEvent() works with
373         // the soft keyboard, we should remove this hack.
374         mEdtInput.addTextChangedListener(new TextWatcher() {
375             public void beforeTextChanged(CharSequence s, int start, int before, int after) {
376             }
377 
378             public void onTextChanged(CharSequence s, int start, int before, int after) {
379                 //log("TextWatcher: " + s);
380                 userActionDetected();
381             }
382 
383             public void afterTextChanged(Editable s) {
384             }
385         });
386 
387         mSendButton.setOnClickListener(new OnClickListener() {
388             public void onClick(View v) {
389                 sendMessage();
390             }
391         });
392     }
393 
onResume()394     public void onResume(){
395         if (mViewType == VIEW_TYPE_CHAT) {
396             Cursor cursor = getMessageCursor();
397             if (cursor == null) {
398                 startQuery();
399             } else {
400                 requeryCursor();
401             }
402             updateWarningView();
403         }
404         registerChatListener();
405         registerForConnEvents();
406     }
407 
onPause()408     public void onPause(){
409         Cursor cursor = getMessageCursor();
410         if (cursor != null) {
411             cursor.deactivate();
412         }
413         cancelRequery();
414         if (mViewType == VIEW_TYPE_CHAT && mChatSession != null) {
415             try {
416                 mChatSession.markAsRead();
417             } catch (RemoteException e) {
418                 mHandler.showServiceErrorAlert();
419             }
420         }
421         unregisterChatListener();
422         unregisterForConnEvents();
423         unregisterChatSessionListener();
424     }
425 
closeSoftKeyboard()426     private void closeSoftKeyboard() {
427         InputMethodManager inputMethodManager =
428             (InputMethodManager)mApp.getSystemService(Context.INPUT_METHOD_SERVICE);
429 
430         inputMethodManager.hideSoftInputFromWindow(mEdtInput.getWindowToken(), 0);
431     }
432 
updateChat()433     void updateChat() {
434         setViewType(VIEW_TYPE_CHAT);
435 
436         long oldChatId = mChatId;
437 
438         updateContactInfo();
439 
440         setStatusIcon();
441         setTitle();
442 
443         IImConnection conn = mApp.getConnection(mProviderId);
444         if (conn == null) {
445             if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) log("Connection has been signed out");
446             mScreen.finish();
447             return;
448         }
449 
450         BrandingResources brandingRes = mApp.getBrandingResource(mProviderId);
451         mHistory.setBackgroundDrawable(
452                 brandingRes.getDrawable(BrandingResourceIDs.DRAWABLE_CHAT_WATERMARK));
453 
454         if (mMarkup == null) {
455             mMarkup = new Markup(brandingRes);
456         }
457 
458         if (mMessageAdapter == null) {
459             mMessageAdapter = new MessageAdapter(mScreen, null);
460             mHistory.setAdapter(mMessageAdapter);
461         }
462 
463         // only change the message adapter when we switch to another chat
464         if (mChatId != oldChatId) {
465             startQuery();
466             mEdtInput.setText("");
467         }
468 
469         updateWarningView();
470     }
471 
updateContactInfo()472     private void updateContactInfo() {
473         mChatId = mCursor.getLong(CONTACT_ID_COLUMN);
474         mProviderId = mCursor.getLong(PROVIDER_COLUMN);
475         mAccountId = mCursor.getLong(ACCOUNT_COLUMN);
476         mPresenceStatus = mCursor.getInt(PRESENCE_STATUS_COLUMN);
477         mType = mCursor.getInt(TYPE_COLUMN);
478         mUserName = mCursor.getString(USERNAME_COLUMN);
479         mNickName = mCursor.getString(NICKNAME_COLUMN);
480     }
481 
setTitle()482     private void setTitle() {
483         if (mType == Imps.Contacts.TYPE_GROUP) {
484             final String[] projection = {Imps.GroupMembers.NICKNAME};
485             Uri memberUri = ContentUris.withAppendedId(Imps.GroupMembers.CONTENT_URI, mChatId);
486             ContentResolver cr = mScreen.getContentResolver();
487             Cursor c = cr.query(memberUri, projection, null, null, null);
488             StringBuilder buf = new StringBuilder();
489             if(c != null) {
490                 while(c.moveToNext()) {
491                     buf.append(c.getString(0));
492                     if(!c.isLast()) {
493                         buf.append(',');
494                     }
495                 }
496                 c.close();
497             }
498             mTitle.setText(mContext.getString(R.string.chat_with, buf.toString()));
499         } else {
500             mTitle.setText(mContext.getString(R.string.chat_with, mNickName));
501         }
502     }
503 
setStatusIcon()504     private void setStatusIcon() {
505         if (mType == Imps.Contacts.TYPE_GROUP) {
506             // hide the status icon for group chat.
507             mStatusIcon.setVisibility(GONE);
508         } else {
509             mStatusIcon.setVisibility(VISIBLE);
510             BrandingResources brandingRes = mApp.getBrandingResource(mProviderId);
511             int presenceResId = PresenceUtils.getStatusIconId(mPresenceStatus);
512             mStatusIcon.setImageDrawable(brandingRes.getDrawable(presenceResId));
513         }
514     }
515 
bindChat(long chatId)516     public void bindChat(long chatId) {
517         if (mCursor != null) {
518             mCursor.deactivate();
519         }
520         Uri contactUri = ContentUris.withAppendedId(Imps.Contacts.CONTENT_URI, chatId);
521         mCursor = mScreen.managedQuery(contactUri, CHAT_PROJECTION, null, null);
522         if (mCursor == null || !mCursor.moveToFirst()) {
523             if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)){
524                 log("Failed to query chat: " + chatId);
525             }
526             mScreen.finish();
527             return;
528         } else {
529             mChatSession = getChatSession(mCursor);
530             updateChat();
531             registerChatListener();
532         }
533     }
534 
bindInvitation(long invitationId)535     public void bindInvitation(long invitationId) {
536         Uri uri = ContentUris.withAppendedId(Imps.Invitation.CONTENT_URI, invitationId);
537         ContentResolver cr = mScreen.getContentResolver();
538         Cursor cursor = cr.query(uri, INVITATION_PROJECT, null, null, null);
539         if (cursor == null || !cursor.moveToFirst()) {
540             if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)){
541                 log("Failed to query invitation: " + invitationId);
542             }
543             mScreen.finish();
544         } else {
545             setViewType(VIEW_TYPE_INVITATION);
546 
547             mInvitationId = cursor.getLong(INVITATION_ID_COLUMN);
548             mProviderId = cursor.getLong(INVITATION_PROVIDER_COLUMN);
549             String sender = cursor.getString(INVITATION_SENDER_COLUMN);
550 
551             TextView mInvitationText = (TextView)findViewById(R.id.txtInvitation);
552             mInvitationText.setText(mContext.getString(R.string.invitation_prompt, sender));
553             mTitle.setText(mContext.getString(R.string.chat_with, sender));
554         }
555 
556         if (cursor != null) {
557             cursor.close();
558         }
559     }
560 
bindSubscription(long providerId, String from)561     public void bindSubscription(long providerId, String from) {
562         mProviderId = providerId;
563         mUserName = from;
564 
565         setViewType(VIEW_TYPE_SUBSCRIPTION);
566 
567         TextView text =  (TextView)findViewById(R.id.txtSubscription);
568         String displayableAddr = ImpsAddressUtils.getDisplayableAddress(from);
569         text.setText(mContext.getString(R.string.subscription_prompt, displayableAddr));
570         mTitle.setText(mContext.getString(R.string.chat_with, displayableAddr));
571 
572         mApp.dismissChatNotification(providerId, from);
573     }
574 
acceptInvitation()575     void acceptInvitation() {
576         try {
577 
578             IImConnection conn = mApp.getConnection(mProviderId);
579             if (conn != null) {
580                 // register a chat session listener and wait for a group chat
581                 // session to be created after we accept the invitation.
582                 registerChatSessionListener();
583                 conn.acceptInvitation(mInvitationId);
584             }
585         } catch (RemoteException e) {
586             mHandler.showServiceErrorAlert();
587         }
588     }
589 
declineInvitation()590     void declineInvitation() {
591         try {
592             IImConnection conn = mApp.getConnection(mProviderId);
593             if (conn != null) {
594                 conn.rejectInvitation(mInvitationId);
595             }
596             mScreen.finish();
597         } catch (RemoteException e) {
598             mHandler.showServiceErrorAlert();
599         }
600     }
601 
approveSubscription()602     void approveSubscription() {
603         IImConnection conn = mApp.getConnection(mProviderId);
604         try {
605             IContactListManager manager = conn.getContactListManager();
606             manager.approveSubscription(mUserName);
607         } catch (RemoteException ex) {
608             mHandler.showServiceErrorAlert();
609         }
610         mScreen.finish();
611     }
612 
declineSubscription()613     void declineSubscription() {
614         IImConnection conn = mApp.getConnection(mProviderId);
615         try {
616             IContactListManager manager = conn.getContactListManager();
617             manager.declineSubscription(mUserName);
618         } catch (RemoteException ex) {
619             mHandler.showServiceErrorAlert();
620         }
621         mScreen.finish();
622     }
623 
setViewType(int type)624     private void setViewType(int type) {
625         mViewType = type;
626         if (type == VIEW_TYPE_CHAT) {
627             findViewById(R.id.invitationPanel).setVisibility(GONE);
628             findViewById(R.id.subscription).setVisibility(GONE);
629             setChatViewEnabled(true);
630         }  else if (type == VIEW_TYPE_INVITATION) {
631             setChatViewEnabled(false);
632             findViewById(R.id.invitationPanel).setVisibility(VISIBLE);
633             findViewById(R.id.btnAccept).requestFocus();
634         } else if (type == VIEW_TYPE_SUBSCRIPTION) {
635             setChatViewEnabled(false);
636             findViewById(R.id.subscription).setVisibility(VISIBLE);
637             findViewById(R.id.btnApproveSubscription).requestFocus();
638         }
639     }
640 
setChatViewEnabled(boolean enabled)641     private void setChatViewEnabled(boolean enabled) {
642         mEdtInput.setEnabled(enabled);
643         mSendButton.setEnabled(enabled);
644         if (enabled) {
645             mEdtInput.requestFocus();
646         } else {
647             mHistory.setAdapter(null);
648         }
649     }
650 
startQuery()651     private void startQuery() {
652         if (mQueryHandler == null) {
653             mQueryHandler = new QueryHandler(mContext);
654         } else {
655             // Cancel any pending queries
656             mQueryHandler.cancelOperation(QUERY_TOKEN);
657         }
658 
659         Uri uri = Imps.Messages.getContentUriByThreadId(mChatId);
660 
661         if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)){
662             log("queryCursor: uri=" + uri);
663         }
664 
665         mQueryHandler.startQuery(QUERY_TOKEN, null,
666                 uri,
667                 null,
668                 null /* selection */,
669                 null /* selection args */,
670                 null);
671     }
672 
scheduleRequery(long interval)673     void scheduleRequery(long interval) {
674         if (mRequeryCallback == null) {
675             mRequeryCallback = new RequeryCallback();
676         } else {
677             mHandler.removeCallbacks(mRequeryCallback);
678         }
679 
680         if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)){
681             log("scheduleRequery");
682         }
683         mHandler.postDelayed(mRequeryCallback, interval);
684     }
685 
cancelRequery()686     void cancelRequery() {
687         if (mRequeryCallback != null) {
688             if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)){
689                 log("cancelRequery");
690             }
691             mHandler.removeCallbacks(mRequeryCallback);
692             mRequeryCallback = null;
693         }
694     }
695 
requeryCursor()696     void requeryCursor() {
697         if (mMessageAdapter.isScrolling()) {
698             mMessageAdapter.setNeedRequeryCursor(true);
699             return;
700         }
701         // TODO: async query?
702         Cursor cursor = getMessageCursor();
703         if (cursor != null) {
704             cursor.requery();
705         }
706     }
707 
getMessageCursor()708     private Cursor getMessageCursor() {
709         return mMessageAdapter == null ? null : mMessageAdapter.getCursor();
710     }
711 
insertSmiley(String smiley)712     public void insertSmiley(String smiley) {
713         mEdtInput.append(mMarkup.applyEmoticons(smiley));
714     }
715 
closeChatSession()716     public void closeChatSession() {
717         if (mChatSession != null) {
718             try {
719                 mChatSession.leave();
720             } catch (RemoteException e) {
721                 mHandler.showServiceErrorAlert();
722             }
723         } else {
724             // the conversation is already closed, clear data in database
725             ContentResolver cr = mContext.getContentResolver();
726             cr.delete(ContentUris.withAppendedId(Imps.Chats.CONTENT_URI, mChatId),
727                     null, null);
728         }
729         mScreen.finish();
730     }
731 
closeChatSessionIfInactive()732     public void closeChatSessionIfInactive() {
733         if (mChatSession != null) {
734             try {
735                 mChatSession.leaveIfInactive();
736             } catch (RemoteException e) {
737                 mHandler.showServiceErrorAlert();
738             }
739         }
740     }
741 
viewProfile()742     public void viewProfile() {
743         Uri data = ContentUris.withAppendedId(Imps.Contacts.CONTENT_URI, mChatId);
744         Intent intent = new Intent(Intent.ACTION_VIEW, data);
745         mScreen.startActivity(intent);
746     }
747 
blockContact()748     public void blockContact() {
749         // TODO: unify with codes in ContactListView
750         DialogInterface.OnClickListener confirmListener = new DialogInterface.OnClickListener(){
751             public void onClick(DialogInterface dialog, int whichButton) {
752                 try {
753                     IImConnection conn = mApp.getConnection(mProviderId);
754                     IContactListManager manager = conn.getContactListManager();
755                     manager.blockContact(mUserName);
756                     mScreen.finish();
757                 } catch (RemoteException e) {
758                     mHandler.showServiceErrorAlert();
759                 }
760             }
761         };
762 
763         Resources r = getResources();
764 
765         // The positive button is deliberately set as no so that
766         // the no is the default value
767         new AlertDialog.Builder(mContext)
768             .setTitle(R.string.confirm)
769             .setMessage(r.getString(R.string.confirm_block_contact, mNickName))
770             .setPositiveButton(R.string.yes, confirmListener) // default button
771             .setNegativeButton(R.string.no, null)
772             .setCancelable(false)
773             .show();
774     }
775 
getProviderId()776     public long getProviderId() {
777         return mProviderId;
778     }
779 
getAccountId()780     public long getAccountId() {
781         return mAccountId;
782     }
783 
getUserName()784     public String getUserName() {
785         return mUserName;
786     }
787 
getChatId()788     public long getChatId () {
789         try {
790             return mChatSession == null ? -1 : mChatSession.getId();
791         } catch (RemoteException e) {
792             mHandler.showServiceErrorAlert();
793             return -1;
794         }
795     }
796 
getCurrentChatSession()797     public IChatSession getCurrentChatSession() {
798         return mChatSession;
799     }
800 
getChatSessionManager(long providerId)801     private IChatSessionManager getChatSessionManager(long providerId) {
802         if (mChatSessionMgr == null) {
803             IImConnection conn = mApp.getConnection(providerId);
804             if (conn != null) {
805                 try {
806                     mChatSessionMgr = conn.getChatSessionManager();
807                 } catch (RemoteException e) {
808                     mHandler.showServiceErrorAlert();
809                 }
810             }
811         }
812         return mChatSessionMgr;
813     }
814 
getChatSession(Cursor cursor)815     private IChatSession getChatSession(Cursor cursor) {
816         long providerId = cursor.getLong(PROVIDER_COLUMN);
817         String username = cursor.getString(USERNAME_COLUMN);
818 
819         IChatSessionManager sessionMgr = getChatSessionManager(providerId);
820         if (sessionMgr != null) {
821             try {
822                 return sessionMgr.getChatSession(username);
823             } catch (RemoteException e) {
824                 mHandler.showServiceErrorAlert();
825             }
826         }
827         return null;
828     }
829 
isGroupChat()830     boolean isGroupChat() {
831         return Imps.Contacts.TYPE_GROUP == mType;
832     }
833 
sendMessage()834     void sendMessage() {
835         String msg = mEdtInput.getText().toString();
836 
837         if (TextUtils.isEmpty(msg.trim())) {
838             return;
839         }
840 
841         if (mChatSession != null) {
842             try {
843                 mChatSession.sendMessage(msg);
844                 mEdtInput.setText("");
845                 mEdtInput.requestFocus();
846                 requeryCursor();
847             } catch (RemoteException e) {
848                 mHandler.showServiceErrorAlert();
849             }
850         }
851 
852         // Close the soft on-screen keyboard if we're in landscape mode so the user can see the
853         // conversation.
854         Configuration config = getResources().getConfiguration();
855         if (config.orientation == config.ORIENTATION_LANDSCAPE) {
856             closeSoftKeyboard();
857         }
858     }
859 
registerChatListener()860     void registerChatListener() {
861         if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)){
862             log("registerChatListener");
863         }
864         try {
865             if (mChatSession != null) {
866                 mChatSession.registerChatListener(mChatListener);
867             }
868             IImConnection conn = mApp.getConnection(mProviderId);
869             if (conn != null) {
870                 IContactListManager listMgr = conn.getContactListManager();
871                 listMgr.registerContactListListener(mContactListListener);
872             }
873             mApp.dismissChatNotification(mProviderId, mUserName);
874         } catch (RemoteException e) {
875             Log.w(ImApp.LOG_TAG, "<ChatView> registerChatListener fail:" + e.getMessage());
876         }
877     }
878 
unregisterChatListener()879     void unregisterChatListener() {
880         if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)){
881             log("unregisterChatListener");
882         }
883         try {
884             if (mChatSession != null) {
885                 mChatSession.unregisterChatListener(mChatListener);
886             }
887             IImConnection conn = mApp.getConnection(mProviderId);
888             if (conn != null) {
889                 IContactListManager listMgr = conn.getContactListManager();
890                 listMgr.unregisterContactListListener(mContactListListener);
891             }
892         } catch (RemoteException e) {
893             Log.w(ImApp.LOG_TAG, "<ChatView> unregisterChatListener fail:" + e.getMessage());
894         }
895     }
896 
registerChatSessionListener()897     void registerChatSessionListener() {
898         IChatSessionManager sessionMgr = getChatSessionManager(mProviderId);
899         if (sessionMgr != null) {
900             mChatSessionListener = new ChatSessionListener();
901             try {
902                 sessionMgr.registerChatSessionListener(mChatSessionListener);
903             } catch (RemoteException e) {
904                 mHandler.showServiceErrorAlert();
905             }
906         }
907     }
908 
unregisterChatSessionListener()909     void unregisterChatSessionListener() {
910         if (mChatSessionListener != null) {
911             try {
912                 IChatSessionManager sessionMgr = getChatSessionManager(mProviderId);
913                 sessionMgr.unregisterChatSessionListener(mChatSessionListener);
914                 // We unregister the listener when the chat session we are
915                 // waiting for has been created or the activity is stopped.
916                 // Clear the listener so that we won't unregister the listener
917                 // twice.
918                 mChatSessionListener = null;
919             } catch (RemoteException e) {
920                 mHandler.showServiceErrorAlert();
921             }
922         }
923     }
924 
updateWarningView()925     void updateWarningView() {
926         int visibility = View.GONE;
927         int iconVisibility = View.GONE;
928         String message = null;
929         boolean isConnected;
930 
931         try {
932             IImConnection conn = mApp.getConnection(mProviderId);
933             isConnected = (conn == null) ? false
934                     : conn.getState() != ImConnection.SUSPENDED;
935         } catch (RemoteException e) {
936             // do nothing
937             return;
938         }
939 
940         if (isConnected) {
941             if (mType == Imps.Contacts.TYPE_TEMPORARY) {
942                 visibility = View.VISIBLE;
943                 message = mContext.getString(R.string.contact_not_in_list_warning, mNickName);
944             } else if (mPresenceStatus == Imps.Presence.OFFLINE) {
945                 visibility = View.VISIBLE;
946                 message = mContext.getString(R.string.contact_offline_warning, mNickName);
947             }
948         } else {
949             visibility = View.VISIBLE;
950             iconVisibility = View.VISIBLE;
951             message = mContext.getString(R.string.disconnected_warning);
952         }
953 
954         mStatusWarningView.setVisibility(visibility);
955         if (visibility == View.VISIBLE) {
956             mWarningIcon.setVisibility(iconVisibility);
957             mWarningText.setText(message);
958         }
959     }
960 
961     @Override
dispatchKeyEvent(KeyEvent event)962     public boolean dispatchKeyEvent(KeyEvent event) {
963         userActionDetected();
964         return super.dispatchKeyEvent(event);
965     }
966 
967     @Override
dispatchTouchEvent(MotionEvent ev)968     public boolean dispatchTouchEvent(MotionEvent ev) {
969         userActionDetected();
970         return super.dispatchTouchEvent(ev);
971     }
972 
973     @Override
dispatchTrackballEvent(MotionEvent ev)974     public boolean dispatchTrackballEvent(MotionEvent ev) {
975         userActionDetected();
976         return super.dispatchTrackballEvent(ev);
977     }
978 
userActionDetected()979     private void userActionDetected() {
980         if (mChatSession != null) {
981             try {
982                 mChatSession.markAsRead();
983             } catch (RemoteException e) {
984                 mHandler.showServiceErrorAlert();
985             }
986         }
987     }
988 
989     private final class ChatViewHandler extends SimpleAlertHandler {
ChatViewHandler()990         public ChatViewHandler() {
991             super(mScreen);
992         }
993 
994         @Override
handleMessage(Message msg)995         public void handleMessage(Message msg) {
996             long providerId = ((long)msg.arg1 << 32) | msg.arg2;
997             if (providerId != mProviderId) {
998                 return;
999             }
1000 
1001             switch(msg.what) {
1002             case ImApp.EVENT_CONNECTION_LOGGED_IN:
1003                 log("Connection resumed");
1004                 updateWarningView();
1005                 return;
1006             case ImApp.EVENT_CONNECTION_SUSPENDED:
1007                 log("Connection suspended");
1008                 updateWarningView();
1009                 return;
1010             }
1011 
1012             super.handleMessage(msg);
1013         }
1014     }
1015 
1016     class ChatSessionListener extends ChatSessionListenerAdapter {
1017         @Override
onChatSessionCreated(IChatSession session)1018         public void onChatSessionCreated(IChatSession session) {
1019             try {
1020                 if (session.isGroupChatSession()) {
1021                     final long id = session.getId();
1022                     unregisterChatSessionListener();
1023                     mHandler.post(new Runnable() {
1024                         public void run() {
1025                             bindChat(id);
1026                         }});
1027                 }
1028             } catch (RemoteException e) {
1029                 mHandler.showServiceErrorAlert();
1030             }
1031         }
1032     }
1033 
1034     public static class DeltaCursor implements Cursor {
1035         static final String DELTA_COLUMN_NAME = "delta";
1036 
1037         private Cursor mInnerCursor;
1038         private String[] mColumnNames;
1039         private int mDateColumn = -1;
1040         private int mDeltaColumn = -1;
1041 
DeltaCursor(Cursor cursor)1042         DeltaCursor(Cursor cursor) {
1043             mInnerCursor = cursor;
1044 
1045             String[] columnNames = cursor.getColumnNames();
1046             int len = columnNames.length;
1047 
1048             mColumnNames = new String[len + 1];
1049 
1050             for (int i = 0 ; i < len ; i++) {
1051                 mColumnNames[i] = columnNames[i];
1052                 if (mColumnNames[i].equals(Imps.Messages.DATE)) {
1053                     mDateColumn = i;
1054                 }
1055             }
1056 
1057             mDeltaColumn = len;
1058             mColumnNames[mDeltaColumn] = DELTA_COLUMN_NAME;
1059 
1060             //if (DBG) log("##### DeltaCursor constructor: mDeltaColumn=" +
1061             //        mDeltaColumn + ", columnName=" + mColumnNames[mDeltaColumn]);
1062         }
1063 
getCount()1064         public int getCount() {
1065             return mInnerCursor.getCount();
1066         }
1067 
getPosition()1068         public int getPosition() {
1069             return mInnerCursor.getPosition();
1070         }
1071 
move(int offset)1072         public boolean move(int offset) {
1073             return mInnerCursor.move(offset);
1074         }
1075 
moveToPosition(int position)1076         public boolean moveToPosition(int position) {
1077             return mInnerCursor.moveToPosition(position);
1078         }
1079 
moveToFirst()1080         public boolean moveToFirst() {
1081             return mInnerCursor.moveToFirst();
1082         }
1083 
moveToLast()1084         public boolean moveToLast() {
1085             return mInnerCursor.moveToLast();
1086         }
1087 
moveToNext()1088         public boolean moveToNext() {
1089             return mInnerCursor.moveToNext();
1090         }
1091 
moveToPrevious()1092         public boolean moveToPrevious() {
1093             return mInnerCursor.moveToPrevious();
1094         }
1095 
isFirst()1096         public boolean isFirst() {
1097             return mInnerCursor.isFirst();
1098         }
1099 
isLast()1100         public boolean isLast() {
1101             return mInnerCursor.isLast();
1102         }
1103 
isBeforeFirst()1104         public boolean isBeforeFirst() {
1105             return mInnerCursor.isBeforeFirst();
1106         }
1107 
isAfterLast()1108         public boolean isAfterLast() {
1109             return mInnerCursor.isAfterLast();
1110         }
1111 
deleteRow()1112         public boolean deleteRow() {
1113             return mInnerCursor.deleteRow();
1114         }
1115 
getColumnIndex(String columnName)1116         public int getColumnIndex(String columnName) {
1117             if (DELTA_COLUMN_NAME.equals(columnName)) {
1118                 return mDeltaColumn;
1119             }
1120 
1121             int columnIndex = mInnerCursor.getColumnIndex(columnName);
1122             return columnIndex;
1123         }
1124 
getColumnIndexOrThrow(String columnName)1125         public int getColumnIndexOrThrow(String columnName) throws IllegalArgumentException {
1126             if (DELTA_COLUMN_NAME.equals(columnName)) {
1127                 return mDeltaColumn;
1128             }
1129 
1130             return mInnerCursor.getColumnIndexOrThrow(columnName);
1131         }
1132 
getColumnName(int columnIndex)1133         public String getColumnName(int columnIndex) {
1134             if (columnIndex == mDeltaColumn) {
1135                 return DELTA_COLUMN_NAME;
1136             }
1137 
1138             return mInnerCursor.getColumnName(columnIndex);
1139         }
1140 
getColumnCount()1141         public int getColumnCount() {
1142             return mInnerCursor.getColumnCount() + 1;
1143         }
1144 
supportsUpdates()1145         public boolean supportsUpdates() {
1146             return mInnerCursor.supportsUpdates();
1147         }
1148 
hasUpdates()1149         public boolean hasUpdates() {
1150             return mInnerCursor.hasUpdates();
1151         }
1152 
updateBlob(int columnIndex, byte[] value)1153         public boolean updateBlob(int columnIndex, byte[] value) {
1154             if (columnIndex == mDeltaColumn) {
1155                 return false;
1156             }
1157 
1158             return mInnerCursor.updateBlob(columnIndex, value);
1159         }
1160 
updateString(int columnIndex, String value)1161         public boolean updateString(int columnIndex, String value) {
1162             if (columnIndex == mDeltaColumn) {
1163                 return false;
1164             }
1165 
1166             return mInnerCursor.updateString(columnIndex, value);
1167         }
1168 
updateShort(int columnIndex, short value)1169         public boolean updateShort(int columnIndex, short value) {
1170             if (columnIndex == mDeltaColumn) {
1171                 return false;
1172             }
1173 
1174             return mInnerCursor.updateShort(columnIndex, value);
1175         }
1176 
updateInt(int columnIndex, int value)1177         public boolean updateInt(int columnIndex, int value) {
1178             if (columnIndex == mDeltaColumn) {
1179                 return false;
1180             }
1181 
1182             return mInnerCursor.updateInt(columnIndex, value);
1183         }
1184 
updateLong(int columnIndex, long value)1185         public boolean updateLong(int columnIndex, long value) {
1186             if (columnIndex == mDeltaColumn) {
1187                 return false;
1188             }
1189 
1190             return mInnerCursor.updateLong(columnIndex, value);
1191         }
1192 
updateFloat(int columnIndex, float value)1193         public boolean updateFloat(int columnIndex, float value) {
1194             if (columnIndex == mDeltaColumn) {
1195                 return false;
1196             }
1197 
1198             return mInnerCursor.updateFloat(columnIndex, value);
1199         }
1200 
updateDouble(int columnIndex, double value)1201         public boolean updateDouble(int columnIndex, double value) {
1202             if (columnIndex == mDeltaColumn) {
1203                 return false;
1204             }
1205 
1206             return mInnerCursor.updateDouble(columnIndex, value);
1207         }
1208 
updateToNull(int columnIndex)1209         public boolean updateToNull(int columnIndex) {
1210             if (columnIndex == mDeltaColumn) {
1211                 return false;
1212             }
1213 
1214             return mInnerCursor.updateToNull(columnIndex);
1215         }
1216 
commitUpdates()1217         public boolean commitUpdates() {
1218             return mInnerCursor.commitUpdates();
1219         }
1220 
commitUpdates(Map<? extends Long, ? extends Map<String,Object>> values)1221         public boolean commitUpdates(Map<? extends Long,
1222                 ? extends Map<String,Object>> values) {
1223             return mInnerCursor.commitUpdates(values);
1224         }
1225 
abortUpdates()1226         public void abortUpdates() {
1227             mInnerCursor.abortUpdates();
1228         }
1229 
deactivate()1230         public void deactivate() {
1231             mInnerCursor.deactivate();
1232         }
1233 
requery()1234         public boolean requery() {
1235             return mInnerCursor.requery();
1236         }
1237 
close()1238         public void close() {
1239             mInnerCursor.close();
1240         }
1241 
isClosed()1242         public boolean isClosed() {
1243             return mInnerCursor.isClosed();
1244         }
1245 
registerContentObserver(ContentObserver observer)1246         public void registerContentObserver(ContentObserver observer) {
1247             mInnerCursor.registerContentObserver(observer);
1248         }
1249 
unregisterContentObserver(ContentObserver observer)1250         public void unregisterContentObserver(ContentObserver observer) {
1251             mInnerCursor.unregisterContentObserver(observer);
1252         }
1253 
registerDataSetObserver(DataSetObserver observer)1254         public void registerDataSetObserver(DataSetObserver observer) {
1255             mInnerCursor.registerDataSetObserver(observer);
1256         }
1257 
unregisterDataSetObserver(DataSetObserver observer)1258         public void unregisterDataSetObserver(DataSetObserver observer) {
1259             mInnerCursor.unregisterDataSetObserver(observer);
1260         }
1261 
setNotificationUri(ContentResolver cr, Uri uri)1262         public void setNotificationUri(ContentResolver cr, Uri uri) {
1263             mInnerCursor.setNotificationUri(cr, uri);
1264         }
1265 
getWantsAllOnMoveCalls()1266         public boolean getWantsAllOnMoveCalls() {
1267             return mInnerCursor.getWantsAllOnMoveCalls();
1268         }
1269 
getExtras()1270         public Bundle getExtras() {
1271             return mInnerCursor.getExtras();
1272         }
1273 
respond(Bundle extras)1274         public Bundle respond(Bundle extras) {
1275             return mInnerCursor.respond(extras);
1276         }
1277 
getColumnNames()1278         public String[] getColumnNames() {
1279             return mColumnNames;
1280         }
1281 
checkPosition()1282         private void checkPosition() {
1283             int pos = mInnerCursor.getPosition();
1284             int count = mInnerCursor.getCount();
1285 
1286             if (-1 == pos || count == pos) {
1287                 throw new CursorIndexOutOfBoundsException(pos, count);
1288             }
1289         }
1290 
getBlob(int column)1291         public byte[] getBlob(int column) {
1292             checkPosition();
1293 
1294             if (column == mDeltaColumn) {
1295                 return null;
1296             }
1297 
1298             return mInnerCursor.getBlob(column);
1299         }
1300 
getString(int column)1301         public String getString(int column) {
1302             checkPosition();
1303 
1304             if (column == mDeltaColumn) {
1305                 long value = getDeltaValue();
1306                 return Long.toString(value);
1307             }
1308 
1309             return mInnerCursor.getString(column);
1310         }
1311 
copyStringToBuffer(int columnIndex, CharArrayBuffer buffer)1312         public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
1313             checkPosition();
1314 
1315             if (columnIndex == mDeltaColumn) {
1316                 long value = getDeltaValue();
1317                 String strValue = Long.toString(value);
1318                 int len = strValue.length();
1319                 char[] data = buffer.data;
1320                 if (data == null || data.length < len) {
1321                     buffer.data = strValue.toCharArray();
1322                 } else {
1323                     strValue.getChars(0, len, data, 0);
1324                 }
1325                 buffer.sizeCopied = strValue.length();
1326             } else {
1327                 mInnerCursor.copyStringToBuffer(columnIndex, buffer);
1328             }
1329         }
1330 
getShort(int column)1331         public short getShort(int column) {
1332             checkPosition();
1333 
1334             if (column == mDeltaColumn) {
1335                 return (short)getDeltaValue();
1336             }
1337 
1338             return mInnerCursor.getShort(column);
1339         }
1340 
getInt(int column)1341         public int getInt(int column) {
1342             checkPosition();
1343 
1344             if (column == mDeltaColumn) {
1345                 return (int)getDeltaValue();
1346             }
1347 
1348             return mInnerCursor.getInt(column);
1349         }
1350 
getLong(int column)1351         public long getLong(int column) {
1352         //if (DBG) log("DeltaCursor.getLong: column=" + column + ", mDeltaColumn=" + mDeltaColumn);
1353             checkPosition();
1354 
1355             if (column == mDeltaColumn) {
1356                 return getDeltaValue();
1357             }
1358 
1359             return mInnerCursor.getLong(column);
1360         }
1361 
getFloat(int column)1362         public float getFloat(int column) {
1363             checkPosition();
1364 
1365             if (column == mDeltaColumn) {
1366                 return getDeltaValue();
1367             }
1368 
1369             return mInnerCursor.getFloat(column);
1370         }
1371 
getDouble(int column)1372         public double getDouble(int column) {
1373             checkPosition();
1374 
1375             if (column == mDeltaColumn) {
1376                 return getDeltaValue();
1377             }
1378 
1379             return mInnerCursor.getDouble(column);
1380         }
1381 
isNull(int column)1382         public boolean isNull(int column) {
1383             checkPosition();
1384 
1385             if (column == mDeltaColumn) {
1386                 return false;
1387             }
1388 
1389             return mInnerCursor.isNull(column);
1390         }
1391 
getDeltaValue()1392         private long getDeltaValue() {
1393             int pos = mInnerCursor.getPosition();
1394             //Log.i(LOG_TAG, "getDeltaValue: mPos=" + mPos);
1395 
1396             long t2, t1;
1397 
1398             if (pos == getCount()-1) {
1399                 t1 = mInnerCursor.getLong(mDateColumn);
1400                 t2 = System.currentTimeMillis();
1401             } else {
1402                 mInnerCursor.moveToPosition(pos + 1);
1403                 t2 = mInnerCursor.getLong(mDateColumn);
1404                 mInnerCursor.moveToPosition(pos);
1405                 t1 = mInnerCursor.getLong(mDateColumn);
1406             }
1407 
1408             return t2 - t1;
1409         }
1410     }
1411 
1412     private class MessageAdapter extends CursorAdapter implements AbsListView.OnScrollListener {
1413         private int mScrollState;
1414         private boolean mNeedRequeryCursor;
1415 
1416         private int mNicknameColumn;
1417         private int mBodyColumn;
1418         private int mDateColumn;
1419         private int mTypeColumn;
1420         private int mErrCodeColumn;
1421         private int mDeltaColumn;
1422         private ChatBackgroundMaker mBgMaker;
1423 
1424         private LayoutInflater mInflater;
1425 
MessageAdapter(Activity context, Cursor c)1426         public MessageAdapter(Activity context, Cursor c) {
1427             super(context, c, false);
1428             mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
1429             mBgMaker = new ChatBackgroundMaker(context);
1430             if (c != null) {
1431                 resolveColumnIndex(c);
1432             }
1433         }
1434 
resolveColumnIndex(Cursor c)1435         private void resolveColumnIndex(Cursor c) {
1436             mNicknameColumn = c.getColumnIndexOrThrow(Imps.Messages.NICKNAME);
1437             mBodyColumn = c.getColumnIndexOrThrow(Imps.Messages.BODY);
1438             mDateColumn = c.getColumnIndexOrThrow(Imps.Messages.DATE);
1439             mTypeColumn = c.getColumnIndexOrThrow(Imps.Messages.TYPE);
1440             mErrCodeColumn = c.getColumnIndexOrThrow(Imps.Messages.ERROR_CODE);
1441             mDeltaColumn = c.getColumnIndexOrThrow(DeltaCursor.DELTA_COLUMN_NAME);
1442         }
1443 
1444         @Override
changeCursor(Cursor cursor)1445         public void changeCursor(Cursor cursor) {
1446             super.changeCursor(cursor);
1447             if (cursor != null) {
1448                 resolveColumnIndex(cursor);
1449             }
1450         }
1451 
1452         @Override
newView(Context context, Cursor cursor, ViewGroup parent)1453         public View newView(Context context, Cursor cursor, ViewGroup parent) {
1454             return mInflater.inflate(R.layout.new_message_item, parent, false);
1455         }
1456 
1457         @Override
bindView(View view, Context context, Cursor cursor)1458         public void bindView(View view, Context context, Cursor cursor) {
1459             MessageView chatMsgView = (MessageView) view;
1460 
1461             int type = cursor.getInt(mTypeColumn);
1462             String contact = isGroupChat() ? cursor.getString(mNicknameColumn) : mNickName;
1463             String body = cursor.getString(mBodyColumn);
1464             long delta = cursor.getLong(mDeltaColumn);
1465             boolean showTimeStamp = (delta > SHOW_TIME_STAMP_INTERVAL);
1466             Date date = showTimeStamp ? new Date(cursor.getLong(mDateColumn)) : null;
1467 
1468             switch (type) {
1469                 case Imps.MessageType.INCOMING:
1470                     chatMsgView.bindIncomingMessage(contact, body, date, mMarkup, isScrolling());
1471                     break;
1472 
1473                 case Imps.MessageType.OUTGOING:
1474                 case Imps.MessageType.POSTPONED:
1475                     int errCode = cursor.getInt(mErrCodeColumn);
1476                     if (errCode != 0) {
1477                         chatMsgView.bindErrorMessage(errCode);
1478                     } else {
1479                         chatMsgView.bindOutgoingMessage(body, date, mMarkup, isScrolling());
1480                     }
1481                     break;
1482 
1483                 default:
1484                     chatMsgView.bindPresenceMessage(contact, type, isGroupChat(), isScrolling());
1485             }
1486             if (!isScrolling()) {
1487                 mBgMaker.setBackground(chatMsgView, contact, type);
1488             }
1489 
1490             // if showTimeStamp is false for the latest message, then set a timer to query the
1491             // cursor again in a minute, so we can update the last message timestamp if no new
1492             // message is received
1493             if (cursor.getPosition() == cursor.getCount()-1) {
1494                 if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)){
1495                     log("delta = " + delta + ", showTs=" + showTimeStamp);
1496                 }
1497                 if (!showTimeStamp) {
1498                     scheduleRequery(SHOW_TIME_STAMP_INTERVAL);
1499                 } else {
1500                     cancelRequery();
1501                 }
1502             }
1503         }
1504 
onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)1505         public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
1506                 int totalItemCount) {
1507             // do nothing
1508         }
1509 
onScrollStateChanged(AbsListView view, int scrollState)1510         public void onScrollStateChanged(AbsListView view, int scrollState) {
1511             int oldState = mScrollState;
1512             mScrollState = scrollState;
1513 
1514             if (mChatSession != null) {
1515                 try {
1516                     mChatSession.markAsRead();
1517                 } catch (RemoteException e) {
1518                     mHandler.showServiceErrorAlert();
1519                 }
1520             }
1521 
1522             if (oldState == OnScrollListener.SCROLL_STATE_FLING) {
1523                 if (mNeedRequeryCursor) {
1524                     requeryCursor();
1525                 } else {
1526                     notifyDataSetChanged();
1527                 }
1528             }
1529         }
1530 
isScrolling()1531         boolean isScrolling() {
1532             return mScrollState == OnScrollListener.SCROLL_STATE_FLING;
1533         }
1534 
setNeedRequeryCursor(boolean requeryCursor)1535         void setNeedRequeryCursor(boolean requeryCursor) {
1536             mNeedRequeryCursor = requeryCursor;
1537         }
1538     }
1539 }
1540