| /* |
| * Copyright (C) 2007-2008 Esmertec AG. |
| * Copyright (C) 2007-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 com.android.im.service; |
| |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| import android.content.ContentResolver; |
| import android.content.ContentUris; |
| import android.content.ContentValues; |
| import android.database.Cursor; |
| import android.net.Uri; |
| import android.os.RemoteCallbackList; |
| import android.os.RemoteException; |
| import android.util.Log; |
| |
| import com.android.im.IChatSessionManager; |
| import com.android.im.IConnectionListener; |
| import com.android.im.IContactListManager; |
| import com.android.im.IImConnection; |
| import com.android.im.IInvitationListener; |
| import com.android.im.engine.ChatGroupManager; |
| import com.android.im.engine.ConnectionListener; |
| import com.android.im.engine.Contact; |
| import com.android.im.engine.ContactListManager; |
| import com.android.im.engine.ImConnection; |
| import com.android.im.engine.ImErrorInfo; |
| import com.android.im.engine.ImException; |
| import com.android.im.engine.Invitation; |
| import com.android.im.engine.InvitationListener; |
| import com.android.im.engine.LoginInfo; |
| import com.android.im.engine.Presence; |
| import com.android.im.provider.Imps; |
| |
| public class ImConnectionAdapter extends IImConnection.Stub { |
| private static final String TAG = RemoteImService.TAG; |
| |
| private static final String[] SESSION_COOKIE_PROJECTION = { |
| Imps.SessionCookies.NAME, |
| Imps.SessionCookies.VALUE, |
| }; |
| |
| private static final int COLUMN_SESSION_COOKIE_NAME = 0; |
| private static final int COLUMN_SESSION_COOKIE_VALUE = 1; |
| |
| ImConnection mConnection; |
| private ConnectionListenerAdapter mConnectionListener; |
| private InvitationListenerAdapter mInvitationListener; |
| |
| final RemoteCallbackList<IConnectionListener> mRemoteConnListeners |
| = new RemoteCallbackList<IConnectionListener>(); |
| |
| ChatSessionManagerAdapter mChatSessionManager; |
| ContactListManagerAdapter mContactListManager; |
| |
| ChatGroupManager mGroupManager; |
| RemoteImService mService; |
| |
| long mProviderId = -1; |
| long mAccountId = -1; |
| boolean mAutoLoadContacts; |
| int mConnectionState = ImConnection.DISCONNECTED; |
| |
| public ImConnectionAdapter(long providerId, ImConnection connection, |
| RemoteImService service) { |
| mProviderId = providerId; |
| mConnection = connection; |
| mService = service; |
| mConnectionListener = new ConnectionListenerAdapter(); |
| mConnection.addConnectionListener(mConnectionListener); |
| if ((connection.getCapability() & ImConnection.CAPABILITY_GROUP_CHAT) != 0) { |
| mGroupManager = mConnection.getChatGroupManager(); |
| mInvitationListener = new InvitationListenerAdapter(); |
| mGroupManager.setInvitationListener(mInvitationListener); |
| } |
| } |
| |
| public ImConnection getAdaptee() { |
| return mConnection; |
| } |
| |
| public RemoteImService getContext() { |
| return mService; |
| } |
| |
| public long getProviderId() { |
| return mProviderId; |
| } |
| |
| public long getAccountId() { |
| return mAccountId; |
| } |
| |
| public int[] getSupportedPresenceStatus() { |
| return mConnection.getSupportedPresenceStatus(); |
| } |
| |
| public void networkTypeChanged() { |
| mConnection.networkTypeChanged(); |
| } |
| |
| void reestablishSession() { |
| mConnectionState = ImConnection.LOGGING_IN; |
| |
| ContentResolver cr = mService.getContentResolver(); |
| if ((mConnection.getCapability() & ImConnection.CAPABILITY_SESSION_REESTABLISHMENT) != 0) { |
| HashMap<String, String> cookie = querySessionCookie(cr); |
| if (cookie != null) { |
| Log.d(TAG, "re-establish session"); |
| try { |
| mConnection.reestablishSessionAsync(cookie); |
| } catch (IllegalArgumentException e) { |
| Log.e(TAG, "Invalid session cookie, probably modified by others."); |
| clearSessionCookie(cr); |
| } |
| } |
| } |
| } |
| |
| private Uri getSessionCookiesUri() { |
| Uri.Builder builder = Imps.SessionCookies.CONTENT_URI_SESSION_COOKIES_BY.buildUpon(); |
| ContentUris.appendId(builder, mProviderId); |
| ContentUris.appendId(builder, mAccountId); |
| |
| return builder.build(); |
| } |
| |
| public void login(long accountId, String userName, String password, |
| boolean autoLoadContacts) { |
| mAccountId = accountId; |
| mAutoLoadContacts = autoLoadContacts; |
| mConnectionState = ImConnection.LOGGING_IN; |
| |
| mConnection.loginAsync(new LoginInfo(userName, password)); |
| |
| mChatSessionManager = new ChatSessionManagerAdapter(this); |
| mContactListManager = new ContactListManagerAdapter(this); |
| } |
| |
| private HashMap<String, String> querySessionCookie(ContentResolver cr) { |
| Cursor c = cr.query(getSessionCookiesUri(), SESSION_COOKIE_PROJECTION, null, null, null); |
| if (c == null) { |
| return null; |
| } |
| |
| HashMap<String, String> cookie = null; |
| if (c.getCount() > 0) { |
| cookie = new HashMap<String, String>(); |
| while(c.moveToNext()) { |
| cookie.put(c.getString(COLUMN_SESSION_COOKIE_NAME), |
| c.getString(COLUMN_SESSION_COOKIE_VALUE)); |
| } |
| } |
| |
| c.close(); |
| return cookie; |
| } |
| |
| public void logout() { |
| mConnectionState = ImConnection.LOGGING_OUT; |
| mConnection.logoutAsync(); |
| } |
| |
| public synchronized void cancelLogin() { |
| if (mConnectionState >= ImConnection.LOGGED_IN) { |
| // too late |
| return; |
| } |
| |
| logout(); |
| } |
| |
| void suspend() { |
| mConnectionState = ImConnection.SUSPENDING; |
| mConnection.suspend(); |
| } |
| |
| public void registerConnectionListener(IConnectionListener listener) { |
| if (listener != null) { |
| mRemoteConnListeners.register(listener); |
| } |
| } |
| |
| public void unregisterConnectionListener(IConnectionListener listener) { |
| if (listener != null) { |
| mRemoteConnListeners.unregister(listener); |
| } |
| } |
| |
| public void setInvitationListener(IInvitationListener listener) { |
| if(mInvitationListener != null) { |
| mInvitationListener.mRemoteListener = listener; |
| } |
| } |
| |
| public IChatSessionManager getChatSessionManager() { |
| return mChatSessionManager; |
| } |
| |
| public IContactListManager getContactListManager() { |
| return mContactListManager; |
| } |
| |
| public int getChatSessionCount() { |
| if (mChatSessionManager == null) { |
| return 0; |
| } |
| return mChatSessionManager.getChatSessionCount(); |
| } |
| |
| public Contact getLoginUser() { |
| return mConnection.getLoginUser(); |
| } |
| |
| public Presence getUserPresence() { |
| return mConnection.getUserPresence(); |
| } |
| |
| public int updateUserPresence(Presence newPresence) { |
| try { |
| mConnection.updateUserPresenceAsync(newPresence); |
| } catch (ImException e) { |
| return e.getImError().getCode(); |
| } |
| |
| return ImErrorInfo.NO_ERROR; |
| } |
| |
| public int getState() { |
| return mConnectionState; |
| } |
| |
| public void rejectInvitation(long id){ |
| handleInvitation(id, false); |
| } |
| |
| public void acceptInvitation(long id) { |
| handleInvitation(id, true); |
| } |
| |
| private void handleInvitation(long id, boolean accept) { |
| if(mGroupManager == null) { |
| return; |
| } |
| ContentResolver cr = mService.getContentResolver(); |
| Cursor c = cr.query(ContentUris.withAppendedId(Imps.Invitation.CONTENT_URI, id), null, null, null, null); |
| if(c == null) { |
| return; |
| } |
| if(c.moveToFirst()) { |
| String inviteId = c.getString(c.getColumnIndexOrThrow(Imps.Invitation.INVITE_ID)); |
| int status; |
| if(accept) { |
| mGroupManager.acceptInvitationAsync(inviteId); |
| status = Imps.Invitation.STATUS_ACCEPTED; |
| } else { |
| mGroupManager.rejectInvitationAsync(inviteId); |
| status = Imps.Invitation.STATUS_REJECTED; |
| } |
| c.updateInt(c.getColumnIndexOrThrow(Imps.Invitation.STATUS), status); |
| c.commitUpdates(); |
| } |
| c.close(); |
| } |
| |
| void saveSessionCookie(ContentResolver cr) { |
| HashMap<String, String> cookies = mConnection.getSessionContext(); |
| |
| int i = 0; |
| ContentValues[] valuesList = new ContentValues[cookies.size()]; |
| |
| for(Map.Entry<String,String> entry : cookies.entrySet()){ |
| ContentValues values = new ContentValues(2); |
| |
| values.put(Imps.SessionCookies.NAME, entry.getKey()); |
| values.put(Imps.SessionCookies.VALUE, entry.getValue()); |
| |
| valuesList[i++] = values; |
| } |
| |
| cr.bulkInsert(getSessionCookiesUri(), valuesList); |
| } |
| |
| void clearSessionCookie(ContentResolver cr) { |
| cr.delete(getSessionCookiesUri(), null, null); |
| } |
| |
| void updateAccountStatusInDb() { |
| Presence p = getUserPresence(); |
| int presenceStatus = Imps.Presence.OFFLINE; |
| int connectionStatus = convertConnStateForDb(mConnectionState); |
| |
| if (p != null) { |
| presenceStatus = ContactListManagerAdapter.convertPresenceStatus(p); |
| } |
| |
| ContentResolver cr = mService.getContentResolver(); |
| Uri uri = Imps.AccountStatus.CONTENT_URI; |
| ContentValues values = new ContentValues(); |
| |
| values.put(Imps.AccountStatus.ACCOUNT, mAccountId); |
| values.put(Imps.AccountStatus.PRESENCE_STATUS, presenceStatus); |
| values.put(Imps.AccountStatus.CONNECTION_STATUS, connectionStatus); |
| |
| cr.insert(uri, values); |
| } |
| |
| private static int convertConnStateForDb(int state) { |
| switch (state) { |
| case ImConnection.DISCONNECTED: |
| case ImConnection.LOGGING_OUT: |
| return Imps.ConnectionStatus.OFFLINE; |
| |
| case ImConnection.LOGGING_IN: |
| return Imps.ConnectionStatus.CONNECTING; |
| |
| case ImConnection.LOGGED_IN: |
| return Imps.ConnectionStatus.ONLINE; |
| |
| case ImConnection.SUSPENDED: |
| case ImConnection.SUSPENDING: |
| return Imps.ConnectionStatus.SUSPENDED; |
| |
| default: |
| return Imps.ConnectionStatus.OFFLINE; |
| } |
| } |
| |
| final class ConnectionListenerAdapter implements ConnectionListener{ |
| public void onStateChanged(final int state, final ImErrorInfo error) { |
| synchronized (this) { |
| if (state == ImConnection.LOGGED_IN |
| && mConnectionState == ImConnection.LOGGING_OUT) { |
| // A bit tricky here. The engine did login successfully |
| // but the notification comes a bit late; user has already |
| // issued a cancelLogin() and that cannot be undone. Here |
| // we have to ignore the LOGGED_IN event and wait for |
| // the upcoming DISCONNECTED. |
| return; |
| } |
| |
| if (state != ImConnection.DISCONNECTED) { |
| mConnectionState = state; |
| } |
| } |
| |
| ContentResolver cr = mService.getContentResolver(); |
| if(state == ImConnection.LOGGED_IN) { |
| if ((mConnection.getCapability() & ImConnection.CAPABILITY_SESSION_REESTABLISHMENT) != 0){ |
| saveSessionCookie(cr); |
| } |
| |
| if(mAutoLoadContacts && mContactListManager.getState() |
| != ContactListManager.LISTS_LOADED) { |
| mContactListManager.loadContactLists(); |
| } |
| |
| for (ChatSessionAdapter session : mChatSessionManager.mActiveSessions.values()) { |
| session.sendPostponedMessages(); |
| } |
| } else if (state == ImConnection.LOGGING_OUT) { |
| // The engine has started to logout the connection, remove it |
| // from the active connection list. |
| mService.removeConnection(ImConnectionAdapter.this); |
| } else if(state == ImConnection.DISCONNECTED) { |
| mService.removeConnection(ImConnectionAdapter.this); |
| |
| clearSessionCookie(cr); |
| // mContactListManager might still be null if we fail |
| // immediately in loginAsync (say, an invalid host URL) |
| if (mContactListManager != null) { |
| mContactListManager.clearOnLogout(); |
| } |
| if (mChatSessionManager != null) { |
| mChatSessionManager.closeAllChatSessions(); |
| } |
| |
| mConnectionState = state; |
| } else if(state == ImConnection.SUSPENDED && error != null) { |
| // re-establish failed, schedule to retry |
| // TODO increase delay after retry failed. |
| mService.scheduleReconnect(15000); |
| } |
| |
| updateAccountStatusInDb(); |
| |
| final int N = mRemoteConnListeners.beginBroadcast(); |
| for (int i = 0; i < N; i++) { |
| IConnectionListener listener = mRemoteConnListeners.getBroadcastItem(i); |
| try { |
| listener.onStateChanged(ImConnectionAdapter.this, state, error); |
| } catch (RemoteException e) { |
| // The RemoteCallbackList will take care of removing the |
| // dead listeners. |
| } |
| } |
| mRemoteConnListeners.finishBroadcast(); |
| } |
| |
| public void onUserPresenceUpdated() { |
| updateAccountStatusInDb(); |
| |
| final int N = mRemoteConnListeners.beginBroadcast(); |
| for (int i = 0; i < N; i++) { |
| IConnectionListener listener = mRemoteConnListeners.getBroadcastItem(i); |
| try { |
| listener.onUserPresenceUpdated(ImConnectionAdapter.this); |
| } catch (RemoteException e) { |
| // The RemoteCallbackList will take care of removing the |
| // dead listeners. |
| } |
| } |
| mRemoteConnListeners.finishBroadcast(); |
| } |
| |
| public void onUpdatePresenceError(final ImErrorInfo error) { |
| final int N = mRemoteConnListeners.beginBroadcast(); |
| for (int i = 0; i < N; i++) { |
| IConnectionListener listener = mRemoteConnListeners.getBroadcastItem(i); |
| try { |
| listener.onUpdatePresenceError(ImConnectionAdapter.this, error); |
| } catch (RemoteException e) { |
| // The RemoteCallbackList will take care of removing the |
| // dead listeners. |
| } |
| } |
| mRemoteConnListeners.finishBroadcast(); |
| } |
| } |
| |
| final class InvitationListenerAdapter implements InvitationListener { |
| IInvitationListener mRemoteListener; |
| |
| public void onGroupInvitation(Invitation invitation) { |
| String sender = invitation.getSender().getScreenName(); |
| ContentValues values = new ContentValues(7); |
| values.put(Imps.Invitation.PROVIDER, mProviderId); |
| values.put(Imps.Invitation.ACCOUNT, mAccountId); |
| values.put(Imps.Invitation.INVITE_ID, invitation.getInviteID()); |
| values.put(Imps.Invitation.SENDER, sender); |
| values.put(Imps.Invitation.GROUP_NAME, invitation.getGroupAddress().getScreenName()); |
| values.put(Imps.Invitation.NOTE, invitation.getReason()); |
| values.put(Imps.Invitation.STATUS, Imps.Invitation.STATUS_PENDING); |
| ContentResolver resolver = mService.getContentResolver(); |
| Uri uri = resolver.insert(Imps.Invitation.CONTENT_URI, values); |
| long id = ContentUris.parseId(uri); |
| try { |
| if (mRemoteListener != null) { |
| mRemoteListener.onGroupInvitation(id); |
| return; |
| } |
| } catch (RemoteException e) { |
| Log.i(TAG, "onGroupInvitation: dead listener " |
| + mRemoteListener +"; removing"); |
| mRemoteListener = null; |
| } |
| // No listener registered or failed to notify the listener, send a |
| // notification instead. |
| mService.getStatusBarNotifier().notifyGroupInvitation(mProviderId, mAccountId, id, sender); |
| } |
| } |
| } |