Merge "Import translations. DO NOT MERGE"
diff --git a/res/layout-land/quickcontact_activity.xml b/res/layout-land/quickcontact_activity.xml
index 552f568..65fcd7d 100644
--- a/res/layout-land/quickcontact_activity.xml
+++ b/res/layout-land/quickcontact_activity.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 The Android Open Source Project
+<!-- Copyright (C) 2014 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.
@@ -13,45 +13,29 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<LinearLayout
+<com.android.contacts.widget.MultiShrinkScroller
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:ex="http://schemas.android.com/apk/res-auto"
- android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:padding="32dip"
- android:orientation="horizontal">
- <view
- class="com.android.contacts.common.widget.ProportionalLayout"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- ex:ratio="1.0"
- ex:direction="heightToWidth">
- <FrameLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent">
- <ImageView
- android:id="@+id/photo"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:scaleType="centerCrop"
- android:contentDescription="@string/description_contact_photo" />
- <!-- Need to set a non null background on Toolbar in order for MenuItem
- ripples to be drawn on this view, instead of another-->
- <Toolbar
- android:layout_width="match_parent"
- android:layout_height="?android:attr/actionBarSize"
- android:background="#00000000"
- android:id="@+id/toolbar"/>
- </FrameLayout>
- </view>
- <com.android.contacts.quickcontact.ExpandingEntryCardView
- style="@style/ExpandingEntryCardStyle"
- android:id="@+id/communication_card"
- android:layout_marginTop="@dimen/communication_card_marginTop"
- android:visibility="gone" />
- <com.android.contacts.quickcontact.ExpandingEntryCardView
- style="@style/ExpandingEntryCardStyle"
- android:id="@+id/recent_card"
- android:visibility="gone" />
-</LinearLayout>
\ No newline at end of file
+ android:orientation="vertical"
+ android:id="@+id/multiscroller"
+ android:focusable="true"
+ android:focusableInTouchMode="true"
+ android:descendantFocusability="afterDescendants" >
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/quickcontact_starting_empty_height"
+ android:id="@+id/transparent_view" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <include layout="@layout/quickcontact_header" />
+
+ <include layout="@layout/quickcontact_content" />
+
+ </LinearLayout>
+
+</com.android.contacts.widget.MultiShrinkScroller>
\ No newline at end of file
diff --git a/res/layout/expanding_entry_card_item.xml b/res/layout/expanding_entry_card_item.xml
index 890f2da..c038d1b 100644
--- a/res/layout/expanding_entry_card_item.xml
+++ b/res/layout/expanding_entry_card_item.xml
@@ -40,8 +40,7 @@
android:layout_alignParentTop="true"
android:layout_toRightOf="@+id/icon"
android:singleLine="true"
- android:textColor="@android:color/black"
- android:textStyle="bold" />
+ android:textColor="@android:color/black" />
<TextView
android:id="@+id/sub_header"
diff --git a/res/layout/quickcontact_activity.xml b/res/layout/quickcontact_activity.xml
index 13b8d9b..7b81ea2 100644
--- a/res/layout/quickcontact_activity.xml
+++ b/res/layout/quickcontact_activity.xml
@@ -29,54 +29,8 @@
android:layout_height="@dimen/quickcontact_starting_empty_height"
android:id="@+id/transparent_view" />
- <FrameLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="@color/card_margin_color"
- android:id="@+id/toolbar_parent">
+ <include layout="@layout/quickcontact_header" />
- <ImageView
- android:id="@+id/photo"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:scaleType="centerCrop"
- android:contentDescription="@string/description_contact_photo" />
-
- <!-- Need to set a non null background on Toolbar in order for MenuItem
- ripples to be drawn on this view, instead of another-->
- <Toolbar
- android:layout_width="match_parent"
- android:layout_height="?android:attr/actionBarSize"
- android:background="#00000000"
- android:id="@+id/toolbar"/>
-
- </FrameLayout>
-
- <com.android.contacts.widget.TouchlessScrollView
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:fillViewport="true"
- android:id="@+id/content_scroller"
- android:background="@color/card_margin_color">
-
- <!-- All the cards should be inserted into this LinearLayout -->
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:id="@+id/card_container">
- <com.android.contacts.quickcontact.ExpandingEntryCardView
- style="@style/ExpandingEntryCardStyle"
- android:id="@+id/communication_card"
- android:layout_marginTop="@dimen/communication_card_marginTop"
- android:visibility="gone" />
-
- <com.android.contacts.quickcontact.ExpandingEntryCardView
- style="@style/ExpandingEntryCardStyle"
- android:id="@+id/recent_card"
- android:visibility="gone" />
- </LinearLayout>
-
- </com.android.contacts.widget.TouchlessScrollView>
+ <include layout="@layout/quickcontact_content" />
</com.android.contacts.widget.MultiShrinkScroller>
\ No newline at end of file
diff --git a/res/layout/quickcontact_content.xml b/res/layout/quickcontact_content.xml
new file mode 100644
index 0000000..b5b2a83
--- /dev/null
+++ b/res/layout/quickcontact_content.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+<com.android.contacts.widget.TouchlessScrollView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fillViewport="true"
+ android:id="@+id/content_scroller"
+ android:background="@color/card_margin_color">
+
+ <!-- All the cards should be inserted into this LinearLayout -->
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:id="@+id/card_container">
+ <com.android.contacts.quickcontact.ExpandingEntryCardView
+ style="@style/ExpandingEntryCardStyle"
+ android:id="@+id/communication_card"
+ android:layout_marginTop="@dimen/communication_card_marginTop"
+ android:visibility="gone" />
+
+ <com.android.contacts.quickcontact.ExpandingEntryCardView
+ style="@style/ExpandingEntryCardStyle"
+ android:id="@+id/recent_card"
+ android:visibility="gone" />
+ </LinearLayout>
+
+</com.android.contacts.widget.TouchlessScrollView>
\ No newline at end of file
diff --git a/res/layout/quickcontact_header.xml b/res/layout/quickcontact_header.xml
new file mode 100644
index 0000000..55b23c9
--- /dev/null
+++ b/res/layout/quickcontact_header.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 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.
+-->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@+id/toolbar_parent">
+
+ <com.android.contacts.widget.QuickContactImageView
+ android:id="@+id/photo"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:scaleType="centerCrop"
+ android:contentDescription="@string/description_contact_photo" />
+
+ <!-- Need to set a non null background on Toolbar in order for MenuItem
+ ripples to be drawn on this view, instead of another-->
+ <Toolbar
+ android:layout_width="match_parent"
+ android:layout_height="?android:attr/actionBarSize"
+ android:background="#00000000"
+ android:id="@+id/toolbar"/>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="@color/actionbar_text_color"
+ android:maxLines="@integer/quickcontact_title_lines"
+ android:ellipsize="end"
+ android:layout_gravity="bottom"
+ android:textSize="@dimen/quickcontact_maximum_title_size"
+ android:id="@+id/large_title"/>
+
+</FrameLayout>
\ No newline at end of file
diff --git a/res/layout/quickcontact_title_placeholder.xml b/res/layout/quickcontact_title_placeholder.xml
new file mode 100644
index 0000000..31d83ff
--- /dev/null
+++ b/res/layout/quickcontact_title_placeholder.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 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.
+ -->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent" >
+
+ <!-- Marks the location and size of the Activity title -->
+ <TextView
+ android:id="@+id/placeholder_textview"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@android:style/TextAppearance.Material.Widget.ActionBar.Title" />
+
+</FrameLayout>
+
+
diff --git a/res/values-land/bools.xml b/res/values-land/bools.xml
new file mode 100644
index 0000000..bd0650f
--- /dev/null
+++ b/res/values-land/bools.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+<resources>
+
+ <bool name="quickcontact_two_panel">true</bool>
+
+</resources>
diff --git a/res/values-land/integers.xml b/res/values-land/integers.xml
index 010a5b7..08e1fe3 100644
--- a/res/values-land/integers.xml
+++ b/res/values-land/integers.xml
@@ -18,4 +18,7 @@
<integer name="contact_tile_column_count_in_favorites">5</integer>
<integer name="contact_tile_column_count">4</integer>
+
+ <!-- Number of lines the QuickContact title can have -->
+ <integer name="quickcontact_title_lines">2</integer>
</resources>
diff --git a/res/values-land/vals.xml b/res/values-land/vals.xml
new file mode 100644
index 0000000..ebcae31
--- /dev/null
+++ b/res/values-land/vals.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <!-- The ratio of width:height for the contact's photo -->
+ <item name="quickcontact_photo_ratio" type="vals" format="float">0.7</item>
+</resources>
diff --git a/res/values/bools.xml b/res/values/bools.xml
new file mode 100644
index 0000000..663845a
--- /dev/null
+++ b/res/values/bools.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+<resources>
+
+ <bool name="quickcontact_two_panel">false</bool>
+
+</resources>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 962fe97..89f39b8 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -39,16 +39,16 @@
<color name="contacts_accent_color">#00acc1</color>
<!-- Color of the separator between entries in an ExpandingEntryCardView -->
- <color name="expanding_entry_card_item_separator_color">#eeeeee</color>
+ <color name="expanding_entry_card_item_separator_color">#e4e4e4</color>
<!-- Color of the text on an ExpandingEntryCard button -->
<color name="expanding_entry_card_button_text_color">@android:color/black</color>
<!-- Background color for an ExpandingEntryCard -->
- <color name="expanding_entry_card_background_color">#f7f8f9</color>
+ <color name="expanding_entry_card_background_color">#ffffff</color>
<!-- Color of the margin for cards -->
- <color name="card_margin_color">#ffbbbbbb</color>
+ <color name="card_margin_color">#f4f4f4</color>
<color name="call_arrow_green">#2aad6f</color>
<color name="call_arrow_red">#ff2e58</color>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 99bc345..9d8ba70 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -18,8 +18,8 @@
<!-- Initial height of transparent space above QuickContacts -->
<dimen name="quickcontact_starting_empty_height">150dp</dimen>
- <!-- Initial height of QuickContact's header/avatar-photo -->
- <dimen name="quickcontact_starting_header_height">200dp</dimen>
+ <!-- Initial size of QuickContact's title size -->
+ <dimen name="quickcontact_maximum_title_size">36dp</dimen>
<!-- Top padding of the entire contact editor -->
<dimen name="editor_padding_top">0dip</dimen>
@@ -147,9 +147,9 @@
<dimen name="expanding_entry_card_marginBottom">8dp</dimen>
<!-- Elevation of an ExpandingEntryCard, for the sake of shadow casting -->
- <dimen name="expanding_entry_card_elevation">2dp</dimen>
+ <dimen name="expanding_entry_card_elevation">1dp</dimen>
<!-- Elevation of the QuickContact's Toolbar, for the sake of shadow casting -->
- <dimen name="quick_contact_toolbar_elevation">6dp</dimen>
+ <dimen name="quick_contact_toolbar_elevation">4.5dp</dimen>
<!-- Top margin for the communication card, used to add space from header. -->
<dimen name="communication_card_marginTop">8dp</dimen>
diff --git a/res/values/integers.xml b/res/values/integers.xml
index a6b43d7..ff34d11 100644
--- a/res/values/integers.xml
+++ b/res/values/integers.xml
@@ -20,4 +20,7 @@
<!-- Determines the number of columns in a ContactTileRow -->
<integer name="contact_tile_column_count">2</integer>
+
+ <!-- Number of lines the QuickContact title can have -->
+ <integer name="quickcontact_title_lines">1</integer>
</resources>
diff --git a/src/com/android/contacts/ContactSaveService.java b/src/com/android/contacts/ContactSaveService.java
index 8688a47..cc687ec 100644
--- a/src/com/android/contacts/ContactSaveService.java
+++ b/src/com/android/contacts/ContactSaveService.java
@@ -829,9 +829,8 @@
// Don't bother undemoting if this contact is the user's profile.
if (id < Profile.MIN_ID) {
- values.clear();
- values.put(String.valueOf(id), PinnedPositions.UNDEMOTE);
- getContentResolver().update(PinnedPositions.UPDATE_URI, values, null, null);
+ getContentResolver().call(ContactsContract.AUTHORITY_URI,
+ PinnedPositions.UNDEMOTE_METHOD, String.valueOf(id), null);
}
}
} finally {
diff --git a/src/com/android/contacts/interactions/CalendarInteraction.java b/src/com/android/contacts/interactions/CalendarInteraction.java
index 68e37f7..4b766f8 100644
--- a/src/com/android/contacts/interactions/CalendarInteraction.java
+++ b/src/com/android/contacts/interactions/CalendarInteraction.java
@@ -61,6 +61,16 @@
// TODO: build callback to update time zone if different than preferences
String localTimezone = Time.getCurrentTimezone();
+ Long dateEnd = getDtend();
+ Long dateStart = getDtstart();
+ if (dateStart == null && dateEnd == null) {
+ return null;
+ } else if (dateEnd == null) {
+ dateEnd = dateStart;
+ } else if (dateStart == null) {
+ dateStart = dateEnd;
+ }
+
String displayedDatetime = CalendarInteractionUtils.getDisplayedDatetime(
getDtstart(), getDtend(), System.currentTimeMillis(), localTimezone,
getAllDay(), context);
@@ -99,39 +109,39 @@
return mValues.getAsString(Attendees.ATTENDEE_NAME);
}
- public int getAttendeeRelationship() {
+ public Integer getAttendeeRelationship() {
return mValues.getAsInteger(Attendees.ATTENDEE_RELATIONSHIP);
}
- public int getAttendeeStatus() {
+ public Integer getAttendeeStatus() {
return mValues.getAsInteger(Attendees.ATTENDEE_STATUS);
}
- public int getAttendeeType() {
+ public Integer getAttendeeType() {
return mValues.getAsInteger(Attendees.ATTENDEE_TYPE);
}
- public int getEventId() {
+ public Integer getEventId() {
return mValues.getAsInteger(Attendees.EVENT_ID);
}
- public int getAccessLevel() {
+ public Integer getAccessLevel() {
return mValues.getAsInteger(Attendees.ACCESS_LEVEL);
}
- public boolean getAllDay() {
+ public Boolean getAllDay() {
return mValues.getAsBoolean(Attendees.ALL_DAY);
}
- public int getAvailability() {
+ public Integer getAvailability() {
return mValues.getAsInteger(Attendees.AVAILABILITY);
}
- public int getCalendarId() {
+ public Integer getCalendarId() {
return mValues.getAsInteger(Attendees.CALENDAR_ID);
}
- public boolean getCanInviteOthers() {
+ public Boolean getCanInviteOthers() {
return mValues.getAsBoolean(Attendees.CAN_INVITE_OTHERS);
}
@@ -147,15 +157,15 @@
return mValues.getAsString(Attendees.DESCRIPTION);
}
- public int getDisplayColor() {
+ public Integer getDisplayColor() {
return mValues.getAsInteger(Attendees.DISPLAY_COLOR);
}
- public long getDtend() {
+ public Long getDtend() {
return mValues.getAsLong(Attendees.DTEND);
}
- public long getDtstart() {
+ public Long getDtstart() {
return mValues.getAsLong(Attendees.DTSTART);
}
@@ -163,7 +173,7 @@
return mValues.getAsString(Attendees.DURATION);
}
- public int getEventColor() {
+ public Integer getEventColor() {
return mValues.getAsInteger(Attendees.EVENT_COLOR);
}
@@ -187,27 +197,27 @@
return mValues.getAsString(Attendees.EXRULE);
}
- public boolean getGuestsCanInviteOthers() {
+ public Boolean getGuestsCanInviteOthers() {
return mValues.getAsBoolean(Attendees.GUESTS_CAN_INVITE_OTHERS);
}
- public boolean getGuestsCanModify() {
+ public Boolean getGuestsCanModify() {
return mValues.getAsBoolean(Attendees.GUESTS_CAN_MODIFY);
}
- public boolean getGuestsCanSeeGuests() {
+ public Boolean getGuestsCanSeeGuests() {
return mValues.getAsBoolean(Attendees.GUESTS_CAN_SEE_GUESTS);
}
- public boolean getHasAlarm() {
+ public Boolean getHasAlarm() {
return mValues.getAsBoolean(Attendees.HAS_ALARM);
}
- public boolean getHasAttendeeData() {
+ public Boolean getHasAttendeeData() {
return mValues.getAsBoolean(Attendees.HAS_ATTENDEE_DATA);
}
- public boolean getHasExtendedProperties() {
+ public Boolean getHasExtendedProperties() {
return mValues.getAsBoolean(Attendees.HAS_EXTENDED_PROPERTIES);
}
@@ -215,11 +225,11 @@
return mValues.getAsString(Attendees.IS_ORGANIZER);
}
- public long getLastDate() {
+ public Long getLastDate() {
return mValues.getAsLong(Attendees.LAST_DATE);
}
- public boolean getLastSynced() {
+ public Boolean getLastSynced() {
return mValues.getAsBoolean(Attendees.LAST_SYNCED);
}
@@ -227,7 +237,7 @@
return mValues.getAsString(Attendees.ORGANIZER);
}
- public boolean getOriginalAllDay() {
+ public Boolean getOriginalAllDay() {
return mValues.getAsBoolean(Attendees.ORIGINAL_ALL_DAY);
}
@@ -235,7 +245,7 @@
return mValues.getAsString(Attendees.ORIGINAL_ID);
}
- public long getOriginalInstanceTime() {
+ public Long getOriginalInstanceTime() {
return mValues.getAsLong(Attendees.ORIGINAL_INSTANCE_TIME);
}
@@ -251,11 +261,11 @@
return mValues.getAsString(Attendees.RRULE);
}
- public int getSelfAttendeeStatus() {
+ public Integer getSelfAttendeeStatus() {
return mValues.getAsInteger(Attendees.SELF_ATTENDEE_STATUS);
}
- public int getStatus() {
+ public Integer getStatus() {
return mValues.getAsInteger(Attendees.STATUS);
}
diff --git a/src/com/android/contacts/interactions/CallLogInteraction.java b/src/com/android/contacts/interactions/CallLogInteraction.java
index 8607974..28b9655 100644
--- a/src/com/android/contacts/interactions/CallLogInteraction.java
+++ b/src/com/android/contacts/interactions/CallLogInteraction.java
@@ -55,7 +55,9 @@
@Override
public Intent getIntent() {
- return new Intent(Intent.ACTION_CALL).setData(Uri.parse(URI_TARGET_PREFIX + getNumber()));
+ String number = getNumber();
+ return number == null ? null : new Intent(Intent.ACTION_CALL).setData(
+ Uri.parse(URI_TARGET_PREFIX + number));
}
@Override
@@ -65,13 +67,14 @@
@Override
public long getInteractionDate() {
- return getDate();
+ Long date = getDate();
+ return date == null ? -1 : date;
}
@Override
public String getViewBody(Context context) {
- int numberType = getCachedNumberType();
- if (numberType == -1) {
+ Integer numberType = getCachedNumberType();
+ if (numberType == null) {
return null;
}
return Phone.getTypeLabel(context.getResources(), getCachedNumberType(),
@@ -80,7 +83,9 @@
@Override
public String getViewFooter(Context context) {
- return ContactInteractionUtil.formatDateStringFromTimestamp(getDate(), context);
+ Long date = getDate();
+ return date == null ? null : ContactInteractionUtil.formatDateStringFromTimestamp(
+ date, context);
}
@Override
@@ -97,7 +102,11 @@
public Drawable getFooterIcon(Context context) {
Drawable callArrow = null;
Resources res = context.getResources();
- switch (getType()) {
+ Integer type = getType();
+ if (type == null) {
+ return null;
+ }
+ switch (type) {
case Calls.INCOMING_TYPE:
callArrow = res.getDrawable(CALL_ARROW_ICON_RES);
callArrow.setColorFilter(res.getColor(R.color.call_arrow_green),
@@ -125,28 +134,27 @@
return mValues.getAsString(Calls.CACHED_NUMBER_LABEL);
}
- public int getCachedNumberType() {
- Integer type = mValues.getAsInteger(Calls.CACHED_NUMBER_TYPE);
- return type != null ? type : -1;
+ public Integer getCachedNumberType() {
+ return mValues.getAsInteger(Calls.CACHED_NUMBER_TYPE);
}
- public long getDate() {
+ public Long getDate() {
return mValues.getAsLong(Calls.DATE);
}
- public long getDuration() {
+ public Long getDuration() {
return mValues.getAsLong(Calls.DURATION);
}
- public boolean getIsRead() {
+ public Boolean getIsRead() {
return mValues.getAsBoolean(Calls.IS_READ);
}
- public int getLimitParamKey() {
+ public Integer getLimitParamKey() {
return mValues.getAsInteger(Calls.LIMIT_PARAM_KEY);
}
- public boolean getNew() {
+ public Boolean getNew() {
return mValues.getAsBoolean(Calls.NEW);
}
@@ -154,15 +162,15 @@
return mValues.getAsString(Calls.NUMBER);
}
- public int getNumberPresentation() {
+ public Integer getNumberPresentation() {
return mValues.getAsInteger(Calls.NUMBER_PRESENTATION);
}
- public int getOffsetParamKey() {
+ public Integer getOffsetParamKey() {
return mValues.getAsInteger(Calls.OFFSET_PARAM_KEY);
}
- public int getType() {
+ public Integer getType() {
return mValues.getAsInteger(Calls.TYPE);
}
}
\ No newline at end of file
diff --git a/src/com/android/contacts/interactions/ContactInteractionUtil.java b/src/com/android/contacts/interactions/ContactInteractionUtil.java
index a8a66f3..98d45ee 100644
--- a/src/com/android/contacts/interactions/ContactInteractionUtil.java
+++ b/src/com/android/contacts/interactions/ContactInteractionUtil.java
@@ -26,7 +26,7 @@
import java.util.Calendar;
-import com.android.contacts.common.R;
+import com.android.contacts.R;
/**
diff --git a/src/com/android/contacts/interactions/SmsInteraction.java b/src/com/android/contacts/interactions/SmsInteraction.java
index c70356e..ac83786 100644
--- a/src/com/android/contacts/interactions/SmsInteraction.java
+++ b/src/com/android/contacts/interactions/SmsInteraction.java
@@ -41,12 +41,15 @@
@Override
public Intent getIntent() {
- return new Intent(Intent.ACTION_VIEW).setData(Uri.parse(URI_TARGET_PREFIX + getAddress()));
+ String address = getAddress();
+ return address == null ? null : new Intent(Intent.ACTION_VIEW).setData(
+ Uri.parse(URI_TARGET_PREFIX + address));
}
@Override
public long getInteractionDate() {
- return getDate();
+ Long date = getDate();
+ return date == null ? -1 : date;
}
@Override
@@ -61,7 +64,9 @@
@Override
public String getViewFooter(Context context) {
- return ContactInteractionUtil.formatDateStringFromTimestamp(getDate(), context);
+ Long date = getDate();
+ return date == null ? null : ContactInteractionUtil.formatDateStringFromTimestamp(
+ date, context);
}
@Override
@@ -87,40 +92,40 @@
return mValues.getAsString(Sms.BODY);
}
- public long getDate() {
+ public Long getDate() {
return mValues.getAsLong(Sms.DATE);
}
- public long getDateSent() {
+ public Long getDateSent() {
return mValues.getAsLong(Sms.DATE_SENT);
}
- public int getErrorCode() {
+ public Integer getErrorCode() {
return mValues.getAsInteger(Sms.ERROR_CODE);
}
- public boolean getLocked() {
+ public Boolean getLocked() {
return mValues.getAsBoolean(Sms.LOCKED);
}
- public int getPerson() {
+ public Integer getPerson() {
return mValues.getAsInteger(Sms.PERSON);
}
- public int getProtocol() {
+ public Integer getProtocol() {
return mValues.getAsInteger(Sms.PROTOCOL);
}
- public boolean getRead() {
+ public Boolean getRead() {
return mValues.getAsBoolean(Sms.READ);
}
- public boolean getReplyPathPresent() {
+ public Boolean getReplyPathPresent() {
return mValues.getAsBoolean(Sms.REPLY_PATH_PRESENT);
}
- public boolean getSeen() {
+ public Boolean getSeen() {
return mValues.getAsBoolean(Sms.SEEN);
}
@@ -128,7 +133,7 @@
return mValues.getAsString(Sms.SERVICE_CENTER);
}
- public int getStatus() {
+ public Integer getStatus() {
return mValues.getAsInteger(Sms.STATUS);
}
@@ -136,11 +141,11 @@
return mValues.getAsString(Sms.SUBJECT);
}
- public int getThreadId() {
+ public Integer getThreadId() {
return mValues.getAsInteger(Sms.THREAD_ID);
}
- public int getType() {
+ public Integer getType() {
return mValues.getAsInteger(Sms.TYPE);
}
}
diff --git a/src/com/android/contacts/quickcontact/Action.java b/src/com/android/contacts/quickcontact/Action.java
index fa23286..7d904ab 100644
--- a/src/com/android/contacts/quickcontact/Action.java
+++ b/src/com/android/contacts/quickcontact/Action.java
@@ -45,7 +45,10 @@
public Intent getAlternateIntent();
/** Checks if the contact data for this action is primary. */
- public Boolean isPrimary();
+ public boolean isPrimary();
+
+ /** Checks if the contact data for this action is super primary. */
+ public boolean isSuperPrimary();
/**
* Returns a lookup (@link Uri) for the contact data item or null if there is no data item
@@ -61,4 +64,14 @@
/** Returns the presence of this item or -1 if it was never set */
public int getPresence();
+
+ /**
+ * Returns the number of times this action has been used.
+ */
+ public Integer getTimesUsed();
+
+ /**
+ * Returns the last time this action was used.
+ */
+ public Long getLastTimeUsed();
}
diff --git a/src/com/android/contacts/quickcontact/DataAction.java b/src/com/android/contacts/quickcontact/DataAction.java
index e29b3ef..a0df1e6 100644
--- a/src/com/android/contacts/quickcontact/DataAction.java
+++ b/src/com/android/contacts/quickcontact/DataAction.java
@@ -29,9 +29,9 @@
import android.text.TextUtils;
import android.util.Log;
+import com.android.contacts.R;
import com.android.contacts.common.CallUtil;
import com.android.contacts.common.ContactsUtils;
-import com.android.contacts.R;
import com.android.contacts.common.MoreContactUtils;
import com.android.contacts.common.model.account.AccountType.EditType;
import com.android.contacts.common.model.dataitem.DataItem;
@@ -55,6 +55,8 @@
private final Context mContext;
private final DataKind mKind;
private final String mMimeType;
+ private final Integer mTimesUsed;
+ private final Long mLastTimeUsed;
private CharSequence mBody;
private CharSequence mSubtitle;
@@ -67,6 +69,7 @@
private Uri mDataUri;
private long mDataId;
private boolean mIsPrimary;
+ private boolean mIsSuperPrimary;
/**
* Create an action from common {@link Data} elements.
@@ -75,6 +78,8 @@
mContext = context;
mKind = kind;
mMimeType = item.getMimeType();
+ mTimesUsed = item.getTimesUsed();
+ mLastTimeUsed = item.getLastTimeUsed();
// Determine type for subtitle
mSubtitle = "";
@@ -96,7 +101,8 @@
}
}
- mIsPrimary = item.isSuperPrimary();
+ mIsPrimary = item.isPrimary();
+ mIsSuperPrimary = item.isSuperPrimary();
mBody = item.buildDataStringForDisplay(context, kind);
mDataId = item.getId();
@@ -265,11 +271,16 @@
}
@Override
- public Boolean isPrimary() {
+ public boolean isPrimary() {
return mIsPrimary;
}
@Override
+ public boolean isSuperPrimary() {
+ return mIsSuperPrimary;
+ }
+
+ @Override
public Drawable getAlternateIcon() {
if (mAlternateIconRes == 0) return null;
@@ -322,4 +333,14 @@
}
return true;
}
+
+ @Override
+ public Integer getTimesUsed() {
+ return mTimesUsed;
+ }
+
+ @Override
+ public Long getLastTimeUsed() {
+ return mLastTimeUsed;
+ }
}
diff --git a/src/com/android/contacts/quickcontact/ExpandingEntryCardView.java b/src/com/android/contacts/quickcontact/ExpandingEntryCardView.java
index 18703d1..209284f 100644
--- a/src/com/android/contacts/quickcontact/ExpandingEntryCardView.java
+++ b/src/com/android/contacts/quickcontact/ExpandingEntryCardView.java
@@ -23,6 +23,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
+import android.graphics.ColorFilter;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.AttributeSet;
@@ -122,7 +123,10 @@
private List<Entry> mEntries;
private List<View> mEntryViews;
private LinearLayout mEntriesViewGroup;
+ private final Drawable mCollapseArrowDrawable;
+ private final Drawable mExpandArrowDrawable;
private int mThemeColor;
+ private ColorFilter mThemeColorFilter;
private final OnClickListener mExpandCollapseButtonListener = new OnClickListener() {
@Override
@@ -146,6 +150,17 @@
mEntriesViewGroup = (LinearLayout)
expandingEntryCardView.findViewById(R.id.content_area_linear_layout);
mTitleTextView = (TextView) expandingEntryCardView.findViewById(R.id.title);
+ mCollapseArrowDrawable =
+ getResources().getDrawable(R.drawable.expanding_entry_card_collapse_white_24);
+ mExpandArrowDrawable =
+ getResources().getDrawable(R.drawable.expanding_entry_card_expand_white_24);
+
+ mExpandCollapseButton = inflater.inflate(
+ R.layout.quickcontact_expanding_entry_card_button, this, false);
+ mExpandCollapseTextView = (TextView) mExpandCollapseButton.findViewById(R.id.text);
+ mExpandCollapseButton.setOnClickListener(mExpandCollapseButtonListener);
+
+
}
/**
@@ -154,19 +169,21 @@
* @param entries The Entry list to display.
*/
public void initialize(List<Entry> entries, int numInitialVisibleEntries,
- boolean isExpanded, int themeColor) {
+ boolean isExpanded) {
LayoutInflater layoutInflater = LayoutInflater.from(getContext());
mIsExpanded = isExpanded;
mEntries = entries;
mEntryViews = new ArrayList<View>(entries.size());
mCollapsedEntriesCount = Math.min(numInitialVisibleEntries, entries.size());
- mThemeColor = themeColor;
- if (mExpandCollapseButton == null) {
- createExpandButton(layoutInflater);
+ if (mIsExpanded) {
+ updateExpandCollapseButton(getCollapseButtonText());
+ } else {
+ updateExpandCollapseButton(getExpandButtonText());
}
inflateViewsIfNeeded(layoutInflater);
insertEntriesIntoViewGroup();
+ applyColor();
}
/**
@@ -260,18 +277,6 @@
}
}
- private void createExpandButton(LayoutInflater layoutInflater) {
- mExpandCollapseButton = layoutInflater.inflate(
- R.layout.quickcontact_expanding_entry_card_button, this, false);
- mExpandCollapseTextView = (TextView) mExpandCollapseButton.findViewById(R.id.text);
- if (mIsExpanded) {
- updateExpandCollapseButton(getCollapseButtonText());
- } else {
- updateExpandCollapseButton(getExpandButtonText());
- }
- mExpandCollapseButton.setOnClickListener(mExpandCollapseButtonListener);
- }
-
/**
* Lazily inflate the number of views currently needed, and bind data from
* mEntries into these views.
@@ -283,6 +288,41 @@
}
}
+ public void setColorAndFilter(int color, ColorFilter colorFilter) {
+ mThemeColor = color;
+ mThemeColorFilter = colorFilter;
+ applyColor();
+ }
+
+ /**
+ * The ColorFilter is passed in along with the color so that a new one only needs to be created
+ * once for the entire activity.
+ * 1. Title
+ * 2. Entry icons
+ * 3. Expand/Collapse Text
+ * 4. Expand/Collapse Button
+ */
+ public void applyColor() {
+ if (mThemeColor != 0 && mThemeColorFilter != null) {
+ // Title
+ if (mTitleTextView != null) {
+ mTitleTextView.setTextColor(mThemeColor);
+ }
+
+ // Entry icons
+ if (mEntries != null) {
+ for (Entry entry : mEntries) {
+ entry.getIcon().setColorFilter(mThemeColorFilter);
+ }
+ }
+
+ // Expand/Collapse
+ mExpandCollapseTextView.setTextColor(mThemeColor);
+ mCollapseArrowDrawable.setColorFilter(mThemeColorFilter);
+ mExpandArrowDrawable.setColorFilter(mThemeColorFilter);
+ }
+ }
+
private View createEntryView(LayoutInflater layoutInflater, Entry entry) {
View view = layoutInflater.inflate(
R.layout.expanding_entry_card_item, this, false);
@@ -335,15 +375,12 @@
}
private void updateExpandCollapseButton(CharSequence buttonText) {
- int resId = mIsExpanded ? R.drawable.expanding_entry_card_collapse_white_24
- : R.drawable.expanding_entry_card_expand_white_24;
- // TODO: apply color theme to the drawable
- Drawable drawable = getResources().getDrawable(resId);
+ final Drawable arrow = mIsExpanded ? mCollapseArrowDrawable : mExpandArrowDrawable;
if (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
- mExpandCollapseTextView.setCompoundDrawablesWithIntrinsicBounds(null, null, drawable,
+ mExpandCollapseTextView.setCompoundDrawablesWithIntrinsicBounds(null, null, arrow,
null);
} else {
- mExpandCollapseTextView.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null,
+ mExpandCollapseTextView.setCompoundDrawablesWithIntrinsicBounds(arrow, null, null,
null);
}
mExpandCollapseTextView.setText(buttonText);
diff --git a/src/com/android/contacts/quickcontact/QuickContactActivity.java b/src/com/android/contacts/quickcontact/QuickContactActivity.java
index b186038..99d8094 100644
--- a/src/com/android/contacts/quickcontact/QuickContactActivity.java
+++ b/src/com/android/contacts/quickcontact/QuickContactActivity.java
@@ -31,6 +31,7 @@
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.Color;
+import android.graphics.ColorFilter;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
@@ -93,7 +94,6 @@
import com.android.contacts.util.SchedulingUtils;
import com.android.contacts.widget.MultiShrinkScroller;
import com.android.contacts.widget.MultiShrinkScroller.MultiShrinkScrollerListener;
-
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
@@ -123,11 +123,13 @@
private static final String TAG = "QuickContact";
+ private static final String KEY_THEME_COLOR = "theme_color";
+
private static final int ANIMATION_SLIDE_OPEN_DURATION = 250;
- private static final int ANIMATION_STATUS_BAR_COLOR_CHANGE_DURATION = 75;
+ private static final int ANIMATION_STATUS_BAR_COLOR_CHANGE_DURATION = 150;
private static final int REQUEST_CODE_CONTACT_EDITOR_ACTIVITY = 1;
private static final float SYSTEM_BAR_BRIGHTNESS_FACTOR = 0.7f;
- private static final int SHIM_COLOR = Color.argb(0x7F, 0, 0, 0);
+ private static final int SCRIM_COLOR = Color.argb(0xB2, 0, 0, 0);
/** This is the Intent action to install a shortcut in the launcher. */
private static final String ACTION_INSTALL_SHORTCUT =
@@ -149,18 +151,17 @@
private MultiShrinkScroller mScroller;
private SelectAccountDialogFragmentListener mSelectAccountFragmentListener;
private AsyncTask<Void, Void, Void> mEntriesAndActionsTask;
- private ColorDrawable mWindowShim;
+ private ColorDrawable mWindowScrim;
private boolean mIsWaitingForOtherPieceOfExitAnimation;
private boolean mIsExitAnimationInProgress;
+ private boolean mHasComputedThemeColor;
private static final int MIN_NUM_COMMUNICATION_ENTRIES_SHOWN = 3;
private static final int MIN_NUM_COLLAPSED_RECENT_ENTRIES_SHOWN = 3;
private Contact mContactData;
private ContactLoader mContactLoader;
-
private PorterDuffColorFilter mColorFilter;
- List<Drawable> mDrawablesToTint;
private final ImageViewDrawableSetter mPhotoSetter = new ImageViewDrawableSetter();
@@ -295,7 +296,7 @@
public void onStartScrollOffBottom() {
// Remove the window shim now that we are starting an Activity exit animation.
final int duration = getResources().getInteger(android.R.integer.config_shortAnimTime);
- final ObjectAnimator animator = ObjectAnimator.ofInt(mWindowShim, "alpha", 0xFF, 0);
+ final ObjectAnimator animator = ObjectAnimator.ofInt(mWindowScrim, "alpha", 0xFF, 0);
animator.addListener(mExitWindowShimAnimationListener);
animator.setDuration(duration).start();
mIsWaitingForOtherPieceOfExitAnimation = true;
@@ -357,15 +358,18 @@
final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setActionBar(toolbar);
- setHeaderNameText(R.string.missing_name);
+ getActionBar().setTitle(null);
+ // Put a TextView with a known resource id into the ActionBar. This allows us to easily
+ // find the correct TextView location & size later.
+ toolbar.addView(getLayoutInflater().inflate(R.layout.quickcontact_title_placeholder, null));
mHasAlreadyBeenOpened = savedInstanceState != null;
- mWindowShim = new ColorDrawable(SHIM_COLOR);
- getWindow().setBackgroundDrawable(mWindowShim);
+ mWindowScrim = new ColorDrawable(SCRIM_COLOR);
+ getWindow().setBackgroundDrawable(mWindowScrim);
if (!mHasAlreadyBeenOpened) {
final int duration = getResources().getInteger(android.R.integer.config_shortAnimTime);
- ObjectAnimator.ofInt(mWindowShim, "alpha", 0, 0xFF).setDuration(duration).start();
+ ObjectAnimator.ofInt(mWindowScrim, "alpha", 0, 0xFF).setDuration(duration).start();
}
if (mScroller != null) {
@@ -380,7 +384,8 @@
}
}
- mDrawablesToTint = new ArrayList<>();
+ setHeaderNameText(R.string.missing_name);
+
mSelectAccountFragmentListener= (SelectAccountDialogFragmentListener) getFragmentManager()
.findFragmentByTag(FRAGMENT_TAG_SELECT_ACCOUNT);
if (mSelectAccountFragmentListener == null) {
@@ -391,6 +396,21 @@
}
mSelectAccountFragmentListener.setQuickContactActivity(this);
+ if (savedInstanceState != null) {
+ final int color = savedInstanceState.getInt(KEY_THEME_COLOR, 0);
+ if (color != 0) {
+ // Wait for pre draw. Setting the header tint before the MultiShrinkScroller has
+ // been measured will cause incorrect tinting calculations.
+ SchedulingUtils.doOnPreDraw(mScroller, /* drawNextFrame = */ true,
+ new Runnable() {
+ @Override
+ public void run() {
+ setThemeColor(color);
+ }
+ });
+ }
+ }
+
Trace.endSection();
}
@@ -407,9 +427,18 @@
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
mHasAlreadyBeenOpened = true;
+ mHasComputedThemeColor = false;
processIntent(intent);
}
+ @Override
+ public void onSaveInstanceState(Bundle savedInstanceState) {
+ super.onSaveInstanceState(savedInstanceState);
+ if (mColorFilter != null) {
+ savedInstanceState.putInt(KEY_THEME_COLOR, mColorFilter.getColor());
+ }
+ }
+
private void processIntent(Intent intent) {
Uri lookupUri = intent.getData();
@@ -454,13 +483,17 @@
/** Assign this string to the view if it is not empty. */
private void setHeaderNameText(int resId) {
- getActionBar().setTitle(getText(resId));
+ if (mScroller != null) {
+ mScroller.setTitle(String.valueOf(getText(resId)));
+ }
}
/** Assign this string to the view if it is not empty. */
private void setHeaderNameText(CharSequence value) {
if (!TextUtils.isEmpty(value)) {
- getActionBar().setTitle(value);
+ if (mScroller != null) {
+ mScroller.setTitle(value.toString());
+ }
}
}
@@ -497,7 +530,6 @@
Trace.endSection();
- final List<String> sortedActionMimeTypes = Lists.newArrayList();
// Maintain a list of phone numbers to pass into SmsInteractionsLoader
final Set<String> phoneNumbers = new HashSet<>();
// Maintain a list of email addresses to pass into CalendarInteractionsLoader
@@ -508,8 +540,7 @@
mEntriesAndActionsTask = new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
- computeEntriesAndActions(data, phoneNumbers, emailAddresses,
- sortedActionMimeTypes, entries);
+ computeEntriesAndActions(data, phoneNumbers, emailAddresses, entries);
return null;
}
@@ -520,8 +551,7 @@
// is still running before binding to UI. A new intent could invalidate
// the results, for example.
if (data == mContactData && !isCancelled()) {
- bindEntriesAndActions(entries, phoneNumbers, emailAddresses,
- sortedActionMimeTypes);
+ bindEntriesAndActions(entries, phoneNumbers, emailAddresses);
showActivity();
}
}
@@ -531,8 +561,7 @@
private void bindEntriesAndActions(List<Entry> entries,
Set<String> phoneNumbers,
- Set<String> emailAddresses,
- List<String> sortedActionMimeTypes) {
+ Set<String> emailAddresses) {
Trace.beginSection("start sms loader");
final Bundle phonesExtraBundle = new Bundle();
phonesExtraBundle.putStringArray(KEY_LOADER_EXTRA_PHONES,
@@ -564,11 +593,10 @@
if (entries.size() > 0) {
mCommunicationCard.initialize(entries,
/* numInitialVisibleEntries = */ MIN_NUM_COMMUNICATION_ENTRIES_SHOWN,
- /* isExpanded = */ false,
- /* themeColor = */ 0);
+ /* isExpanded = */ false);
}
- final boolean hasData = !sortedActionMimeTypes.isEmpty();
+ final boolean hasData = !entries.isEmpty();
mCommunicationCard.setVisibility(hasData ? View.VISIBLE : View.GONE);
Trace.endSection();
@@ -588,7 +616,7 @@
}
private void computeEntriesAndActions(Contact data, Set<String> phoneNumbers,
- Set<String> emailAddresses, List<String> sortedActionMimeTypes, List<Entry> entries) {
+ Set<String> emailAddresses, List<Entry> entries) {
Trace.beginSection("inflate entries and actions");
final ResolveCache cache = ResolveCache.getInstance(this);
@@ -655,35 +683,85 @@
Trace.endSection();
Trace.beginSection("sort mimetypes");
- // All the mime-types to add.
- final Set<String> containedTypes = new HashSet<String>(mActions.keySet());
- // First, add LEADING_MIMETYPES, which are most common.
- for (String mimeType : LEADING_MIMETYPES) {
- if (containedTypes.contains(mimeType)) {
- sortedActionMimeTypes.add(mimeType);
- containedTypes.remove(mimeType);
- entries.addAll(actionsToEntries(mActions.get(mimeType)));
- }
+ /*
+ * Sorting is a multi part step. The end result is to a have a sorted list of the most
+ * used actions, one per mimetype. Then, within each mimetype, the list of actions for that
+ * type is also sorted, based off of {super primary, primary, times used} in that order.
+ */
+ final List<Action> topActions = new ArrayList<>();
+ for (List<Action> mimeTypeActions : mActions.values()) {
+ Collections.sort(mimeTypeActions, new Comparator<Action>() {
+ @Override
+ public int compare(Action lhs, Action rhs) {
+ /*
+ * Actions are compared to the same mimetype based off of three qualities:
+ * 1. Super primary
+ * 2. Primary
+ * 3. Times used
+ */
+ if (lhs.isSuperPrimary()) {
+ return -1;
+ } else if (rhs.isSuperPrimary()) {
+ return 1;
+ } else if (lhs.isPrimary() && !rhs.isPrimary()) {
+ return -1;
+ } else if (!lhs.isPrimary() && rhs.isPrimary()) {
+ return 1;
+ } else {
+ int lhsTimesUsed = lhs.getTimesUsed() == null ? 0 : lhs.getTimesUsed();
+ int rhsTimesUsed = rhs.getTimesUsed() == null ? 0 : rhs.getTimesUsed();
+
+ return rhsTimesUsed - lhsTimesUsed;
+ }
+ }
+ });
+ topActions.add(mimeTypeActions.get(0));
}
- // Add all the remaining ones that are not TRAILING
- for (String mimeType : containedTypes.toArray(new String[containedTypes.size()])) {
- if (!TRAILING_MIMETYPES.contains(mimeType)) {
- sortedActionMimeTypes.add(mimeType);
- containedTypes.remove(mimeType);
- entries.addAll(actionsToEntries(mActions.get(mimeType)));
- }
- }
+ // topActions now contains the top action for each mimetype. This list now needs to be
+ // sorted, based off of {times used, last used, statically defined} in that order.
+ Collections.sort(topActions, new Comparator<Action>() {
+ @Override
+ public int compare(Action lhs, Action rhs) {
+ int lhsTimesUsed = lhs.getTimesUsed() == null ? 0 : lhs.getTimesUsed();
+ int rhsTimesUsed = rhs.getTimesUsed() == null ? 0 : rhs.getTimesUsed();
+ int timesUsedDifference = rhsTimesUsed - lhsTimesUsed;
+ if (timesUsedDifference != 0) {
+ return timesUsedDifference;
+ }
- // Then, add TRAILING_MIMETYPES, which are least common.
- for (String mimeType : TRAILING_MIMETYPES) {
- if (containedTypes.contains(mimeType)) {
- containedTypes.remove(mimeType);
- sortedActionMimeTypes.add(mimeType);
- entries.addAll(actionsToEntries(mActions.get(mimeType)));
- }
- }
+ long lhsLastTimeUsed = lhs.getLastTimeUsed() == null ? 0 : lhs.getLastTimeUsed();
+ long rhsLastTimeUsed = rhs.getLastTimeUsed() == null ? 0 : rhs.getLastTimeUsed();
+ long lastTimeUsedDifference = rhsLastTimeUsed - lhsLastTimeUsed;
+ if (lastTimeUsedDifference > 0) {
+ return 1;
+ } else if (lastTimeUsedDifference < 0) {
+ return -1;
+ }
+ // Times used and last time used are the same. Resort to statically defined.
+ String lhsMimeType = lhs.getMimeType();
+ String rhsMimeType = rhs.getMimeType();
+ for (String mimeType : LEADING_MIMETYPES) {
+ if (lhsMimeType.equals(mimeType)) {
+ return -1;
+ } else if (rhsMimeType.equals(mimeType)) {
+ return 1;
+ }
+ }
+ // Trailing types come last, so flip the returns
+ for (String mimeType : TRAILING_MIMETYPES) {
+ if (lhsMimeType.equals(mimeType)) {
+ return 1;
+ } else if (rhsMimeType.equals(mimeType)) {
+ return -1;
+ }
+ }
+ return 0;
+ }
+ });
+
+ entries.addAll(actionsToEntries(topActions));
Trace.endSection();
}
@@ -705,24 +783,7 @@
return colorFromBitmap(bitmap);
}
if (imageViewDrawable instanceof LetterTileDrawable) {
- // LetterTileDrawable doesn't normally draw unless it is visible. Therefore,
- // we need to directly ask it for its color via getColor(). We could directly
- // return this color. However, in the future Palette#generate() may incorporate
- // saturation boosting. So I want to use Palette#generate() for the sake of
- // consistency.
- final LetterTileDrawable tileDrawable = (LetterTileDrawable) imageViewDrawable;
- final int PALETTE_BITMAP_SIZE = 1;
- final Bitmap bitmap = Bitmap.createBitmap(PALETTE_BITMAP_SIZE,
- PALETTE_BITMAP_SIZE, Bitmap.Config.ARGB_8888);
- // If Palette can not extract a primary color, our UX person says we are better
- // off using the LetterTileDrawable's non vibrant color than falling back
- // to the app's default color.
- final int color = colorFromBitmap(bitmap);
- if (color == 0) {
- return tileDrawable.getColor();
- } else {
- return color;
- }
+ return ((LetterTileDrawable) imageViewDrawable).getColor();
}
return 0;
}
@@ -730,31 +791,46 @@
@Override
protected void onPostExecute(Integer color) {
super.onPostExecute(color);
- mColorFilter = new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP);
- // Make sure the color is valid. Also check that the Photo has not changed. If it
- // has changed, the new tint color needs to be extracted
- if (color != 0 && imageViewDrawable == mPhotoView.getDrawable()) {
- // TODO: animate from the previous tint.
- mScroller.setHeaderTintColor(color);
-
- // Create a darker version of the actionbar color. HSV is device dependent
- // and not perceptually-linear. Therefore, we can't say mStatusBarColor is
- // 70% as bright as the action bar color. We can only say: it is a bit darker.
- final float hsvComponents[] = new float[3];
- Color.colorToHSV(color, hsvComponents);
- hsvComponents[2] *= SYSTEM_BAR_BRIGHTNESS_FACTOR;
- mStatusBarColor = Color.HSVToColor(hsvComponents);
-
- updateStatusBarColor();
- for (Drawable drawable : mDrawablesToTint) {
- applyThemeColorIfAvailable(drawable);
- }
- mDrawablesToTint.clear();
+ if (mHasComputedThemeColor) {
+ // If we had previously computed a theme color from the contact photo,
+ // then do not update the theme color. Changing the theme color several
+ // seconds after QC has started, as a result of an updated/upgraded photo,
+ // is a jarring experience. On the other hand, changing the theme color after
+ // a rotation or onNewIntent() is perfectly fine.
+ return;
+ }
+ // Check that the Photo has not changed. If it has changed, the new tint
+ // color needs to be extracted
+ if (imageViewDrawable == mPhotoView.getDrawable()) {
+ mHasComputedThemeColor = true;
+ setThemeColor(color);
}
}
}.execute();
}
+ private void setThemeColor(int color) {
+ // If the color is invalid, use the predefined default
+ if (color == 0) {
+ color = getResources().getColor(R.color.actionbar_background_color);
+ }
+ mScroller.setHeaderTintColor(color);
+
+ // Create a darker version of the actionbar color. HSV is device dependent
+ // and not perceptually-linear. Therefore, we can't say mStatusBarColor is
+ // 70% as bright as the action bar color. We can only say: it is a bit darker.
+ final float hsvComponents[] = new float[3];
+ Color.colorToHSV(color, hsvComponents);
+ hsvComponents[2] *= SYSTEM_BAR_BRIGHTNESS_FACTOR;
+ mStatusBarColor = Color.HSVToColor(hsvComponents);
+ updateStatusBarColor();
+
+ mColorFilter =
+ new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP);
+ mCommunicationCard.setColorAndFilter(color, mColorFilter);
+ mRecentCard.setColorAndFilter(color, mColorFilter);
+ }
+
private void updateStatusBarColor() {
if (mScroller == null) {
return;
@@ -819,20 +895,17 @@
case Phone.CONTENT_ITEM_TYPE:
header = String.valueOf(action.getBody());
footer = String.valueOf(action.getSubtitle());
- icon = applyThemeColorIfAvailable(
- getResources().getDrawable(R.drawable.ic_phone_24dp));
+ icon = getResources().getDrawable(R.drawable.ic_phone_24dp);
break;
case Email.CONTENT_ITEM_TYPE:
header = String.valueOf(action.getBody());
footer = String.valueOf(action.getSubtitle());
- icon = applyThemeColorIfAvailable(
- getResources().getDrawable(R.drawable.ic_email_24dp));
+ icon = getResources().getDrawable(R.drawable.ic_email_24dp);
break;
case StructuredPostal.CONTENT_ITEM_TYPE:
header = String.valueOf(action.getBody());
footer = String.valueOf(action.getSubtitle());
- icon = applyThemeColorIfAvailable(
- getResources().getDrawable(R.drawable.ic_place_24dp));
+ icon = getResources().getDrawable(R.drawable.ic_place_24dp);
break;
default:
header = String.valueOf(action.getSubtitle());
@@ -844,8 +917,7 @@
// Add SMS in addition to phone calls
if (action.getMimeType().equals(Phone.CONTENT_ITEM_TYPE)) {
- entries.add(new Entry(applyThemeColorIfAvailable(getResources().getDrawable(
- R.drawable.ic_message_24dp)),
+ entries.add(new Entry(getResources().getDrawable(R.drawable.ic_message_24dp),
getResources().getString(R.string.send_message), null, header,
action.getAlternateIntent(), /* isEditable = */ false));
}
@@ -856,7 +928,7 @@
private List<Entry> contactInteractionsToEntries(List<ContactInteraction> interactions) {
List<Entry> entries = new ArrayList<>();
for (ContactInteraction interaction : interactions) {
- entries.add(new Entry(applyThemeColorIfAvailable(interaction.getIcon(this)),
+ entries.add(new Entry(interaction.getIcon(this),
interaction.getViewHeader(this),
interaction.getViewBody(this),
interaction.getBodyIcon(this),
@@ -911,7 +983,7 @@
// contact is invisible. If it is, we need to display an "Add to Contacts" MenuItem.
return new ContactLoader(getApplicationContext(), mLookupUri,
true /*loadGroupMetaData*/, false /*loadInvitableAccountTypes*/,
- false /*postViewNotification*/, true /*computeFormattedPhoneNumber*/);
+ true /*postViewNotification*/, true /*computeFormattedPhoneNumber*/);
}
};
@@ -1012,8 +1084,7 @@
if (allInteractions.size() > 0) {
mRecentCard.initialize(contactInteractionsToEntries(allInteractions),
/* numInitialVisibleEntries = */ MIN_NUM_COLLAPSED_RECENT_ENTRIES_SHOWN,
- /* isExpanded = */ false,
- /* themeColor = */ 0);
+ /* isExpanded = */ false);
mRecentCard.setVisibility(View.VISIBLE);
}
}
@@ -1032,20 +1103,6 @@
}
/**
- * Applies the theme color as extracted in
- * {@link #extractAndApplyTintFromPhotoViewAsynchronously()} if available. If the color is not
- * available, store a reference to the drawable to tint when a color becomes available.
- */
- private Drawable applyThemeColorIfAvailable(Drawable drawable) {
- if (mColorFilter != null) {
- drawable.setColorFilter(mColorFilter);
- } else {
- mDrawablesToTint.add(drawable);
- }
- return drawable;
- }
-
- /**
* Returns true if it is possible to edit the current contact.
*/
private boolean isContactEditable() {
diff --git a/src/com/android/contacts/widget/MultiShrinkScroller.java b/src/com/android/contacts/widget/MultiShrinkScroller.java
index b38f5cf..3666420 100644
--- a/src/com/android/contacts/widget/MultiShrinkScroller.java
+++ b/src/com/android/contacts/widget/MultiShrinkScroller.java
@@ -11,10 +11,14 @@
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffColorFilter;
+import android.graphics.Color;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Point;
+import android.graphics.Rect;
import android.hardware.display.DisplayManagerGlobal;
import android.util.AttributeSet;
+import android.util.TypedValue;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.MotionEvent;
@@ -22,12 +26,14 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewConfiguration;
+import android.view.WindowManager;
import android.view.animation.Interpolator;
import android.widget.EdgeEffect;
-import android.widget.ImageView;
+import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.Scroller;
import android.widget.ScrollView;
+import android.widget.TextView;
/**
* A custom {@link ViewGroup} that operates similarly to a {@link ScrollView}, except with multiple
@@ -54,6 +60,11 @@
*/
private static final int EXIT_FLING_ANIMATION_DURATION_MS = 300;
+ /**
+ * In portrait mode, the height:width ratio of the photo's starting height.
+ */
+ private static final float INTERMEDIATE_HEADER_HEIGHT_RATIO = 0.5f;
+
private float[] mLastEventPosition = { 0, 0 };
private VelocityTracker mVelocityTracker;
private boolean mIsBeingDragged = false;
@@ -62,24 +73,49 @@
private ScrollView mScrollView;
private View mScrollViewChild;
private View mToolbar;
- private ImageView mPhotoView;
+ private QuickContactImageView mPhotoView;
private View mPhotoViewContainer;
private View mTransparentView;
private MultiShrinkScrollerListener mListener;
+ private TextView mLargeTextView;
+ /** Contains desired location/size of the title, once the header is fully compressed */
+ private TextView mInvisiblePlaceholderTextView;
private int mHeaderTintColor;
private int mMaximumHeaderHeight;
+ private int mMinimumHeaderHeight;
+ private int mIntermediateHeaderHeight;
+ private int mMaximumHeaderTextSize;
private final Scroller mScroller;
private final EdgeEffect mEdgeGlowBottom;
private final int mTouchSlop;
private final int mMaximumVelocity;
private final int mMinimumVelocity;
- private final int mIntermediateHeaderHeight;
- private final int mMinimumHeaderHeight;
private final int mTransparentStartHeight;
private final float mToolbarElevation;
- private final PorterDuffColorFilter mColorFilter
- = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_ATOP);
+ private final boolean mIsTwoPanel;
+ final Rect mLargeTextViewRect = new Rect();
+ final Rect mInvisiblePlaceholderTextViewRect = new Rect();
+
+ // Objects used to perform color filtering on the header. These are stored as fields for
+ // the sole purpose of avoiding "new" operations inside animation loops.
+ private final ColorMatrix mWhitenessColorMatrix = new ColorMatrix();
+ private final ColorMatrixColorFilter mColorFilter = new ColorMatrixColorFilter(
+ mWhitenessColorMatrix);
+ private final ColorMatrix mColorMatrix = new ColorMatrix();
+ private final float[] mAlphaMatrixValues = {
+ 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0
+ };
+ private final ColorMatrix mMultiplyBlendMatrix = new ColorMatrix();
+ private final float[] mMultiplyBlendMatrixValues = {
+ 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0
+ };
public interface MultiShrinkScrollerListener {
void onScrolledOffBottom();
@@ -148,14 +184,11 @@
mTouchSlop = configuration.getScaledTouchSlop();
mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
- mIntermediateHeaderHeight = (int) getResources().getDimension(
- R.dimen.quickcontact_starting_header_height);
mTransparentStartHeight = (int) getResources().getDimension(
R.dimen.quickcontact_starting_empty_height);
- mHeaderTintColor = mContext.getResources().getColor(
- R.color.actionbar_background_color);
mToolbarElevation = mContext.getResources().getDimension(
R.dimen.quick_contact_toolbar_elevation);
+ mIsTwoPanel = mContext.getResources().getBoolean(R.bool.quickcontact_two_panel);
final TypedArray attributeArray = context.obtainStyledAttributes(
new int[]{android.R.attr.actionBarSize});
@@ -172,10 +205,11 @@
mToolbar = findViewById(R.id.toolbar_parent);
mPhotoViewContainer = findViewById(R.id.toolbar_parent);
mTransparentView = findViewById(R.id.transparent_view);
+ mLargeTextView = (TextView) findViewById(R.id.large_title);
+ mInvisiblePlaceholderTextView = (TextView) findViewById(R.id.placeholder_textview);
mListener = listener;
- mPhotoView = (ImageView) findViewById(R.id.photo);
- setHeaderHeight(mIntermediateHeaderHeight);
+ mPhotoView = (QuickContactImageView) findViewById(R.id.photo);
mPhotoView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
@@ -183,15 +217,49 @@
}
});
- SchedulingUtils.doOnPreDraw(this, /* drawNextFrame = */ true, new Runnable() {
+ final WindowManager windowManager = (WindowManager) getContext().getSystemService(
+ Context.WINDOW_SERVICE);
+ final Point windowSize = new Point();
+ windowManager.getDefaultDisplay().getSize(windowSize);
+ if (!mIsTwoPanel) {
+ // We never want the height of the photo view to exceed its width.
+ mMaximumHeaderHeight = windowSize.x;
+ mIntermediateHeaderHeight = (int) (mMaximumHeaderHeight
+ * INTERMEDIATE_HEADER_HEIGHT_RATIO);
+ }
+ setHeaderHeight(mIntermediateHeaderHeight);
+
+ SchedulingUtils.doOnPreDraw(this, /* drawNextFrame = */ false, new Runnable() {
@Override
public void run() {
- // We never want the height of the photo view to exceed its width.
- mMaximumHeaderHeight = mToolbar.getWidth();
+ mMaximumHeaderTextSize = mLargeTextView.getHeight();
+ // Unlike Window width, we can't know the usable window height until predraw
+ // has occured. Therefore, setting these constraints must be done inside
+ // onPreDraw for the two panel layout. Fortunately, the two panel layout
+ // doesn't need these values anywhere else inside the activity's creation.
+ if (mIsTwoPanel) {
+ mMaximumHeaderHeight = getHeight();
+ mMinimumHeaderHeight = mMaximumHeaderHeight;
+ mIntermediateHeaderHeight = mMaximumHeaderHeight;
+ final TypedValue photoRatio = new TypedValue();
+ getResources().getValue(R.vals.quickcontact_photo_ratio, photoRatio,
+ /* resolveRefs = */ true);
+ final LayoutParams layoutParams
+ = (LayoutParams) mPhotoViewContainer.getLayoutParams();
+ layoutParams.height = mMaximumHeaderHeight;
+ layoutParams.width = (int) (mMaximumHeaderHeight * photoRatio.getFloat());
+ mPhotoViewContainer.setLayoutParams(layoutParams);
+ }
+
+ updateHeaderTextSize();
}
});
}
+ public void setTitle(String title) {
+ mLargeTextView.setText(title);
+ }
+
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
// The only time we want to intercept touch events is when we are being dragged.
@@ -259,14 +327,13 @@
mReceivedDown = false;
if (mIsBeingDragged) {
- final int heightScrollViewChild = mScrollViewChild.getHeight();
- final int pulledToY = mScrollView.getScrollY() + (int) delta;
- if (pulledToY > heightScrollViewChild - mScrollView.getHeight()
- && mToolbar.getHeight() == mMinimumHeaderHeight) {
+ final int distanceFromMaxScrolling = getMaximumScrollUpwards() - getScroll();
+ if (delta > distanceFromMaxScrolling) {
// The ScrollView is being pulled upwards while there is no more
// content offscreen, and the view port is already fully expanded.
mEdgeGlowBottom.onPull(delta / getHeight(), 1 - event.getX() / getWidth());
}
+
if (!mEdgeGlowBottom.isFinished()) {
postInvalidateOnAnimation();
}
@@ -287,6 +354,9 @@
public void setHeaderTintColor(int color) {
mHeaderTintColor = color;
updatePhotoTintAndDropShadow();
+ // We want to use the same amount of alpha on the new tint color as the previous tint color.
+ final int edgeEffectAlpha = Color.alpha(mEdgeGlowBottom.getColor());
+ mEdgeGlowBottom.setColor((color & 0xffffff) | Color.argb(edgeEffectAlpha, 0, 0, 0));
}
/**
@@ -401,6 +471,7 @@
scrollDown(delta);
}
updatePhotoTintAndDropShadow();
+ updateHeaderTextSize();
final boolean isFullscreen = getScrollNeededToBeFullScreen() <= 0;
if (mListener != null) {
if (wasFullscreen && !isFullscreen) {
@@ -421,6 +492,7 @@
toolbarLayoutParams.height = height;
mToolbar.setLayoutParams(toolbarLayoutParams);
updatePhotoTintAndDropShadow();
+ updateHeaderTextSize();
}
@NeededForReflection
@@ -512,7 +584,12 @@
height + getMaximumScrollUpwards() - getScroll());
canvas.rotate(180, width, 0);
- mEdgeGlowBottom.setSize(width, height);
+ if (mIsTwoPanel) {
+ // Only show the EdgeEffect on the bottom of the ScrollView.
+ mEdgeGlowBottom.setSize(mScrollView.getWidth(), height);
+ } else {
+ mEdgeGlowBottom.setSize(width, height);
+ }
if (mEdgeGlowBottom.draw(canvas)) {
postInvalidateOnAnimation();
}
@@ -537,11 +614,18 @@
}
private int getMaximumScrollUpwards() {
- return mTransparentStartHeight
- // How much the Header view can compress
- + mIntermediateHeaderHeight - mMinimumHeaderHeight
- // How much the ScrollView can scroll. 0, if child is smaller than ScrollView.
- + Math.max(0, mScrollViewChild.getHeight() - getHeight() + mMinimumHeaderHeight);
+ if (!mIsTwoPanel) {
+ return mTransparentStartHeight
+ // How much the Header view can compress
+ + mIntermediateHeaderHeight - mMinimumHeaderHeight
+ // How much the ScrollView can scroll. 0, if child is smaller than ScrollView.
+ + Math.max(0, mScrollViewChild.getHeight() - getHeight()
+ + mMinimumHeaderHeight);
+ } else {
+ return mTransparentStartHeight
+ // How much the ScrollView can scroll. 0, if child is smaller than ScrollView.
+ + Math.max(0, mScrollViewChild.getHeight() - getHeight());
+ }
}
private int getTransparentViewHeight() {
@@ -604,27 +688,167 @@
}
}
+ /**
+ * Set the header size and padding, based on the current scroll position.
+ */
+ private void updateHeaderTextSize() {
+ if (mIsTwoPanel) {
+ // The text size stays constant on two panel layouts.
+ return;
+ }
+
+ // The pivot point for scaling should be middle of the starting side.
+ if (isLayoutRtl()) {
+ mLargeTextView.setPivotX(mLargeTextView.getWidth());
+ } else {
+ mLargeTextView.setPivotX(0);
+ }
+ mLargeTextView.setPivotY(mLargeTextView.getHeight() / 2);
+
+ final int START_TEXT_SCALING_THRESHOLD_COEFFICIENT = 2;
+ final int threshold = START_TEXT_SCALING_THRESHOLD_COEFFICIENT * mMinimumHeaderHeight;
+ final int toolbarHeight = mToolbar.getLayoutParams().height;
+ if (toolbarHeight >= threshold) {
+ // Keep the text at maximum size since the header is smaller than threshold.
+ mLargeTextView.setScaleX(1);
+ mLargeTextView.setScaleY(1);
+ configureLargeTitlePadding();
+ return;
+ }
+ final float ratio = (toolbarHeight - mMinimumHeaderHeight)
+ / (float)(threshold - mMinimumHeaderHeight);
+ final float minimumSize = mInvisiblePlaceholderTextView.getHeight();
+ final float scale = (minimumSize + (mMaximumHeaderTextSize - minimumSize) * ratio)
+ / mMaximumHeaderTextSize;
+
+ mLargeTextView.setScaleX(scale);
+ mLargeTextView.setScaleY(scale);
+ configureLargeTitlePadding();
+ }
+
+ /**
+ * Configure the padding around mLargeTextView so that it will look appropriate once it
+ * finishes moving into its target location/size.
+ */
+ private void configureLargeTitlePadding() {
+ mToolbar.getBoundsOnScreen(mLargeTextViewRect);
+ mInvisiblePlaceholderTextView.getBoundsOnScreen(mInvisiblePlaceholderTextViewRect);
+ final int neededPaddingStart;
+ if (isLayoutRtl()) {
+ neededPaddingStart = mInvisiblePlaceholderTextViewRect.right - mLargeTextViewRect.right;
+ } else {
+ neededPaddingStart = mInvisiblePlaceholderTextViewRect.left - mLargeTextViewRect.left;
+ }
+
+ // Distance between top of toolbar to the center of the target rectangle.
+ final int desiredTopToCenter = (
+ mInvisiblePlaceholderTextViewRect.top + mInvisiblePlaceholderTextViewRect.bottom)
+ / 2 - mLargeTextViewRect.top;
+ // Additional padding needed on the mLargeTextView so that it has the same amount of
+ // padding as the target rectangle.
+ final int additionalBottomPaddingNeeded = desiredTopToCenter - mLargeTextView.getHeight()
+ / 2;
+
+ final FrameLayout.LayoutParams layoutParams
+ = (FrameLayout.LayoutParams) mLargeTextView.getLayoutParams();
+ layoutParams.bottomMargin = additionalBottomPaddingNeeded;
+ layoutParams.setMarginStart(neededPaddingStart);
+ mLargeTextView.setLayoutParams(layoutParams);
+ }
+
private void updatePhotoTintAndDropShadow() {
// We need to use toolbarLayoutParams to determine the height, since the layout
// params can be updated before the height change is reflected inside the View#getHeight().
final int toolbarHeight = mToolbar.getLayoutParams().height;
+
+ if (toolbarHeight <= mMinimumHeaderHeight && !mIsTwoPanel) {
+ mPhotoViewContainer.setElevation(mToolbarElevation);
+ } else {
+ mPhotoViewContainer.setElevation(0);
+ }
+
// Reuse an existing mColorFilter (to avoid GC pauses) to change the photo's tint.
mPhotoView.clearColorFilter();
- if (toolbarHeight >= mMaximumHeaderHeight) {
- mPhotoViewContainer.setElevation(0);
- return;
+
+ final float ratio;
+ final float intermediateRatio;
+ if (!mIsTwoPanel) {
+ // Ratio of current size to maximum size of the header.
+ ratio = (toolbarHeight - mMinimumHeaderHeight)
+ / (float) (mMaximumHeaderHeight - mMinimumHeaderHeight) ;
+ // The value that "ratio" will have when the header is at its
+ // starting/intermediate size.
+ intermediateRatio = (mIntermediateHeaderHeight - mMinimumHeaderHeight)
+ / (float) (mMaximumHeaderHeight - mMinimumHeaderHeight);
+ } else {
+ // Set ratio and intermediateRatio to the same arbitrary value, so that
+ // the math below considers us to be in the intermediate position. The specific
+ // values are not very important.
+ ratio = 0.5f;
+ intermediateRatio = 0.5f;
}
- if (toolbarHeight <= mMinimumHeaderHeight) {
- mColorFilter.setColor(mHeaderTintColor);
- mPhotoView.setColorFilter(mColorFilter);
- mPhotoViewContainer.setElevation(mToolbarElevation);
+
+ final float linearBeforeMiddle = Math.max(1 - (1 - ratio) / intermediateRatio, 0);
+
+ // Want a function with a derivative of 0 at x=0. I don't want it to grow too
+ // slowly before x=0.5. x^1.1 satisfies both requirements.
+ final float EXPONENT_ALMOST_ONE = 1.1f;
+ final float semiLinearBeforeMiddle = (float) Math.pow(linearBeforeMiddle,
+ EXPONENT_ALMOST_ONE);
+ mColorMatrix.reset();
+ mColorMatrix.setSaturation(semiLinearBeforeMiddle);
+ mColorMatrix.postConcat(alphaMatrix(ratio, Color.WHITE));
+
+ final float colorAlpha;
+ if (mPhotoView.isBasedOffLetterTile()) {
+ // Since the letter tile only has white and grey, tint it more slowly. Otherwise
+ // it will be completely invisible before we reach the intermediate point.
+ final float SLOWING_FACTOR = 1.6f;
+ float linearBeforeMiddleish = Math.max(1 - (1 - ratio) / intermediateRatio
+ / SLOWING_FACTOR, 0);
+ colorAlpha = 1 - (float) Math.pow(linearBeforeMiddleish, EXPONENT_ALMOST_ONE);
+ mColorMatrix.postConcat(alphaMatrix(colorAlpha, mHeaderTintColor));
+ } else {
+ colorAlpha = 1 - semiLinearBeforeMiddle;
+ mColorMatrix.postConcat(multiplyBlendMatrix(mHeaderTintColor, colorAlpha));
}
- mPhotoViewContainer.setElevation(0);
- final int alphaBits = 0xff - 0xff * (toolbarHeight - mMinimumHeaderHeight)
- / (mMaximumHeaderHeight - mMinimumHeaderHeight);
- final int color = alphaBits << 24 | (mHeaderTintColor & 0xffffff);
- mColorFilter.setColor(color);
+
+ mColorFilter.setColorMatrix(mColorMatrix);
mPhotoView.setColorFilter(mColorFilter);
+ // Tell the photo view what tint we are trying to achieve. Depending on the type of
+ // drawable used, the photo view may or may not use this tint.
+ mPhotoView.setTint(((int) (0xFF * colorAlpha)) << 24 | (mHeaderTintColor & 0xffffff));
+ }
+
+ /**
+ * Simulates alpha blending an image with {@param color}.
+ */
+ private ColorMatrix alphaMatrix(float alpha, int color) {
+ mAlphaMatrixValues[0] = Color.red(color) * alpha / 255;
+ mAlphaMatrixValues[6] = Color.green(color) * alpha / 255;
+ mAlphaMatrixValues[12] = Color.blue(color) * alpha / 255;
+ mAlphaMatrixValues[4] = 255 * (1 - alpha);
+ mAlphaMatrixValues[9] = 255 * (1 - alpha);
+ mAlphaMatrixValues[14] = 255 * (1 - alpha);
+ mWhitenessColorMatrix.set(mAlphaMatrixValues);
+ return mWhitenessColorMatrix;
+ }
+
+ /**
+ * Simulates multiply blending an image with a single {@param color}.
+ *
+ * Multiply blending is [Sa * Da, Sc * Dc]. See {@link android.graphics.PorterDuff}.
+ */
+ private ColorMatrix multiplyBlendMatrix(int color, float alpha) {
+ mMultiplyBlendMatrixValues[0] = multiplyBlend(Color.red(color), alpha);
+ mMultiplyBlendMatrixValues[6] = multiplyBlend(Color.green(color), alpha);
+ mMultiplyBlendMatrixValues[12] = multiplyBlend(Color.blue(color), alpha);
+ mMultiplyBlendMatrix.set(mMultiplyBlendMatrixValues);
+ return mMultiplyBlendMatrix;
+ }
+
+ private float multiplyBlend(int color, float alpha) {
+ return color * alpha / 255.0f + (1 - alpha);
}
private void updateLastEventPosition(MotionEvent event) {
diff --git a/src/com/android/contacts/widget/QuickContactImageView.java b/src/com/android/contacts/widget/QuickContactImageView.java
new file mode 100644
index 0000000..9dbf85e
--- /dev/null
+++ b/src/com/android/contacts/widget/QuickContactImageView.java
@@ -0,0 +1,85 @@
+package com.android.contacts.widget;
+
+
+import com.android.contacts.common.lettertiles.LetterTileDrawable;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Xfermode;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+
+/**
+ * An {@link ImageView} designed to display QuickContact's contact photo. In addition to
+ * supporting {@link ImageView#setColorFilter} this also performs a second color blending with
+ * the tint set in {@link #setTint}. This requires a second draw pass.
+ */
+public class QuickContactImageView extends ImageView {
+
+ private Xfermode mMode = new PorterDuffXfermode(Mode.MULTIPLY);
+ private int mTintColor;
+ private BitmapDrawable mBitmapDrawable;
+ private Drawable mOriginalDrawable;
+
+ public QuickContactImageView(Context context) {
+ this(context, null);
+ }
+
+ public QuickContactImageView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public QuickContactImageView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public QuickContactImageView(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ public void setTint(int color) {
+ mTintColor = color;
+ postInvalidate();
+ }
+
+ public boolean isBasedOffLetterTile() {
+ return mOriginalDrawable instanceof LetterTileDrawable;
+ }
+
+ @Override
+ public void setImageDrawable(Drawable drawable) {
+ // There is no way to avoid all this casting. Blending modes aren't equally
+ // supported for all drawable types.
+ if (drawable == null || drawable instanceof BitmapDrawable) {
+ mBitmapDrawable = (BitmapDrawable) drawable;
+ } else if (drawable instanceof LetterTileDrawable) {
+ // TODO: set a desired hardcoded BitmapDrawable here
+ mBitmapDrawable = null;
+ } else {
+ throw new IllegalArgumentException("Does not support this type of drawable");
+
+ }
+ mOriginalDrawable = drawable;
+ super.setImageDrawable(mBitmapDrawable);
+ }
+
+ @Override
+ public Drawable getDrawable() {
+ return mOriginalDrawable;
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ if (isBasedOffLetterTile()) {
+ // The LetterTileDrawable's bitmaps have a lot of pixels with alpha=0. These
+ // look stupid unless we fill in the background and use a different blending mode.
+ canvas.drawColor(((LetterTileDrawable) mOriginalDrawable).getColor());
+ }
+ super.onDraw(canvas);
+ }
+}