ContactDetailActivity.java revision 43fbbc1e5bbc295d0be5033a002e9f532fdfb119
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17package com.android.contacts.activities; 18 19import com.android.contacts.ContactLoader; 20import com.android.contacts.ContactSaveService; 21import com.android.contacts.ContactsActivity; 22import com.android.contacts.ContactsSearchManager; 23import com.android.contacts.R; 24import com.android.contacts.detail.ContactDetailDisplayUtils; 25import com.android.contacts.detail.ContactDetailFragment; 26import com.android.contacts.detail.ContactDetailFragmentCarousel; 27import com.android.contacts.detail.ContactDetailTabCarousel; 28import com.android.contacts.detail.ContactDetailUpdatesFragment; 29import com.android.contacts.detail.ContactLoaderFragment; 30import com.android.contacts.detail.ContactLoaderFragment.ContactLoaderFragmentListener; 31import com.android.contacts.interactions.ContactDeletionInteraction; 32import com.android.contacts.util.PhoneCapabilityTester; 33 34import android.accounts.Account; 35import android.app.ActionBar; 36import android.app.Fragment; 37import android.app.FragmentManager; 38import android.app.FragmentTransaction; 39import android.content.ActivityNotFoundException; 40import android.content.ContentValues; 41import android.content.Context; 42import android.content.Intent; 43import android.net.Uri; 44import android.os.Bundle; 45import android.os.Handler; 46import android.support.v13.app.FragmentPagerAdapter; 47import android.support.v4.view.ViewPager; 48import android.support.v4.view.ViewPager.OnPageChangeListener; 49import android.util.Log; 50import android.view.KeyEvent; 51import android.view.LayoutInflater; 52import android.view.Menu; 53import android.view.MenuInflater; 54import android.view.MenuItem; 55import android.view.View; 56import android.view.View.OnClickListener; 57import android.view.ViewGroup; 58import android.widget.AbsListView; 59import android.widget.AbsListView.OnScrollListener; 60import android.widget.CheckBox; 61import android.widget.Toast; 62 63import java.util.ArrayList; 64 65// TODO: Use {@link ContactDetailLayoutController} so there isn't duplicated code 66public class ContactDetailActivity extends ContactsActivity { 67 private static final String TAG = "ContactDetailActivity"; 68 69 private static final String KEY_DETAIL_FRAGMENT_TAG = "detailFragTag"; 70 private static final String KEY_UPDATES_FRAGMENT_TAG = "updatesFragTag"; 71 72 public static final int FRAGMENT_COUNT = 2; 73 74 private ContactLoader.Result mContactData; 75 private Uri mLookupUri; 76 77 private ContactLoaderFragment mLoaderFragment; 78 private ContactDetailFragment mDetailFragment; 79 private ContactDetailUpdatesFragment mUpdatesFragment; 80 81 private ContactDetailTabCarousel mTabCarousel; 82 private ViewPager mViewPager; 83 84 private ContactDetailFragmentCarousel mFragmentCarousel; 85 86 private ViewGroup mRootView; 87 private ViewGroup mContentView; 88 private LayoutInflater mInflater; 89 90 private Handler mHandler = new Handler(); 91 92 /** 93 * Whether or not the contact has updates, which dictates whether the 94 * {@link ContactDetailUpdatesFragment} will be shown. 95 */ 96 private boolean mContactHasUpdates; 97 98 @Override 99 public void onCreate(Bundle savedState) { 100 super.onCreate(savedState); 101 if (PhoneCapabilityTester.isUsingTwoPanes(this)) { 102 // This activity must not be shown. We have to select the contact in the 103 // PeopleActivity instead ==> Create a forward intent and finish 104 final Intent originalIntent = getIntent(); 105 Intent intent = new Intent(); 106 intent.setAction(originalIntent.getAction()); 107 intent.setDataAndType(originalIntent.getData(), originalIntent.getType()); 108 intent.setFlags( 109 Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS | Intent.FLAG_ACTIVITY_FORWARD_RESULT 110 | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); 111 112 intent.setClass(this, PeopleActivity.class); 113 startActivity(intent); 114 finish(); 115 return; 116 } 117 118 setContentView(R.layout.contact_detail_activity); 119 mRootView = (ViewGroup) findViewById(R.id.contact_detail_view); 120 mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); 121 122 // Manually remove any {@link ViewPager} fragments if there was an orientation change 123 // because the {@link ViewPager} is not used in both orientations. (If we leave the 124 // fragments around, they'll be around in the {@link FragmentManager} but won't be visible 125 // on screen and the {@link ViewPager} won't ask to initialize them again). 126 if (savedState != null) { 127 String aboutFragmentTag = savedState.getString(KEY_DETAIL_FRAGMENT_TAG); 128 String updatesFragmentTag = savedState.getString(KEY_UPDATES_FRAGMENT_TAG); 129 130 FragmentManager fragmentManager = getFragmentManager(); 131 mDetailFragment = (ContactDetailFragment) fragmentManager.findFragmentByTag( 132 aboutFragmentTag); 133 mUpdatesFragment = (ContactDetailUpdatesFragment) fragmentManager.findFragmentByTag( 134 updatesFragmentTag); 135 136 if (mDetailFragment != null && mUpdatesFragment != null) { 137 FragmentTransaction ft = fragmentManager.beginTransaction(); 138 ft.remove(mDetailFragment); 139 ft.remove(mUpdatesFragment); 140 ft.commit(); 141 } 142 } 143 144 ActionBar actionBar = getActionBar(); 145 if (actionBar != null) { 146 actionBar.setDisplayHomeAsUpEnabled(true); 147 } 148 149 Log.i(TAG, getIntent().getData().toString()); 150 } 151 152 @Override 153 public void onAttachFragment(Fragment fragment) { 154 if (fragment instanceof ContactDetailFragment) { 155 mDetailFragment = (ContactDetailFragment) fragment; 156 mDetailFragment.setListener(mFragmentListener); 157 mDetailFragment.setVerticalScrollListener(mVerticalScrollListener); 158 mDetailFragment.setData(mLookupUri, mContactData); 159 // If the contact has social updates, then the photo should be shown in the tab 160 // carousel, so don't show the photo again in the scrolling list of contact details. 161 // We also don't want to show the photo if there is a fragment carousel because then 162 // the picture will already be on the left of the list of contact details. 163 mDetailFragment.setShowPhotoInHeader(!mContactHasUpdates && mFragmentCarousel == null); 164 } else if (fragment instanceof ContactDetailUpdatesFragment) { 165 mUpdatesFragment = (ContactDetailUpdatesFragment) fragment; 166 mUpdatesFragment.setData(mLookupUri, mContactData); 167 } else if (fragment instanceof ContactLoaderFragment) { 168 mLoaderFragment = (ContactLoaderFragment) fragment; 169 mLoaderFragment.setListener(mLoaderFragmentListener); 170 mLoaderFragment.loadUri(getIntent().getData()); 171 } 172 } 173 174 @Override 175 public void startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData, 176 boolean globalSearch) { 177 // Ignore search key press 178 } 179 180 @Override 181 public boolean onCreateOptionsMenu(Menu menu) { 182 super.onCreateOptionsMenu(menu); 183 MenuInflater inflater = getMenuInflater(); 184 inflater.inflate(R.menu.star, menu); 185 return true; 186 } 187 188 @Override 189 public boolean onPrepareOptionsMenu(Menu menu) { 190 MenuItem starredMenuItem = menu.findItem(R.id.menu_star); 191 ViewGroup starredContainer = (ViewGroup) getLayoutInflater().inflate( 192 R.layout.favorites_star, null, false); 193 final CheckBox starredView = (CheckBox) starredContainer.findViewById(R.id.star); 194 starredView.setOnClickListener(new OnClickListener() { 195 @Override 196 public void onClick(View v) { 197 // Toggle "starred" state 198 // Make sure there is a contact 199 if (mLookupUri != null) { 200 Intent intent = ContactSaveService.createSetStarredIntent( 201 ContactDetailActivity.this, mLookupUri, starredView.isChecked()); 202 ContactDetailActivity.this.startService(intent); 203 } 204 } 205 }); 206 // If there is contact data, update the starred state 207 if (mContactData != null) { 208 ContactDetailDisplayUtils.setStarred(mContactData, starredView); 209 } 210 starredMenuItem.setActionView(starredContainer); 211 return true; 212 } 213 214 @Override 215 public boolean onKeyDown(int keyCode, KeyEvent event) { 216 // First check if the {@link ContactLoaderFragment} can handle the key 217 if (mLoaderFragment != null && mLoaderFragment.handleKeyDown(keyCode)) return true; 218 219 // Otherwise find the correct fragment to handle the event 220 FragmentKeyListener mCurrentFragment; 221 switch (getCurrentPage()) { 222 case 0: 223 mCurrentFragment = mDetailFragment; 224 break; 225 case 1: 226 mCurrentFragment = mUpdatesFragment; 227 break; 228 default: 229 throw new IllegalStateException("Invalid current item for ViewPager"); 230 } 231 if (mCurrentFragment != null && mCurrentFragment.handleKeyDown(keyCode)) return true; 232 233 // In the last case, give the key event to the superclass. 234 return super.onKeyDown(keyCode, event); 235 } 236 237 private int getCurrentPage() { 238 // If the contact doesn't have any social updates, there is only 1 page (detail fragment). 239 if (!mContactHasUpdates) { 240 return 0; 241 } 242 // Otherwise find the current page based on the {@link ViewPager} or fragment carousel. 243 if (mViewPager != null) { 244 return mViewPager.getCurrentItem(); 245 } else if (mFragmentCarousel != null) { 246 return mFragmentCarousel.getCurrentPage(); 247 } 248 throw new IllegalStateException("Can't figure out the currently selected page. If the " + 249 "contact has social updates, there must be a ViewPager or fragment carousel"); 250 } 251 252 @Override 253 protected void onSaveInstanceState(Bundle outState) { 254 super.onSaveInstanceState(outState); 255 if (mViewPager != null) { 256 outState.putString(KEY_DETAIL_FRAGMENT_TAG, mDetailFragment.getTag()); 257 outState.putString(KEY_UPDATES_FRAGMENT_TAG, mUpdatesFragment.getTag()); 258 return; 259 } 260 } 261 262 private final ContactLoaderFragmentListener mLoaderFragmentListener = 263 new ContactLoaderFragmentListener() { 264 @Override 265 public void onContactNotFound() { 266 finish(); 267 } 268 269 @Override 270 public void onDetailsLoaded(final ContactLoader.Result result) { 271 if (result == null) { 272 return; 273 } 274 // Since {@link FragmentTransaction}s cannot be done in the onLoadFinished() of the 275 // {@link LoaderCallbacks}, then post this {@link Runnable} to the {@link Handler} 276 // on the main thread to execute later. 277 mHandler.post(new Runnable() { 278 @Override 279 public void run() { 280 mContactData = result; 281 mLookupUri = result.getLookupUri(); 282 mContactHasUpdates = result.getSocialSnippet() != null; 283 invalidateOptionsMenu(); 284 setupTitle(); 285 if (mContactHasUpdates) { 286 setupContactWithUpdates(); 287 } else { 288 setupContactWithoutUpdates(); 289 } 290 } 291 }); 292 } 293 294 @Override 295 public void onEditRequested(Uri contactLookupUri) { 296 startActivity(new Intent(Intent.ACTION_EDIT, contactLookupUri)); 297 } 298 299 @Override 300 public void onDeleteRequested(Uri contactUri) { 301 ContactDeletionInteraction.start(ContactDetailActivity.this, contactUri, true); 302 } 303 }; 304 305 /** 306 * Setup the activity title and subtitle with contact name and company. 307 */ 308 private void setupTitle() { 309 CharSequence displayName = ContactDetailDisplayUtils.getDisplayName(this, mContactData); 310 String company = ContactDetailDisplayUtils.getCompany(this, mContactData); 311 312 ActionBar actionBar = getActionBar(); 313 actionBar.setTitle(displayName); 314 actionBar.setSubtitle(company); 315 } 316 317 private void setupContactWithUpdates() { 318 if (mContentView == null) { 319 mContentView = (ViewGroup) mInflater.inflate( 320 R.layout.contact_detail_container_with_updates, mRootView, false); 321 mRootView.addView(mContentView); 322 323 // Make sure all needed views are retrieved. Note that narrow width screens have a 324 // {@link ViewPager} and {@link ContactDetailTabCarousel}, while wide width screens have 325 // a {@link ContactDetailFragmentCarousel}. 326 mViewPager = (ViewPager) findViewById(R.id.pager); 327 if (mViewPager != null) { 328 mViewPager.removeAllViews(); 329 mViewPager.setAdapter(new ViewPagerAdapter(getFragmentManager())); 330 mViewPager.setOnPageChangeListener(mOnPageChangeListener); 331 } 332 333 mTabCarousel = (ContactDetailTabCarousel) findViewById(R.id.tab_carousel); 334 if (mTabCarousel != null) { 335 mTabCarousel.setListener(mTabCarouselListener); 336 } 337 338 mFragmentCarousel = (ContactDetailFragmentCarousel) 339 findViewById(R.id.fragment_carousel); 340 } 341 342 // Then reset the contact data to the appropriate views 343 if (mTabCarousel != null) { 344 mTabCarousel.loadData(mContactData); 345 } 346 if (mFragmentCarousel != null) { 347 if (mDetailFragment != null) mFragmentCarousel.setAboutFragment(mDetailFragment); 348 if (mUpdatesFragment != null) mFragmentCarousel.setUpdatesFragment(mUpdatesFragment); 349 } 350 if (mDetailFragment != null) { 351 mDetailFragment.setData(mLookupUri, mContactData); 352 } 353 if (mUpdatesFragment != null) { 354 mUpdatesFragment.setData(mLookupUri, mContactData); 355 } 356 } 357 358 private void setupContactWithoutUpdates() { 359 if (mContentView == null) { 360 mContentView = (ViewGroup) mInflater.inflate( 361 R.layout.contact_detail_container_without_updates, mRootView, false); 362 mRootView.addView(mContentView); 363 } 364 // Reset contact data 365 if (mDetailFragment != null) { 366 mDetailFragment.setData(mLookupUri, mContactData); 367 } 368 } 369 370 private final ContactDetailFragment.Listener mFragmentListener = 371 new ContactDetailFragment.Listener() { 372 @Override 373 public void onItemClicked(Intent intent) { 374 try { 375 startActivity(intent); 376 } catch (ActivityNotFoundException e) { 377 Log.e(TAG, "No activity found for intent: " + intent); 378 } 379 } 380 381 @Override 382 public void onCreateRawContactRequested( 383 ArrayList<ContentValues> values, Account account) { 384 Toast.makeText(ContactDetailActivity.this, R.string.toast_making_personal_copy, 385 Toast.LENGTH_LONG).show(); 386 Intent serviceIntent = ContactSaveService.createNewRawContactIntent( 387 ContactDetailActivity.this, values, account, 388 ContactDetailActivity.class, Intent.ACTION_VIEW); 389 startService(serviceIntent); 390 391 } 392 }; 393 394 public class ViewPagerAdapter extends FragmentPagerAdapter{ 395 396 public ViewPagerAdapter(FragmentManager fm) { 397 super(fm); 398 } 399 400 @Override 401 public Fragment getItem(int position) { 402 switch (position) { 403 case 0: 404 return new ContactDetailFragment(); 405 case 1: 406 return new ContactDetailUpdatesFragment(); 407 } 408 throw new IllegalStateException("No fragment at position " + position); 409 } 410 411 @Override 412 public int getCount() { 413 return FRAGMENT_COUNT; 414 } 415 } 416 417 private OnPageChangeListener mOnPageChangeListener = new OnPageChangeListener() { 418 419 @Override 420 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 421 // The user is horizontally dragging the {@link ViewPager}, so send 422 // these scroll changes to the tab carousel. Ignore these events though if the carousel 423 // is actually controlling the {@link ViewPager} scrolls because it will already be 424 // in the correct position. 425 if (mViewPager.isFakeDragging()) { 426 return; 427 } 428 int x = (int) ((position + positionOffset) * 429 mTabCarousel.getAllowedHorizontalScrollLength()); 430 mTabCarousel.scrollTo(x, 0); 431 } 432 433 @Override 434 public void onPageSelected(int position) { 435 // Since a new page has been selected by the {@link ViewPager}, 436 // update the tab selection in the carousel. 437 mTabCarousel.setCurrentTab(position); 438 } 439 440 @Override 441 public void onPageScrollStateChanged(int state) {} 442 443 }; 444 445 private ContactDetailTabCarousel.Listener mTabCarouselListener = 446 new ContactDetailTabCarousel.Listener() { 447 448 @Override 449 public void onTouchDown() { 450 // The user just started scrolling the carousel, so begin "fake dragging" the 451 // {@link ViewPager} if it's not already doing so. 452 if (mViewPager.isFakeDragging()) { 453 return; 454 } 455 mViewPager.beginFakeDrag(); 456 } 457 458 @Override 459 public void onTouchUp() { 460 // The user just stopped scrolling the carousel, so stop "fake dragging" the 461 // {@link ViewPager} if was doing so before. 462 if (mViewPager.isFakeDragging()) { 463 mViewPager.endFakeDrag(); 464 } 465 } 466 467 @Override 468 public void onScrollChanged(int l, int t, int oldl, int oldt) { 469 // The user is scrolling the carousel, so send the scroll deltas to the 470 // {@link ViewPager} so it can move in sync. 471 if (mViewPager.isFakeDragging()) { 472 mViewPager.fakeDragBy(oldl-l); 473 } 474 } 475 476 @Override 477 public void onTabSelected(int position) { 478 // The user selected a tab, so update the {@link ViewPager} 479 mViewPager.setCurrentItem(position); 480 } 481 }; 482 483 private OnScrollListener mVerticalScrollListener = new OnScrollListener() { 484 485 @Override 486 public void onScroll( 487 AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { 488 if (mTabCarousel == null) { 489 return; 490 } 491 // If the FIRST item is not visible on the screen, then the carousel must be pinned 492 // at the top of the screen. 493 if (firstVisibleItem != 0) { 494 mTabCarousel.setY(-mTabCarousel.getAllowedVerticalScrollLength()); 495 return; 496 } 497 View topView = view.getChildAt(firstVisibleItem); 498 if (topView == null) { 499 return; 500 } 501 int amtToScroll = Math.max((int) view.getChildAt(firstVisibleItem).getY(), 502 - mTabCarousel.getAllowedVerticalScrollLength()); 503 mTabCarousel.setY(amtToScroll); 504 } 505 506 @Override 507 public void onScrollStateChanged(AbsListView view, int scrollState) {} 508 509 }; 510 511 /** 512 * This interface should be implemented by {@link Fragment}s within this 513 * activity so that the activity can determine whether the currently 514 * displayed view is handling the key event or not. 515 */ 516 public interface FragmentKeyListener { 517 /** 518 * Returns true if the key down event will be handled by the implementing class, or false 519 * otherwise. 520 */ 521 public boolean handleKeyDown(int keyCode); 522 } 523 524 @Override 525 public boolean onOptionsItemSelected(MenuItem item) { 526 527 switch (item.getItemId()) { 528 case android.R.id.home: 529 finish(); 530 return true; 531 default: 532 break; 533 } 534 return super.onOptionsItemSelected(item); 535 } 536} 537