ContactDetailActivity.java revision 2eb969cc399d87b659a45568fa951d394c216917
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.Activity; 37import android.app.Fragment; 38import android.app.FragmentManager; 39import android.app.FragmentTransaction; 40import android.content.ActivityNotFoundException; 41import android.content.ContentValues; 42import android.content.Context; 43import android.content.Intent; 44import android.net.Uri; 45import android.os.Bundle; 46import android.os.Handler; 47import android.support.v13.app.FragmentPagerAdapter; 48import android.support.v4.view.ViewPager; 49import android.support.v4.view.ViewPager.OnPageChangeListener; 50import android.util.Log; 51import android.view.KeyEvent; 52import android.view.Menu; 53import android.view.MenuInflater; 54import android.view.MenuItem; 55import android.view.View.OnClickListener; 56import android.view.LayoutInflater; 57import android.view.View; 58import android.view.ViewGroup; 59import android.widget.AbsListView; 60import android.widget.AbsListView.OnScrollListener; 61import android.widget.CheckBox; 62import android.widget.Toast; 63 64import java.util.ArrayList; 65 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 Log.i(TAG, getIntent().getData().toString()); 145 } 146 147 @Override 148 public void onAttachFragment(Fragment fragment) { 149 if (fragment instanceof ContactDetailFragment) { 150 mDetailFragment = (ContactDetailFragment) fragment; 151 mDetailFragment.setListener(mFragmentListener); 152 mDetailFragment.setVerticalScrollListener(mVerticalScrollListener); 153 mDetailFragment.setData(mContactData); 154 // If the contact has social updates, then the photo should be shown in the tab 155 // carousel, so don't show the photo again in the scrolling list of contact details. 156 // We also don't want to show the photo if there is a fragment carousel because then 157 // the picture will already be on the left of the list of contact details. 158 mDetailFragment.setShowPhotoInHeader(!mContactHasUpdates && mFragmentCarousel == null); 159 } else if (fragment instanceof ContactDetailUpdatesFragment) { 160 mUpdatesFragment = (ContactDetailUpdatesFragment) fragment; 161 mUpdatesFragment.setData(mContactData); 162 } else if (fragment instanceof ContactLoaderFragment) { 163 mLoaderFragment = (ContactLoaderFragment) fragment; 164 mLoaderFragment.setListener(mLoaderFragmentListener); 165 mLoaderFragment.loadUri(getIntent().getData()); 166 } 167 } 168 169 @Override 170 public void startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData, 171 boolean globalSearch) { 172 if (globalSearch) { 173 super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch); 174 } else { 175 ContactsSearchManager.startSearch(this, initialQuery); 176 } 177 } 178 179 @Override 180 public boolean onCreateOptionsMenu(Menu menu) { 181 super.onCreateOptionsMenu(menu); 182 MenuInflater inflater = getMenuInflater(); 183 inflater.inflate(R.menu.star, menu); 184 return true; 185 } 186 187 @Override 188 public boolean onPrepareOptionsMenu(Menu menu) { 189 MenuItem starredMenuItem = menu.findItem(R.id.menu_star); 190 ViewGroup starredContainer = (ViewGroup) getLayoutInflater().inflate( 191 R.layout.favorites_star, null, false); 192 final CheckBox starredView = (CheckBox) starredContainer.findViewById(R.id.star); 193 starredView.setOnClickListener(new OnClickListener() { 194 @Override 195 public void onClick(View v) { 196 // Toggle "starred" state 197 // Make sure there is a contact 198 if (mLookupUri != null) { 199 Intent intent = ContactSaveService.createSetStarredIntent( 200 ContactDetailActivity.this, mLookupUri, starredView.isChecked()); 201 ContactDetailActivity.this.startService(intent); 202 } 203 } 204 }); 205 // If there is contact data, update the starred state 206 if (mContactData != null) { 207 ContactDetailDisplayUtils.setStarred(mContactData, starredView); 208 } 209 starredMenuItem.setActionView(starredContainer); 210 return true; 211 } 212 213 @Override 214 public boolean onKeyDown(int keyCode, KeyEvent event) { 215 FragmentKeyListener mCurrentFragment; 216 switch (getCurrentPage()) { 217 case 0: 218 mCurrentFragment = (FragmentKeyListener) mDetailFragment; 219 break; 220 case 1: 221 mCurrentFragment = (FragmentKeyListener) mUpdatesFragment; 222 break; 223 default: 224 throw new IllegalStateException("Invalid current item for ViewPager"); 225 } 226 if (mCurrentFragment.handleKeyDown(keyCode)) return true; 227 228 return super.onKeyDown(keyCode, event); 229 } 230 231 private int getCurrentPage() { 232 // If the contact doesn't have any social updates, there is only 1 page (detail fragment). 233 if (!mContactHasUpdates) { 234 return 0; 235 } 236 // Otherwise find the current page based on the {@link ViewPager} or fragment carousel. 237 if (mViewPager != null) { 238 return mViewPager.getCurrentItem(); 239 } else if (mFragmentCarousel != null) { 240 return mFragmentCarousel.getCurrentPage(); 241 } 242 throw new IllegalStateException("Can't figure out the currently selected page. If the " + 243 "contact has social updates, there must be a ViewPager or fragment carousel"); 244 } 245 246 @Override 247 protected void onSaveInstanceState(Bundle outState) { 248 super.onSaveInstanceState(outState); 249 if (mViewPager != null) { 250 outState.putString(KEY_DETAIL_FRAGMENT_TAG, mDetailFragment.getTag()); 251 outState.putString(KEY_UPDATES_FRAGMENT_TAG, mUpdatesFragment.getTag()); 252 return; 253 } 254 } 255 256 private final ContactLoaderFragmentListener mLoaderFragmentListener = 257 new ContactLoaderFragmentListener() { 258 @Override 259 public void onDetailsLoaded(final ContactLoader.Result result) { 260 if (result == null) { 261 return; 262 } 263 // Since {@link FragmentTransaction}s cannot be done in the onLoadFinished() of the 264 // {@link LoaderCallbacks}, then post this {@link Runnable} to the {@link Handler} 265 // on the main thread to execute later. 266 mHandler.post(new Runnable() { 267 @Override 268 public void run() { 269 mContactData = result; 270 mLookupUri = result.getLookupUri(); 271 mContactHasUpdates = result.getSocialSnippet() != null; 272 invalidateOptionsMenu(); 273 setupTitle(); 274 if (mContactHasUpdates) { 275 setupContactWithUpdates(); 276 } else { 277 setupContactWithoutUpdates(); 278 } 279 } 280 }); 281 } 282 }; 283 284 /** 285 * Setup the activity title and subtitle with contact name and company. 286 */ 287 private void setupTitle() { 288 CharSequence displayName = ContactDetailDisplayUtils.getDisplayName(this, mContactData); 289 String company = ContactDetailDisplayUtils.getCompany(this, mContactData); 290 291 ActionBar actionBar = getActionBar(); 292 actionBar.setTitle(displayName); 293 actionBar.setSubtitle(company); 294 } 295 296 private void setupContactWithUpdates() { 297 if (mContentView == null) { 298 mContentView = (ViewGroup) mInflater.inflate( 299 R.layout.contact_detail_container_with_updates, mRootView, false); 300 mRootView.addView(mContentView); 301 } 302 303 // Narrow width screens have a {@link ViewPager} and {@link ContactDetailTabCarousel} 304 mViewPager = (ViewPager) findViewById(R.id.pager); 305 if (mViewPager != null) { 306 mViewPager.removeAllViews(); 307 mViewPager.setAdapter(new ViewPagerAdapter(getFragmentManager())); 308 mViewPager.setOnPageChangeListener(mOnPageChangeListener); 309 } 310 311 mTabCarousel = (ContactDetailTabCarousel) findViewById(R.id.tab_carousel); 312 if (mTabCarousel != null) { 313 mTabCarousel.setListener(mTabCarouselListener); 314 mTabCarousel.loadData(mContactData); 315 } 316 317 // Otherwise, wide width screens have a {@link ContactDetailFragmentCarousel} 318 mFragmentCarousel = (ContactDetailFragmentCarousel) findViewById(R.id.fragment_carousel); 319 if (mFragmentCarousel != null) { 320 if (mDetailFragment != null) mFragmentCarousel.setAboutFragment(mDetailFragment); 321 if (mUpdatesFragment != null) mFragmentCarousel.setUpdatesFragment(mUpdatesFragment); 322 } 323 } 324 325 private void setupContactWithoutUpdates() { 326 if (mContentView == null) { 327 mContentView = (ViewGroup) mInflater.inflate( 328 R.layout.contact_detail_container_without_updates, mRootView, false); 329 mRootView.addView(mContentView); 330 } 331 } 332 333 private final ContactDetailFragment.Listener mFragmentListener = 334 new ContactDetailFragment.Listener() { 335 @Override 336 public void onContactNotFound() { 337 finish(); 338 } 339 340 @Override 341 public void onEditRequested(Uri contactLookupUri) { 342 startActivity(new Intent(Intent.ACTION_EDIT, contactLookupUri)); 343 } 344 345 @Override 346 public void onItemClicked(Intent intent) { 347 try { 348 startActivity(intent); 349 } catch (ActivityNotFoundException e) { 350 Log.e(TAG, "No activity found for intent: " + intent); 351 } 352 } 353 354 @Override 355 public void onDeleteRequested(Uri contactUri) { 356 ContactDeletionInteraction.start(ContactDetailActivity.this, contactUri, true); 357 } 358 359 @Override 360 public void onCreateRawContactRequested( 361 ArrayList<ContentValues> values, Account account) { 362 Toast.makeText(ContactDetailActivity.this, R.string.toast_making_personal_copy, 363 Toast.LENGTH_LONG).show(); 364 Intent serviceIntent = ContactSaveService.createNewRawContactIntent( 365 ContactDetailActivity.this, values, account, 366 ContactDetailActivity.class, Intent.ACTION_VIEW); 367 startService(serviceIntent); 368 369 } 370 }; 371 372 public class ViewPagerAdapter extends FragmentPagerAdapter{ 373 374 public ViewPagerAdapter(FragmentManager fm) { 375 super(fm); 376 } 377 378 @Override 379 public Fragment getItem(int position) { 380 switch (position) { 381 case 0: 382 return new ContactDetailFragment(); 383 case 1: 384 return new ContactDetailUpdatesFragment(); 385 } 386 throw new IllegalStateException("No fragment at position " + position); 387 } 388 389 @Override 390 public int getCount() { 391 return FRAGMENT_COUNT; 392 } 393 } 394 395 private OnPageChangeListener mOnPageChangeListener = new OnPageChangeListener() { 396 397 @Override 398 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 399 // The user is horizontally dragging the {@link ViewPager}, so send 400 // these scroll changes to the tab carousel. Ignore these events though if the carousel 401 // is actually controlling the {@link ViewPager} scrolls because it will already be 402 // in the correct position. 403 if (mViewPager.isFakeDragging()) { 404 return; 405 } 406 int x = (int) ((position + positionOffset) * 407 mTabCarousel.getAllowedHorizontalScrollLength()); 408 mTabCarousel.scrollTo(x, 0); 409 } 410 411 @Override 412 public void onPageSelected(int position) { 413 // Since a new page has been selected by the {@link ViewPager}, 414 // update the tab selection in the carousel. 415 mTabCarousel.setCurrentTab(position); 416 } 417 418 @Override 419 public void onPageScrollStateChanged(int state) {} 420 421 }; 422 423 private ContactDetailTabCarousel.Listener mTabCarouselListener = 424 new ContactDetailTabCarousel.Listener() { 425 426 @Override 427 public void onTouchDown() { 428 // The user just started scrolling the carousel, so begin "fake dragging" the 429 // {@link ViewPager} if it's not already doing so. 430 if (mViewPager.isFakeDragging()) { 431 return; 432 } 433 mViewPager.beginFakeDrag(); 434 } 435 436 @Override 437 public void onTouchUp() { 438 // The user just stopped scrolling the carousel, so stop "fake dragging" the 439 // {@link ViewPager} if was doing so before. 440 if (mViewPager.isFakeDragging()) { 441 mViewPager.endFakeDrag(); 442 } 443 } 444 445 @Override 446 public void onScrollChanged(int l, int t, int oldl, int oldt) { 447 // The user is scrolling the carousel, so send the scroll deltas to the 448 // {@link ViewPager} so it can move in sync. 449 if (mViewPager.isFakeDragging()) { 450 mViewPager.fakeDragBy(oldl-l); 451 } 452 } 453 454 @Override 455 public void onTabSelected(int position) { 456 // The user selected a tab, so update the {@link ViewPager} 457 mViewPager.setCurrentItem(position); 458 } 459 }; 460 461 private OnScrollListener mVerticalScrollListener = new OnScrollListener() { 462 463 @Override 464 public void onScroll( 465 AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { 466 if (mTabCarousel == null) { 467 return; 468 } 469 // Only re-position the tab carousel vertically if the FIRST item is still visible on 470 // the screen, otherwise the carousel should be in the correct place (pinned at the 471 // top). 472 if (firstVisibleItem != 0) { 473 return; 474 } 475 View topView = view.getChildAt(firstVisibleItem); 476 if (topView == null) { 477 return; 478 } 479 int amtToScroll = Math.max((int) view.getChildAt(firstVisibleItem).getY(), 480 -mTabCarousel.getAllowedVerticalScrollLength()); 481 mTabCarousel.setY(amtToScroll); 482 } 483 484 @Override 485 public void onScrollStateChanged(AbsListView view, int scrollState) {} 486 487 }; 488 489 /** 490 * This interface should be implemented by {@link Fragment}s within this 491 * activity so that the activity can determine whether the currently 492 * displayed view is handling the key event or not. 493 */ 494 public interface FragmentKeyListener { 495 /** 496 * Returns true if the key down event will be handled by the implementing class, or false 497 * otherwise. 498 */ 499 public boolean handleKeyDown(int keyCode); 500 } 501} 502