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 package com.android.im.app; 18 19 import com.android.im.IChatSession; 20 import com.android.im.IChatSessionManager; 21 import com.android.im.IContactListListener; 22 import com.android.im.IContactListManager; 23 import com.android.im.IImConnection; 24 import com.android.im.ISubscriptionListener; 25 import com.android.im.R; 26 import com.android.im.app.adapter.ContactListListenerAdapter; 27 import com.android.im.engine.Contact; 28 import com.android.im.engine.ContactListManager; 29 import com.android.im.engine.ImErrorInfo; 30 import com.android.im.provider.Imps; 31 import com.android.im.service.ImServiceConstants; 32 33 import android.app.Activity; 34 import android.app.AlertDialog; 35 import android.content.ContentUris; 36 import android.content.Context; 37 import android.content.DialogInterface; 38 import android.content.Intent; 39 import android.content.res.Resources; 40 import android.database.Cursor; 41 import android.graphics.Canvas; 42 import android.net.Uri; 43 import android.os.Parcel; 44 import android.os.Parcelable; 45 import android.os.RemoteException; 46 import android.util.AttributeSet; 47 import android.util.Log; 48 import android.view.View; 49 import android.widget.ExpandableListView; 50 import android.widget.LinearLayout; 51 import android.widget.ExpandableListView.OnChildClickListener; 52 53 public class ContactListView extends LinearLayout { 54 55 Activity mScreen; 56 IImConnection mConn; 57 SimpleAlertHandler mHandler; 58 private final IContactListListener mContactListListener; 59 60 UserPresenceView mPresenceView; 61 ExpandableListView mContactsList; 62 private ContactListTreeAdapter mAdapter; 63 private boolean mHideOfflineContacts; 64 private SavedState mSavedState; 65 private boolean mAutoRefresh = true; 66 67 public ContactListView(Context screen, AttributeSet attrs) { 68 super(screen, attrs); 69 mScreen = (Activity)screen; 70 mHandler = new SimpleAlertHandler(mScreen); 71 mContactListListener = new MyContactListListener(mHandler); 72 } 73 74 private class MyContactListListener extends ContactListListenerAdapter { 75 public MyContactListListener(SimpleAlertHandler handler) { 76 super(handler); 77 } 78 79 @Override 80 public void onAllContactListsLoaded() { 81 if (mAdapter != null) { 82 mAdapter.startAutoRequery(); 83 } 84 } 85 } 86 87 private final ISubscriptionListener.Stub mSubscriptionListener = new ISubscriptionListener.Stub() { 88 89 public void onSubScriptionRequest(Contact from) { 90 querySubscription(); 91 } 92 93 public void onSubscriptionApproved(String contact) { 94 querySubscription(); 95 } 96 97 public void onSubscriptionDeclined(String contact) { 98 querySubscription(); 99 } 100 101 private void querySubscription() { 102 if (mAdapter != null) { 103 mAdapter.startQuerySubscriptions(); 104 } 105 } 106 }; 107 108 @Override 109 protected void onFinishInflate() { 110 super.onFinishInflate(); 111 mPresenceView = (UserPresenceView)findViewById(R.id.userPresence); 112 mContactsList = (ExpandableListView) findViewById(R.id.contactsList); 113 mContactsList.setOnChildClickListener(mOnChildClickListener); 114 } 115 116 public ExpandableListView getListView() { 117 return mContactsList; 118 } 119 120 public void setConnection(IImConnection conn) { 121 if (mConn != conn) { 122 if (mConn != null) { 123 unregisterListeners(); 124 } 125 mConn = conn; 126 127 if (conn != null) { 128 registerListeners(); 129 mPresenceView.setConnection(conn); 130 131 if (mAdapter == null) { 132 mAdapter = new ContactListTreeAdapter(conn, mScreen); 133 mAdapter.setHideOfflineContacts(mHideOfflineContacts); 134 mContactsList.setAdapter(mAdapter); 135 mContactsList.setOnScrollListener(mAdapter); 136 if (mSavedState != null) { 137 int[] expandedGroups = mSavedState.mExpandedGroups; 138 if(expandedGroups != null) { 139 for (int group : expandedGroups) { 140 mContactsList.expandGroup(group); 141 } 142 } 143 } 144 } else { 145 mAdapter.changeConnection(conn); 146 } 147 try { 148 IContactListManager listMgr = conn.getContactListManager(); 149 if (listMgr.getState() == ContactListManager.LISTS_LOADED) { 150 mAdapter.startAutoRequery(); 151 } 152 } catch (RemoteException e) { 153 Log.e(ImApp.LOG_TAG, "Service died!"); 154 } 155 } 156 } else { 157 mContactsList.invalidateViews(); 158 } 159 } 160 161 public void setHideOfflineContacts(boolean hide) { 162 if (mAdapter != null) { 163 mAdapter.setHideOfflineContacts(hide); 164 } else { 165 mHideOfflineContacts = hide; 166 } 167 } 168 169 public void startChat() { 170 startChat(getSelectedContact()); 171 } 172 173 public void startChatAtPosition(long packedPosition) { 174 startChat(getContactAtPosition(packedPosition)); 175 } 176 177 void startChat(Cursor c) { 178 if (c != null) { 179 long id = c.getLong(c.getColumnIndexOrThrow(Imps.Contacts._ID)); 180 String username = c.getString(c.getColumnIndexOrThrow(Imps.Contacts.USERNAME)); 181 try { 182 IChatSessionManager manager = mConn.getChatSessionManager(); 183 IChatSession session = manager.getChatSession(username); 184 if(session == null) { 185 manager.createChatSession(username); 186 } 187 188 Uri data = ContentUris.withAppendedId(Imps.Chats.CONTENT_URI, id); 189 Intent i = new Intent(Intent.ACTION_VIEW, data); 190 i.addCategory(ImApp.IMPS_CATEGORY); 191 mScreen.startActivity(i); 192 setAutoRefreshContacts(false); 193 } catch (RemoteException e) { 194 mHandler.showServiceErrorAlert(); 195 } 196 clearFocusIfEmpty(c); 197 } 198 } 199 200 private void clearFocusIfEmpty(Cursor c) { 201 // clear focus if there's only one item so that it would focus on the 202 // "empty" item after the contact removed. 203 if (c.getCount() == 1) { 204 clearFocus(); 205 } 206 } 207 208 public void endChat() { 209 endChat(getSelectedContact()); 210 } 211 212 public void endChatAtPosition(long packedPosition) { 213 endChat(getContactAtPosition(packedPosition)); 214 } 215 216 void endChat(Cursor c) { 217 if(c != null) { 218 String username = c.getString(c.getColumnIndexOrThrow(Imps.Contacts.USERNAME)); 219 try { 220 IChatSessionManager manager = mConn.getChatSessionManager(); 221 IChatSession session = manager.getChatSession(username); 222 if(session != null) { 223 session.leave(); 224 } 225 } catch (RemoteException e) { 226 mHandler.showServiceErrorAlert(); 227 } 228 clearFocusIfEmpty(c); 229 } 230 } 231 232 public void viewContactPresence() { 233 viewContactPresence(getSelectedContact()); 234 } 235 236 public void viewContactPresenceAtPostion(long packedPosition) { 237 viewContactPresence(getContactAtPosition(packedPosition)); 238 } 239 240 public void viewContactPresence(Cursor c) { 241 if (c != null) { 242 long id = c.getLong(c.getColumnIndexOrThrow(Imps.Contacts._ID)); 243 Uri data = ContentUris.withAppendedId(Imps.Contacts.CONTENT_URI, id); 244 Intent i = new Intent(Intent.ACTION_VIEW, data); 245 mScreen.startActivity(i); 246 } 247 } 248 249 public boolean isContactAtPosition(long packedPosition) { 250 int type = ExpandableListView.getPackedPositionType(packedPosition); 251 int groupPosition = ExpandableListView.getPackedPositionGroup(packedPosition); 252 return (type == ExpandableListView.PACKED_POSITION_TYPE_CHILD) 253 && !mAdapter.isPosForSubscription(groupPosition); 254 } 255 256 public boolean isContactSelected() { 257 long pos = mContactsList.getSelectedPosition(); 258 return isContactAtPosition(pos); 259 } 260 261 public boolean isConversationAtPosition(long packedPosition) { 262 int type = ExpandableListView.getPackedPositionType(packedPosition); 263 int groupPosition = ExpandableListView.getPackedPositionGroup(packedPosition); 264 return (type == ExpandableListView.PACKED_POSITION_TYPE_CHILD) 265 && mAdapter.isPosForOngoingConversation(groupPosition); 266 } 267 268 public boolean isConversationSelected () { 269 long pos = mContactsList.getSelectedPosition(); 270 return isConversationAtPosition(pos); 271 } 272 273 public boolean isContactsLoaded() { 274 try { 275 IContactListManager manager = mConn.getContactListManager(); 276 return (manager.getState() == ContactListManager.LISTS_LOADED); 277 } catch (RemoteException e) { 278 mHandler.showServiceErrorAlert(); 279 return false; 280 } 281 } 282 283 public void removeContact() { 284 removeContact(getSelectedContact()); 285 } 286 287 public void removeContactAtPosition(long packedPosition) { 288 removeContact(getContactAtPosition(packedPosition)); 289 } 290 291 void removeContact(Cursor c) { 292 if (c == null) { 293 mHandler.showAlert(R.string.error, R.string.select_contact); 294 } else { 295 String nickname = c.getString(c.getColumnIndexOrThrow(Imps.Contacts.NICKNAME)); 296 final String address = c.getString(c.getColumnIndexOrThrow(Imps.Contacts.USERNAME)); 297 DialogInterface.OnClickListener confirmListener = new DialogInterface.OnClickListener(){ 298 public void onClick(DialogInterface dialog, int whichButton) { 299 try { 300 IContactListManager manager = mConn.getContactListManager(); 301 int res = manager.removeContact(address); 302 if (res != ImErrorInfo.NO_ERROR) { 303 mHandler.showAlert(R.string.error, 304 ErrorResUtils.getErrorRes(getResources(), res, address)); 305 } 306 } catch (RemoteException e) { 307 mHandler.showServiceErrorAlert(); 308 } 309 } 310 }; 311 Resources r = getResources(); 312 313 new AlertDialog.Builder(mContext) 314 .setTitle(R.string.confirm) 315 .setMessage(r.getString(R.string.confirm_delete_contact, nickname)) 316 .setPositiveButton(R.string.yes, confirmListener) // default button 317 .setNegativeButton(R.string.no, null) 318 .setCancelable(false) 319 .show(); 320 321 clearFocusIfEmpty(c); 322 } 323 } 324 325 public void blockContact() { 326 blockContact(getSelectedContact()); 327 } 328 329 public void blockContactAtPosition(long packedPosition) { 330 blockContact(getContactAtPosition(packedPosition)); 331 } 332 333 void blockContact(Cursor c) { 334 if (c == null) { 335 mHandler.showAlert(R.string.error, R.string.select_contact); 336 } else { 337 String nickname = c.getString(c.getColumnIndexOrThrow(Imps.Contacts.NICKNAME)); 338 final String address = c.getString(c.getColumnIndexOrThrow(Imps.Contacts.USERNAME)); 339 DialogInterface.OnClickListener confirmListener = new DialogInterface.OnClickListener(){ 340 public void onClick(DialogInterface dialog, int whichButton) { 341 try { 342 IContactListManager manager = mConn.getContactListManager(); 343 int res = manager.blockContact(address); 344 if (res != ImErrorInfo.NO_ERROR) { 345 mHandler.showAlert(R.string.error, 346 ErrorResUtils.getErrorRes(getResources(), res, address)); 347 } 348 } catch (RemoteException e) { 349 mHandler.showServiceErrorAlert(); 350 } 351 } 352 }; 353 354 Resources r = getResources(); 355 356 new AlertDialog.Builder(mContext) 357 .setTitle(R.string.confirm) 358 .setMessage(r.getString(R.string.confirm_block_contact, nickname)) 359 .setPositiveButton(R.string.yes, confirmListener) // default button 360 .setNegativeButton(R.string.no, null) 361 .setCancelable(false) 362 .show(); 363 clearFocusIfEmpty(c); 364 } 365 } 366 367 public Cursor getContactAtPosition(long packedPosition) { 368 int type = ExpandableListView.getPackedPositionType(packedPosition); 369 if (type == ExpandableListView.PACKED_POSITION_TYPE_CHILD) { 370 int groupPosition = ExpandableListView.getPackedPositionGroup(packedPosition); 371 int childPosition = ExpandableListView.getPackedPositionChild(packedPosition); 372 return (Cursor) mAdapter.getChild(groupPosition, childPosition); 373 } 374 return null; 375 } 376 377 public Cursor getSelectedContact() { 378 long pos = mContactsList.getSelectedPosition(); 379 if (ExpandableListView.getPackedPositionType(pos) 380 == ExpandableListView.PACKED_POSITION_TYPE_CHILD) { 381 return (Cursor)mContactsList.getSelectedItem(); 382 } 383 return null; 384 } 385 386 public String getSelectedContactList() { 387 long pos = mContactsList.getSelectedPosition(); 388 int groupPos = ExpandableListView.getPackedPositionGroup(pos); 389 if (groupPos == -1) { 390 return null; 391 } 392 393 Cursor cursor = (Cursor)mAdapter.getGroup(groupPos); 394 if (cursor == null) { 395 return null; 396 } 397 return cursor.getString(cursor.getColumnIndexOrThrow(Imps.ContactList.NAME)); 398 } 399 400 private void registerListeners() { 401 try{ 402 IContactListManager listManager = mConn.getContactListManager(); 403 listManager.registerContactListListener(mContactListListener); 404 listManager.registerSubscriptionListener(mSubscriptionListener); 405 }catch(RemoteException e) { 406 mHandler.showServiceErrorAlert(); 407 } 408 } 409 410 private void unregisterListeners() { 411 try{ 412 IContactListManager listManager = mConn.getContactListManager(); 413 listManager.unregisterContactListListener(mContactListListener); 414 listManager.unregisterSubscriptionListener(mSubscriptionListener); 415 }catch(RemoteException e) { 416 mHandler.showServiceErrorAlert(); 417 } 418 } 419 420 private final OnChildClickListener mOnChildClickListener = new OnChildClickListener() { 421 public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, 422 int childPosition, long id) { 423 Cursor cursor = (Cursor)parent.getExpandableListAdapter().getChild( 424 groupPosition, childPosition); 425 if (cursor == null) { 426 Log.w(ImApp.LOG_TAG, 427 "[ContactListView.OnChildClickListener.onChildClick] cursor null! groupPos=" 428 + groupPosition + ", childPos=" + childPosition, 429 new RuntimeException()); 430 return false; 431 } 432 433 int subscriptionType = cursor.getInt(ContactView.COLUMN_SUBSCRIPTION_TYPE); 434 int subscriptionStatus = cursor.getInt(ContactView.COLUMN_SUBSCRIPTION_STATUS); 435 if ((subscriptionType == Imps.Contacts.SUBSCRIPTION_TYPE_FROM) 436 && (subscriptionStatus == Imps.Contacts.SUBSCRIPTION_STATUS_SUBSCRIBE_PENDING)){ 437 long providerId = cursor.getLong(ContactView.COLUMN_CONTACT_PROVIDER); 438 String username = cursor.getString(ContactView.COLUMN_CONTACT_USERNAME); 439 Intent intent = new Intent(ImServiceConstants.ACTION_MANAGE_SUBSCRIPTION, 440 ContentUris.withAppendedId(Imps.Contacts.CONTENT_URI, id)); 441 intent.putExtra(ImServiceConstants.EXTRA_INTENT_PROVIDER_ID, providerId); 442 intent.putExtra(ImServiceConstants.EXTRA_INTENT_FROM_ADDRESS, username); 443 mScreen.startActivity(intent); 444 } else { 445 startChat(cursor); 446 } 447 return true; 448 } 449 }; 450 451 static class SavedState extends BaseSavedState { 452 int[] mExpandedGroups; 453 454 SavedState(Parcelable superState, int[] expandedGroups) { 455 super(superState); 456 mExpandedGroups = expandedGroups; 457 } 458 459 private SavedState(Parcel in) { 460 super(in); 461 mExpandedGroups = in.createIntArray(); 462 } 463 464 @Override 465 public void writeToParcel(Parcel out, int flags) { 466 super.writeToParcel(out, flags); 467 out.writeIntArray(mExpandedGroups); 468 } 469 470 public static final Parcelable.Creator<SavedState> CREATOR 471 = new Parcelable.Creator<SavedState>() { 472 public SavedState createFromParcel(Parcel in) { 473 return new SavedState(in); 474 } 475 476 public SavedState[] newArray(int size) { 477 return new SavedState[size]; 478 } 479 }; 480 } 481 482 @Override 483 public Parcelable onSaveInstanceState() { 484 Parcelable superState = super.onSaveInstanceState(); 485 int[] expandedGroups = mAdapter == null ? null 486 : mAdapter.getExpandedGroups(); 487 return new SavedState(superState, expandedGroups); 488 } 489 490 @Override 491 public void onRestoreInstanceState(Parcelable state) { 492 SavedState ss = (SavedState) state; 493 494 super.onRestoreInstanceState(ss.getSuperState()); 495 496 mSavedState = ss; 497 } 498 499 protected void setAutoRefreshContacts(boolean isRefresh) { 500 mAutoRefresh = isRefresh; 501 } 502 503 @Override 504 protected void onLayout(boolean changed, int l, int t, int r, int b) { 505 if (mAutoRefresh) { 506 super.onLayout(changed, l, t, r, b); 507 } 508 } 509 } 510