Integrate ContactHeaderWidget loading into ContactLoader to save queries. Remove old Activity.
Bug:2579760
Change-Id: Ie1e6c9249c1f9550da2f6e2d3951a9b78d7e11db
diff --git a/src/com/android/contacts/ViewContactActivity.java b/src/com/android/contacts/ViewContactActivity.java
deleted file mode 100644
index c15a40d..0000000
--- a/src/com/android/contacts/ViewContactActivity.java
+++ /dev/null
@@ -1,1360 +0,0 @@
-/*
- * Copyright (C) 2007 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;
-import com.android.contacts.Collapser.Collapsible;
-import com.android.contacts.model.ContactsSource;
-import com.android.contacts.model.Sources;
-import com.android.contacts.model.ContactsSource.DataKind;
-import com.android.contacts.ui.EditContactActivity;
-import com.android.contacts.util.Constants;
-import com.android.contacts.util.DataStatus;
-import com.android.contacts.util.NotifyingAsyncQueryHandler;
-import com.android.internal.telephony.ITelephony;
-import com.android.internal.widget.ContactHeaderWidget;
-import com.google.android.collect.Lists;
-import com.google.android.collect.Maps;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.content.ActivityNotFoundException;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Entity;
-import android.content.EntityIterator;
-import android.content.Intent;
-import android.content.Entity.NamedContentValues;
-import android.content.res.Resources;
-import android.database.ContentObserver;
-import android.database.Cursor;
-import android.graphics.drawable.Drawable;
-import android.net.ParseException;
-import android.net.Uri;
-import android.net.WebAddress;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.AggregationExceptions;
-import android.provider.ContactsContract.CommonDataKinds;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.DisplayNameSources;
-import android.provider.ContactsContract.RawContacts;
-import android.provider.ContactsContract.RawContactsEntity;
-import android.provider.ContactsContract.StatusUpdates;
-import android.provider.ContactsContract.CommonDataKinds.Email;
-import android.provider.ContactsContract.CommonDataKinds.Im;
-import android.provider.ContactsContract.CommonDataKinds.Nickname;
-import android.provider.ContactsContract.CommonDataKinds.Note;
-import android.provider.ContactsContract.CommonDataKinds.Organization;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
-import android.provider.ContactsContract.CommonDataKinds.Website;
-import android.telephony.PhoneNumberUtils;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.ContextMenu;
-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.view.Window;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.widget.AdapterView;
-import android.widget.ImageView;
-import android.widget.ListView;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
-/**
- * Displays the details of a specific contact.
- */
-public class ViewContactActivity extends Activity
- implements View.OnCreateContextMenuListener, DialogInterface.OnClickListener,
- AdapterView.OnItemClickListener, NotifyingAsyncQueryHandler.AsyncQueryListener {
- private static final String TAG = "ViewContact";
-
- private static final boolean SHOW_SEPARATORS = false;
-
- private static final int DIALOG_CONFIRM_DELETE = 1;
- private static final int DIALOG_CONFIRM_READONLY_DELETE = 2;
- private static final int DIALOG_CONFIRM_MULTIPLE_DELETE = 3;
- private static final int DIALOG_CONFIRM_READONLY_HIDE = 4;
-
- private static final int REQUEST_JOIN_CONTACT = 1;
- private static final int REQUEST_EDIT_CONTACT = 2;
-
- public static final int MENU_ITEM_MAKE_DEFAULT = 3;
-
- protected Uri mLookupUri;
- private ContentResolver mResolver;
- private ViewAdapter mAdapter;
- private int mNumPhoneNumbers = 0;
-
- /**
- * A list of distinct contact IDs included in the current contact.
- */
- private ArrayList<Long> mRawContactIds = new ArrayList<Long>();
-
- /* package */ ArrayList<ViewEntry> mPhoneEntries = new ArrayList<ViewEntry>();
- /* package */ ArrayList<ViewEntry> mSmsEntries = new ArrayList<ViewEntry>();
- /* package */ ArrayList<ViewEntry> mEmailEntries = new ArrayList<ViewEntry>();
- /* package */ ArrayList<ViewEntry> mPostalEntries = new ArrayList<ViewEntry>();
- /* package */ ArrayList<ViewEntry> mImEntries = new ArrayList<ViewEntry>();
- /* package */ ArrayList<ViewEntry> mNicknameEntries = new ArrayList<ViewEntry>();
- /* package */ ArrayList<ViewEntry> mOrganizationEntries = new ArrayList<ViewEntry>();
- /* package */ ArrayList<ViewEntry> mGroupEntries = new ArrayList<ViewEntry>();
- /* package */ ArrayList<ViewEntry> mOtherEntries = new ArrayList<ViewEntry>();
- /* package */ ArrayList<ArrayList<ViewEntry>> mSections = new ArrayList<ArrayList<ViewEntry>>();
-
- private Cursor mCursor;
-
- protected ContactHeaderWidget mContactHeaderWidget;
- private NotifyingAsyncQueryHandler mHandler;
-
- protected LayoutInflater mInflater;
-
- protected int mReadOnlySourcesCnt;
- protected int mWritableSourcesCnt;
- protected boolean mAllRestricted;
-
- protected Uri mPrimaryPhoneUri = null;
-
- protected ArrayList<Long> mWritableRawContactIds = new ArrayList<Long>();
-
- private static final int TOKEN_ENTITIES = 0;
- private static final int TOKEN_STATUSES = 1;
-
- private boolean mHasEntities = false;
- private boolean mHasStatuses = false;
-
- private long mNameRawContactId = -1;
- private int mDisplayNameSource = DisplayNameSources.UNDEFINED;
-
- private ArrayList<Entity> mEntities = Lists.newArrayList();
- private HashMap<Long, DataStatus> mStatuses = Maps.newHashMap();
-
- /**
- * The view shown if the detail list is empty.
- * We set this to the list view when first bind the adapter, so that it won't be shown while
- * we're loading data.
- */
- private View mEmptyView;
-
- private ContentObserver mObserver = new ContentObserver(new Handler()) {
- @Override
- public boolean deliverSelfNotifications() {
- return true;
- }
-
- @Override
- public void onChange(boolean selfChange) {
- if (mCursor != null && !mCursor.isClosed()) {
- startEntityQuery();
- }
- }
- };
-
- public void onClick(DialogInterface dialog, int which) {
- closeCursor();
- getContentResolver().delete(mLookupUri, null, null);
- finish();
- }
-
- private ListView mListView;
- private boolean mShowSmsLinksForAllPhones;
-
- @Override
- protected void onCreate(Bundle icicle) {
- super.onCreate(icicle);
-
- final Intent intent = getIntent();
- Uri data = intent.getData();
- String authority = data.getAuthority();
- if (ContactsContract.AUTHORITY.equals(authority)) {
- mLookupUri = data;
- } else if (android.provider.Contacts.AUTHORITY.equals(authority)) {
- final long rawContactId = ContentUris.parseId(data);
- mLookupUri = RawContacts.getContactLookupUri(getContentResolver(),
- ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
-
- }
- mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-
- requestWindowFeature(Window.FEATURE_NO_TITLE);
- setContentView(R.layout.contact_card_layout);
-
- mContactHeaderWidget = (ContactHeaderWidget) findViewById(R.id.contact_header_widget);
- mContactHeaderWidget.showStar(true);
- mContactHeaderWidget.setExcludeMimes(new String[] {
- Contacts.CONTENT_ITEM_TYPE
- });
-
- mHandler = new NotifyingAsyncQueryHandler(this, this);
-
- mListView = (ListView) findViewById(R.id.contact_data);
- mListView.setOnCreateContextMenuListener(this);
- mListView.setScrollBarStyle(ListView.SCROLLBARS_OUTSIDE_OVERLAY);
- mListView.setOnItemClickListener(this);
- // Don't set it to mListView yet. We do so later when we bind the adapter.
- mEmptyView = findViewById(android.R.id.empty);
-
- mResolver = getContentResolver();
-
- // Build the list of sections. The order they're added to mSections dictates the
- // order they are displayed in the list.
- mSections.add(mPhoneEntries);
- mSections.add(mSmsEntries);
- mSections.add(mEmailEntries);
- mSections.add(mImEntries);
- mSections.add(mPostalEntries);
- mSections.add(mNicknameEntries);
- mSections.add(mOrganizationEntries);
- mSections.add(mGroupEntries);
- mSections.add(mOtherEntries);
-
- //TODO Read this value from a preference
- mShowSmsLinksForAllPhones = true;
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- startEntityQuery();
- }
-
- @Override
- protected void onPause() {
- super.onPause();
- closeCursor();
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- closeCursor();
- }
-
- @Override
- protected Dialog onCreateDialog(int id) {
- switch (id) {
- case DIALOG_CONFIRM_DELETE:
- return new AlertDialog.Builder(this)
- .setTitle(R.string.deleteConfirmation_title)
- .setIcon(android.R.drawable.ic_dialog_alert)
- .setMessage(R.string.deleteConfirmation)
- .setNegativeButton(android.R.string.cancel, null)
- .setPositiveButton(android.R.string.ok, this)
- .setCancelable(false)
- .create();
- case DIALOG_CONFIRM_READONLY_DELETE:
- return new AlertDialog.Builder(this)
- .setTitle(R.string.deleteConfirmation_title)
- .setIcon(android.R.drawable.ic_dialog_alert)
- .setMessage(R.string.readOnlyContactDeleteConfirmation)
- .setNegativeButton(android.R.string.cancel, null)
- .setPositiveButton(android.R.string.ok, this)
- .setCancelable(false)
- .create();
- case DIALOG_CONFIRM_MULTIPLE_DELETE:
- return new AlertDialog.Builder(this)
- .setTitle(R.string.deleteConfirmation_title)
- .setIcon(android.R.drawable.ic_dialog_alert)
- .setMessage(R.string.multipleContactDeleteConfirmation)
- .setNegativeButton(android.R.string.cancel, null)
- .setPositiveButton(android.R.string.ok, this)
- .setCancelable(false)
- .create();
- case DIALOG_CONFIRM_READONLY_HIDE: {
- return new AlertDialog.Builder(this)
- .setTitle(R.string.deleteConfirmation_title)
- .setIcon(android.R.drawable.ic_dialog_alert)
- .setMessage(R.string.readOnlyContactWarning)
- .setPositiveButton(android.R.string.ok, this)
- .create();
- }
-
- }
- return null;
- }
-
- /** {@inheritDoc} */
- public void onQueryComplete(int token, Object cookie, final Cursor cursor) {
- if (token == TOKEN_STATUSES) {
- try {
- // Read available social rows and consider binding
- readStatuses(cursor);
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- considerBindData();
- return;
- }
-
- // One would think we could just iterate over the Cursor
- // directly here, as the result set should be small, and we've
- // already run the query in an AsyncTask, but a lot of ANRs
- // were being reported in this code nonetheless. See bug
- // 2539603 for details. The real bug which makes this result
- // set huge and CPU-heavy may be elsewhere.
- // TODO: if we keep this async, perhaps the entity iteration
- // should also be original AsyncTask, rather than ping-ponging
- // between threads like this.
- final ArrayList<Entity> oldEntities = mEntities;
- (new AsyncTask<Void, Void, ArrayList<Entity>>() {
- @Override
- protected ArrayList<Entity> doInBackground(Void... params) {
- ArrayList<Entity> newEntities = new ArrayList<Entity>(cursor.getCount());
- EntityIterator iterator = RawContacts.newEntityIterator(cursor);
- try {
- while (iterator.hasNext()) {
- Entity entity = iterator.next();
- newEntities.add(entity);
- }
- } finally {
- iterator.close();
- }
- return newEntities;
- }
-
- @Override
- protected void onPostExecute(ArrayList<Entity> newEntities) {
- if (newEntities == null) {
- // There was an error loading.
- return;
- }
- synchronized (ViewContactActivity.this) {
- if (mEntities != oldEntities) {
- // Multiple async tasks were in flight and we
- // lost the race.
- return;
- }
- mEntities = newEntities;
- mHasEntities = true;
- }
- considerBindData();
- }
- }).execute();
- }
-
- private long getRefreshedContactId() {
- Uri freshContactUri = Contacts.lookupContact(getContentResolver(), mLookupUri);
- if (freshContactUri != null) {
- return ContentUris.parseId(freshContactUri);
- }
- return -1;
- }
-
- /**
- * Read from the given {@link Cursor} and build a set of {@link DataStatus}
- * objects to match any valid statuses found.
- */
- private synchronized void readStatuses(Cursor cursor) {
- mStatuses.clear();
-
- // Walk found statuses, creating internal row for each
- while (cursor.moveToNext()) {
- final DataStatus status = new DataStatus(cursor);
- final long dataId = cursor.getLong(StatusQuery._ID);
- mStatuses.put(dataId, status);
- }
-
- mHasStatuses = true;
- }
-
- private static Cursor setupContactCursor(ContentResolver resolver, Uri lookupUri) {
- if (lookupUri == null) {
- return null;
- }
- final List<String> segments = lookupUri.getPathSegments();
- if (segments.size() != 4) {
- return null;
- }
-
- // Contains an Id.
- final long uriContactId = Long.parseLong(segments.get(3));
- final String uriLookupKey = Uri.encode(segments.get(2));
- final Uri dataUri = Uri.withAppendedPath(
- ContentUris.withAppendedId(Contacts.CONTENT_URI, uriContactId),
- Contacts.Data.CONTENT_DIRECTORY);
-
- // This cursor has several purposes:
- // - Fetch NAME_RAW_CONTACT_ID and DISPLAY_NAME_SOURCE
- // - Fetch the lookup-key to ensure we are looking at the right record
- // - Watcher for change events
- Cursor cursor = resolver.query(dataUri,
- new String[] {
- Contacts.NAME_RAW_CONTACT_ID,
- Contacts.DISPLAY_NAME_SOURCE,
- Contacts.LOOKUP_KEY
- }, null, null, null);
-
- if (cursor.moveToFirst()) {
- String lookupKey =
- cursor.getString(cursor.getColumnIndex(Contacts.LOOKUP_KEY));
- if (!lookupKey.equals(uriLookupKey)) {
- // ID and lookup key do not match
- cursor.close();
- return null;
- }
- return cursor;
- } else {
- cursor.close();
- return null;
- }
- }
-
- private synchronized void startEntityQuery() {
- closeCursor();
-
- // Interprete mLookupUri
- mCursor = setupContactCursor(mResolver, mLookupUri);
-
- // If mCursor is null now we did not succeed in using the Uri's Id (or it didn't contain
- // a Uri). Instead we now have to use the lookup key to find the record
- if (mCursor == null) {
- mLookupUri = Contacts.getLookupUri(getContentResolver(), mLookupUri);
- mCursor = setupContactCursor(mResolver, mLookupUri);
- }
-
- // If mCursor is still null, we were unsuccessful in finding the record
- if (mCursor == null) {
- mNameRawContactId = -1;
- mDisplayNameSource = DisplayNameSources.UNDEFINED;
- // TODO either figure out a way to prevent a flash of black background or
- // use some other UI than a toast
- Toast.makeText(this, R.string.invalidContactMessage, Toast.LENGTH_SHORT).show();
- Log.e(TAG, "invalid contact uri: " + mLookupUri);
- finish();
- return;
- }
-
- final long contactId = ContentUris.parseId(mLookupUri);
-
- mNameRawContactId =
- mCursor.getLong(mCursor.getColumnIndex(Contacts.NAME_RAW_CONTACT_ID));
- mDisplayNameSource =
- mCursor.getInt(mCursor.getColumnIndex(Contacts.DISPLAY_NAME_SOURCE));
-
- mCursor.registerContentObserver(mObserver);
-
- // Clear flags and start queries to data and status
- mHasEntities = false;
- mHasStatuses = false;
-
- mHandler.startQuery(TOKEN_ENTITIES, null, RawContactsEntity.CONTENT_URI, null,
- RawContacts.CONTACT_ID + "=?", new String[] {
- String.valueOf(contactId)
- }, null);
- final Uri dataUri = Uri.withAppendedPath(
- ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId),
- Contacts.Data.CONTENT_DIRECTORY);
- mHandler.startQuery(TOKEN_STATUSES, null, dataUri, StatusQuery.PROJECTION,
- StatusUpdates.PRESENCE + " IS NOT NULL OR " + StatusUpdates.STATUS
- + " IS NOT NULL", null, null);
-
- mContactHeaderWidget.bindFromContactLookupUri(mLookupUri);
- }
-
- private void closeCursor() {
- if (mCursor != null) {
- mCursor.unregisterContentObserver(mObserver);
- mCursor.close();
- mCursor = null;
- }
- }
-
- /**
- * Consider binding views after any of several background queries has
- * completed. We check internal flags and only bind when all data has
- * arrived.
- */
- private void considerBindData() {
- if (mHasEntities && mHasStatuses) {
- bindData();
- }
- }
-
- private void bindData() {
-
- // Build up the contact entries
- buildEntries();
-
- // Collapse similar data items in select sections.
- Collapser.collapseList(mPhoneEntries);
- Collapser.collapseList(mSmsEntries);
- Collapser.collapseList(mEmailEntries);
- Collapser.collapseList(mPostalEntries);
- Collapser.collapseList(mImEntries);
-
- if (mAdapter == null) {
- mAdapter = new ViewAdapter(this, mSections);
- mListView.setAdapter(mAdapter);
- } else {
- mAdapter.setSections(mSections, SHOW_SEPARATORS);
- }
- mListView.setEmptyView(mEmptyView);
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- super.onCreateOptionsMenu(menu);
-
- final MenuInflater inflater = getMenuInflater();
- inflater.inflate(R.menu.view, menu);
- return true;
- }
-
- @Override
- public boolean onPrepareOptionsMenu(Menu menu) {
- super.onPrepareOptionsMenu(menu);
-
- // Only allow edit when we have at least one raw_contact id
- final boolean hasRawContact = (mRawContactIds.size() > 0);
- menu.findItem(R.id.menu_edit).setEnabled(hasRawContact);
-
- // Only allow share when unrestricted contacts available
- menu.findItem(R.id.menu_share).setEnabled(!mAllRestricted);
-
- return true;
- }
-
- @Override
- public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
- AdapterView.AdapterContextMenuInfo info;
- try {
- info = (AdapterView.AdapterContextMenuInfo) menuInfo;
- } catch (ClassCastException e) {
- Log.e(TAG, "bad menuInfo", e);
- return;
- }
-
- // This can be null sometimes, don't crash...
- if (info == null) {
- Log.e(TAG, "bad menuInfo");
- return;
- }
-
- ViewEntry entry = ContactEntryAdapter.getEntry(mSections, info.position, SHOW_SEPARATORS);
- menu.setHeaderTitle(R.string.contactOptionsTitle);
- if (entry.mimetype.equals(CommonDataKinds.Phone.CONTENT_ITEM_TYPE)) {
- menu.add(0, 0, 0, R.string.menu_call).setIntent(entry.intent);
- menu.add(0, 0, 0, R.string.menu_sendSMS).setIntent(entry.secondaryIntent);
- if (!entry.isPrimary) {
- menu.add(0, MENU_ITEM_MAKE_DEFAULT, 0, R.string.menu_makeDefaultNumber);
- }
- } else if (entry.mimetype.equals(CommonDataKinds.Email.CONTENT_ITEM_TYPE)) {
- menu.add(0, 0, 0, R.string.menu_sendEmail).setIntent(entry.intent);
- if (!entry.isPrimary) {
- menu.add(0, MENU_ITEM_MAKE_DEFAULT, 0, R.string.menu_makeDefaultEmail);
- }
- } else if (entry.mimetype.equals(CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE)) {
- menu.add(0, 0, 0, R.string.menu_viewAddress).setIntent(entry.intent);
- }
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case R.id.menu_edit: {
- Long rawContactIdToEdit = null;
- if (mRawContactIds.size() > 0) {
- rawContactIdToEdit = mRawContactIds.get(0);
- } else {
- // There is no rawContact to edit.
- break;
- }
- Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI,
- rawContactIdToEdit);
- startActivityForResult(new Intent(Intent.ACTION_EDIT, rawContactUri),
- REQUEST_EDIT_CONTACT);
- break;
- }
- case R.id.menu_delete: {
- // Get confirmation
- if (mReadOnlySourcesCnt > 0 & mWritableSourcesCnt > 0) {
- showDialog(DIALOG_CONFIRM_READONLY_DELETE);
- } else if (mReadOnlySourcesCnt > 0 && mWritableSourcesCnt == 0) {
- showDialog(DIALOG_CONFIRM_READONLY_HIDE);
- } else if (mReadOnlySourcesCnt == 0 && mWritableSourcesCnt > 1) {
- showDialog(DIALOG_CONFIRM_MULTIPLE_DELETE);
- } else {
- showDialog(DIALOG_CONFIRM_DELETE);
- }
- return true;
- }
- case R.id.menu_join: {
- showJoinAggregateActivity();
- return true;
- }
- case R.id.menu_options: {
- showOptionsActivity();
- return true;
- }
- case R.id.menu_share: {
- if (mAllRestricted) return false;
-
- // TODO: Keep around actual LOOKUP_KEY, or formalize method of extracting
- final String lookupKey = Uri.encode(mLookupUri.getPathSegments().get(2));
- 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 = getText(R.string.share_via);
- final Intent chooseIntent = Intent.createChooser(intent, chooseTitle);
-
- try {
- startActivity(chooseIntent);
- } catch (ActivityNotFoundException ex) {
- Toast.makeText(this, R.string.share_error, Toast.LENGTH_SHORT).show();
- }
- return true;
- }
- }
- return super.onOptionsItemSelected(item);
- }
-
- @Override
- public boolean onContextItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case MENU_ITEM_MAKE_DEFAULT: {
- if (makeItemDefault(item)) {
- return true;
- }
- break;
- }
- }
-
- return super.onContextItemSelected(item);
- }
-
- private boolean makeItemDefault(MenuItem item) {
- ViewEntry entry = getViewEntryForMenuItem(item);
- if (entry == null) {
- return false;
- }
-
- // Update the primary values in the data record.
- ContentValues values = new ContentValues(1);
- values.put(Data.IS_SUPER_PRIMARY, 1);
- getContentResolver().update(ContentUris.withAppendedId(Data.CONTENT_URI, entry.id),
- values, null, null);
- startEntityQuery();
- return true;
- }
-
- /**
- * Shows a list of aggregates that can be joined into the currently viewed aggregate.
- */
- public void showJoinAggregateActivity() {
- long freshId = getRefreshedContactId();
- if (freshId > 0) {
- String displayName = null;
- if (mCursor.moveToFirst()) {
- displayName = mCursor.getString(0);
- }
- Intent intent = new Intent(JoinContactActivity.JOIN_CONTACT);
- intent.putExtra(JoinContactActivity.EXTRA_TARGET_CONTACT_ID, freshId);
- startActivityForResult(intent, REQUEST_JOIN_CONTACT);
- }
- }
-
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
- if (requestCode == REQUEST_JOIN_CONTACT) {
- if (resultCode == RESULT_OK && intent != null) {
- final long contactId = ContentUris.parseId(intent.getData());
- joinAggregate(contactId);
- }
- } else if (requestCode == REQUEST_EDIT_CONTACT) {
- if (resultCode == EditContactActivity.RESULT_CLOSE_VIEW_ACTIVITY) {
- finish();
- } else if (resultCode == Activity.RESULT_OK) {
- mLookupUri = intent.getData();
- if (mLookupUri == null) {
- finish();
- }
- }
- }
- }
-
- private void joinAggregate(final long contactId) {
- Cursor c = mResolver.query(RawContacts.CONTENT_URI, new String[] {RawContacts._ID},
- RawContacts.CONTACT_ID + "=" + contactId, null, null);
-
- try {
- while(c.moveToNext()) {
- long rawContactId = c.getLong(0);
- setAggregationException(rawContactId, AggregationExceptions.TYPE_KEEP_TOGETHER);
- }
- } finally {
- c.close();
- }
-
- Toast.makeText(this, R.string.contactsJoinedMessage, Toast.LENGTH_LONG).show();
- startEntityQuery();
- }
-
- /**
- * Given a contact ID sets an aggregation exception to either join the contact with the
- * current aggregate or split off.
- */
- protected void setAggregationException(long rawContactId, int exceptionType) {
- ContentValues values = new ContentValues(3);
- for (long aRawContactId : mRawContactIds) {
- if (aRawContactId != rawContactId) {
- values.put(AggregationExceptions.RAW_CONTACT_ID1, aRawContactId);
- values.put(AggregationExceptions.RAW_CONTACT_ID2, rawContactId);
- values.put(AggregationExceptions.TYPE, exceptionType);
- mResolver.update(AggregationExceptions.CONTENT_URI, values, null, null);
- }
- }
- }
-
- private void showOptionsActivity() {
- final Intent intent = new Intent(this, ContactOptionsActivity.class);
- intent.setData(mLookupUri);
- startActivity(intent);
- }
-
- private ViewEntry getViewEntryForMenuItem(MenuItem item) {
- AdapterView.AdapterContextMenuInfo info;
- try {
- info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
- } catch (ClassCastException e) {
- Log.e(TAG, "bad menuInfo", e);
- return null;
- }
-
- return ContactEntryAdapter.getEntry(mSections, info.position, SHOW_SEPARATORS);
- }
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- switch (keyCode) {
- case KeyEvent.KEYCODE_CALL: {
- try {
- ITelephony phone = ITelephony.Stub.asInterface(
- ServiceManager.checkService("phone"));
- if (phone != null && !phone.isIdle()) {
- // Skip out and let the key be handled at a higher level
- break;
- }
- } catch (RemoteException re) {
- // Fall through and try to call the contact
- }
-
- int index = mListView.getSelectedItemPosition();
- if (index != -1) {
- ViewEntry entry = ViewAdapter.getEntry(mSections, index, SHOW_SEPARATORS);
- if (entry != null &&
- entry.intent.getAction() == Intent.ACTION_CALL_PRIVILEGED) {
- startActivity(entry.intent);
- return true;
- }
- } else if (mPrimaryPhoneUri != null) {
- // There isn't anything selected, call the default number
- final Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
- mPrimaryPhoneUri);
- startActivity(intent);
- return true;
- }
- return false;
- }
-
- case KeyEvent.KEYCODE_DEL: {
- if (mReadOnlySourcesCnt > 0 & mWritableSourcesCnt > 0) {
- showDialog(DIALOG_CONFIRM_READONLY_DELETE);
- } else if (mReadOnlySourcesCnt > 0 && mWritableSourcesCnt == 0) {
- showDialog(DIALOG_CONFIRM_READONLY_HIDE);
- } else if (mReadOnlySourcesCnt == 0 && mWritableSourcesCnt > 1) {
- showDialog(DIALOG_CONFIRM_MULTIPLE_DELETE);
- } else {
- showDialog(DIALOG_CONFIRM_DELETE);
- }
- return true;
- }
- }
-
- return super.onKeyDown(keyCode, event);
- }
-
- public void onItemClick(AdapterView parent, View v, int position, long id) {
- ViewEntry entry = ViewAdapter.getEntry(mSections, position, SHOW_SEPARATORS);
- if (entry != null) {
- Intent intent = entry.intent;
- if (intent != null) {
- try {
- startActivity(intent);
- } catch (ActivityNotFoundException e) {
- Log.e(TAG, "No activity found for intent: " + intent);
- signalError();
- }
- } else {
- signalError();
- }
- } else {
- signalError();
- }
- }
-
- /**
- * Signal an error to the user via a beep, or some other method.
- */
- private void signalError() {
- //TODO: implement this when we have the sonification APIs
- }
-
- /**
- * Build up the entries to display on the screen.
- *
- * @param personCursor the URI for the contact being displayed
- */
- private final void buildEntries() {
- // Clear out the old entries
- final int numSections = mSections.size();
- for (int i = 0; i < numSections; i++) {
- mSections.get(i).clear();
- }
-
- mRawContactIds.clear();
-
- mReadOnlySourcesCnt = 0;
- mWritableSourcesCnt = 0;
- mAllRestricted = true;
- mPrimaryPhoneUri = null;
-
- mWritableRawContactIds.clear();
-
- final Context context = this;
- final Sources sources = Sources.getInstance(context);
-
- // Build up method entries
- if (mLookupUri != null) {
- for (Entity entity: mEntities) {
- final ContentValues entValues = entity.getEntityValues();
- final String accountType = entValues.getAsString(RawContacts.ACCOUNT_TYPE);
- final long rawContactId = entValues.getAsLong(RawContacts._ID);
-
- // Mark when this contact has any unrestricted components
- final boolean isRestricted = entValues.getAsInteger(RawContacts.IS_RESTRICTED) != 0;
- if (!isRestricted) mAllRestricted = false;
-
- if (!mRawContactIds.contains(rawContactId)) {
- mRawContactIds.add(rawContactId);
- }
- ContactsSource contactsSource = sources.getInflatedSource(accountType,
- ContactsSource.LEVEL_SUMMARY);
- if (contactsSource != null && contactsSource.readOnly) {
- mReadOnlySourcesCnt += 1;
- } else {
- mWritableSourcesCnt += 1;
- mWritableRawContactIds.add(rawContactId);
- }
-
-
- for (NamedContentValues subValue : entity.getSubValues()) {
- final ContentValues entryValues = subValue.values;
- entryValues.put(Data.RAW_CONTACT_ID, rawContactId);
-
- final long dataId = entryValues.getAsLong(Data._ID);
- final String mimeType = entryValues.getAsString(Data.MIMETYPE);
- if (mimeType == null) continue;
-
- final DataKind kind = sources.getKindOrFallback(accountType, mimeType, this,
- ContactsSource.LEVEL_MIMETYPES);
- if (kind == null) continue;
-
- final ViewEntry entry = ViewEntry.fromValues(context, mimeType, kind,
- rawContactId, dataId, entryValues);
-
- final boolean hasData = !TextUtils.isEmpty(entry.data);
- final boolean isSuperPrimary = entryValues.getAsInteger(
- Data.IS_SUPER_PRIMARY) != 0;
-
- if (Phone.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
- // Build phone entries
- mNumPhoneNumbers++;
-
- entry.intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
- Uri.fromParts(Constants.SCHEME_TEL, entry.data, null));
- entry.secondaryIntent = new Intent(Intent.ACTION_SENDTO,
- Uri.fromParts(Constants.SCHEME_SMSTO, entry.data, null));
-
- // Remember super-primary phone
- if (isSuperPrimary) mPrimaryPhoneUri = entry.uri;
-
- entry.isPrimary = isSuperPrimary;
- mPhoneEntries.add(entry);
-
- if (entry.type == CommonDataKinds.Phone.TYPE_MOBILE
- || mShowSmsLinksForAllPhones) {
- // Add an SMS entry
- if (kind.iconAltRes > 0) {
- entry.secondaryActionIcon = kind.iconAltRes;
- }
- }
- } else if (Email.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
- // Build email entries
- entry.intent = new Intent(Intent.ACTION_SENDTO,
- Uri.fromParts(Constants.SCHEME_MAILTO, entry.data, null));
- entry.isPrimary = isSuperPrimary;
- mEmailEntries.add(entry);
-
- // When Email rows have status, create additional Im row
- final DataStatus status = mStatuses.get(entry.id);
- if (status != null) {
- final String imMime = Im.CONTENT_ITEM_TYPE;
- final DataKind imKind = sources.getKindOrFallback(accountType,
- imMime, this, ContactsSource.LEVEL_MIMETYPES);
- final ViewEntry imEntry = ViewEntry.fromValues(context,
- imMime, imKind, rawContactId, dataId, entryValues);
- imEntry.intent = ContactsUtils.buildImIntent(entryValues);
- imEntry.applyStatus(status, false);
- mImEntries.add(imEntry);
- }
- } else if (StructuredPostal.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
- // Build postal entries
- entry.maxLines = 4;
- entry.intent = new Intent(Intent.ACTION_VIEW, entry.uri);
- mPostalEntries.add(entry);
- } else if (Im.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
- // Build IM entries
- entry.intent = ContactsUtils.buildImIntent(entryValues);
- if (TextUtils.isEmpty(entry.label)) {
- entry.label = getString(R.string.chat).toLowerCase();
- }
-
- // Apply presence and status details when available
- final DataStatus status = mStatuses.get(entry.id);
- if (status != null) {
- entry.applyStatus(status, false);
- }
- mImEntries.add(entry);
- } else if (Organization.CONTENT_ITEM_TYPE.equals(mimeType) &&
- (hasData || !TextUtils.isEmpty(entry.label))) {
- // Build organization entries
- final boolean isNameRawContact = (mNameRawContactId == rawContactId);
-
- final boolean duplicatesTitle =
- isNameRawContact
- && mDisplayNameSource == DisplayNameSources.ORGANIZATION
- && (!hasData || TextUtils.isEmpty(entry.label));
-
- if (!duplicatesTitle) {
- entry.uri = null;
-
- if (TextUtils.isEmpty(entry.label)) {
- entry.label = entry.data;
- entry.data = "";
- }
-
- mOrganizationEntries.add(entry);
- }
- } else if (Nickname.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
- // Build nickname entries
- final boolean isNameRawContact = (mNameRawContactId == rawContactId);
-
- final boolean duplicatesTitle =
- isNameRawContact
- && mDisplayNameSource == DisplayNameSources.NICKNAME;
-
- if (!duplicatesTitle) {
- entry.uri = null;
- mNicknameEntries.add(entry);
- }
- } else if (Note.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
- // Build note entries
- entry.uri = null;
- entry.maxLines = 100;
- mOtherEntries.add(entry);
- } else if (Website.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
- // Build note entries
- entry.uri = null;
- entry.maxLines = 10;
- try {
- WebAddress webAddress = new WebAddress(entry.data);
- entry.intent = new Intent(Intent.ACTION_VIEW,
- Uri.parse(webAddress.toString()));
- } catch (ParseException e) {
- Log.e(TAG, "Couldn't parse website: " + entry.data);
- }
- mOtherEntries.add(entry);
- } else {
- // Handle showing custom rows
- entry.intent = new Intent(Intent.ACTION_VIEW, entry.uri);
-
- // Use social summary when requested by external source
- final DataStatus status = mStatuses.get(entry.id);
- final boolean hasSocial = kind.actionBodySocial && status != null;
- if (hasSocial) {
- entry.applyStatus(status, true);
- }
-
- if (hasSocial || hasData) {
- mOtherEntries.add(entry);
- }
- }
- }
- }
- }
- }
-
- static String buildActionString(DataKind kind, ContentValues values, boolean lowerCase,
- Context context) {
- if (kind.actionHeader == null) {
- return null;
- }
- CharSequence actionHeader = kind.actionHeader.inflateUsing(context, values);
- if (actionHeader == null) {
- return null;
- }
- return lowerCase ? actionHeader.toString().toLowerCase() : actionHeader.toString();
- }
-
- static String buildDataString(DataKind kind, ContentValues values, Context context) {
- if (kind.actionBody == null) {
- return null;
- }
- CharSequence actionBody = kind.actionBody.inflateUsing(context, values);
- return actionBody == null ? null : actionBody.toString();
- }
-
- /**
- * A basic structure with the data for a contact entry in the list.
- */
- static class ViewEntry extends ContactEntryAdapter.Entry implements Collapsible<ViewEntry> {
- public Context context = null;
- public String resPackageName = null;
- public int actionIcon = -1;
- public boolean isPrimary = false;
- public int secondaryActionIcon = -1;
- public Intent intent;
- public Intent secondaryIntent = null;
- public int maxLabelLines = 1;
- public ArrayList<Long> ids = new ArrayList<Long>();
- public int collapseCount = 0;
-
- public int presence = -1;
-
- public CharSequence footerLine = null;
-
- private ViewEntry() {
- }
-
- /**
- * Build new {@link ViewEntry} and populate from the given values.
- */
- public static ViewEntry fromValues(Context context, String mimeType, DataKind kind,
- long rawContactId, long dataId, ContentValues values) {
- final ViewEntry entry = new ViewEntry();
- entry.context = context;
- entry.contactId = rawContactId;
- entry.id = dataId;
- entry.uri = ContentUris.withAppendedId(Data.CONTENT_URI, entry.id);
- entry.mimetype = mimeType;
- entry.label = buildActionString(kind, values, false, context);
- entry.data = buildDataString(kind, values, context);
-
- if (kind.typeColumn != null && values.containsKey(kind.typeColumn)) {
- entry.type = values.getAsInteger(kind.typeColumn);
- }
- if (kind.iconRes > 0) {
- entry.resPackageName = kind.resPackageName;
- entry.actionIcon = kind.iconRes;
- }
-
- return entry;
- }
-
- /**
- * Apply given {@link DataStatus} values over this {@link ViewEntry}
- *
- * @param fillData When true, the given status replaces {@link #data}
- * and {@link #footerLine}. Otherwise only {@link #presence}
- * is updated.
- */
- public ViewEntry applyStatus(DataStatus status, boolean fillData) {
- presence = status.getPresence();
- if (fillData && status.isValid()) {
- this.data = status.getStatus().toString();
- this.footerLine = status.getTimestampLabel(context);
- }
-
- return this;
- }
-
- public boolean collapseWith(ViewEntry entry) {
- // assert equal collapse keys
- if (!shouldCollapseWith(entry)) {
- return false;
- }
-
- // Choose the label associated with the highest type precedence.
- if (TypePrecedence.getTypePrecedence(mimetype, type)
- > TypePrecedence.getTypePrecedence(entry.mimetype, entry.type)) {
- type = entry.type;
- label = entry.label;
- }
-
- // Choose the max of the maxLines and maxLabelLines values.
- maxLines = Math.max(maxLines, entry.maxLines);
- maxLabelLines = Math.max(maxLabelLines, entry.maxLabelLines);
-
- // Choose the presence with the highest precedence.
- if (StatusUpdates.getPresencePrecedence(presence)
- < StatusUpdates.getPresencePrecedence(entry.presence)) {
- presence = entry.presence;
- }
-
- // If any of the collapsed entries are primary make the whole thing primary.
- isPrimary = entry.isPrimary ? true : isPrimary;
-
- // uri, and contactdId, shouldn't make a difference. Just keep the original.
-
- // Keep track of all the ids that have been collapsed with this one.
- ids.add(entry.id);
- collapseCount++;
- return true;
- }
-
- public boolean shouldCollapseWith(ViewEntry entry) {
- if (entry == null) {
- return false;
- }
-
- if (!ContactsUtils.shouldCollapse(context, mimetype, data, entry.mimetype,
- entry.data)) {
- return false;
- }
-
- if (!TextUtils.equals(mimetype, entry.mimetype)
- || !ContactsUtils.areIntentActionEqual(intent, entry.intent)
- || !ContactsUtils.areIntentActionEqual(secondaryIntent, entry.secondaryIntent)
- || actionIcon != entry.actionIcon) {
- return false;
- }
-
- return true;
- }
- }
-
- /** Cache of the children views of a row */
- static class ViewCache {
- public TextView label;
- public TextView data;
- public TextView footer;
- public ImageView actionIcon;
- public ImageView presenceIcon;
- public ImageView primaryIcon;
- public ImageView secondaryActionButton;
- public View secondaryActionDivider;
-
- // Need to keep track of this too
- ViewEntry entry;
- }
-
- private final class ViewAdapter extends ContactEntryAdapter<ViewEntry>
- implements View.OnClickListener {
-
-
- ViewAdapter(Context context, ArrayList<ArrayList<ViewEntry>> sections) {
- super(context, sections, SHOW_SEPARATORS);
- }
-
- public void onClick(View v) {
- Intent intent = (Intent) v.getTag();
- startActivity(intent);
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- ViewEntry entry = getEntry(mSections, position, false);
- View v;
-
- ViewCache views;
-
- // Check to see if we can reuse convertView
- if (convertView != null) {
- v = convertView;
- views = (ViewCache) v.getTag();
- } else {
- // Create a new view if needed
- v = mInflater.inflate(R.layout.list_item_text_icons, parent, false);
-
- // Cache the children
- views = new ViewCache();
- views.label = (TextView) v.findViewById(android.R.id.text1);
- views.data = (TextView) v.findViewById(android.R.id.text2);
- views.footer = (TextView) v.findViewById(R.id.footer);
- views.actionIcon = (ImageView) v.findViewById(R.id.action_icon);
- views.primaryIcon = (ImageView) v.findViewById(R.id.primary_icon);
- views.presenceIcon = (ImageView) v.findViewById(R.id.presence_icon);
- views.secondaryActionButton = (ImageView) v.findViewById(
- R.id.secondary_action_button);
- views.secondaryActionButton.setOnClickListener(this);
- views.secondaryActionDivider = v.findViewById(R.id.divider);
- v.setTag(views);
- }
-
- // Update the entry in the view cache
- views.entry = entry;
-
- // Bind the data to the view
- bindView(v, entry);
- return v;
- }
-
- @Override
- protected View newView(int position, ViewGroup parent) {
- // getView() handles this
- throw new UnsupportedOperationException();
- }
-
- @Override
- protected void bindView(View view, ViewEntry entry) {
- final Resources resources = mContext.getResources();
- ViewCache views = (ViewCache) view.getTag();
-
- // Set the label
- TextView label = views.label;
- setMaxLines(label, entry.maxLabelLines);
- label.setText(entry.label);
-
- // Set the data
- TextView data = views.data;
- if (data != null) {
- if (entry.mimetype.equals(Phone.CONTENT_ITEM_TYPE)
- || entry.mimetype.equals(Constants.MIME_SMS_ADDRESS)) {
- data.setText(PhoneNumberUtils.formatNumber(entry.data));
- } else {
- data.setText(entry.data);
- }
- setMaxLines(data, entry.maxLines);
- }
-
- // Set the footer
- if (!TextUtils.isEmpty(entry.footerLine)) {
- views.footer.setText(entry.footerLine);
- views.footer.setVisibility(View.VISIBLE);
- } else {
- views.footer.setVisibility(View.GONE);
- }
-
- // Set the primary icon
- views.primaryIcon.setVisibility(entry.isPrimary ? View.VISIBLE : View.GONE);
-
- // Set the action icon
- ImageView action = views.actionIcon;
- if (entry.actionIcon != -1) {
- Drawable actionIcon;
- if (entry.resPackageName != null) {
- // Load external resources through PackageManager
- actionIcon = mContext.getPackageManager().getDrawable(entry.resPackageName,
- entry.actionIcon, null);
- } else {
- actionIcon = resources.getDrawable(entry.actionIcon);
- }
- action.setImageDrawable(actionIcon);
- action.setVisibility(View.VISIBLE);
- } else {
- // Things should still line up as if there was an icon, so make it invisible
- action.setVisibility(View.INVISIBLE);
- }
-
- // Set the presence icon
- Drawable presenceIcon = ContactPresenceIconUtil.getPresenceIcon(
- mContext, entry.presence);
- ImageView presenceIconView = views.presenceIcon;
- if (presenceIcon != null) {
- presenceIconView.setImageDrawable(presenceIcon);
- presenceIconView.setVisibility(View.VISIBLE);
- } else {
- presenceIconView.setVisibility(View.GONE);
- }
-
- // Set the secondary action button
- ImageView secondaryActionView = views.secondaryActionButton;
- Drawable secondaryActionIcon = null;
- if (entry.secondaryActionIcon != -1) {
- secondaryActionIcon = resources.getDrawable(entry.secondaryActionIcon);
- }
- if (entry.secondaryIntent != null && secondaryActionIcon != null) {
- secondaryActionView.setImageDrawable(secondaryActionIcon);
- secondaryActionView.setTag(entry.secondaryIntent);
- secondaryActionView.setVisibility(View.VISIBLE);
- views.secondaryActionDivider.setVisibility(View.VISIBLE);
- } else {
- secondaryActionView.setVisibility(View.GONE);
- views.secondaryActionDivider.setVisibility(View.GONE);
- }
- }
-
- private void setMaxLines(TextView textView, int maxLines) {
- if (maxLines == 1) {
- textView.setSingleLine(true);
- textView.setEllipsize(TextUtils.TruncateAt.END);
- } else {
- textView.setSingleLine(false);
- textView.setMaxLines(maxLines);
- textView.setEllipsize(null);
- }
- }
- }
-
- private interface StatusQuery {
- final String[] PROJECTION = new String[] {
- Data._ID,
- Data.STATUS,
- Data.STATUS_RES_PACKAGE,
- Data.STATUS_ICON,
- Data.STATUS_LABEL,
- Data.STATUS_TIMESTAMP,
- Data.PRESENCE,
- };
-
- final int _ID = 0;
- }
-
- @Override
- public void startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData,
- boolean globalSearch) {
- if (globalSearch) {
- super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
- } else {
- ContactsSearchManager.startSearch(this, initialQuery);
- }
- }
-}
diff --git a/src/com/android/contacts/views/detail/ContactDetailView.java b/src/com/android/contacts/views/detail/ContactDetailView.java
index 424c63a..82d0699 100644
--- a/src/com/android/contacts/views/detail/ContactDetailView.java
+++ b/src/com/android/contacts/views/detail/ContactDetailView.java
@@ -152,11 +152,20 @@
public void setData(Result data) {
mContactData = data;
- mContactHeaderWidget.bindFromContactLookupUri(data.getUri());
bindData();
}
private void bindData() {
+ // Set the header
+ mContactHeaderWidget.setContactUri(mContactData.getLookupUri());
+ mContactHeaderWidget.setDisplayName(mContactData.getDisplayName(),
+ mContactData.getPhoneticName());
+ mContactHeaderWidget.setPhotoId(mContactData.getPhotoId(), mContactData.getLookupUri());
+ mContactHeaderWidget.setStared(mContactData.getStarred());
+ mContactHeaderWidget.setPresence(mContactData.getPresence());
+ mContactHeaderWidget.setStatus(
+ mContactData.getStatus(), mContactData.getStatusTimestamp(),
+ mContactData.getStatusLabel(), mContactData.getStatusResPackage());
// Build up the contact entries
buildEntries();
diff --git a/src/com/android/contacts/views/detail/ContactLoader.java b/src/com/android/contacts/views/detail/ContactLoader.java
index 06dd68e..55abd9f 100644
--- a/src/com/android/contacts/views/detail/ContactLoader.java
+++ b/src/com/android/contacts/views/detail/ContactLoader.java
@@ -44,7 +44,7 @@
* Loads a single Contact and all it constituent RawContacts.
*/
public class ContactLoader extends Loader<ContactLoader.Result> {
- private static boolean sIsSynchronous;
+ private boolean mIsSynchronous;
private Uri mLookupUri;
private Result mContact;
private ForceLoadContentObserver mObserver;
@@ -69,10 +69,19 @@
private final String mLookupKey;
private final Uri mUri;
private final long mId;
- private final ArrayList<Entity> mEntities;
- private final HashMap<Long, DataStatus> mStatuses;
private final long mNameRawContactId;
private final int mDisplayNameSource;
+ private final long mPhotoId;
+ private final String mDisplayName;
+ private final String mPhoneticName;
+ private final boolean mStarred;
+ private final Integer mPresence;
+ private final ArrayList<Entity> mEntities;
+ private final HashMap<Long, DataStatus> mStatuses;
+ private final String mStatus;
+ private final Long mStatusTimestamp;
+ private final Integer mStatusLabel;
+ private final String mStatusResPackage;
/**
* Constructor for case "no contact found". This must only be used for the
@@ -87,13 +96,24 @@
mStatuses = null;
mNameRawContactId = -1;
mDisplayNameSource = DisplayNameSources.UNDEFINED;
+ mPhotoId = -1;
+ mDisplayName = null;
+ mPhoneticName = null;
+ mStarred = false;
+ mPresence = null;
+ mStatus = null;
+ mStatusTimestamp = null;
+ mStatusLabel = null;
+ mStatusResPackage = null;
}
/**
* Constructor to call when contact was found
*/
private Result(Uri lookupUri, String lookupKey, Uri uri, long id, long nameRawContactId,
- int displayNameSource) {
+ int displayNameSource, long photoId, String displayName, String phoneticName,
+ boolean starred, Integer presence, String status, Long statusTimestamp,
+ Integer statusLabel, String statusResPackage) {
mLookupUri = lookupUri;
mLookupKey = lookupKey;
mUri = uri;
@@ -102,6 +122,15 @@
mStatuses = new HashMap<Long, DataStatus>();
mNameRawContactId = nameRawContactId;
mDisplayNameSource = displayNameSource;
+ mPhotoId = photoId;
+ mDisplayName = displayName;
+ mPhoneticName = phoneticName;
+ mStarred = starred;
+ mPresence = presence;
+ mStatus = status;
+ mStatusTimestamp = statusTimestamp;
+ mStatusLabel = statusLabel;
+ mStatusResPackage = statusResPackage;
}
public Uri getLookupUri() {
@@ -116,21 +145,48 @@
public long getId() {
return mId;
}
- public ArrayList<Entity> getEntities() {
- return mEntities;
- }
- public HashMap<Long, DataStatus> getStatuses() {
- return mStatuses;
- }
public long getNameRawContactId() {
return mNameRawContactId;
}
public int getDisplayNameSource() {
return mDisplayNameSource;
}
+ public long getPhotoId() {
+ return mPhotoId;
+ }
+ public String getDisplayName() {
+ return mDisplayName;
+ }
+ public String getPhoneticName() {
+ return mPhoneticName;
+ }
+ public boolean getStarred() {
+ return mStarred;
+ }
+ public Integer getPresence() {
+ return mPresence;
+ }
+ public String getStatus() {
+ return mStatus;
+ }
+ public Long getStatusTimestamp() {
+ return mStatusTimestamp;
+ }
+ public Integer getStatusLabel() {
+ return mStatusLabel;
+ }
+ public String getStatusResPackage() {
+ return mStatusResPackage;
+ }
+ public ArrayList<Entity> getEntities() {
+ return mEntities;
+ }
+ public HashMap<Long, DataStatus> getStatuses() {
+ return mStatuses;
+ }
}
- interface StatusQuery {
+ private interface StatusQuery {
final String[] PROJECTION = new String[] {
Data._ID, Data.STATUS, Data.STATUS_RES_PACKAGE, Data.STATUS_ICON,
Data.STATUS_LABEL, Data.STATUS_TIMESTAMP, Data.PRESENCE,
@@ -139,15 +195,38 @@
final int _ID = 0;
}
- public final class LoadContactTask extends AsyncTask<Void, Void, Result> {
+ private interface ContactQuery {
+ //Projection used for the summary info in the header.
+ final static String[] COLUMNS = new String[] {
+ Contacts.NAME_RAW_CONTACT_ID,
+ Contacts.DISPLAY_NAME_SOURCE,
+ Contacts.LOOKUP_KEY,
+ Contacts.DISPLAY_NAME,
+ Contacts.PHONETIC_NAME,
+ Contacts.PHOTO_ID,
+ Contacts.STARRED,
+ Contacts.CONTACT_PRESENCE,
+ Contacts.CONTACT_STATUS,
+ Contacts.CONTACT_STATUS_TIMESTAMP,
+ Contacts.CONTACT_STATUS_RES_PACKAGE,
+ Contacts.CONTACT_STATUS_LABEL,
+ };
+ final static int NAME_RAW_CONTACT_ID = 0;
+ final static int DISPLAY_NAME_SOURCE = 1;
+ final static int LOOKUP_KEY = 2;
+ final static int DISPLAY_NAME = 3;
+ final static int PHONETIC_NAME = 4;
+ final static int PHOTO_ID = 5;
+ final static int STARRED = 6;
+ final static int CONTACT_PRESENCE = 7;
+ final static int CONTACT_STATUS = 8;
+ final static int CONTACT_STATUS_TIMESTAMP = 9;
+ final static int CONTACT_STATUS_RES_PACKAGE = 10;
+ final static int CONTACT_STATUS_LABEL = 11;
+ }
- /**
- * Used for synchronuous calls in unit test
- * @hide
- */
- public Result testExecute() {
- return doInBackground();
- }
+
+ public final class LoadContactTask extends AsyncTask<Void, Void, Result> {
@Override
protected Result doInBackground(Void... args) {
@@ -223,12 +302,8 @@
final String uriLookupKey = Uri.encode(segments.get(2));
final Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, uriContactId);
- final Cursor cursor = resolver.query(contactUri,
- new String[] {
- Contacts.NAME_RAW_CONTACT_ID,
- Contacts.DISPLAY_NAME_SOURCE,
- Contacts.LOOKUP_KEY
- }, null, null, null);
+ final Cursor cursor = resolver.query(contactUri, ContactQuery.COLUMNS, null, null,
+ null);
if (cursor == null) {
Log.e(TAG, "No cursor returned in trySetupContactHeader/query");
return null;
@@ -239,8 +314,7 @@
"ContactId must have changed or item has been removed");
return Result.NOT_FOUND;
}
- String lookupKey =
- cursor.getString(cursor.getColumnIndex(Contacts.LOOKUP_KEY));
+ final String lookupKey = cursor.getString(ContactQuery.LOOKUP_KEY);
if (!lookupKey.equals(uriLookupKey)) {
// ID and lookup key do not match
Log.w(TAG, "Contact with Id=" + uriContactId + " has a wrong lookupKey ("
@@ -248,13 +322,28 @@
return Result.NOT_FOUND;
}
- long nameRawContactId = cursor.getLong(cursor.getColumnIndex(
- Contacts.NAME_RAW_CONTACT_ID));
- int displayNameSource = cursor.getInt(cursor.getColumnIndex(
- Contacts.DISPLAY_NAME_SOURCE));
+ final long nameRawContactId = cursor.getLong(ContactQuery.NAME_RAW_CONTACT_ID);
+ final int displayNameSource = cursor.getInt(ContactQuery.DISPLAY_NAME_SOURCE);
+ final String displayName = cursor.getString(ContactQuery.DISPLAY_NAME);
+ final String phoneticName = cursor.getString(ContactQuery.PHONETIC_NAME);
+ final long photoId = cursor.getLong(ContactQuery.PHOTO_ID);
+ final boolean starred = cursor.getInt(ContactQuery.STARRED) != 0;
+ final Integer presence = cursor.isNull(ContactQuery.CONTACT_PRESENCE)
+ ? null
+ : cursor.getInt(ContactQuery.CONTACT_PRESENCE);
+ final String status = cursor.getString(ContactQuery.CONTACT_STATUS);
+ final Long statusTimestamp = cursor.isNull(ContactQuery.CONTACT_STATUS_TIMESTAMP)
+ ? null
+ : cursor.getLong(ContactQuery.CONTACT_STATUS_TIMESTAMP);
+ final Integer statusLabel = cursor.isNull(ContactQuery.CONTACT_STATUS_LABEL)
+ ? null
+ : cursor.getInt(ContactQuery.CONTACT_STATUS_LABEL);
+ final String statusResPackage = cursor.getString(
+ ContactQuery.CONTACT_STATUS_RES_PACKAGE);
return new Result(lookupUri, lookupKey, contactUri, uriContactId, nameRawContactId,
- displayNameSource);
+ displayNameSource, photoId, displayName, phoneticName, starred, presence,
+ status, statusTimestamp, statusLabel, statusResPackage);
} finally {
cursor.close();
}
@@ -371,7 +460,7 @@
@Override
public void forceLoad() {
LoadContactTask task = new LoadContactTask();
- if (sIsSynchronous) {
+ if (mIsSynchronous) {
task.onPostExecute(task.doInBackground((Void[])null));
return;
}
@@ -393,7 +482,7 @@
}
- public static void setSynchronous(boolean value) {
- sIsSynchronous = value;
+ public void setSynchronous(boolean value) {
+ mIsSynchronous = value;
}
}
diff --git a/tests/src/com/android/contacts/ContactDetailLoaderTest.java b/tests/src/com/android/contacts/ContactDetailLoaderTest.java
index 981ed74..b3a7b88 100644
--- a/tests/src/com/android/contacts/ContactDetailLoaderTest.java
+++ b/tests/src/com/android/contacts/ContactDetailLoaderTest.java
@@ -16,6 +16,7 @@
package com.android.contacts;
+import com.android.contacts.mvcframework.Loader.OnLoadCompleteListener;
import com.android.contacts.tests.mocks.ContactsMockContext;
import com.android.contacts.tests.mocks.MockContentProvider;
import com.android.contacts.views.detail.ContactLoader;
@@ -45,7 +46,6 @@
@Override
protected void setUp() throws Exception {
super.setUp();
- ContactLoader.setSynchronous(true);
mMockContext = new ContactsMockContext(getContext());
mContactsProvider = mMockContext.getContactsProvider();
}
@@ -53,7 +53,6 @@
@Override
protected void tearDown() throws Exception {
super.tearDown();
- ContactLoader.setSynchronous(false);
}
/**
@@ -77,8 +76,16 @@
private ContactLoader.Result assertLoadContact(Uri uri) {
final ContactLoader loader = new ContactLoader(mMockContext, uri);
- final ContactLoader.LoadContactTask loadContactTask = loader.new LoadContactTask();
- return loadContactTask.testExecute();
+ loader.setSynchronous(true);
+ // A regular variable can not be written from an anonymous class, so use a 1-array
+ final ContactLoader.Result[] result = new ContactLoader.Result[1];
+ loader.registerListener(0, new OnLoadCompleteListener<ContactLoader.Result>() {
+ public void onLoadComplete(int id, ContactLoader.Result data) {
+ result[0] = data;
+ }
+ });
+ loader.startLoading();
+ return result[0];
}
public void testNullUri() {
@@ -418,11 +425,29 @@
.withProjection(
Contacts.NAME_RAW_CONTACT_ID,
Contacts.DISPLAY_NAME_SOURCE,
- Contacts.LOOKUP_KEY)
+ Contacts.LOOKUP_KEY,
+ Contacts.DISPLAY_NAME,
+ Contacts.PHONETIC_NAME,
+ Contacts.PHOTO_ID,
+ Contacts.STARRED,
+ Contacts.CONTACT_PRESENCE,
+ Contacts.CONTACT_STATUS,
+ Contacts.CONTACT_STATUS_TIMESTAMP,
+ Contacts.CONTACT_STATUS_RES_PACKAGE,
+ Contacts.CONTACT_STATUS_LABEL)
.returnRow(
expectedRawContactId,
DisplayNameSources.STRUCTURED_NAME,
- expectedEncodedLookup);
+ expectedEncodedLookup,
+ "contactDisplayName",
+ "contactPhoneticName",
+ null,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ null);
}
private void fetchHeaderDataNoResult(final Uri uri) {
@@ -430,7 +455,16 @@
.withProjection(
Contacts.NAME_RAW_CONTACT_ID,
Contacts.DISPLAY_NAME_SOURCE,
- Contacts.LOOKUP_KEY);
+ Contacts.LOOKUP_KEY,
+ Contacts.DISPLAY_NAME,
+ Contacts.PHONETIC_NAME,
+ Contacts.PHOTO_ID,
+ Contacts.STARRED,
+ Contacts.CONTACT_PRESENCE,
+ Contacts.CONTACT_STATUS,
+ Contacts.CONTACT_STATUS_TIMESTAMP,
+ Contacts.CONTACT_STATUS_RES_PACKAGE,
+ Contacts.CONTACT_STATUS_LABEL);
}
private void fetchLookupAndId(final Uri sourceUri, final long expectedContactId,
diff --git a/tests/src/com/android/contacts/ContactDetailTest.java b/tests/src/com/android/contacts/ContactDetailTest.java
index 16538e6..926a353 100644
--- a/tests/src/com/android/contacts/ContactDetailTest.java
+++ b/tests/src/com/android/contacts/ContactDetailTest.java
@@ -21,7 +21,6 @@
@Override
protected void setUp() throws Exception {
super.setUp();
- ContactLoader.setSynchronous(true);
mContext = new ContactsMockContext(getInstrumentation().getTargetContext());
mContactsProvider = mContext.getContactsProvider();
setActivityContext(mContext);
@@ -30,7 +29,6 @@
@Override
protected void tearDown() throws Exception {
super.tearDown();
- ContactLoader.setSynchronous(false);
}
// public void testFoo() {
diff --git a/tests/src/com/android/contacts/tests/mocks/MockContentProvider.java b/tests/src/com/android/contacts/tests/mocks/MockContentProvider.java
index 4a11eb0..00e1f03 100644
--- a/tests/src/com/android/contacts/tests/mocks/MockContentProvider.java
+++ b/tests/src/com/android/contacts/tests/mocks/MockContentProvider.java
@@ -104,7 +104,7 @@
return true;
}
- private boolean equals(String[] array1, String[] array2) {
+ private static boolean equals(String[] array1, String[] array2) {
boolean empty1 = array1 == null || array1.length == 0;
boolean empty2 = array2 == null || array2.length == 0;
if (empty1 && empty2) {
@@ -114,6 +114,8 @@
return false;
}
+ if (array1.length != array2.length) return false;
+
for (int i = 0; i < array1.length; i++) {
if (!array1[i].equals(array2[i])) {
return false;