1 /* 2 * Copyright (C) 2007 Esmertec AG. 3 * Copyright (C) 2007 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.imps; 19 20 import java.util.ArrayList; 21 22 import com.android.im.engine.Address; 23 import com.android.im.engine.ChatGroup; 24 import com.android.im.engine.ChatGroupManager; 25 import com.android.im.engine.Contact; 26 import com.android.im.engine.GroupListener; 27 import com.android.im.engine.GroupMemberListener; 28 import com.android.im.engine.Invitation; 29 30 /** 31 * The implementation of ChatGroupManager with IMPS protocol. 32 */ 33 public class ImpsChatGroupManager extends ChatGroupManager implements 34 ServerTransactionListener { 35 private ImpsConnection mConnection; 36 private ImpsTransactionManager mTransactionManager; 37 ImpsChatGroupManager(ImpsConnection connection)38 ImpsChatGroupManager(ImpsConnection connection) { 39 mConnection = connection; 40 41 mTransactionManager = connection.getTransactionManager(); 42 mTransactionManager.setTransactionListener(ImpsTags.GroupChangeNotice, this); 43 mTransactionManager.setTransactionListener(ImpsTags.LeaveGroup_Response, this); 44 mTransactionManager.setTransactionListener(ImpsTags.InviteUser_Request, this); 45 mTransactionManager.setTransactionListener(ImpsTags.Invite_Response, this); 46 } 47 48 @Override addGroupMemberAsync(final ChatGroup group, final Contact contact)49 protected void addGroupMemberAsync(final ChatGroup group, final Contact contact){ 50 Primitive request = buildAddGroupMemberRequest(group, contact); 51 52 AsyncTransaction tx = new AsyncTransaction(mTransactionManager) { 53 @Override 54 public void onResponseError(ImpsErrorInfo error) { 55 notifyGroupMemberError(group, error); 56 } 57 58 @Override 59 public void onResponseOk(Primitive response) { 60 notifyMemberJoined(group, contact); 61 } 62 }; 63 tx.sendRequest(request); 64 } 65 66 @Override createChatGroupAsync(final String name)67 public void createChatGroupAsync(final String name) { 68 final ImpsAddress loginUserAddress = mConnection.getSession() 69 .getLoginUserAddress(); 70 final ImpsAddress groupAddress = new ImpsGroupAddress(loginUserAddress, name); 71 72 Primitive primitive = buildCreateGroupRequest(name, groupAddress); 73 74 AsyncTransaction tx = new AsyncTransaction(mTransactionManager) { 75 @Override 76 public void onResponseError(ImpsErrorInfo error) { 77 notifyGroupError(GroupListener.ERROR_CREATING_GROUP, name, error); 78 } 79 80 @Override 81 public void onResponseOk(Primitive response) { 82 // The current user is joined into the group automatically. 83 ArrayList<Contact> members = new ArrayList<Contact>(); 84 members.add(new Contact(loginUserAddress, loginUserAddress 85 .getUser())); 86 87 ChatGroup group = new ChatGroup(groupAddress, name, members, 88 ImpsChatGroupManager.this); 89 notifyGroupCreated(group); 90 } 91 }; 92 tx.sendRequest(primitive); 93 } 94 95 @Override deleteChatGroupAsync(final ChatGroup group)96 public void deleteChatGroupAsync(final ChatGroup group) { 97 Primitive request = new Primitive(ImpsTags.DeleteGroup_Request); 98 request.addElement(ImpsTags.GroupID, group.getAddress().getFullName()); 99 100 AsyncTransaction tx = new AsyncTransaction(mTransactionManager) { 101 @Override 102 public void onResponseError(ImpsErrorInfo error) { 103 notifyGroupError(GroupListener.ERROR_DELETING_GROUP, 104 group.getName(), error); 105 } 106 107 @Override 108 public void onResponseOk(Primitive response) { 109 notifyGroupDeleted(group); 110 } 111 }; 112 tx.sendRequest(request); 113 } 114 115 @Override inviteUserAsync(final ChatGroup group, Contact contact)116 public void inviteUserAsync(final ChatGroup group, Contact contact) { 117 Primitive request = buildInviteUserRequest(group, contact); 118 119 AsyncTransaction tx = new AsyncTransaction(mTransactionManager){ 120 @Override 121 public void onResponseError(ImpsErrorInfo error) { 122 notifyGroupMemberError(group, error); 123 } 124 125 @Override 126 public void onResponseOk(Primitive response) { 127 //Do nothing 128 } 129 }; 130 tx.sendRequest(request); 131 } 132 133 @Override acceptInvitationAsync(Invitation invitation)134 public void acceptInvitationAsync(Invitation invitation){ 135 joinChatGroupAsync(invitation.getGroupAddress()); 136 sendInvitationResposne(invitation, true); 137 } 138 139 @Override rejectInvitationAsync(Invitation invitation)140 public void rejectInvitationAsync(Invitation invitation) { 141 sendInvitationResposne(invitation, false); 142 } 143 sendInvitationResposne(Invitation invitation, boolean accept)144 private void sendInvitationResposne(Invitation invitation, boolean accept) { 145 Primitive response = new Primitive(ImpsTags.InviteUser_Response); 146 response.addElement(ImpsTags.InviteID, invitation.getInviteID()); 147 response.addElement(ImpsTags.Acceptance, ImpsUtils.toImpsBool(accept)); 148 ImpsAddress sender = (ImpsAddress) invitation.getSender(); 149 response.addElement(sender.toPrimitiveElement()); 150 AsyncTransaction tx = new AsyncTransaction(mTransactionManager){ 151 @Override 152 public void onResponseError(ImpsErrorInfo error) { 153 // Ignore 154 } 155 @Override 156 public void onResponseOk(Primitive res) { 157 // Ignore 158 } 159 }; 160 tx.sendRequest(response); 161 } 162 163 @Override joinChatGroupAsync(final Address address)164 public void joinChatGroupAsync(final Address address) { 165 Primitive request = buildJoinGroupRequest(address); 166 167 AsyncTransaction tx = new AsyncTransaction(mTransactionManager) { 168 @Override 169 public void onResponseError(ImpsErrorInfo error) { 170 notifyGroupError(GroupListener.ERROR_JOINING_IN_GROUP, 171 address.getScreenName(), error); 172 } 173 174 @Override 175 public void onResponseOk(Primitive response) { 176 ArrayList<Contact> members = new ArrayList<Contact>(); 177 // FIXME: UserMapList is a child of Joined in version 1.3 178 PrimitiveElement userMapping = response 179 .getElement(ImpsTags.UserMapList); 180 extractUserMapList(userMapping, members); 181 182 ChatGroup group = new ChatGroup(address, address 183 .getScreenName(), members, ImpsChatGroupManager.this); 184 notifyJoinedGroup(group); 185 } 186 }; 187 tx.sendRequest(request); 188 } 189 190 @Override leaveChatGroupAsync(final ChatGroup group)191 public void leaveChatGroupAsync(final ChatGroup group) { 192 Primitive leaveRequest = buildLeaveGroupRequest(group); 193 AsyncTransaction tx = new AsyncTransaction(mTransactionManager) { 194 @Override 195 public void onResponseError(ImpsErrorInfo error) { 196 notifyGroupError(GroupListener.ERROR_LEAVING_GROUP, 197 group.getName(), error); 198 } 199 200 @Override 201 public void onResponseOk(Primitive response) { 202 notifyLeftGroup(group); 203 } 204 }; 205 tx.sendRequest(leaveRequest); 206 } 207 208 @Override removeGroupMemberAsync(final ChatGroup group, final Contact contact)209 protected void removeGroupMemberAsync(final ChatGroup group, final Contact contact) { 210 Primitive request = buildRemoveGroupMemberRequest(group, contact); 211 212 AsyncTransaction tx = new AsyncTransaction(mTransactionManager) { 213 @Override 214 public void onResponseError(ImpsErrorInfo error) { 215 notifyGroupMemberError(group, error); 216 } 217 218 @Override 219 public void onResponseOk(Primitive response) { 220 notifyMemberLeft(group, contact); 221 } 222 }; 223 tx.sendRequest(request); 224 } 225 226 /** 227 * Loads the members of a ChatGroup asynchronously. This method will create 228 * a ChatGroup and return immediately. After the members are fetched from 229 * the server, 230 * {@link GroupMemberListener#onMemberJoined(ChatGroup, Contact)}} will be 231 * called. 232 * 233 * @param address 234 */ loadGroupMembersAsync(ImpsGroupAddress address)235 ChatGroup loadGroupMembersAsync(ImpsGroupAddress address) { 236 //FIXME: NowIMP Server doesn't support this primitive. 237 // Primitive request = new Primitive(ImpsTags.GetJoinedUsers_Request); 238 // request.addElement(ImpsTags.GroupID, address.getFullName()); 239 // Primitive response = mConnection.sendRequest(request); 240 // ImpsUtils.checkResult(response); 241 // 242 // ChatGroup group = new ChatGroup(address, address.getScreenName(), this); 243 throw new RuntimeException("Not implemented yet"); 244 } 245 notifyServerTransaction(ServerTransaction tx)246 public void notifyServerTransaction(ServerTransaction tx) { 247 final Primitive primitive = tx.getRequest(); 248 String type = primitive.getType(); 249 if (ImpsTags.GroupChangeNotice.equals(type)) { 250 tx.sendStatusResponse(ImpsConstants.SUCCESS_CODE); 251 handleGroupChange(primitive); 252 } else if (ImpsTags.InviteUser_Request.equals(type)) { 253 tx.sendStatusResponse(ImpsConstants.SUCCESS_CODE); 254 255 String inviteType = primitive.getElementContents(ImpsTags.InviteType); 256 // We only handle Group invitation right now. 257 if (ImpsConstants.GROUP_INVITATION.equals(inviteType)) { 258 handleInvitation(primitive); 259 } 260 } else if (ImpsTags.LeaveGroup_Response.equals(type)) { 261 tx.sendStatusResponse(ImpsConstants.SUCCESS_CODE); 262 String groupId = primitive.getElementContents(ImpsTags.GroupID); 263 ChatGroup group = mGroups.get(new ImpsGroupAddress(groupId)); 264 if(group != null) { 265 notifyLeftGroup(group); 266 } else { 267 ImpsLog.log("Leave unknown group:" + groupId); 268 } 269 } else if (ImpsTags.Invite_Response.equals(type)) { 270 tx.sendStatusResponse(ImpsConstants.SUCCESS_CODE); 271 //TODO: notify the user? 272 } 273 } 274 handleGroupChange(final Primitive primitive)275 void handleGroupChange(final Primitive primitive) { 276 String groupId = primitive.getElementContents(ImpsTags.GroupID); 277 ImpsGroupAddress address = new ImpsGroupAddress(groupId); 278 279 ArrayList<Contact> joined = new ArrayList<Contact>(); 280 PrimitiveElement joinedElem = primitive.getElement(ImpsTags.Joined); 281 if (joinedElem != null) { 282 extractUserMapList(joinedElem.getFirstChild(), joined); 283 } 284 ArrayList<Contact> left = new ArrayList<Contact>(); 285 PrimitiveElement leftElem = primitive.getElement(ImpsTags.Left); 286 if (leftElem != null) { 287 extractUserMapList(leftElem.getFirstChild(), left); 288 } 289 290 notifyGroupChanged(address, joined, left); 291 } 292 handleInvitation(final Primitive primitive)293 void handleInvitation(final Primitive primitive) { 294 String inviteId = primitive.getElementContents(ImpsTags.InviteID); 295 PrimitiveElement sender = primitive.getElement(ImpsTags.Sender); 296 ImpsAddress senderAddress = ImpsAddress.fromPrimitiveElement(sender 297 .getFirstChild()); 298 String groupId = primitive.getElementContents(ImpsTags.GroupID); 299 Address groupAddress = new ImpsGroupAddress(groupId); 300 String inviteNote = primitive.getElementContents(ImpsTags.InviteNote); 301 302 Invitation invitation = new Invitation(inviteId, groupAddress, 303 senderAddress, inviteNote); 304 notifyGroupInvitation(invitation); 305 } 306 buildAddGroupMemberRequest(final ChatGroup group, final Contact contact)307 private Primitive buildAddGroupMemberRequest(final ChatGroup group, final Contact contact) { 308 Primitive request = new Primitive(ImpsTags.AddGroupMembers_Request); 309 request.addElement(ImpsTags.GroupID, group.getAddress().getFullName()); 310 311 // FIXME: It's UserIDList in 1.3 312 PrimitiveElement userList = request.addElement(ImpsTags.UserList); 313 PrimitiveElement user = userList.addChild(ImpsTags.User); 314 user.addChild(ImpsTags.UserID, contact.getAddress().getFullName()); 315 return request; 316 } 317 buildCreateGroupRequest(String name, ImpsAddress groupAddress)318 private Primitive buildCreateGroupRequest(String name, ImpsAddress groupAddress) { 319 Primitive primitive = new Primitive(ImpsTags.CreateGroup_Request); 320 primitive.addElement(ImpsTags.GroupID, groupAddress.getFullName()); 321 PrimitiveElement propertiesElem = primitive.addElement(ImpsTags.GroupProperties); 322 propertiesElem.addPropertyChild(ImpsTags.Name, name); 323 propertiesElem.addPropertyChild(ImpsTags.Accesstype, ImpsConstants.Open); 324 propertiesElem.addPropertyChild(ImpsTags.PrivateMessaging, false); 325 propertiesElem.addPropertyChild(ImpsTags.Searchable, false); 326 propertiesElem.addPropertyChild(ImpsTags.AutoDelete, true); 327 328 // TODO: welcome note? 329 primitive.addElement(ImpsTags.JoinGroup, true); 330 331 PrimitiveElement screenName = primitive.addElement(ImpsTags.ScreenName); 332 screenName.addChild(ImpsTags.SName, mConnection.getLoginUserName()); 333 screenName.addChild(ImpsTags.GroupID, groupAddress.getFullName()); 334 335 primitive.addElement(ImpsTags.SubscribeNotification, true); 336 337 return primitive; 338 } 339 buildInviteUserRequest(ChatGroup group, Contact contact)340 private Primitive buildInviteUserRequest(ChatGroup group, Contact contact) { 341 String inviteId = nextInviteID(); 342 Primitive request = new Primitive(ImpsTags.Invite_Request); 343 request.addElement(ImpsTags.InviteID, inviteId); 344 request.addElement(ImpsTags.InviteType, ImpsConstants.GROUP_INVITATION); 345 346 // FIXME: <Sender> is only required in IMPS1.3 347 // ImpsGroupAddress groupAddress = (ImpsGroupAddress)group.getAddress(); 348 // groupAddress.setScreenName(mConnection.getLoginUser()); 349 // request.addElement(ImpsTags.Sender).addChild(groupAddress.toPrimitiveElement()); 350 351 request.addElement(ImpsTags.Recipient).addChild( 352 ((ImpsAddress)contact.getAddress()).toPrimitiveElement()); 353 request.addElement(ImpsTags.GroupID, group.getAddress().getFullName()); 354 355 // FIXME: <ScreenName> is only needed in IMPS1.2 356 PrimitiveElement screenName = request.addElement(ImpsTags.ScreenName); 357 screenName.addChild(ImpsTags.SName, mConnection.getLoginUserName()); 358 screenName.addChild(ImpsTags.GroupID, group.getAddress().getFullName()); 359 return request; 360 } 361 buildJoinGroupRequest(Address address)362 private Primitive buildJoinGroupRequest(Address address) { 363 Primitive request = new Primitive(ImpsTags.JoinGroup_Request); 364 request.addElement(ImpsTags.GroupID, address.getFullName()); 365 PrimitiveElement screenName = request.addElement(ImpsTags.ScreenName); 366 screenName.addChild(ImpsTags.SName, mConnection.getLoginUserName()); 367 screenName.addChild(ImpsTags.GroupID, address.getFullName()); 368 369 request.addElement(ImpsTags.JoinedRequest, true); 370 request.addElement(ImpsTags.SubscribeNotification, true); 371 return request; 372 } 373 buildLeaveGroupRequest(ChatGroup group)374 private Primitive buildLeaveGroupRequest(ChatGroup group) { 375 Primitive leaveRequest = new Primitive(ImpsTags.LeaveGroup_Request); 376 leaveRequest.addElement(ImpsTags.GroupID, group.getAddress().getFullName()); 377 return leaveRequest; 378 } 379 buildRemoveGroupMemberRequest(ChatGroup group, Contact contact)380 private Primitive buildRemoveGroupMemberRequest(ChatGroup group, Contact contact) { 381 Primitive request = new Primitive(ImpsTags.RemoveGroupMembers_Request); 382 request.addElement(ImpsTags.GroupID, group.getAddress().getFullName()); 383 384 // FIXME: It's UserIDList in 1.3 385 PrimitiveElement userList = request.addElement(ImpsTags.UserList); 386 PrimitiveElement user = userList.addChild(ImpsTags.User); 387 user.addChild(ImpsTags.UserID, contact.getAddress().getFullName()); 388 return request; 389 } 390 extractUserMapList(PrimitiveElement userMapList, ArrayList<Contact> list)391 private void extractUserMapList(PrimitiveElement userMapList, 392 ArrayList<Contact> list) { 393 // FIXME: UserMapping is the child of UserMapList in version 1.3 394 PrimitiveElement userMapping = userMapList; 395 396 if (userMapping != null) { 397 for (PrimitiveElement mapping : userMapping.getChildren()) { 398 String name = mapping.getChildContents(ImpsTags.SName); 399 String id = mapping.getChildContents(ImpsTags.UserID); 400 if (id == null) { 401 id = name; 402 } 403 list.add((Contact)(new ImpsUserAddress(id)).getEntity(mConnection)); 404 } 405 } 406 } 407 private static int sInviteID = 0; 408 nextInviteID()409 private synchronized String nextInviteID() { 410 return "invite" + System.currentTimeMillis() + (sInviteID++); 411 } 412 } 413