Merge "UI improvements in the call details."
diff --git a/res/layout-sw580dp-w1000dp/contact_detail_container.xml b/res/layout-sw580dp-w1000dp/contact_detail_container.xml
new file mode 100644
index 0000000..b6a6319
--- /dev/null
+++ b/res/layout-sw580dp-w1000dp/contact_detail_container.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!--
+ Two-column layout for a contact with social updates. If the contact does not
+ have social updates, then the second fragment view will just be hidden.
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:scrollbars="none"
+ android:orientation="horizontal">
+
+ <fragment class="com.android.contacts.detail.ContactDetailFragment"
+ android:id="@+id/about_fragment"
+ android:layout_width="0dip"
+ android:layout_height="match_parent"
+ android:layout_weight="3" />
+
+ <fragment class="com.android.contacts.detail.ContactDetailUpdatesFragment"
+ android:id="@+id/updates_fragment"
+ android:layout_width="0dip"
+ android:layout_height="match_parent"
+ android:layout_weight="2" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout-sw580dp/people_activity.xml b/res/layout-sw580dp/people_activity.xml
index 6d702e7..7301d24 100644
--- a/res/layout-sw580dp/people_activity.xml
+++ b/res/layout-sw580dp/people_activity.xml
@@ -79,16 +79,27 @@
ex:animationDuration="200"
android:visibility="gone">
- <fragment
- android:id="@+id/contact_detail_fragment"
- class="com.android.contacts.detail.ContactDetailFragment"
+ <!-- This layout includes all possible views needed for a contact detail page -->
+ <include
+ layout="@layout/contact_detail_container"
android:layout_width="match_parent"
- android:layout_height="match_parent" />
+ android:layout_height="match_parent"/>
+
+ <!-- This invisible worker fragment loads the contact's details -->
+ <fragment
+ android:id="@+id/contact_detail_loader_fragment"
+ class="com.android.contacts.detail.ContactLoaderFragment"
+ android:layout_height="0dip"
+ android:layout_width="0dip"
+ android:visibility="gone"/>
+
+ <!-- This is the group detail page -->
<fragment
android:id="@+id/group_detail_fragment"
class="com.android.contacts.group.GroupDetailFragment"
android:layout_width="match_parent"
- android:layout_height="match_parent" />
+ android:layout_height="match_parent"
+ android:visibility="gone" />
</view>
<view
diff --git a/res/layout/contact_detail_container.xml b/res/layout/contact_detail_container.xml
new file mode 100644
index 0000000..0b5b85a
--- /dev/null
+++ b/res/layout/contact_detail_container.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!--
+ Layout for the contact card page. If the contact has social updates, then
+ the ViewPager and ContactDetailTabCarousel are shown together. If there
+ aren't any social updates, then just the ContactDetailFragment will be
+ shown. We include all 3 views even though they are never shown
+ simultaneously because this layout is reused but set with different data
+ each time (i.e. on a tablet).
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <android.support.v4.view.ViewPager
+ android:id="@+id/pager"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentLeft="true"
+ android:paddingTop="20dip"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ <include
+ android:id="@+id/tab_carousel"
+ layout="@layout/contact_detail_tab_carousel"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentLeft="true"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="20dip"
+ android:paddingLeft="10dip"
+ android:paddingRight="10dip"/>
+
+ <fragment
+ android:id="@+id/contact_detail_fragment"
+ class="com.android.contacts.detail.ContactDetailFragment"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/res/values-sw580dp-w1000dp/dimens.xml b/res/values-sw580dp-w1000dp/dimens.xml
index 45da0a8..dfa1b81 100644
--- a/res/values-sw580dp-w1000dp/dimens.xml
+++ b/res/values-sw580dp-w1000dp/dimens.xml
@@ -21,4 +21,5 @@
<dimen name="action_bar_search_spacing">32dip</dimen>
<dimen name="detail_header_view_margin">16dip</dimen>
<dimen name="detail_header_attribution_height">56dip</dimen>
+ <dimen name="detail_tab_carousel_height">0dip</dimen>
</resources>
diff --git a/res/values-sw580dp/dimens.xml b/res/values-sw580dp/dimens.xml
index f1e5736..0c1e5fb 100644
--- a/res/values-sw580dp/dimens.xml
+++ b/res/values-sw580dp/dimens.xml
@@ -32,5 +32,5 @@
<dimen name="shortcut_icon_size">64dip</dimen>
<dimen name="list_section_height">37dip</dimen>
<dimen name="directory_header_height">56dip</dimen>
- <dimen name="detail_tab_carousel_height">150dip</dimen>
+ <dimen name="detail_tab_carousel_height">256dip</dimen>
</resources>
diff --git a/src/com/android/contacts/activities/ContactDetailActivity.java b/src/com/android/contacts/activities/ContactDetailActivity.java
index b45ba39..800bfb1 100644
--- a/src/com/android/contacts/activities/ContactDetailActivity.java
+++ b/src/com/android/contacts/activities/ContactDetailActivity.java
@@ -62,6 +62,7 @@
import java.util.ArrayList;
+// TODO: Use {@link ContactDetailLayoutController} so there isn't duplicated code
public class ContactDetailActivity extends ContactsActivity {
private static final String TAG = "ContactDetailActivity";
@@ -216,19 +217,24 @@
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
+ // First check if the {@link ContactLoaderFragment} can handle the key
+ if (mLoaderFragment.handleKeyDown(keyCode)) return true;
+
+ // Otherwise find the correct fragment to handle the event
FragmentKeyListener mCurrentFragment;
switch (getCurrentPage()) {
case 0:
- mCurrentFragment = (FragmentKeyListener) mDetailFragment;
+ mCurrentFragment = mDetailFragment;
break;
case 1:
- mCurrentFragment = (FragmentKeyListener) mUpdatesFragment;
+ mCurrentFragment = mUpdatesFragment;
break;
default:
throw new IllegalStateException("Invalid current item for ViewPager");
}
if (mCurrentFragment.handleKeyDown(keyCode)) return true;
+ // In the last case, give the key event to the superclass.
return super.onKeyDown(keyCode, event);
}
@@ -260,6 +266,11 @@
private final ContactLoaderFragmentListener mLoaderFragmentListener =
new ContactLoaderFragmentListener() {
@Override
+ public void onContactNotFound() {
+ finish();
+ }
+
+ @Override
public void onDetailsLoaded(final ContactLoader.Result result) {
if (result == null) {
return;
@@ -283,6 +294,16 @@
}
});
}
+
+ @Override
+ public void onEditRequested(Uri contactLookupUri) {
+ startActivity(new Intent(Intent.ACTION_EDIT, contactLookupUri));
+ }
+
+ @Override
+ public void onDeleteRequested(Uri contactUri) {
+ ContactDeletionInteraction.start(ContactDetailActivity.this, contactUri, true);
+ }
};
/**
@@ -337,16 +358,6 @@
private final ContactDetailFragment.Listener mFragmentListener =
new ContactDetailFragment.Listener() {
@Override
- public void onContactNotFound() {
- finish();
- }
-
- @Override
- public void onEditRequested(Uri contactLookupUri) {
- startActivity(new Intent(Intent.ACTION_EDIT, contactLookupUri));
- }
-
- @Override
public void onItemClicked(Intent intent) {
try {
startActivity(intent);
@@ -356,11 +367,6 @@
}
@Override
- public void onDeleteRequested(Uri contactUri) {
- ContactDeletionInteraction.start(ContactDetailActivity.this, contactUri, true);
- }
-
- @Override
public void onCreateRawContactRequested(
ArrayList<ContentValues> values, Account account) {
Toast.makeText(ContactDetailActivity.this, R.string.toast_making_personal_copy,
diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index eecb375..d5bdd61 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -16,10 +16,16 @@
package com.android.contacts.activities;
+import com.android.contacts.ContactLoader;
import com.android.contacts.ContactSaveService;
import com.android.contacts.ContactsActivity;
import com.android.contacts.R;
import com.android.contacts.detail.ContactDetailFragment;
+import com.android.contacts.detail.ContactDetailLayoutController;
+import com.android.contacts.detail.ContactDetailTabCarousel;
+import com.android.contacts.detail.ContactDetailUpdatesFragment;
+import com.android.contacts.detail.ContactLoaderFragment;
+import com.android.contacts.detail.ContactLoaderFragment.ContactLoaderFragmentListener;
import com.android.contacts.group.GroupBrowseListFragment;
import com.android.contacts.group.GroupBrowseListFragment.OnGroupBrowserActionListener;
import com.android.contacts.group.GroupDetailFragment;
@@ -64,11 +70,13 @@
import android.content.res.TypedArray;
import android.net.Uri;
import android.os.Bundle;
+import android.os.Handler;
import android.provider.ContactsContract;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Intents;
import android.provider.ContactsContract.ProviderStatus;
import android.provider.Settings;
+import android.support.v4.view.ViewPager;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
@@ -122,12 +130,17 @@
private boolean mContentPaneDisplayed;
private ContactDetailFragment mContactDetailFragment;
+ private ContactDetailUpdatesFragment mContactDetailUpdatesFragment;
private final ContactDetailFragmentListener mContactDetailFragmentListener =
new ContactDetailFragmentListener();
- private final GroupDetailFragmentListener mGroupDetailFragmentListener =
- new GroupDetailFragmentListener();
+
+ private ContactLoaderFragment mContactDetailLoaderFragment;
+ private final ContactDetailLoaderFragmentListener mContactDetailLoaderFragmentListener =
+ new ContactDetailLoaderFragmentListener();
private GroupDetailFragment mGroupDetailFragment;
+ private final GroupDetailFragmentListener mGroupDetailFragmentListener =
+ new GroupDetailFragmentListener();
private StrequentContactListFragment.Listener mFavoritesFragmentListener =
new StrequentContactListFragmentListener();
@@ -153,6 +166,10 @@
private View mAddGroupImageView;
+ private ContactDetailLayoutController mContactDetailLayoutController;
+
+ private Handler mHandler = new Handler();
+
private enum TabState {
FAVORITES, CONTACTS, GROUPS
}
@@ -188,11 +205,17 @@
mContactDetailFragment = (ContactDetailFragment) fragment;
mContactDetailFragment.setListener(mContactDetailFragmentListener);
mContentPaneDisplayed = true;
+ } else if (fragment instanceof ContactDetailUpdatesFragment) {
+ mContactDetailUpdatesFragment = (ContactDetailUpdatesFragment) fragment;
} else if (fragment instanceof ContactsUnavailableFragment) {
mContactsUnavailableFragment = (ContactsUnavailableFragment)fragment;
mContactsUnavailableFragment.setProviderStatusLoader(mProviderStatusLoader);
mContactsUnavailableFragment.setOnContactsUnavailableActionListener(
new ContactsUnavailableFragmentListener());
+ } else if (fragment instanceof ContactLoaderFragment) {
+ mContactDetailLoaderFragment = (ContactLoaderFragment) fragment;
+ mContactDetailLoaderFragment.setListener(mContactDetailLoaderFragmentListener);
+ mContentPaneDisplayed = true;
} else if (fragment instanceof GroupDetailFragment) {
mGroupDetailFragment = (GroupDetailFragment) fragment;
mGroupDetailFragment.setListener(mGroupDetailFragmentListener);
@@ -283,6 +306,13 @@
mActionBarAdapter.onCreate(savedState, mRequest, getActionBar(), !mContentPaneDisplayed);
mActionBarAdapter.setContactListFilterController(mContactListFilterController);
+ ViewPager viewPager = (ViewPager) findViewById(R.id.pager);
+ ContactDetailTabCarousel tabCarousel = (ContactDetailTabCarousel)
+ findViewById(R.id.tab_carousel);
+ mContactDetailLayoutController = new ContactDetailLayoutController(
+ getFragmentManager(), viewPager, tabCarousel,
+ mContactDetailFragmentListener);
+
if (createContentView) {
actionBar.removeAllTabs();
Tab favoritesTab = actionBar.newTab();
@@ -294,7 +324,7 @@
Tab peopleTab = actionBar.newTab();
peopleTab.setText(getString(R.string.people));
peopleTab.setTabListener(new TabChangeListener(mContactsFragment,
- mContactDetailFragment, TabState.CONTACTS));
+ mContactDetailLoaderFragment, TabState.CONTACTS));
actionBar.addTab(peopleTab);
Tab groupsTab = actionBar.newTab();
@@ -487,7 +517,7 @@
}
private void setupContactDetailFragment(final Uri contactLookupUri) {
- mContactDetailFragment.loadUri(contactLookupUri);
+ mContactDetailLoaderFragment.loadUri(contactLookupUri);
invalidateOptionsMenuIfNeeded();
}
@@ -726,19 +756,49 @@
}
}
- private class ContactDetailFragmentListener implements ContactDetailFragment.Listener {
+ private class ContactDetailLoaderFragmentListener implements ContactLoaderFragmentListener {
@Override
public void onContactNotFound() {
// Nothing needs to be done here
}
@Override
+ public void onDetailsLoaded(final ContactLoader.Result result) {
+ if (result == null) {
+ return;
+ }
+ // Since {@link FragmentTransaction}s cannot be done in the onLoadFinished() of the
+ // {@link LoaderCallbacks}, then post this {@link Runnable} to the {@link Handler}
+ // on the main thread to execute later.
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (!mContactDetailLayoutController.isInitialized()) {
+ mContactDetailLayoutController.setContactDetailFragment(
+ mContactDetailFragment);
+ mContactDetailLayoutController.setContactDetailUpdatesFragment(
+ mContactDetailUpdatesFragment);
+ mContactDetailLayoutController.initialize();
+ }
+ mContactDetailLayoutController.setContactData(result);
+ }
+ });
+ }
+
+ @Override
public void onEditRequested(Uri contactLookupUri) {
startActivityForResult(
new Intent(Intent.ACTION_EDIT, contactLookupUri), SUBACTIVITY_EDIT_CONTACT);
}
@Override
+ public void onDeleteRequested(Uri contactUri) {
+ ContactDeletionInteraction.start(PeopleActivity.this, contactUri, false);
+ }
+ }
+
+ public class ContactDetailFragmentListener implements ContactDetailFragment.Listener {
+ @Override
public void onItemClicked(Intent intent) {
try {
startActivity(intent);
@@ -748,11 +808,6 @@
}
@Override
- public void onDeleteRequested(Uri contactUri) {
- ContactDeletionInteraction.start(PeopleActivity.this, contactUri, false);
- }
-
- @Override
public void onCreateRawContactRequested(ArrayList<ContentValues> values, Account account) {
Toast.makeText(PeopleActivity.this, R.string.toast_making_personal_copy,
Toast.LENGTH_LONG).show();
@@ -925,7 +980,8 @@
return true;
}
- if (mContactDetailFragment != null && mContactDetailFragment.isOptionsMenuChanged()) {
+ if (mContactDetailLoaderFragment != null &&
+ mContactDetailLoaderFragment.isOptionsMenuChanged()) {
return true;
}
@@ -1194,6 +1250,9 @@
if (mActionBarAdapter != null) {
mActionBarAdapter.onSaveInstanceState(outState);
}
+ if (mContactDetailLayoutController != null) {
+ mContactDetailLayoutController.onSaveInstanceState(outState);
+ }
}
@Override
@@ -1203,6 +1262,9 @@
if (mActionBarAdapter != null) {
mActionBarAdapter.onRestoreInstanceState(inState);
}
+ if (mContactDetailLayoutController != null) {
+ mContactDetailLayoutController.onRestoreInstanceState(inState);
+ }
}
@Override
diff --git a/src/com/android/contacts/detail/ContactDetailFragment.java b/src/com/android/contacts/detail/ContactDetailFragment.java
index 1f7fac8..677b73a 100644
--- a/src/com/android/contacts/detail/ContactDetailFragment.java
+++ b/src/com/android/contacts/detail/ContactDetailFragment.java
@@ -19,7 +19,6 @@
import com.android.contacts.Collapser;
import com.android.contacts.Collapser.Collapsible;
import com.android.contacts.ContactLoader;
-import com.android.contacts.ContactOptionsActivity;
import com.android.contacts.ContactPresenceIconUtil;
import com.android.contacts.ContactSaveService;
import com.android.contacts.ContactsUtils;
@@ -41,13 +40,9 @@
import com.android.internal.telephony.ITelephony;
import android.accounts.Account;
-import android.app.ActionBar;
import android.app.Activity;
import android.app.Fragment;
-import android.app.LoaderManager;
-import android.app.LoaderManager.LoaderCallbacks;
import android.app.SearchManager;
-import android.content.ActivityNotFoundException;
import android.content.ClipboardManager;
import android.content.ContentUris;
import android.content.ContentValues;
@@ -55,7 +50,6 @@
import android.content.Entity;
import android.content.Entity.NamedContentValues;
import android.content.Intent;
-import android.content.Loader;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.net.ParseException;
@@ -90,9 +84,6 @@
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
@@ -137,17 +128,12 @@
private Uri mPrimaryPhoneUri = null;
private Button mCopyGalToLocalButton;
- private boolean mAllRestricted;
private final ArrayList<Long> mWritableRawContactIds = new ArrayList<Long>();
private int mNumPhoneNumbers = 0;
private String mDefaultCountryIso;
private boolean mContactDataDisplayed;
private boolean mContactPhotoDisplayedInHeader = true;
- private boolean mOptionsMenuOptions;
- private boolean mOptionsMenuEditable;
- private boolean mOptionsMenuShareable;
-
/**
* Device capability: Set during buildEntries and used in the long-press context menu
*/
@@ -245,8 +231,6 @@
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
mView = inflater.inflate(R.layout.contact_detail_fragment, container, false);
- setHasOptionsMenu(true);
-
mInflater = inflater;
mPhotoView = (ImageView) mView.findViewById(R.id.photo);
@@ -336,37 +320,6 @@
return mLookupUri;
}
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
-
- if (mLookupUri != null) {
- Bundle args = new Bundle();
- args.putParcelable(LOADER_ARG_CONTACT_URI, mLookupUri);
- getLoaderManager().initLoader(LOADER_DETAILS, args, mDetailLoaderListener);
- }
- }
-
- public void loadUri(Uri lookupUri) {
- if ((lookupUri != null && lookupUri.equals(mLookupUri))
- || (lookupUri == null && mLookupUri == null)) {
- return;
- }
-
- mLookupUri = lookupUri;
- mTransitionAnimationRequested = mContactDataDisplayed;
- mContactDataDisplayed = true;
- if (mLookupUri == null) {
- getLoaderManager().destroyLoader(LOADER_DETAILS);
- mContactData = null;
- bindData();
- } else if (getActivity() != null) {
- Bundle args = new Bundle();
- args.putParcelable(LOADER_ARG_CONTACT_URI, mLookupUri);
- getLoaderManager().restartLoader(LOADER_DETAILS, args, mDetailLoaderListener);
- }
- }
-
/**
* Sets whether or not the contact photo should be shown in the list of contact details in this
* {@link Fragment}.
@@ -458,7 +411,6 @@
mRawContactIds.clear();
- mAllRestricted = true;
mPrimaryPhoneUri = null;
mNumPhoneNumbers = 0;
@@ -477,11 +429,6 @@
final String accountType = entValues.getAsString(RawContacts.ACCOUNT_TYPE);
final long rawContactId = entValues.getAsLong(RawContacts._ID);
- // Mark when this contact has any unrestricted components
- Integer restricted = entValues.getAsInteger(RawContacts.IS_RESTRICTED);
- final boolean isRestricted = restricted != null && restricted != 0;
- if (!isRestricted) mAllRestricted = false;
-
if (!mRawContactIds.contains(rawContactId)) {
mRawContactIds.add(rawContactId);
}
@@ -1392,95 +1339,6 @@
}
}
- @Override
- public void onCreateOptionsMenu(Menu menu, final MenuInflater inflater) {
- inflater.inflate(R.menu.view_contact, menu);
- }
-
- public boolean isOptionsMenuChanged() {
- return mOptionsMenuOptions != isContactOptionsChangeEnabled()
- || mOptionsMenuEditable != isContactEditable()
- || mOptionsMenuShareable != isContactShareable();
- }
-
- @Override
- public void onPrepareOptionsMenu(Menu menu) {
- mOptionsMenuOptions = isContactOptionsChangeEnabled();
- mOptionsMenuEditable = isContactEditable();
- mOptionsMenuShareable = isContactShareable();
-
- // Options only shows telephony-related settings (ringtone, send to voicemail).
- // ==> Hide if we don't have a telephone
- final MenuItem optionsMenu = menu.findItem(R.id.menu_options);
- optionsMenu.setVisible(mOptionsMenuOptions);
-
- final MenuItem editMenu = menu.findItem(R.id.menu_edit);
- editMenu.setVisible(mOptionsMenuEditable);
-
- final MenuItem deleteMenu = menu.findItem(R.id.menu_delete);
- deleteMenu.setVisible(mOptionsMenuEditable);
-
- final MenuItem shareMenu = menu.findItem(R.id.menu_share);
- shareMenu.setVisible(mOptionsMenuShareable);
- }
-
- public boolean isContactOptionsChangeEnabled() {
- return mContactData != null && !mContactData.isDirectoryEntry()
- && PhoneCapabilityTester.isPhone(mContext);
- }
-
- public boolean isContactEditable() {
- return mContactData != null && !mContactData.isDirectoryEntry();
- }
-
- public boolean isContactShareable() {
- return mContactData != null && !mContactData.isDirectoryEntry() && !mAllRestricted;
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case R.id.menu_edit: {
- if (mListener != null) mListener.onEditRequested(mLookupUri);
- break;
- }
- case R.id.menu_delete: {
- if (mListener != null) mListener.onDeleteRequested(mLookupUri);
- return true;
- }
- case R.id.menu_options: {
- if (mContactData == null) return false;
- final Intent intent = new Intent(mContext, ContactOptionsActivity.class);
- intent.setData(mContactData.getLookupUri());
- mContext.startActivity(intent);
- return true;
- }
- case R.id.menu_share: {
- if (mAllRestricted) return false;
- if (mContactData == null) return false;
-
- final String lookupKey = mContactData.getLookupKey();
- final Uri shareUri = Uri.withAppendedPath(Contacts.CONTENT_VCARD_URI, lookupKey);
-
- final Intent intent = new Intent(Intent.ACTION_SEND);
- intent.setType(Contacts.CONTENT_VCARD_TYPE);
- intent.putExtra(Intent.EXTRA_STREAM, shareUri);
-
- // Launch chooser to share contact via
- final CharSequence chooseTitle = mContext.getText(R.string.share_via);
- final Intent chooseIntent = Intent.createChooser(intent, chooseTitle);
-
- try {
- mContext.startActivity(chooseIntent);
- } catch (ActivityNotFoundException ex) {
- Toast.makeText(mContext, R.string.share_error, Toast.LENGTH_SHORT).show();
- }
- return true;
- }
- }
- return false;
- }
-
private void makePersonalCopy() {
if (mListener == null) {
return;
@@ -1588,76 +1446,18 @@
}
return false;
}
-
- case KeyEvent.KEYCODE_DEL: {
- if (mListener != null) mListener.onDeleteRequested(mLookupUri);
- return true;
- }
}
return false;
}
- /**
- * The listener for the detail loader
- */
- private final LoaderManager.LoaderCallbacks<ContactLoader.Result> mDetailLoaderListener =
- new LoaderCallbacks<ContactLoader.Result>() {
- @Override
- public Loader<ContactLoader.Result> onCreateLoader(int id, Bundle args) {
- Uri lookupUri = args.getParcelable(LOADER_ARG_CONTACT_URI);
- return new ContactLoader(mContext, lookupUri, true /* loadGroupMetaData */);
- }
-
- @Override
- public void onLoadFinished(Loader<ContactLoader.Result> loader, ContactLoader.Result data) {
- if (!mLookupUri.equals(data.getUri())) {
- return;
- }
-
- if (data != ContactLoader.Result.NOT_FOUND && data != ContactLoader.Result.ERROR) {
- mContactData = data;
- } else {
- Log.i(TAG, "No contact found: " + ((ContactLoader)loader).getLookupUri());
- mContactData = null;
- }
-
- bindData();
-
- if (mContactData == null && mListener != null) {
- mListener.onContactNotFound();
- }
- }
-
- public void onLoaderReset(Loader<ContactLoader.Result> loader) {
- mContactData = null;
- bindData();
- }
- };
-
public static interface Listener {
/**
- * Contact was not found, so somehow close this fragment. This is raised after a contact
- * is removed via Menu/Delete
- */
- public void onContactNotFound();
-
- /**
- * User decided to go to Edit-Mode
- */
- public void onEditRequested(Uri lookupUri);
-
- /**
* User clicked a single item (e.g. mail)
*/
public void onItemClicked(Intent intent);
/**
- * User decided to delete the contact
- */
- public void onDeleteRequested(Uri lookupUri);
-
- /**
* User requested creation of a new contact with the specified values.
*
* @param values ContentValues containing data rows for the new contact.
diff --git a/src/com/android/contacts/detail/ContactDetailLayoutController.java b/src/com/android/contacts/detail/ContactDetailLayoutController.java
new file mode 100644
index 0000000..826d720
--- /dev/null
+++ b/src/com/android/contacts/detail/ContactDetailLayoutController.java
@@ -0,0 +1,331 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.contacts.detail;
+
+import com.android.contacts.ContactLoader;
+import com.android.contacts.activities.PeopleActivity.ContactDetailFragmentListener;
+
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
+import android.os.Bundle;
+import android.support.v13.app.FragmentPagerAdapter;
+import android.support.v4.view.ViewPager;
+import android.support.v4.view.ViewPager.OnPageChangeListener;
+import android.view.View;
+import android.widget.AbsListView;
+import android.widget.AbsListView.OnScrollListener;
+
+/**
+ * Determines the layout of the contact card.
+ */
+public class ContactDetailLayoutController {
+
+ public static final int FRAGMENT_COUNT = 2;
+
+ private static final String KEY_DETAIL_FRAGMENT_TAG = "detailFragTag";
+ private static final String KEY_UPDATES_FRAGMENT_TAG = "updatesFragTag";
+
+ private String mDetailFragmentTag;
+ private String mUpdatesFragmentTag;
+
+ private enum LayoutMode {
+ TWO_COLUMN, VIEW_PAGER_AND_CAROUSEL,
+ }
+
+ private final FragmentManager mFragmentManager;
+
+ private ContactDetailFragment mContactDetailFragment;
+ private ContactDetailUpdatesFragment mContactDetailUpdatesFragment;
+
+ private final ViewPager mViewPager;
+ private final ContactDetailTabCarousel mTabCarousel;
+ private ContactDetailFragment mPagerContactDetailFragment;
+ private ContactDetailUpdatesFragment mPagerContactDetailUpdatesFragment;
+
+ private ContactDetailFragmentListener mContactDetailFragmentListener;
+
+ private ContactLoader.Result mContactData;
+
+ private boolean mIsInitialized;
+
+ private LayoutMode mLayoutMode;
+
+ public ContactDetailLayoutController(FragmentManager fragmentManager, ViewPager viewPager,
+ ContactDetailTabCarousel tabCarousel, ContactDetailFragmentListener
+ contactDetailFragmentListener) {
+ if (fragmentManager == null) {
+ throw new IllegalStateException("Cannot initialize a ContactDetailLayoutController "
+ + "without a non-null FragmentManager");
+ }
+
+ mFragmentManager = fragmentManager;
+ mViewPager = viewPager;
+ mTabCarousel = tabCarousel;
+ mContactDetailFragmentListener = contactDetailFragmentListener;
+
+ // Determine the layout based on whether the {@link ViewPager} is null or not. If the
+ // {@link ViewPager} is null, then this is a wide screen and the content can be displayed
+ // in 2 columns side by side. If the {@link ViewPager} is non-null, then this is a narrow
+ // screen and the user will need to swipe to see all the data.
+ mLayoutMode = (mViewPager == null) ? LayoutMode.TWO_COLUMN :
+ LayoutMode.VIEW_PAGER_AND_CAROUSEL;
+
+ }
+
+ public boolean isInitialized() {
+ return mIsInitialized;
+ }
+
+ public void initialize() {
+ mIsInitialized = true;
+ if (mDetailFragmentTag != null || mUpdatesFragmentTag != null) {
+ // Manually remove any {@link ViewPager} fragments if there was an orientation change
+ ContactDetailFragment oldDetailFragment = (ContactDetailFragment) mFragmentManager.
+ findFragmentByTag(mDetailFragmentTag);
+ ContactDetailUpdatesFragment oldUpdatesFragment = (ContactDetailUpdatesFragment)
+ mFragmentManager.findFragmentByTag(mUpdatesFragmentTag);
+
+ if (oldDetailFragment != null && oldUpdatesFragment != null) {
+ FragmentTransaction ft = mFragmentManager.beginTransaction();
+ ft.remove(oldDetailFragment);
+ ft.remove(oldUpdatesFragment);
+ ft.commit();
+ }
+ }
+ if (mViewPager != null) {
+ mViewPager.setAdapter(new ViewPagerAdapter(mFragmentManager));
+ mViewPager.setOnPageChangeListener(mOnPageChangeListener);
+ mTabCarousel.setListener(mTabCarouselListener);
+ }
+ }
+
+ public void setContactDetailFragment(ContactDetailFragment contactDetailFragment) {
+ mContactDetailFragment = contactDetailFragment;
+ }
+
+ public void setContactDetailUpdatesFragment(ContactDetailUpdatesFragment updatesFragment) {
+ mContactDetailUpdatesFragment = updatesFragment;
+ }
+
+ public void setContactData(ContactLoader.Result data) {
+ mContactData = data;
+ if (mContactData.getSocialSnippet() != null) {
+ showContactWithUpdates();
+ } else {
+ showContactWithoutUpdates();
+ }
+ }
+
+ private void showContactWithUpdates() {
+ FragmentTransaction ft = mFragmentManager.beginTransaction();
+
+ switch (mLayoutMode) {
+ case TWO_COLUMN: {
+ // Set the contact data
+ mContactDetailFragment.setData(mContactData.getLookupUri(), mContactData);
+ mContactDetailUpdatesFragment.setData(mContactData.getLookupUri(), mContactData);
+
+ // Update fragment visibility
+ ft.show(mContactDetailUpdatesFragment);
+ break;
+ }
+ case VIEW_PAGER_AND_CAROUSEL: {
+ // Set the contact data
+ mTabCarousel.loadData(mContactData);
+ mPagerContactDetailFragment.setData(mContactData.getLookupUri(), mContactData);
+ mPagerContactDetailUpdatesFragment.setData(mContactData.getLookupUri(),
+ mContactData);
+
+ // Update fragment and view visibility
+ mViewPager.setVisibility(View.VISIBLE);
+ mTabCarousel.setVisibility(View.VISIBLE);
+ ft.hide(mContactDetailFragment);
+ break;
+ }
+ default:
+ throw new IllegalStateException("Invalid LayoutMode " + mLayoutMode);
+ }
+
+ ft.commit();
+ }
+
+ private void showContactWithoutUpdates() {
+ FragmentTransaction ft = mFragmentManager.beginTransaction();
+
+ switch (mLayoutMode) {
+ case TWO_COLUMN:
+ mContactDetailFragment.setData(mContactData.getLookupUri(), mContactData);
+ ft.hide(mContactDetailUpdatesFragment);
+ break;
+ case VIEW_PAGER_AND_CAROUSEL:
+ mContactDetailFragment.setData(mContactData.getLookupUri(), mContactData);
+ ft.show(mContactDetailFragment);
+ mViewPager.setVisibility(View.GONE);
+ mTabCarousel.setVisibility(View.GONE);
+ break;
+ default:
+ throw new IllegalStateException("Invalid LayoutMode " + mLayoutMode);
+ }
+
+ ft.commit();
+ }
+
+ public void onSaveInstanceState(Bundle outState) {
+ if (mPagerContactDetailFragment != null) {
+ outState.putString(KEY_DETAIL_FRAGMENT_TAG,
+ mPagerContactDetailFragment.getTag());
+ outState.putString(KEY_UPDATES_FRAGMENT_TAG,
+ mPagerContactDetailUpdatesFragment.getTag());
+ }
+ }
+
+ public void onRestoreInstanceState(Bundle savedState) {
+ mDetailFragmentTag = savedState.getString(KEY_DETAIL_FRAGMENT_TAG);
+ mUpdatesFragmentTag = savedState.getString(KEY_UPDATES_FRAGMENT_TAG);
+ }
+
+ public class ViewPagerAdapter extends FragmentPagerAdapter{
+
+ public ViewPagerAdapter(FragmentManager fm) {
+ super(fm);
+ }
+
+ @Override
+ public Fragment getItem(int position) {
+ switch (position) {
+ case 0:
+ mPagerContactDetailFragment = new ContactDetailFragment();
+ if (mContactData != null) {
+ mPagerContactDetailFragment.setData(mContactData.getLookupUri(),
+ mContactData);
+ }
+ mPagerContactDetailFragment.setListener(mContactDetailFragmentListener);
+ mPagerContactDetailFragment.setVerticalScrollListener(mVerticalScrollListener);
+ mPagerContactDetailFragment.setShowPhotoInHeader(false);
+ return mPagerContactDetailFragment;
+ case 1:
+ mPagerContactDetailUpdatesFragment = new ContactDetailUpdatesFragment();
+ if (mContactData != null) {
+ mPagerContactDetailUpdatesFragment.setData(mContactData.getLookupUri(),
+ mContactData);
+ }
+ return mPagerContactDetailUpdatesFragment;
+ }
+ throw new IllegalStateException("No fragment at position " + position);
+ }
+
+ @Override
+ public int getCount() {
+ return FRAGMENT_COUNT;
+ }
+ }
+
+ private OnPageChangeListener mOnPageChangeListener = new OnPageChangeListener() {
+
+ @Override
+ public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+ // The user is horizontally dragging the {@link ViewPager}, so send
+ // these scroll changes to the tab carousel. Ignore these events though if the carousel
+ // is actually controlling the {@link ViewPager} scrolls because it will already be
+ // in the correct position.
+ if (mViewPager.isFakeDragging()) {
+ return;
+ }
+ int x = (int) ((position + positionOffset) *
+ mTabCarousel.getAllowedHorizontalScrollLength());
+ mTabCarousel.scrollTo(x, 0);
+ }
+
+ @Override
+ public void onPageSelected(int position) {
+ // Since a new page has been selected by the {@link ViewPager},
+ // update the tab selection in the carousel.
+ mTabCarousel.setCurrentTab(position);
+ }
+
+ @Override
+ public void onPageScrollStateChanged(int state) {}
+
+ };
+
+ private ContactDetailTabCarousel.Listener mTabCarouselListener =
+ new ContactDetailTabCarousel.Listener() {
+
+ @Override
+ public void onTouchDown() {
+ // The user just started scrolling the carousel, so begin "fake dragging" the
+ // {@link ViewPager} if it's not already doing so.
+ if (mViewPager.isFakeDragging()) {
+ return;
+ }
+ mViewPager.beginFakeDrag();
+ }
+
+ @Override
+ public void onTouchUp() {
+ // The user just stopped scrolling the carousel, so stop "fake dragging" the
+ // {@link ViewPager} if was doing so before.
+ if (mViewPager.isFakeDragging()) {
+ mViewPager.endFakeDrag();
+ }
+ }
+
+ @Override
+ public void onScrollChanged(int l, int t, int oldl, int oldt) {
+ // The user is scrolling the carousel, so send the scroll deltas to the
+ // {@link ViewPager} so it can move in sync.
+ if (mViewPager.isFakeDragging()) {
+ mViewPager.fakeDragBy(oldl-l);
+ }
+ }
+
+ @Override
+ public void onTabSelected(int position) {
+ // The user selected a tab, so update the {@link ViewPager}
+ mViewPager.setCurrentItem(position);
+ }
+ };
+
+ private OnScrollListener mVerticalScrollListener = new OnScrollListener() {
+
+ @Override
+ public void onScroll(
+ AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
+ if (mTabCarousel == null) {
+ return;
+ }
+ // If the FIRST item is not visible on the screen, then the carousel must be pinned
+ // at the top of the screen.
+ if (firstVisibleItem != 0) {
+ mTabCarousel.setY(-mTabCarousel.getAllowedVerticalScrollLength());
+ return;
+ }
+ View topView = view.getChildAt(firstVisibleItem);
+ if (topView == null) {
+ return;
+ }
+ int amtToScroll = Math.max((int) view.getChildAt(firstVisibleItem).getY(),
+ -mTabCarousel.getAllowedVerticalScrollLength());
+ mTabCarousel.setY(amtToScroll);
+ }
+
+ @Override
+ public void onScrollStateChanged(AbsListView view, int scrollState) {}
+
+ };
+}
diff --git a/src/com/android/contacts/detail/ContactLoaderFragment.java b/src/com/android/contacts/detail/ContactLoaderFragment.java
index 3ece0ce..5504473 100644
--- a/src/com/android/contacts/detail/ContactLoaderFragment.java
+++ b/src/com/android/contacts/detail/ContactLoaderFragment.java
@@ -17,36 +17,74 @@
package com.android.contacts.detail;
import com.android.contacts.ContactLoader;
+import com.android.contacts.ContactOptionsActivity;
import com.android.contacts.R;
+import com.android.contacts.activities.ContactDetailActivity.FragmentKeyListener;
+import com.android.contacts.util.PhoneCapabilityTester;
import com.android.internal.util.Objects;
import android.app.Activity;
import android.app.Fragment;
import android.app.LoaderManager;
import android.app.LoaderManager.LoaderCallbacks;
+import android.content.ActivityNotFoundException;
+import android.content.ContentValues;
import android.content.Context;
+import android.content.Entity;
+import android.content.Intent;
import android.content.Loader;
import android.net.Uri;
import android.os.Bundle;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.RawContacts;
import android.util.Log;
+import android.view.KeyEvent;
import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.Toast;
/**
* This is an invisible worker {@link Fragment} that loads the contact details for the contact card.
* The data is then passed to the listener, who can then pass the data to other {@link View}s.
*/
-public class ContactLoaderFragment extends Fragment {
+public class ContactLoaderFragment extends Fragment implements FragmentKeyListener {
private static final String TAG = ContactLoaderFragment.class.getSimpleName();
+ private boolean mOptionsMenuOptions;
+ private boolean mOptionsMenuEditable;
+ private boolean mOptionsMenuShareable;
+
/**
* This is a listener to the {@link ContactLoaderFragment} and will be notified when the
- * contact details have finished loading.
+ * contact details have finished loading or if the user selects any menu options.
*/
public static interface ContactLoaderFragmentListener {
+ /**
+ * Contact was not found, so somehow close this fragment. This is raised after a contact
+ * is removed via Menu/Delete
+ */
+ public void onContactNotFound();
+
+ /**
+ * Contact details have finished loading.
+ */
public void onDetailsLoaded(ContactLoader.Result result);
+
+ /**
+ * User decided to go to Edit-Mode
+ */
+ public void onEditRequested(Uri lookupUri);
+
+ /**
+ * User decided to delete the contact
+ */
+ public void onDeleteRequested(Uri lookupUri);
+
}
private static final int LOADER_DETAILS = 1;
@@ -60,6 +98,8 @@
private ContactLoader.Result mContactData;
+ private boolean mAllRestricted;
+
public ContactLoaderFragment() {
}
@@ -85,6 +125,7 @@
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
+ setHasOptionsMenu(true);
// This is an empty view that is set to visibility gone.
return inflater.inflate(R.layout.contact_detail_loader_fragment, container, false);
}
@@ -148,8 +189,22 @@
mContactData = null;
}
+ mAllRestricted = true;
+
+ for (Entity entity: mContactData.getEntities()) {
+ final ContentValues entValues = entity.getEntityValues();
+ // Mark when this contact has any unrestricted components
+ Integer restricted = entValues.getAsInteger(RawContacts.IS_RESTRICTED);
+ final boolean isRestricted = restricted != null && restricted != 0;
+ if (!isRestricted) mAllRestricted = false;
+ }
+
if (mListener != null) {
- mListener.onDetailsLoaded(mContactData);
+ if (mContactData == null) {
+ mListener.onContactNotFound();
+ } else {
+ mListener.onDetailsLoaded(mContactData);
+ }
}
}
@@ -160,4 +215,105 @@
}
}
};
+
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, final MenuInflater inflater) {
+ inflater.inflate(R.menu.view_contact, menu);
+ }
+
+ public boolean isOptionsMenuChanged() {
+ return mOptionsMenuOptions != isContactOptionsChangeEnabled()
+ || mOptionsMenuEditable != isContactEditable()
+ || mOptionsMenuShareable != isContactShareable();
+ }
+
+ @Override
+ public void onPrepareOptionsMenu(Menu menu) {
+ mOptionsMenuOptions = isContactOptionsChangeEnabled();
+ mOptionsMenuEditable = isContactEditable();
+ mOptionsMenuShareable = isContactShareable();
+
+ // Options only shows telephony-related settings (ringtone, send to voicemail).
+ // ==> Hide if we don't have a telephone
+ final MenuItem optionsMenu = menu.findItem(R.id.menu_options);
+ optionsMenu.setVisible(mOptionsMenuOptions);
+
+ final MenuItem editMenu = menu.findItem(R.id.menu_edit);
+ editMenu.setVisible(mOptionsMenuEditable);
+
+ final MenuItem deleteMenu = menu.findItem(R.id.menu_delete);
+ deleteMenu.setVisible(mOptionsMenuEditable);
+
+ final MenuItem shareMenu = menu.findItem(R.id.menu_share);
+ shareMenu.setVisible(mOptionsMenuShareable);
+ }
+
+ public boolean isContactOptionsChangeEnabled() {
+ return mContactData != null && !mContactData.isDirectoryEntry()
+ && PhoneCapabilityTester.isPhone(mContext);
+ }
+
+ public boolean isContactEditable() {
+ return mContactData != null && !mContactData.isDirectoryEntry();
+ }
+
+ public boolean isContactShareable() {
+ return mContactData != null && !mContactData.isDirectoryEntry() && !mAllRestricted;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.menu_edit: {
+ if (mListener != null) mListener.onEditRequested(mLookupUri);
+ break;
+ }
+ case R.id.menu_delete: {
+ if (mListener != null) mListener.onDeleteRequested(mLookupUri);
+ return true;
+ }
+ case R.id.menu_options: {
+ if (mContactData == null) return false;
+ final Intent intent = new Intent(mContext, ContactOptionsActivity.class);
+ intent.setData(mContactData.getLookupUri());
+ mContext.startActivity(intent);
+ return true;
+ }
+ case R.id.menu_share: {
+ if (mAllRestricted) return false;
+ if (mContactData == null) return false;
+
+ final String lookupKey = mContactData.getLookupKey();
+ final Uri shareUri = Uri.withAppendedPath(Contacts.CONTENT_VCARD_URI, lookupKey);
+
+ final Intent intent = new Intent(Intent.ACTION_SEND);
+ intent.setType(Contacts.CONTENT_VCARD_TYPE);
+ intent.putExtra(Intent.EXTRA_STREAM, shareUri);
+
+ // Launch chooser to share contact via
+ final CharSequence chooseTitle = mContext.getText(R.string.share_via);
+ final Intent chooseIntent = Intent.createChooser(intent, chooseTitle);
+
+ try {
+ mContext.startActivity(chooseIntent);
+ } catch (ActivityNotFoundException ex) {
+ Toast.makeText(mContext, R.string.share_error, Toast.LENGTH_SHORT).show();
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean handleKeyDown(int keyCode) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DEL: {
+ if (mListener != null) mListener.onDeleteRequested(mLookupUri);
+ return true;
+ }
+ }
+ return false;
+ }
}