Merge "Import translations. DO NOT MERGE" into lmp-dev
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 4175ef4..0457681 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -47,7 +47,7 @@
<application
android:name="com.android.contacts.ContactsApplication"
android:label="@string/contactsList"
- android:icon="@mipmap/ic_launcher_contacts"
+ android:icon="@mipmap/ic_contacts_clr_48cv_44dp"
android:taskAffinity="android.task.contacts"
android:hardwareAccelerated="true"
android:supportsRtl="true"
diff --git a/res/drawable-hdpi/ic_dialer_sip_black_24dp.png b/res/drawable-hdpi/ic_dialer_sip_black_24dp.png
new file mode 100644
index 0000000..37dabfc
--- /dev/null
+++ b/res/drawable-hdpi/ic_dialer_sip_black_24dp.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_dialer_sip_black_24dp.png b/res/drawable-mdpi/ic_dialer_sip_black_24dp.png
new file mode 100644
index 0000000..51d5e13
--- /dev/null
+++ b/res/drawable-mdpi/ic_dialer_sip_black_24dp.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_dialer_sip_black_24dp.png b/res/drawable-xhdpi/ic_dialer_sip_black_24dp.png
new file mode 100644
index 0000000..619a79f
--- /dev/null
+++ b/res/drawable-xhdpi/ic_dialer_sip_black_24dp.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_dialer_sip_black_24dp.png b/res/drawable-xxhdpi/ic_dialer_sip_black_24dp.png
new file mode 100644
index 0000000..f1466a1
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_dialer_sip_black_24dp.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_dialer_sip_black_24dp.png b/res/drawable-xxxhdpi/ic_dialer_sip_black_24dp.png
new file mode 100644
index 0000000..f812810
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_dialer_sip_black_24dp.png
Binary files differ
diff --git a/res/layout/custom_action_bar.xml b/res/layout/custom_action_bar.xml
index af104fe..5b930ea 100644
--- a/res/layout/custom_action_bar.xml
+++ b/res/layout/custom_action_bar.xml
@@ -33,6 +33,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:iconifiedByDefault="false"
- android:inputType="textFilter" />
+ android:inputType="textFilter"
+ android:theme="@style/ContactPickerSearchTheme" />
</FrameLayout>
diff --git a/res/layout/expanding_entry_card_item.xml b/res/layout/expanding_entry_card_item.xml
index 138a3a5..4710df2 100644
--- a/res/layout/expanding_entry_card_item.xml
+++ b/res/layout/expanding_entry_card_item.xml
@@ -90,8 +90,8 @@
android:layout_alignParentTop="true"
android:visibility="gone"
android:background="?android:attr/selectableItemBackgroundBorderless"
- android:layout_marginEnd="@dimen/expanding_entry_card_item_alternate_icon_margin_end"
- android:layout_marginTop="@dimen/expanding_entry_card_item_icon_margin_top"
- android:layout_marginBottom="@dimen/expanding_entry_card_item_alternate_icon_margin_bottom" />
+ android:paddingTop="@dimen/expanding_entry_card_item_icon_margin_top"
+ android:paddingBottom="@dimen/expanding_entry_card_item_alternate_icon_margin_bottom"
+ android:layout_marginStart="@dimen/expanding_entry_card_item_alternate_icon_start_margin" />
</view>
diff --git a/res/layout/quickcontact_content.xml b/res/layout/quickcontact_content.xml
index 5ead702..494b93b 100644
--- a/res/layout/quickcontact_content.xml
+++ b/res/layout/quickcontact_content.xml
@@ -23,7 +23,7 @@
android:background="@color/card_margin_color">
<!-- All the cards should be inserted into this LinearLayout -->
- <view class="com.android.contacts.common.widget.ActivityTouchLinearLayout"
+ <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
@@ -54,6 +54,6 @@
android:visibility="gone"
cardview:cardCornerRadius="@dimen/expanding_entry_card_card_corner_radius" />
- </view>
+ </LinearLayout>
</com.android.contacts.widget.TouchlessScrollView>
\ No newline at end of file
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 52918d5..d422627 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -173,7 +173,7 @@
<dimen name="expanding_entry_card_item_icon_margin_top">7dp</dimen>
<dimen name="expanding_entry_card_item_header_only_margin_top">6dp</dimen>
- <dimen name="expanding_entry_card_item_alternate_icon_margin_end">0dp</dimen>
+ <dimen name="expanding_entry_card_item_alternate_icon_start_margin">16dp</dimen>
<dimen name="expanding_entry_card_item_alternate_icon_margin_bottom">10dp</dimen>
<dimen name="expanding_entry_card_badge_separator_margin">8dp</dimen>
@@ -181,6 +181,7 @@
<dimen name="expanding_entry_card_header_margin_bottom">2dp</dimen>
<!-- The top margin when the sub header and text views are both gone -->
<dimen name="expanding_entry_card_item_header_only_margin_bottom">2dp</dimen>
+ <dimen name="expanding_entry_card_item_no_icon_margin_top">6dp</dimen>
<dimen name="people_activity_card_elevation">2dp</dimen>
<!-- The width the that the tabs occupy in the ActionBar when in landscape mode.
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 5f17bcd..5497ee2 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -702,4 +702,7 @@
<!-- Content description for directions secondary button [CHAR LIMIT=NONE] -->
<string name="content_description_directions">directions to location</string>
+ <!-- Prefix for messages that you sent [CHAR LIMIT=40] -->
+ <string name="message_from_you_prefix">You: <xliff:g id="sms_body">%s</xliff:g></string>
+
</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 8ff6469..73329b3 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -66,6 +66,8 @@
<item name="android:windowBackground">@color/background_primary</item>
<item name="android:colorPrimaryDark">@color/primary_color_dark</item>
<item name="android:colorPrimary">@color/primary_color</item>
+ <item name="android:colorAccent">@color/primary_color</item>
+ <item name="android:alertDialogTheme">@style/ContactsAlertDialogTheme</item>
<item name="list_item_height">?android:attr/listPreferredItemHeight</item>
<item name="activated_background">@drawable/list_item_activated_background</item>
<item name="section_header_background">@drawable/list_title_holo</item>
@@ -128,6 +130,13 @@
<item name="android:displayOptions"></item>
</style>
+ <style name="ContactPickerSearchTheme" parent="@style/PeopleTheme">
+ <item name="android:textColorPrimary">@android:color/white</item>
+ <item name="android:textColorHint">?android:textColorHintInverse</item>
+ <item name="android:colorControlActivated">?android:textColorHintInverse</item>
+ <item name="android:colorControlNormal">@android:color/white</item>
+ </style>
+
<!-- Text in the action bar at the top of the screen -->
<style name="ContactsActionBarTitleText"
parent="@android:style/TextAppearance.Material.Widget.ActionBar.Title">
@@ -287,4 +296,10 @@
<item name="android:textColor">#363636</item>
<item name="android:fontFamily">sans-serif</item>
</style>
+
+ <!-- Inherit from Theme.Material.Light.Dialog instead of Theme.Material.Light.Dialog.Alert
+ since the Alert dialog is private. They are identical anyway. -->
+ <style name="ContactsAlertDialogTheme" parent="@android:style/Theme.Material.Light.Dialog">
+ <item name="android:colorAccent">@color/primary_color</item>
+ </style>
</resources>
diff --git a/src/com/android/contacts/editor/ContactEditorFragment.java b/src/com/android/contacts/editor/ContactEditorFragment.java
index b4ec7f9..f112847 100644
--- a/src/com/android/contacts/editor/ContactEditorFragment.java
+++ b/src/com/android/contacts/editor/ContactEditorFragment.java
@@ -131,6 +131,9 @@
private static final String KEY_NEW_CONTACT_READY = "newContactDataReady";
private static final String KEY_EXISTING_CONTACT_READY = "existingContactDataReady";
private static final String KEY_RAW_CONTACTS = "rawContacts";
+ private static final String KEY_SEND_TO_VOICE_MAIL_STATE = "sendToVoicemailState";
+ private static final String KEY_CUSTOM_RINGTONE = "customRingtone";
+ private static final String KEY_ARE_PHONE_OPTIONS_CHANGEABLE = "arePhoneOptionsChangable";
public static final String SAVE_MODE_EXTRA_KEY = "saveMode";
@@ -509,7 +512,9 @@
mExistingContactDataReady = savedState.getBoolean(KEY_EXISTING_CONTACT_READY);
mRawContacts = ImmutableList.copyOf(savedState.<RawContact>getParcelableArrayList(
KEY_RAW_CONTACTS));
-
+ mSendToVoicemailState = savedState.getBoolean(KEY_SEND_TO_VOICE_MAIL_STATE);
+ mCustomRingtone = savedState.getString(KEY_CUSTOM_RINGTONE);
+ mArePhoneOptionsChangable = savedState.getBoolean(KEY_ARE_PHONE_OPTIONS_CHANGEABLE);
}
// mState can still be null because it may not have have finished loading before
@@ -1772,6 +1777,9 @@
outState.putParcelableArrayList(KEY_RAW_CONTACTS,
mRawContacts == null ?
Lists.<RawContact> newArrayList() : Lists.newArrayList(mRawContacts));
+ outState.putBoolean(KEY_SEND_TO_VOICE_MAIL_STATE, mSendToVoicemailState);
+ outState.putString(KEY_CUSTOM_RINGTONE, mCustomRingtone);
+ outState.putBoolean(KEY_ARE_PHONE_OPTIONS_CHANGEABLE, mArePhoneOptionsChangable);
super.onSaveInstanceState(outState);
}
diff --git a/src/com/android/contacts/interactions/SmsInteraction.java b/src/com/android/contacts/interactions/SmsInteraction.java
index ac83786..3f86eef 100644
--- a/src/com/android/contacts/interactions/SmsInteraction.java
+++ b/src/com/android/contacts/interactions/SmsInteraction.java
@@ -54,7 +54,11 @@
@Override
public String getViewHeader(Context context) {
- return getBody();
+ String body = getBody();
+ if (getType() == Sms.MESSAGE_TYPE_SENT) {
+ body = context.getResources().getString(R.string.message_from_you_prefix, body);
+ }
+ return body;
}
@Override
diff --git a/src/com/android/contacts/quickcontact/ExpandingEntryCardView.java b/src/com/android/contacts/quickcontact/ExpandingEntryCardView.java
index 7501bb8..9369cbe 100644
--- a/src/com/android/contacts/quickcontact/ExpandingEntryCardView.java
+++ b/src/com/android/contacts/quickcontact/ExpandingEntryCardView.java
@@ -357,7 +357,9 @@
if (TextUtils.isEmpty(mTitleTextView.getText()) &&
mEntriesViewGroup.getChildCount() == 0) {
entry.setPadding(entry.getPaddingLeft(),
- entry.getPaddingTop() + getResources().getDimensionPixelSize(
+ getResources().getDimensionPixelSize(
+ R.dimen.expanding_entry_card_item_padding_top) +
+ getResources().getDimensionPixelSize(
R.dimen.expanding_entry_card_null_title_top_extra_padding),
entry.getPaddingRight(),
entry.getPaddingBottom());
@@ -431,7 +433,7 @@
for (int j = 1; j < entryList.size() && numInflated < mCollapsedEntriesCount &&
extraEntries > 0; j++) {
entryViewList.add(createEntryView(layoutInflater, entryList.get(j),
- /* showIcon = */ View.VISIBLE));
+ /* showIcon = */ View.INVISIBLE));
numInflated++;
extraEntries--;
}
@@ -577,6 +579,11 @@
view.setTag(new EntryTag(entry.getId(), entry.getIntent()));
}
+ if (entry.getIntent() == null && entry.getEntryContextMenuInfo() == null) {
+ // Remove the click effect
+ view.setBackground(null);
+ }
+
// If only the header is visible, add a top margin to match icon's top margin.
// Also increase the space below the header for visual comfort.
if (header.getVisibility() == View.VISIBLE && subHeader.getVisibility() == View.GONE &&
@@ -620,13 +627,21 @@
});
}
- // Decrease margin for entries that have an invisible icon
- if (iconVisibility == View.INVISIBLE) {
+ // Adjust the top padding size for entries with an invisible icon. The padding depends on
+ // if there is a sub header or text section
+ if (iconVisibility == View.INVISIBLE &&
+ (!TextUtils.isEmpty(entry.getSubHeader()) || !TextUtils.isEmpty(entry.getText()))) {
+ view.setPaddingRelative(view.getPaddingStart(),
+ getResources().getDimensionPixelSize(
+ R.dimen.expanding_entry_card_item_no_icon_margin_top),
+ view.getPaddingEnd(),
+ view.getPaddingBottom());
+ } else if (iconVisibility == View.INVISIBLE && TextUtils.isEmpty(entry.getSubHeader())
+ && TextUtils.isEmpty(entry.getText())) {
view.setPaddingRelative(view.getPaddingStart(), 0, view.getPaddingEnd(),
view.getPaddingBottom());
}
-
view.setOnCreateContextMenuListener(mOnCreateContextMenuListener);
return view;
diff --git a/src/com/android/contacts/quickcontact/QuickContactActivity.java b/src/com/android/contacts/quickcontact/QuickContactActivity.java
index 357cae6..9bcdde8 100644
--- a/src/com/android/contacts/quickcontact/QuickContactActivity.java
+++ b/src/com/android/contacts/quickcontact/QuickContactActivity.java
@@ -25,13 +25,13 @@
import android.content.ActivityNotFoundException;
import android.content.ContentUris;
import android.content.ContentValues;
+import android.content.Context;
import android.content.Intent;
import android.content.Loader;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.Bitmap;
import android.graphics.Color;
-import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.BitmapDrawable;
@@ -68,12 +68,12 @@
import android.telecomm.TelecommManager;
import android.text.TextUtils;
import android.util.Log;
-import android.util.Pair;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
+import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnCreateContextMenuListener;
@@ -169,7 +169,7 @@
private static final int ANIMATION_STATUS_BAR_COLOR_CHANGE_DURATION = 150;
private static final int REQUEST_CODE_CONTACT_EDITOR_ACTIVITY = 1;
- private static final int SCRIM_COLOR = Color.argb(0xB2, 0, 0, 0);
+ private static final int SCRIM_COLOR = Color.argb(0xC8, 0, 0, 0);
private static final String MIMETYPE_SMS = "vnd.android-dir/mms-sms";
/** This is the Intent action to install a shortcut in the launcher. */
@@ -186,6 +186,10 @@
"vnd.android.cursor.item/vnd.googleplus.profile.comm";
private static final String INTENT_DATA_HANGOUTS_VIDEO = "Start video call";
+ /**
+ * The URI used to load the the Contact. Once the contact is loaded, use Contact#getLookupUri()
+ * instead of referencing this URI.
+ */
private Uri mLookupUri;
private String[] mExcludeMimes;
private int mExtraMode;
@@ -193,26 +197,17 @@
private boolean mHasAlreadyBeenOpened;
private ImageView mPhotoView;
- private View mTransparentView;
private ExpandingEntryCardView mContactCard;
private ExpandingEntryCardView mNoContactDetailsCard;
private ExpandingEntryCardView mRecentCard;
private ExpandingEntryCardView mAboutCard;
- /**
- * This list contains all the {@link DataItem}s. Each nested list contains all data items of a
- * specific mimetype in sorted order, using mWithinMimeTypeDataItemComparator. The mimetype
- * lists are sorted using mAmongstMimeTypeDataItemComparator.
- */
- private List<List<DataItem>> mDataItemsList;
- /**
- * A map between a mimetype string and the corresponding list of data items. The data items
- * are in sorted order using mWithinMimeTypeDataItemComparator.
- */
- private Map<String, List<DataItem>> mDataItemsMap;
private MultiShrinkScroller mScroller;
private SelectAccountDialogFragmentListener mSelectAccountFragmentListener;
- private AsyncTask<Void, Void, Pair<List<List<DataItem>>, Map<String, List<DataItem>>>>
- mEntriesAndActionsTask;
+ private AsyncTask<Void, Void, Cp2DataCardModel> mEntriesAndActionsTask;
+ /**
+ * The last copy of Cp2DataCardModel that was passed to {@link #populateContactAndAboutCard}.
+ */
+ private Cp2DataCardModel mCachedCp2DataCardModel;
/**
* This scrim's opacity is controlled in two different ways. 1) Before the initial entrance
* animation finishes, the opacity is animated by a value animator. This is designed to
@@ -225,6 +220,12 @@
private boolean mIsExitAnimationInProgress;
private boolean mHasComputedThemeColor;
+ /**
+ * Used to stop the ExpandingEntry cards from adjusting between an entry click and the intent
+ * being launched.
+ */
+ private boolean mHasIntentLaunched;
+
private Contact mContactData;
private ContactLoader mContactLoader;
private PorterDuffColorFilter mColorFilter;
@@ -336,15 +337,15 @@
// Pass the touch point through the intent for use in the InCallUI
if (Intent.ACTION_CALL.equals(intent.getAction())) {
- final Point touchPoint = TouchPointManager.getInstance().getPoint();
-
- if (touchPoint.x != 0 || touchPoint.y != 0) {
+ if (TouchPointManager.getInstance().hasValidPoint()) {
Bundle extras = new Bundle();
- extras.putParcelable(TouchPointManager.TOUCH_POINT, touchPoint);
+ extras.putParcelable(TouchPointManager.TOUCH_POINT,
+ TouchPointManager.getInstance().getPoint());
intent.putExtra(TelecommManager.EXTRA_OUTGOING_CALL_EXTRAS, extras);
}
}
+ mHasIntentLaunched = true;
startActivity(intent);
}
};
@@ -535,6 +536,14 @@
};
@Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ TouchPointManager.getInstance().setPoint((int) ev.getRawX(), (int) ev.getRawY());
+ }
+ return super.dispatchTouchEvent(ev);
+ }
+
+ @Override
protected void onCreate(Bundle savedInstanceState) {
Trace.beginSection("onCreate()");
super.onCreate(savedInstanceState);
@@ -571,9 +580,9 @@
mAboutCard.setOnCreateContextMenuListener(mEntryContextMenuListener);
mPhotoView = (ImageView) findViewById(R.id.photo);
- mTransparentView = findViewById(R.id.transparent_view);
+ final View transparentView = findViewById(R.id.transparent_view);
if (mScroller != null) {
- mTransparentView.setOnClickListener(new OnClickListener() {
+ transparentView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mScroller.scrollOffBottom();
@@ -773,26 +782,22 @@
Trace.endSection();
- mEntriesAndActionsTask = new AsyncTask<Void, Void,
- Pair<List<List<DataItem>>, Map<String, List<DataItem>>>>() {
+ mEntriesAndActionsTask = new AsyncTask<Void, Void, Cp2DataCardModel>() {
@Override
- protected Pair<List<List<DataItem>>, Map<String, List<DataItem>>> doInBackground(
+ protected Cp2DataCardModel doInBackground(
Void... params) {
return generateDataModelFromContact(data);
}
@Override
- protected void onPostExecute(Pair<List<List<DataItem>>,
- Map<String, List<DataItem>>> dataItemsPair) {
- super.onPostExecute(dataItemsPair);
- mDataItemsList = dataItemsPair.first;
- mDataItemsMap = dataItemsPair.second;
+ protected void onPostExecute(Cp2DataCardModel cardDataModel) {
+ super.onPostExecute(cardDataModel);
// Check that original AsyncTask parameters are still valid and the activity
// is still running before binding to UI. A new intent could invalidate
// the results, for example.
if (data == mContactData && !isCancelled()) {
- bindDataToCards();
+ bindDataToCards(cardDataModel);
showActivity();
}
}
@@ -800,13 +805,14 @@
mEntriesAndActionsTask.execute();
}
- private void bindDataToCards() {
- startInteractionLoaders();
- populateContactAndAboutCard();
+ private void bindDataToCards(Cp2DataCardModel cp2DataCardModel) {
+ startInteractionLoaders(cp2DataCardModel);
+ populateContactAndAboutCard(cp2DataCardModel);
}
- private void startInteractionLoaders() {
- final List<DataItem> phoneDataItems = mDataItemsMap.get(Phone.CONTENT_ITEM_TYPE);
+ private void startInteractionLoaders(Cp2DataCardModel cp2DataCardModel) {
+ final Map<String, List<DataItem>> dataItemsMap = cp2DataCardModel.dataItemsMap;
+ final List<DataItem> phoneDataItems = dataItemsMap.get(Phone.CONTENT_ITEM_TYPE);
String[] phoneNumbers = null;
if (phoneDataItems != null) {
phoneNumbers = new String[phoneDataItems.size()];
@@ -833,7 +839,7 @@
Trace.beginSection("start calendar loader");
- final List<DataItem> emailDataItems = mDataItemsMap.get(Email.CONTENT_ITEM_TYPE);
+ final List<DataItem> emailDataItems = dataItemsMap.get(Email.CONTENT_ITEM_TYPE);
String[] emailAddresses = null;
if (emailDataItems != null) {
emailAddresses = new String[emailDataItems.size()];
@@ -863,14 +869,17 @@
}
}
- private List<List<Entry>> buildAboutCardEntries() {
+ private List<List<Entry>> buildAboutCardEntries(Map<String, List<DataItem>> dataItemsMap) {
final List<List<Entry>> aboutCardEntries = new ArrayList<>();
for (String mimetype : SORTED_ABOUT_CARD_MIMETYPES) {
- final List<DataItem> mimeTypeItems = mDataItemsMap.get(mimetype);
+ final List<DataItem> mimeTypeItems = dataItemsMap.get(mimetype);
if (mimeTypeItems == null) {
continue;
}
- final List<Entry> aboutEntries = dataItemsToEntries(mimeTypeItems);
+ // Set aboutCardTitleOut = null, since SORTED_ABOUT_CARD_MIMETYPES doesn't contain
+ // the name mimetype.
+ final List<Entry> aboutEntries = dataItemsToEntries(mimeTypeItems,
+ /* aboutCardTitleOut = */ null);
if (aboutEntries.size() > 0) {
aboutCardEntries.add(aboutEntries);
}
@@ -878,25 +887,26 @@
return aboutCardEntries;
}
- private void populateContactAndAboutCard() {
+ @Override
+ protected void onResume() {
+ super.onResume();
+ // If returning from a launched activity, repopulate the contact and about card
+ if (mHasIntentLaunched) {
+ mHasIntentLaunched = false;
+ populateContactAndAboutCard(mCachedCp2DataCardModel);
+ }
+ }
+
+ private void populateContactAndAboutCard(Cp2DataCardModel cp2DataCardModel) {
+ mCachedCp2DataCardModel = cp2DataCardModel;
+ if (mHasIntentLaunched || cp2DataCardModel == null) {
+ return;
+ }
Trace.beginSection("bind contact card");
- final List<List<Entry>> contactCardEntries = new ArrayList<>();
- final List<List<Entry>> aboutCardEntries = buildAboutCardEntries();
-
- for (int i = 0; i < mDataItemsList.size(); ++i) {
- final List<DataItem> dataItemsByMimeType = mDataItemsList.get(i);
- final DataItem topDataItem = dataItemsByMimeType.get(0);
- if (SORTED_ABOUT_CARD_MIMETYPES.contains(topDataItem.getMimeType())) {
- // About card mimetypes are built in buildAboutCardEntries, skip here
- continue;
- } else {
- List<Entry> contactEntries = dataItemsToEntries(mDataItemsList.get(i));
- if (contactEntries.size() > 0) {
- contactCardEntries.add(contactEntries);
- }
- }
- }
+ final List<List<Entry>> contactCardEntries = cp2DataCardModel.contactCardEntries;
+ final List<List<Entry>> aboutCardEntries = cp2DataCardModel.aboutCardEntries;
+ final String customAboutCardName = cp2DataCardModel.customAboutCardName;
if (contactCardEntries.size() > 0) {
mContactCard.initialize(contactCardEntries,
@@ -939,6 +949,10 @@
}
}
+ if (!TextUtils.isEmpty(customAboutCardName)) {
+ mAboutCard.setTitle(customAboutCardName);
+ }
+
mAboutCard.initialize(aboutCardEntries,
/* numInitialVisibleEntries = */ 1,
/* isExpanded = */ true,
@@ -1006,7 +1020,7 @@
* amongst mimetype. The map goes from mimetype string to the sorted list of data items within
* mimetype
*/
- private Pair<List<List<DataItem>>, Map<String, List<DataItem>>> generateDataModelFromContact(
+ private Cp2DataCardModel generateDataModelFromContact(
Contact data) {
Trace.beginSection("Build data items map");
@@ -1065,16 +1079,69 @@
Collections.sort(dataItemsList, mAmongstMimeTypeDataItemComparator);
Trace.endSection();
- return new Pair<>(dataItemsList, dataItemsMap);
+ Trace.beginSection("cp2 data items to entries");
+
+ final List<List<Entry>> contactCardEntries = new ArrayList<>();
+ final List<List<Entry>> aboutCardEntries = buildAboutCardEntries(dataItemsMap);
+ final MutableString aboutCardName = new MutableString();
+
+ for (int i = 0; i < dataItemsList.size(); ++i) {
+ final List<DataItem> dataItemsByMimeType = dataItemsList.get(i);
+ final DataItem topDataItem = dataItemsByMimeType.get(0);
+ if (SORTED_ABOUT_CARD_MIMETYPES.contains(topDataItem.getMimeType())) {
+ // About card mimetypes are built in buildAboutCardEntries, skip here
+ continue;
+ } else {
+ List<Entry> contactEntries = dataItemsToEntries(dataItemsList.get(i),
+ aboutCardName);
+ if (contactEntries.size() > 0) {
+ contactCardEntries.add(contactEntries);
+ }
+ }
+ }
+
+ Trace.endSection();
+
+ final Cp2DataCardModel dataModel = new Cp2DataCardModel();
+ dataModel.customAboutCardName = aboutCardName.value;
+ dataModel.aboutCardEntries = aboutCardEntries;
+ dataModel.contactCardEntries = contactCardEntries;
+ dataModel.dataItemsMap = dataItemsMap;
+ return dataModel;
+ }
+
+ /**
+ * Class used to hold the About card and Contact cards' data model that gets generated
+ * on a background thread. All data is from CP2.
+ */
+ private static class Cp2DataCardModel {
+ /**
+ * A map between a mimetype string and the corresponding list of data items. The data items
+ * are in sorted order using mWithinMimeTypeDataItemComparator.
+ */
+ public Map<String, List<DataItem>> dataItemsMap;
+ public List<List<Entry>> aboutCardEntries;
+ public List<List<Entry>> contactCardEntries;
+ public String customAboutCardName;
+ }
+
+ private static class MutableString {
+ public String value;
}
/**
* Converts a {@link DataItem} into an {@link ExpandingEntryCardView.Entry} for display.
* If the {@link ExpandingEntryCardView.Entry} has no visual elements, null is returned.
+ *
+ * This runs on a background thread. This is set as static to avoid accidentally adding
+ * additional dependencies on unsafe things (like the Activity).
+ *
* @param dataItem The {@link DataItem} to convert.
* @return The {@link ExpandingEntryCardView.Entry}, or null if no visual elements are present.
*/
- private Entry dataItemToEntry(DataItem dataItem) {
+ private static Entry dataItemToEntry(DataItem dataItem,
+ Context context, Contact contactData,
+ final MutableString aboutCardName) {
Drawable icon = null;
String header = null;
String subHeader = null;
@@ -1089,11 +1156,12 @@
final boolean isEditable = false;
EntryContextMenuInfo entryContextMenuInfo = null;
+ context = context.getApplicationContext();
DataKind kind = dataItem.getDataKind();
if (dataItem instanceof ImDataItem) {
final ImDataItem im = (ImDataItem) dataItem;
- intent = ContactsUtils.buildImIntent(this, im).first;
+ intent = ContactsUtils.buildImIntent(context, im).first;
final boolean isEmail = im.isCreatedFromEmail();
final int protocol;
if (!im.isProtocolValid()) {
@@ -1104,19 +1172,19 @@
if (protocol == Im.PROTOCOL_CUSTOM) {
// If the protocol is custom, display the "IM" entry header as well to distinguish
// this entry from other ones
- header = getResources().getString(R.string.header_im_entry);
- subHeader = Im.getProtocolLabel(getResources(), protocol,
+ header = context.getResources().getString(R.string.header_im_entry);
+ subHeader = Im.getProtocolLabel(context.getResources(), protocol,
im.getCustomProtocol()).toString();
text = im.getData();
} else {
- header = Im.getProtocolLabel(getResources(), protocol,
+ header = Im.getProtocolLabel(context.getResources(), protocol,
im.getCustomProtocol()).toString();
subHeader = im.getData();
}
entryContextMenuInfo = new EntryContextMenuInfo(im.getData(), header);
} else if (dataItem instanceof OrganizationDataItem) {
final OrganizationDataItem organization = (OrganizationDataItem) dataItem;
- header = getResources().getString(R.string.header_organization_entry);
+ header = context.getResources().getString(R.string.header_organization_entry);
subHeader = organization.getCompany();
entryContextMenuInfo = new EntryContextMenuInfo(subHeader, header);
text = organization.getTitle();
@@ -1124,36 +1192,37 @@
final NicknameDataItem nickname = (NicknameDataItem) dataItem;
// Build nickname entries
final boolean isNameRawContact =
- (mContactData.getNameRawContactId() == dataItem.getRawContactId());
+ (contactData.getNameRawContactId() == dataItem.getRawContactId());
final boolean duplicatesTitle =
isNameRawContact
- && mContactData.getDisplayNameSource() == DisplayNameSources.NICKNAME;
+ && contactData.getDisplayNameSource() == DisplayNameSources.NICKNAME;
if (!duplicatesTitle) {
- header = getResources().getString(R.string.header_nickname_entry);
+ header = context.getResources().getString(R.string.header_nickname_entry);
subHeader = nickname.getName();
entryContextMenuInfo = new EntryContextMenuInfo(subHeader, header);
}
} else if (dataItem instanceof NoteDataItem) {
final NoteDataItem note = (NoteDataItem) dataItem;
- header = getResources().getString(R.string.header_note_entry);
+ header = context.getResources().getString(R.string.header_note_entry);
subHeader = note.getNote();
entryContextMenuInfo = new EntryContextMenuInfo(subHeader, header);
} else if (dataItem instanceof WebsiteDataItem) {
final WebsiteDataItem website = (WebsiteDataItem) dataItem;
- header = getResources().getString(R.string.header_website_entry);
+ header = context.getResources().getString(R.string.header_website_entry);
subHeader = website.getUrl();
entryContextMenuInfo = new EntryContextMenuInfo(subHeader, header);
try {
- final WebAddress webAddress = new WebAddress(website.buildDataString(this, kind));
+ final WebAddress webAddress = new WebAddress(website.buildDataString(context,
+ kind));
intent = new Intent(Intent.ACTION_VIEW, Uri.parse(webAddress.toString()));
} catch (final ParseException e) {
- Log.e(TAG, "Couldn't parse website: " + website.buildDataString(this, kind));
+ Log.e(TAG, "Couldn't parse website: " + website.buildDataString(context, kind));
}
} else if (dataItem instanceof EventDataItem) {
final EventDataItem event = (EventDataItem) dataItem;
- final String dataString = event.buildDataString(this, kind);
+ final String dataString = event.buildDataString(context, kind);
final Calendar cal = DateUtils.parseDate(dataString, false);
if (cal != null) {
final Date nextAnniversary =
@@ -1163,46 +1232,47 @@
ContentUris.appendId(builder, nextAnniversary.getTime());
intent = new Intent(Intent.ACTION_VIEW).setData(builder.build());
}
- header = getResources().getString(R.string.header_event_entry);
+ header = context.getResources().getString(R.string.header_event_entry);
if (event.hasKindTypeColumn(kind)) {
- subHeader = Event.getTypeLabel(getResources(), event.getKindTypeColumn(kind),
+ subHeader = Event.getTypeLabel(context.getResources(), event.getKindTypeColumn(kind),
event.getLabel()).toString();
}
- text = DateUtils.formatDate(this, dataString);
+ text = DateUtils.formatDate(context, dataString);
entryContextMenuInfo = new EntryContextMenuInfo(text, header);
} else if (dataItem instanceof RelationDataItem) {
final RelationDataItem relation = (RelationDataItem) dataItem;
- final String dataString = relation.buildDataString(this, kind);
+ final String dataString = relation.buildDataString(context, kind);
if (!TextUtils.isEmpty(dataString)) {
intent = new Intent(Intent.ACTION_SEARCH);
intent.putExtra(SearchManager.QUERY, dataString);
intent.setType(Contacts.CONTENT_TYPE);
}
- header = getResources().getString(R.string.header_relation_entry);
+ header = context.getResources().getString(R.string.header_relation_entry);
subHeader = relation.getName();
entryContextMenuInfo = new EntryContextMenuInfo(subHeader, header);
if (relation.hasKindTypeColumn(kind)) {
- text = Relation.getTypeLabel(getResources(), relation.getKindTypeColumn(kind),
+ text = Relation.getTypeLabel(context.getResources(),
+ relation.getKindTypeColumn(kind),
relation.getLabel()).toString();
}
} else if (dataItem instanceof PhoneDataItem) {
final PhoneDataItem phone = (PhoneDataItem) dataItem;
if (!TextUtils.isEmpty(phone.getNumber())) {
- header = phone.buildDataString(this, kind);
+ header = phone.buildDataString(context, kind);
entryContextMenuInfo = new EntryContextMenuInfo(header,
- getResources().getString(R.string.phoneLabelsGroup));
+ context.getResources().getString(R.string.phoneLabelsGroup));
if (phone.hasKindTypeColumn(kind)) {
- text = Phone.getTypeLabel(getResources(), phone.getKindTypeColumn(kind),
+ text = Phone.getTypeLabel(context.getResources(), phone.getKindTypeColumn(kind),
phone.getLabel()).toString();
}
- icon = getResources().getDrawable(R.drawable.ic_phone_24dp);
- if (PhoneCapabilityTester.isPhone(this)) {
+ icon = context.getResources().getDrawable(R.drawable.ic_phone_24dp);
+ if (PhoneCapabilityTester.isPhone(context)) {
intent = CallUtil.getCallIntent(phone.getNumber());
}
alternateIntent = new Intent(Intent.ACTION_SENDTO,
Uri.fromParts(CallUtil.SCHEME_SMSTO, phone.getNumber(), null));
- alternateIcon = getResources().getDrawable(R.drawable.ic_message_24dp);
- alternateContentDescription = getResources().getString(R.string.sms_other);
+ alternateIcon = context.getResources().getDrawable(R.drawable.ic_message_24dp);
+ alternateContentDescription = context.getResources().getString(R.string.sms_other);
}
} else if (dataItem instanceof EmailDataItem) {
final EmailDataItem email = (EmailDataItem) dataItem;
@@ -1212,12 +1282,12 @@
intent = new Intent(Intent.ACTION_SENDTO, mailUri);
header = email.getAddress();
entryContextMenuInfo = new EntryContextMenuInfo(header,
- getResources().getString(R.string.emailLabelsGroup));
+ context.getResources().getString(R.string.emailLabelsGroup));
if (email.hasKindTypeColumn(kind)) {
- text = Email.getTypeLabel(getResources(), email.getKindTypeColumn(kind),
+ text = Email.getTypeLabel(context.getResources(), email.getKindTypeColumn(kind),
email.getLabel()).toString();
}
- icon = getResources().getDrawable(R.drawable.ic_email_24dp);
+ icon = context.getResources().getDrawable(R.drawable.ic_email_24dp);
}
} else if (dataItem instanceof StructuredPostalDataItem) {
StructuredPostalDataItem postal = (StructuredPostalDataItem) dataItem;
@@ -1226,18 +1296,18 @@
intent = StructuredPostalUtils.getViewPostalAddressIntent(postalAddress);
header = postal.getFormattedAddress();
entryContextMenuInfo = new EntryContextMenuInfo(header,
- getResources().getString(R.string.postalLabelsGroup));
+ context.getResources().getString(R.string.postalLabelsGroup));
if (postal.hasKindTypeColumn(kind)) {
- text = StructuredPostal.getTypeLabel(getResources(),
+ text = StructuredPostal.getTypeLabel(context.getResources(),
postal.getKindTypeColumn(kind), postal.getLabel()).toString();
}
alternateIntent =
StructuredPostalUtils.getViewPostalAddressDirectionsIntent(postalAddress);
- alternateIcon = getResources().getDrawable(R.drawable.ic_directions_24dp);
- icon = getResources().getDrawable(R.drawable.ic_place_24dp);
+ alternateIcon = context.getResources().getDrawable(R.drawable.ic_directions_24dp);
+ icon = context.getResources().getDrawable(R.drawable.ic_place_24dp);
}
} else if (dataItem instanceof SipAddressDataItem) {
- if (PhoneCapabilityTester.isSipPhone(this)) {
+ if (PhoneCapabilityTester.isSipPhone(context)) {
final SipAddressDataItem sip = (SipAddressDataItem) dataItem;
final String address = sip.getSipAddress();
if (!TextUtils.isEmpty(address)) {
@@ -1245,35 +1315,25 @@
intent = CallUtil.getCallIntent(callUri);
header = address;
entryContextMenuInfo = new EntryContextMenuInfo(header,
- getResources().getString(R.string.phoneLabelsGroup));
+ context.getResources().getString(R.string.phoneLabelsGroup));
if (sip.hasKindTypeColumn(kind)) {
- text = SipAddress.getTypeLabel(getResources(), sip.getKindTypeColumn(kind),
- sip.getLabel()).toString();
+ text = SipAddress.getTypeLabel(context.getResources(),
+ sip.getKindTypeColumn(kind), sip.getLabel()).toString();
}
- // Note that this item will get a SIP-specific variant
- // of the "call phone" icon, rather than the standard
- // app icon for the Phone app (which we show for
- // regular phone numbers.) That's because the phone
- // app explicitly specifies an android:icon attribute
- // for the SIP-related intent-filters in its manifest.
- icon = ResolveCache.getInstance(this).getIcon(sip.getMimeType(), intent);
- // Call mutate to create a new Drawable.ConstantState for color filtering
- if (icon != null) {
- icon.mutate();
- }
+ icon = context.getResources().getDrawable(R.drawable.ic_dialer_sip_black_24dp);
}
}
} else if (dataItem instanceof StructuredNameDataItem) {
final String givenName = ((StructuredNameDataItem) dataItem).getGivenName();
if (!TextUtils.isEmpty(givenName)) {
- mAboutCard.setTitle(getResources().getString(R.string.about_card_title) +
- " " + givenName);
+ aboutCardName.value = context.getResources().getString(R.string.about_card_title) +
+ " " + givenName;
} else {
- mAboutCard.setTitle(getResources().getString(R.string.about_card_title));
+ aboutCardName.value = context.getResources().getString(R.string.about_card_title);
}
} else {
// Custom DataItem
- header = dataItem.buildDataStringForDisplay(this, kind);
+ header = dataItem.buildDataStringForDisplay(context, kind);
text = kind.typeColumn;
intent = new Intent(Intent.ACTION_VIEW);
final Uri uri = ContentUris.withAppendedId(Data.CONTENT_URI, dataItem.getId());
@@ -1287,22 +1347,22 @@
case MIMETYPE_GPLUS_PROFILE:
if (INTENT_DATA_GPLUS_PROFILE_ADD_TO_CIRCLE.equals(
intent.getDataString())) {
- icon = getResources().getDrawable(
+ icon = context.getResources().getDrawable(
R.drawable.ic_add_to_circles_black_24);
} else {
- icon = getResources().getDrawable(R.drawable.ic_google_plus_24dp);
+ icon = context.getResources().getDrawable(R.drawable.ic_google_plus_24dp);
}
break;
case MIMETYPE_HANGOUTS:
if (INTENT_DATA_HANGOUTS_VIDEO.equals(intent.getDataString())) {
- icon = getResources().getDrawable(R.drawable.ic_hangout_video_24dp);
+ icon = context.getResources().getDrawable(R.drawable.ic_hangout_video_24dp);
} else {
- icon = getResources().getDrawable(R.drawable.ic_hangout_24dp);
+ icon = context.getResources().getDrawable(R.drawable.ic_hangout_24dp);
}
break;
default:
entryContextMenuInfo = new EntryContextMenuInfo(header, mimetype);
- icon = ResolveCache.getInstance(this).getIcon(
+ icon = ResolveCache.getInstance(context).getIcon(
dataItem.getMimeType(), intent);
// Call mutate to create a new Drawable.ConstantState for color filtering
if (icon != null) {
@@ -1315,20 +1375,20 @@
if (intent != null) {
// Do not set the intent is there are no resolves
- if (!PhoneCapabilityTester.isIntentRegistered(this, intent)) {
+ if (!PhoneCapabilityTester.isIntentRegistered(context, intent)) {
intent = null;
}
}
if (alternateIntent != null) {
// Do not set the alternate intent is there are no resolves
- if (!PhoneCapabilityTester.isIntentRegistered(this, alternateIntent)) {
+ if (!PhoneCapabilityTester.isIntentRegistered(context, alternateIntent)) {
alternateIntent = null;
}
// Attempt to use package manager to find a suitable content description if needed
if (TextUtils.isEmpty(alternateContentDescription)) {
- alternateContentDescription = getIntentResolveLabel(alternateIntent);
+ alternateContentDescription = getIntentResolveLabel(alternateIntent, context);
}
}
@@ -1347,10 +1407,11 @@
isEditable, entryContextMenuInfo);
}
- private List<Entry> dataItemsToEntries(List<DataItem> dataItems) {
+ private List<Entry> dataItemsToEntries(List<DataItem> dataItems,
+ MutableString aboutCardTitleOut) {
final List<Entry> entries = new ArrayList<>();
for (DataItem dataItem : dataItems) {
- final Entry entry = dataItemToEntry(dataItem);
+ final Entry entry = dataItemToEntry(dataItem, this, mContactData, aboutCardTitleOut);
if (entry != null) {
entries.add(entry);
}
@@ -1358,8 +1419,8 @@
return entries;
}
- private String getIntentResolveLabel(Intent intent) {
- final List<ResolveInfo> matches = getPackageManager().queryIntentActivities(intent,
+ private static String getIntentResolveLabel(Intent intent, Context context) {
+ final List<ResolveInfo> matches = context.getPackageManager().queryIntentActivities(intent,
PackageManager.MATCH_DEFAULT_ONLY);
// Pick first match, otherwise best found
@@ -1368,14 +1429,14 @@
if (size == 1) {
bestResolve = matches.get(0);
} else if (size > 1) {
- bestResolve = ResolveCache.getInstance(this).getBestResolve(intent, matches);
+ bestResolve = ResolveCache.getInstance(context).getBestResolve(intent, matches);
}
if (bestResolve == null) {
return null;
}
- return String.valueOf(bestResolve.loadLabel(getPackageManager()));
+ return String.valueOf(bestResolve.loadLabel(context.getPackageManager()));
}
/**
@@ -1710,19 +1771,20 @@
}
private Intent getEditContactIntent() {
- final Intent intent = new Intent(Intent.ACTION_EDIT, mLookupUri);
+ final Intent intent = new Intent(Intent.ACTION_EDIT, mContactData.getLookupUri());
mContactLoader.cacheResult();
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
return intent;
}
private void editContact() {
+ mHasIntentLaunched = true;
startActivityForResult(getEditContactIntent(), REQUEST_CODE_CONTACT_EDITOR_ACTIVITY);
}
private void toggleStar(MenuItem starredMenuItem) {
// Make sure there is a contact
- if (mLookupUri != null) {
+ if (mContactData != null) {
// Read the current starred value from the UI instead of using the last
// loaded state. This allows rapid tapping without writing the same
// value several times
@@ -1735,7 +1797,7 @@
// Now perform the real save
final Intent intent = ContactSaveService.createSetStarredIntent(
- QuickContactActivity.this, mLookupUri, !isStarred);
+ QuickContactActivity.this, mContactData.getLookupUri(), !isStarred);
startService(intent);
final CharSequence accessibilityText = !isStarred
@@ -1785,6 +1847,7 @@
final Intent chooseIntent = Intent.createChooser(intent, chooseTitle);
try {
+ mHasIntentLaunched = true;
this.startActivity(chooseIntent);
} catch (final ActivityNotFoundException ex) {
Toast.makeText(this, R.string.share_error, Toast.LENGTH_SHORT).show();
@@ -1813,7 +1876,7 @@
}
});
- builder.createContactShortcutIntent(mLookupUri);
+ builder.createContactShortcutIntent(mContactData.getLookupUri());
}
@Override
diff --git a/src/com/android/contacts/util/ImageViewDrawableSetter.java b/src/com/android/contacts/util/ImageViewDrawableSetter.java
index 226909e..e926e54 100644
--- a/src/com/android/contacts/util/ImageViewDrawableSetter.java
+++ b/src/com/android/contacts/util/ImageViewDrawableSetter.java
@@ -17,15 +17,13 @@
package com.android.contacts.util;
import android.content.res.Resources;
-import android.content.res.Resources.NotFoundException;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.TransitionDrawable;
-import android.provider.ContactsContract.DisplayNameSources;
+import android.media.ThumbnailUtils;
import android.text.TextUtils;
-import android.util.Log;
import android.widget.ImageView;
import com.android.contacts.common.ContactPhotoManager;
@@ -161,8 +159,13 @@
}
private BitmapDrawable decodedBitmapDrawable(byte[] compressed) {
- Resources rsrc = mTarget.getResources();
+ final Resources rsrc = mTarget.getResources();
Bitmap bitmap = BitmapFactory.decodeByteArray(compressed, 0, compressed.length);
+ if (bitmap.getHeight() != bitmap.getWidth()) {
+ // Crop the bitmap into a square.
+ final int size = Math.min(bitmap.getWidth(), bitmap.getHeight());
+ bitmap = ThumbnailUtils.extractThumbnail(bitmap, size, size);
+ }
return new BitmapDrawable(rsrc, bitmap);
}
}
diff --git a/src/com/android/contacts/widget/MultiShrinkScroller.java b/src/com/android/contacts/widget/MultiShrinkScroller.java
index 82b3970..26b032b 100644
--- a/src/com/android/contacts/widget/MultiShrinkScroller.java
+++ b/src/com/android/contacts/widget/MultiShrinkScroller.java
@@ -54,6 +54,13 @@
* MultiShrinkScroller's code is heavily influenced by ScrollView. Nonetheless, several ScrollView
* features are missing. For example: handling of KEYCODES, OverScroll bounce and saving
* scroll state in savedInstanceState bundles.
+ *
+ * Before copying this approach to nested scrolling, consider whether something simpler & less
+ * customized will work for you. For example, see the re-usable StickyHeaderListView used by
+ * WifiSetupActivity (very nice). Alternatively, check out Google+'s cover photo scrolling or
+ * Android L's built in nested scrolling support. I thought I needed a more custom ViewGroup in
+ * order to track velocity, modify EdgeEffect color & perform specific animations such as the ones
+ * inside snapToBottom(). As a result this ViewGroup has non-standard talkback and keyboard support.
*/
public class MultiShrinkScroller extends FrameLayout {