FileDocCategorySizeDatePackage
ImConnectionAdapter.javaAPI DocAndroid 1.5 API17015Wed May 06 22:42:46 BST 2009com.android.im.service

ImConnectionAdapter.java

/*
 * 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.provider.Im;
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;

public class ImConnectionAdapter extends IImConnection.Stub {
    private static final String TAG = RemoteImService.TAG;

    private static final String[] SESSION_COOKIE_PROJECTION = {
        Im.SessionCookies.NAME,
        Im.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 = Im.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(Im.Invitation.CONTENT_URI, id), null, null, null, null);
        if(c == null) {
            return;
        }
        if(c.moveToFirst()) {
            String inviteId = c.getString(c.getColumnIndexOrThrow(Im.Invitation.INVITE_ID));
            int status;
            if(accept) {
                mGroupManager.acceptInvitationAsync(inviteId);
                status = Im.Invitation.STATUS_ACCEPTED;
            } else {
                mGroupManager.rejectInvitationAsync(inviteId);
                status = Im.Invitation.STATUS_REJECTED;
            }
            c.updateInt(c.getColumnIndexOrThrow(Im.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(Im.SessionCookies.NAME, entry.getKey());
            values.put(Im.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 = Im.Presence.OFFLINE;
        int connectionStatus = convertConnStateForDb(mConnectionState);

        if (p != null) {
            presenceStatus = ContactListManagerAdapter.convertPresenceStatus(p);
        }

        ContentResolver cr = mService.getContentResolver();
        Uri uri = Im.AccountStatus.CONTENT_URI;
        ContentValues values = new ContentValues();

        values.put(Im.AccountStatus.ACCOUNT, mAccountId);
        values.put(Im.AccountStatus.PRESENCE_STATUS, presenceStatus);
        values.put(Im.AccountStatus.CONNECTION_STATUS, connectionStatus);

        cr.insert(uri, values);
    }

    private static int convertConnStateForDb(int state) {
        switch (state) {
        case ImConnection.DISCONNECTED:
        case ImConnection.LOGGING_OUT:
            return Im.ConnectionStatus.OFFLINE;

        case ImConnection.LOGGING_IN:
            return Im.ConnectionStatus.CONNECTING;

        case ImConnection.LOGGED_IN:
            return Im.ConnectionStatus.ONLINE;

        case ImConnection.SUSPENDED:
        case ImConnection.SUSPENDING:
            return Im.ConnectionStatus.SUSPENDED;

        default:
            return Im.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(Im.Invitation.PROVIDER, mProviderId);
            values.put(Im.Invitation.ACCOUNT, mAccountId);
            values.put(Im.Invitation.INVITE_ID, invitation.getInviteID());
            values.put(Im.Invitation.SENDER, sender);
            values.put(Im.Invitation.GROUP_NAME, invitation.getGroupAddress().getScreenName());
            values.put(Im.Invitation.NOTE, invitation.getReason());
            values.put(Im.Invitation.STATUS, Im.Invitation.STATUS_PENDING);
            ContentResolver resolver = mService.getContentResolver();
            Uri uri = resolver.insert(Im.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);
        }
    }
}