1 /* 2 * Copyright (C) 2007-2008 Esmertec AG. 3 * Copyright (C) 2007-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.service; 19 20 import java.util.HashMap; 21 import java.util.Map; 22 23 import android.content.ContentResolver; 24 import android.content.ContentUris; 25 import android.content.ContentValues; 26 import android.database.Cursor; 27 import android.net.Uri; 28 import android.os.RemoteCallbackList; 29 import android.os.RemoteException; 30 import android.util.Log; 31 32 import com.android.im.IChatSessionManager; 33 import com.android.im.IConnectionListener; 34 import com.android.im.IContactListManager; 35 import com.android.im.IImConnection; 36 import com.android.im.IInvitationListener; 37 import com.android.im.engine.ChatGroupManager; 38 import com.android.im.engine.ConnectionListener; 39 import com.android.im.engine.Contact; 40 import com.android.im.engine.ContactListManager; 41 import com.android.im.engine.ImConnection; 42 import com.android.im.engine.ImErrorInfo; 43 import com.android.im.engine.ImException; 44 import com.android.im.engine.Invitation; 45 import com.android.im.engine.InvitationListener; 46 import com.android.im.engine.LoginInfo; 47 import com.android.im.engine.Presence; 48 import com.android.im.provider.Imps; 49 50 public class ImConnectionAdapter extends IImConnection.Stub { 51 private static final String TAG = RemoteImService.TAG; 52 53 private static final String[] SESSION_COOKIE_PROJECTION = { 54 Imps.SessionCookies.NAME, 55 Imps.SessionCookies.VALUE, 56 }; 57 58 private static final int COLUMN_SESSION_COOKIE_NAME = 0; 59 private static final int COLUMN_SESSION_COOKIE_VALUE = 1; 60 61 ImConnection mConnection; 62 private ConnectionListenerAdapter mConnectionListener; 63 private InvitationListenerAdapter mInvitationListener; 64 65 final RemoteCallbackList<IConnectionListener> mRemoteConnListeners 66 = new RemoteCallbackList<IConnectionListener>(); 67 68 ChatSessionManagerAdapter mChatSessionManager; 69 ContactListManagerAdapter mContactListManager; 70 71 ChatGroupManager mGroupManager; 72 RemoteImService mService; 73 74 long mProviderId = -1; 75 long mAccountId = -1; 76 boolean mAutoLoadContacts; 77 int mConnectionState = ImConnection.DISCONNECTED; 78 ImConnectionAdapter(long providerId, ImConnection connection, RemoteImService service)79 public ImConnectionAdapter(long providerId, ImConnection connection, 80 RemoteImService service) { 81 mProviderId = providerId; 82 mConnection = connection; 83 mService = service; 84 mConnectionListener = new ConnectionListenerAdapter(); 85 mConnection.addConnectionListener(mConnectionListener); 86 if ((connection.getCapability() & ImConnection.CAPABILITY_GROUP_CHAT) != 0) { 87 mGroupManager = mConnection.getChatGroupManager(); 88 mInvitationListener = new InvitationListenerAdapter(); 89 mGroupManager.setInvitationListener(mInvitationListener); 90 } 91 } 92 getAdaptee()93 public ImConnection getAdaptee() { 94 return mConnection; 95 } 96 getContext()97 public RemoteImService getContext() { 98 return mService; 99 } 100 getProviderId()101 public long getProviderId() { 102 return mProviderId; 103 } 104 getAccountId()105 public long getAccountId() { 106 return mAccountId; 107 } 108 getSupportedPresenceStatus()109 public int[] getSupportedPresenceStatus() { 110 return mConnection.getSupportedPresenceStatus(); 111 } 112 networkTypeChanged()113 public void networkTypeChanged() { 114 mConnection.networkTypeChanged(); 115 } 116 reestablishSession()117 void reestablishSession() { 118 mConnectionState = ImConnection.LOGGING_IN; 119 120 ContentResolver cr = mService.getContentResolver(); 121 if ((mConnection.getCapability() & ImConnection.CAPABILITY_SESSION_REESTABLISHMENT) != 0) { 122 HashMap<String, String> cookie = querySessionCookie(cr); 123 if (cookie != null) { 124 Log.d(TAG, "re-establish session"); 125 try { 126 mConnection.reestablishSessionAsync(cookie); 127 } catch (IllegalArgumentException e) { 128 Log.e(TAG, "Invalid session cookie, probably modified by others."); 129 clearSessionCookie(cr); 130 } 131 } 132 } 133 } 134 getSessionCookiesUri()135 private Uri getSessionCookiesUri() { 136 Uri.Builder builder = Imps.SessionCookies.CONTENT_URI_SESSION_COOKIES_BY.buildUpon(); 137 ContentUris.appendId(builder, mProviderId); 138 ContentUris.appendId(builder, mAccountId); 139 140 return builder.build(); 141 } 142 login(long accountId, String userName, String password, boolean autoLoadContacts)143 public void login(long accountId, String userName, String password, 144 boolean autoLoadContacts) { 145 mAccountId = accountId; 146 mAutoLoadContacts = autoLoadContacts; 147 mConnectionState = ImConnection.LOGGING_IN; 148 149 mConnection.loginAsync(new LoginInfo(userName, password)); 150 151 mChatSessionManager = new ChatSessionManagerAdapter(this); 152 mContactListManager = new ContactListManagerAdapter(this); 153 } 154 querySessionCookie(ContentResolver cr)155 private HashMap<String, String> querySessionCookie(ContentResolver cr) { 156 Cursor c = cr.query(getSessionCookiesUri(), SESSION_COOKIE_PROJECTION, null, null, null); 157 if (c == null) { 158 return null; 159 } 160 161 HashMap<String, String> cookie = null; 162 if (c.getCount() > 0) { 163 cookie = new HashMap<String, String>(); 164 while(c.moveToNext()) { 165 cookie.put(c.getString(COLUMN_SESSION_COOKIE_NAME), 166 c.getString(COLUMN_SESSION_COOKIE_VALUE)); 167 } 168 } 169 170 c.close(); 171 return cookie; 172 } 173 logout()174 public void logout() { 175 mConnectionState = ImConnection.LOGGING_OUT; 176 mConnection.logoutAsync(); 177 } 178 cancelLogin()179 public synchronized void cancelLogin() { 180 if (mConnectionState >= ImConnection.LOGGED_IN) { 181 // too late 182 return; 183 } 184 185 logout(); 186 } 187 suspend()188 void suspend() { 189 mConnectionState = ImConnection.SUSPENDING; 190 mConnection.suspend(); 191 } 192 registerConnectionListener(IConnectionListener listener)193 public void registerConnectionListener(IConnectionListener listener) { 194 if (listener != null) { 195 mRemoteConnListeners.register(listener); 196 } 197 } 198 unregisterConnectionListener(IConnectionListener listener)199 public void unregisterConnectionListener(IConnectionListener listener) { 200 if (listener != null) { 201 mRemoteConnListeners.unregister(listener); 202 } 203 } 204 setInvitationListener(IInvitationListener listener)205 public void setInvitationListener(IInvitationListener listener) { 206 if(mInvitationListener != null) { 207 mInvitationListener.mRemoteListener = listener; 208 } 209 } 210 getChatSessionManager()211 public IChatSessionManager getChatSessionManager() { 212 return mChatSessionManager; 213 } 214 getContactListManager()215 public IContactListManager getContactListManager() { 216 return mContactListManager; 217 } 218 getChatSessionCount()219 public int getChatSessionCount() { 220 if (mChatSessionManager == null) { 221 return 0; 222 } 223 return mChatSessionManager.getChatSessionCount(); 224 } 225 getLoginUser()226 public Contact getLoginUser() { 227 return mConnection.getLoginUser(); 228 } 229 getUserPresence()230 public Presence getUserPresence() { 231 return mConnection.getUserPresence(); 232 } 233 updateUserPresence(Presence newPresence)234 public int updateUserPresence(Presence newPresence) { 235 try { 236 mConnection.updateUserPresenceAsync(newPresence); 237 } catch (ImException e) { 238 return e.getImError().getCode(); 239 } 240 241 return ImErrorInfo.NO_ERROR; 242 } 243 getState()244 public int getState() { 245 return mConnectionState; 246 } 247 rejectInvitation(long id)248 public void rejectInvitation(long id){ 249 handleInvitation(id, false); 250 } 251 acceptInvitation(long id)252 public void acceptInvitation(long id) { 253 handleInvitation(id, true); 254 } 255 handleInvitation(long id, boolean accept)256 private void handleInvitation(long id, boolean accept) { 257 if(mGroupManager == null) { 258 return; 259 } 260 ContentResolver cr = mService.getContentResolver(); 261 Cursor c = cr.query(ContentUris.withAppendedId(Imps.Invitation.CONTENT_URI, id), null, null, null, null); 262 if(c == null) { 263 return; 264 } 265 if(c.moveToFirst()) { 266 String inviteId = c.getString(c.getColumnIndexOrThrow(Imps.Invitation.INVITE_ID)); 267 int status; 268 if(accept) { 269 mGroupManager.acceptInvitationAsync(inviteId); 270 status = Imps.Invitation.STATUS_ACCEPTED; 271 } else { 272 mGroupManager.rejectInvitationAsync(inviteId); 273 status = Imps.Invitation.STATUS_REJECTED; 274 } 275 c.updateInt(c.getColumnIndexOrThrow(Imps.Invitation.STATUS), status); 276 c.commitUpdates(); 277 } 278 c.close(); 279 } 280 saveSessionCookie(ContentResolver cr)281 void saveSessionCookie(ContentResolver cr) { 282 HashMap<String, String> cookies = mConnection.getSessionContext(); 283 284 int i = 0; 285 ContentValues[] valuesList = new ContentValues[cookies.size()]; 286 287 for(Map.Entry<String,String> entry : cookies.entrySet()){ 288 ContentValues values = new ContentValues(2); 289 290 values.put(Imps.SessionCookies.NAME, entry.getKey()); 291 values.put(Imps.SessionCookies.VALUE, entry.getValue()); 292 293 valuesList[i++] = values; 294 } 295 296 cr.bulkInsert(getSessionCookiesUri(), valuesList); 297 } 298 clearSessionCookie(ContentResolver cr)299 void clearSessionCookie(ContentResolver cr) { 300 cr.delete(getSessionCookiesUri(), null, null); 301 } 302 updateAccountStatusInDb()303 void updateAccountStatusInDb() { 304 Presence p = getUserPresence(); 305 int presenceStatus = Imps.Presence.OFFLINE; 306 int connectionStatus = convertConnStateForDb(mConnectionState); 307 308 if (p != null) { 309 presenceStatus = ContactListManagerAdapter.convertPresenceStatus(p); 310 } 311 312 ContentResolver cr = mService.getContentResolver(); 313 Uri uri = Imps.AccountStatus.CONTENT_URI; 314 ContentValues values = new ContentValues(); 315 316 values.put(Imps.AccountStatus.ACCOUNT, mAccountId); 317 values.put(Imps.AccountStatus.PRESENCE_STATUS, presenceStatus); 318 values.put(Imps.AccountStatus.CONNECTION_STATUS, connectionStatus); 319 320 cr.insert(uri, values); 321 } 322 convertConnStateForDb(int state)323 private static int convertConnStateForDb(int state) { 324 switch (state) { 325 case ImConnection.DISCONNECTED: 326 case ImConnection.LOGGING_OUT: 327 return Imps.ConnectionStatus.OFFLINE; 328 329 case ImConnection.LOGGING_IN: 330 return Imps.ConnectionStatus.CONNECTING; 331 332 case ImConnection.LOGGED_IN: 333 return Imps.ConnectionStatus.ONLINE; 334 335 case ImConnection.SUSPENDED: 336 case ImConnection.SUSPENDING: 337 return Imps.ConnectionStatus.SUSPENDED; 338 339 default: 340 return Imps.ConnectionStatus.OFFLINE; 341 } 342 } 343 344 final class ConnectionListenerAdapter implements ConnectionListener{ onStateChanged(final int state, final ImErrorInfo error)345 public void onStateChanged(final int state, final ImErrorInfo error) { 346 synchronized (this) { 347 if (state == ImConnection.LOGGED_IN 348 && mConnectionState == ImConnection.LOGGING_OUT) { 349 // A bit tricky here. The engine did login successfully 350 // but the notification comes a bit late; user has already 351 // issued a cancelLogin() and that cannot be undone. Here 352 // we have to ignore the LOGGED_IN event and wait for 353 // the upcoming DISCONNECTED. 354 return; 355 } 356 357 if (state != ImConnection.DISCONNECTED) { 358 mConnectionState = state; 359 } 360 } 361 362 ContentResolver cr = mService.getContentResolver(); 363 if(state == ImConnection.LOGGED_IN) { 364 if ((mConnection.getCapability() & ImConnection.CAPABILITY_SESSION_REESTABLISHMENT) != 0){ 365 saveSessionCookie(cr); 366 } 367 368 if(mAutoLoadContacts && mContactListManager.getState() 369 != ContactListManager.LISTS_LOADED) { 370 mContactListManager.loadContactLists(); 371 } 372 373 for (ChatSessionAdapter session : mChatSessionManager.mActiveSessions.values()) { 374 session.sendPostponedMessages(); 375 } 376 } else if (state == ImConnection.LOGGING_OUT) { 377 // The engine has started to logout the connection, remove it 378 // from the active connection list. 379 mService.removeConnection(ImConnectionAdapter.this); 380 } else if(state == ImConnection.DISCONNECTED) { 381 mService.removeConnection(ImConnectionAdapter.this); 382 383 clearSessionCookie(cr); 384 // mContactListManager might still be null if we fail 385 // immediately in loginAsync (say, an invalid host URL) 386 if (mContactListManager != null) { 387 mContactListManager.clearOnLogout(); 388 } 389 if (mChatSessionManager != null) { 390 mChatSessionManager.closeAllChatSessions(); 391 } 392 393 mConnectionState = state; 394 } else if(state == ImConnection.SUSPENDED && error != null) { 395 // re-establish failed, schedule to retry 396 // TODO increase delay after retry failed. 397 mService.scheduleReconnect(15000); 398 } 399 400 updateAccountStatusInDb(); 401 402 final int N = mRemoteConnListeners.beginBroadcast(); 403 for (int i = 0; i < N; i++) { 404 IConnectionListener listener = mRemoteConnListeners.getBroadcastItem(i); 405 try { 406 listener.onStateChanged(ImConnectionAdapter.this, state, error); 407 } catch (RemoteException e) { 408 // The RemoteCallbackList will take care of removing the 409 // dead listeners. 410 } 411 } 412 mRemoteConnListeners.finishBroadcast(); 413 } 414 onUserPresenceUpdated()415 public void onUserPresenceUpdated() { 416 updateAccountStatusInDb(); 417 418 final int N = mRemoteConnListeners.beginBroadcast(); 419 for (int i = 0; i < N; i++) { 420 IConnectionListener listener = mRemoteConnListeners.getBroadcastItem(i); 421 try { 422 listener.onUserPresenceUpdated(ImConnectionAdapter.this); 423 } catch (RemoteException e) { 424 // The RemoteCallbackList will take care of removing the 425 // dead listeners. 426 } 427 } 428 mRemoteConnListeners.finishBroadcast(); 429 } 430 onUpdatePresenceError(final ImErrorInfo error)431 public void onUpdatePresenceError(final ImErrorInfo error) { 432 final int N = mRemoteConnListeners.beginBroadcast(); 433 for (int i = 0; i < N; i++) { 434 IConnectionListener listener = mRemoteConnListeners.getBroadcastItem(i); 435 try { 436 listener.onUpdatePresenceError(ImConnectionAdapter.this, error); 437 } catch (RemoteException e) { 438 // The RemoteCallbackList will take care of removing the 439 // dead listeners. 440 } 441 } 442 mRemoteConnListeners.finishBroadcast(); 443 } 444 } 445 446 final class InvitationListenerAdapter implements InvitationListener { 447 IInvitationListener mRemoteListener; 448 onGroupInvitation(Invitation invitation)449 public void onGroupInvitation(Invitation invitation) { 450 String sender = invitation.getSender().getScreenName(); 451 ContentValues values = new ContentValues(7); 452 values.put(Imps.Invitation.PROVIDER, mProviderId); 453 values.put(Imps.Invitation.ACCOUNT, mAccountId); 454 values.put(Imps.Invitation.INVITE_ID, invitation.getInviteID()); 455 values.put(Imps.Invitation.SENDER, sender); 456 values.put(Imps.Invitation.GROUP_NAME, invitation.getGroupAddress().getScreenName()); 457 values.put(Imps.Invitation.NOTE, invitation.getReason()); 458 values.put(Imps.Invitation.STATUS, Imps.Invitation.STATUS_PENDING); 459 ContentResolver resolver = mService.getContentResolver(); 460 Uri uri = resolver.insert(Imps.Invitation.CONTENT_URI, values); 461 long id = ContentUris.parseId(uri); 462 try { 463 if (mRemoteListener != null) { 464 mRemoteListener.onGroupInvitation(id); 465 return; 466 } 467 } catch (RemoteException e) { 468 Log.i(TAG, "onGroupInvitation: dead listener " 469 + mRemoteListener +"; removing"); 470 mRemoteListener = null; 471 } 472 // No listener registered or failed to notify the listener, send a 473 // notification instead. 474 mService.getStatusBarNotifier().notifyGroupInvitation(mProviderId, mAccountId, id, sender); 475 } 476 } 477 } 478