Update callers *GifCloseFile for new GIFLIB DO NOT MERGE am: 60519c717e am: 0cf3a7d3ec am: 93facf6314
am: cc1dbfc497 -s ours
Change-Id: I15259006a672182c3d5abab0172eb51d94e64c2b
diff --git a/Android.mk b/Android.mk
index 725c89d..e2ed18b 100644
--- a/Android.mk
+++ b/Android.mk
@@ -75,8 +75,6 @@
LOCAL_PROGUARD_FLAG_FILES += proguard-release.flags
endif
-LOCAL_JACK_ENABLED := disabled
-
LOCAL_PACKAGE_NAME := messaging
LOCAL_CERTIFICATE := platform
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 4b16a82..22459c4 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -18,7 +18,7 @@
package="com.android.messaging"
android:installLocation="internalOnly">
- <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="23" />
+ <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="24" />
<!-- Application holds CPU wakelock while working in background -->
<uses-permission android:name="android.permission.WAKE_LOCK" />
diff --git a/proguard.flags b/proguard.flags
index 759f2d4..76b033c 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -23,7 +23,7 @@
}
# Keep methods that have the @VisibleForAnimation annotation
--keep @interface com.android.messaging.annotation.VisibleForAnimation
+-keep interface com.android.messaging.annotation.VisibleForAnimation
-keepclassmembers class * {
@com.android.messaging.annotation.VisibleForAnimation *;
}
diff --git a/res/drawable-hdpi/ic_work_profile.png b/res/drawable-hdpi/ic_work_profile.png
new file mode 100644
index 0000000..5c2e457
--- /dev/null
+++ b/res/drawable-hdpi/ic_work_profile.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_work_profile.png b/res/drawable-mdpi/ic_work_profile.png
new file mode 100644
index 0000000..b7ffd2b
--- /dev/null
+++ b/res/drawable-mdpi/ic_work_profile.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_work_profile.png b/res/drawable-xhdpi/ic_work_profile.png
new file mode 100644
index 0000000..b47b485
--- /dev/null
+++ b/res/drawable-xhdpi/ic_work_profile.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_work_profile.png b/res/drawable-xxhdpi/ic_work_profile.png
new file mode 100644
index 0000000..de257e7
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_work_profile.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_work_profile.png b/res/drawable-xxxhdpi/ic_work_profile.png
new file mode 100644
index 0000000..72292bd
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_work_profile.png
Binary files differ
diff --git a/res/layout/contact_list_item_view.xml b/res/layout/contact_list_item_view.xml
index 3015ae3..71a7370 100644
--- a/res/layout/contact_list_item_view.xml
+++ b/res/layout/contact_list_item_view.xml
@@ -63,15 +63,30 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical" >
- <TextView
- android:id="@+id/contact_name"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:paddingBottom="4dp"
- android:singleLine="true"
- android:maxLines="1"
- android:ellipsize="end"
- style="@style/ContactListItem" />
+
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <TextView
+ android:id="@+id/contact_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingBottom="4dp"
+ android:singleLine="true"
+ android:maxLines="1"
+ android:ellipsize="end"
+ style="@style/ContactListItem" />
+
+ <ImageView android:id="@+id/work_profile_icon"
+ android:src="@drawable/ic_work_profile"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:scaleType="center"
+ android:layout_gravity="center_vertical"
+ android:visibility="gone" />
+ </LinearLayout>
<LinearLayout
android:orientation="horizontal"
diff --git a/res/layout/work_directory_header.xml b/res/layout/work_directory_header.xml
new file mode 100644
index 0000000..3c882f5
--- /dev/null
+++ b/res/layout/work_directory_header.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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 A`NY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- Layout used for list section separators. -->
+<TextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:text="@string/work_directory_display_name"
+ style="@style/DirectoryHeaderStyle"
+ />
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 270ea9e..5ff0eb7 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -188,4 +188,10 @@
<dimen name="fastscroll_preview_margin_left_right">8dp</dimen>
<dimen name="fastscroll_preview_text_size">24sp</dimen>
+ <dimen name="directory_header_padding_start">16dp</dimen>
+ <dimen name="directory_header_padding_end">32dp</dimen>
+ <dimen name="directory_header_padding_top">18dp</dimen>
+ <dimen name="directory_header_padding_bottom">8dp</dimen>
+ <dimen name="directory_header_text_size">14sp</dimen>
+
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index ec675ef..012d87f 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -152,7 +152,7 @@
<!-- While sending a message display this message. -->
<string name="message_status_sending">Sending…</string>
<!-- When sending a message failed display this message. -->
- <string name="message_status_send_failed">Not sent. Touch to try again.</string>
+ <string name="message_status_send_failed">Not sent. Tap to try again.</string>
<!-- When retrying sending for a message. -->
<string name="message_status_send_retrying">Not sent. Trying again…</string>
<!-- When showing resend action display this message. -->
@@ -171,9 +171,9 @@
<!-- Title line for MMS which failed to download. -->
<string name="message_title_download_failed">Couldn\'t download</string>
<!-- Timestamp line for MMS which failed to download. -->
- <string name="message_status_download_failed">Touch to try again</string>
+ <string name="message_status_download_failed">Tap to try again</string>
<!-- Timestamp line for MMS which failed to download. -->
- <string name="message_status_download">Touch to download</string>
+ <string name="message_status_download">Tap to download</string>
<!-- Timestamp line for MMS which is selected. -->
<string name="message_status_download_action">Download or delete</string>
<!-- Timestamp line to display while downloading a message. -->
@@ -296,7 +296,7 @@
<!-- Error occurred while recording audio -->
<string name="audio_recording_error"> Couldn\'t save audio. Try again.</string>
<!-- Hint text on the audio recorder that instructs user how to start recording -->
- <string name="audio_picker_hint_text">Touch & hold</string>
+ <string name="audio_picker_hint_text">Tap & hold</string>
<!-- An enumeration comma for separating multiple names in notifications. [CHAR LIMIT=2] -->
<string name="enumeration_comma">,\u0020</string>
@@ -348,7 +348,7 @@
<string name="notification_on_toast_message">Notifications turned on</string>
<!-- Toast shown when the user tries to send a message, and then sets Bugle as the default SMS app. -->
- <string name="toast_after_setting_default_sms_app_for_message_send">All set. Touch Send again.</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send">All set. Tap Send again.</string>
<!-- Toast shown when the user successfully sets Bugle as the default SMS app. -->
<string name="toast_after_setting_default_sms_app">Messaging successfully set as the default SMS app.</string>
@@ -378,7 +378,7 @@
<string name="group_outgoing_failed_message_prefix">Failed message to <xliff:g id="group">%s</xliff:g>: <xliff:g id="message">%s</xliff:g>. Time: <xliff:g id="time">%s</xliff:g>.</string>
<string name="group_outgoing_successful_message_prefix">Message to <xliff:g id="group">%s</xliff:g>: <xliff:g id="message">%s</xliff:g>. Time: <xliff:g id="time">%s</xliff:g>.</string>
<!-- Accessibility description for conversation list for failed messages -->
- <string name="failed_message_content_description">Failed message. Touch to retry.</string>
+ <string name="failed_message_content_description">Failed message. Tap to retry.</string>
<!-- Accessibility name for a group conversation -->
<string name="group_conversation_description">Conversation with <xliff:g id="participants">%s</xliff:g></string>
@@ -905,7 +905,7 @@
<string name="send_button_long_click_description_with_sim_selector">Select SIM or edit subject</string>
<!-- Content description for the audio record view -->
- <string name="audio_record_view_content_description">Touch and hold to record audio</string>
+ <string name="audio_record_view_content_description">Tap & hold to record audio</string>
<!-- Content description for new conversation button in desktop widget -->
<string name="widget_new_conversation_content_description">Start new conversation</string>
@@ -931,7 +931,7 @@
<string name="conversation_deleted">Conversation deleted</string>
<!-- Displayed when user adds a new conversation widget. Tapping on the widget in this
mode will bring user to the conversation selection screen -->
- <string name="tap_to_configure">Conversation deleted. Touch to show a different Messaging conversation</string>
+ <string name="tap_to_configure">Conversation deleted. Tap to show a different Messaging conversation</string>
<!-- Toast message telling the user that someone was blocked -->
<string name="update_destination_blocked">Blocked</string>
@@ -973,4 +973,6 @@
<!-- The accessibility text read when the sim chooser pops up to read the current selected sim -->
<string name="selected_sim_content_message"><xliff:g id="selected_sim">%s</xliff:g> selected</string>
+
+ <string name="work_directory_display_name">Work Profile contacts</string>
</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index d45f2e1..582c755 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -623,4 +623,14 @@
<item name="android:textColor">@android:color/white</item>
<item name="android:gravity">center</item>
</style>
+ <style name="DirectoryHeaderStyle">
+ <item name="android:paddingLeft">@dimen/directory_header_padding_start</item>
+ <item name="android:paddingStart">@dimen/directory_header_padding_start</item>
+ <item name="android:paddingRight">@dimen/directory_header_padding_end</item>
+ <item name="android:paddingEnd">@dimen/directory_header_padding_end</item>
+ <item name="android:paddingTop">@dimen/directory_header_padding_top</item>
+ <item name="android:paddingBottom">@dimen/directory_header_padding_bottom</item>
+ <item name="android:textSize">@dimen/directory_header_text_size</item>
+ <item name="android:textStyle">bold</item>
+ </style>
</resources>
diff --git a/src/android/support/v7/mms/pdu/EncodedStringValue.java b/src/android/support/v7/mms/pdu/EncodedStringValue.java
index d8fcacf..6a914ae 100644
--- a/src/android/support/v7/mms/pdu/EncodedStringValue.java
+++ b/src/android/support/v7/mms/pdu/EncodedStringValue.java
@@ -145,7 +145,7 @@
}
try {
return new String(mData, CharacterSets.MIMENAME_ISO_8859_1);
- } catch (UnsupportedEncodingException _) {
+ } catch (UnsupportedEncodingException e2) {
return new String(mData); // system default encoding.
}
}
@@ -216,7 +216,7 @@
try {
ret[i] = new EncodedStringValue(mCharacterSet,
temp[i].getBytes());
- } catch (NullPointerException _) {
+ } catch (NullPointerException e) {
// Can't arrive here
return null;
}
diff --git a/src/com/android/messaging/datamodel/data/ContactListItemData.java b/src/com/android/messaging/datamodel/data/ContactListItemData.java
index dcc7e20..b9c7e85 100644
--- a/src/com/android/messaging/datamodel/data/ContactListItemData.java
+++ b/src/com/android/messaging/datamodel/data/ContactListItemData.java
@@ -46,6 +46,9 @@
// existing chip for which we show full contact detail for the selected contact).
private boolean mSingleRecipient;
+ // Is the contact in managed profile.
+ private boolean mIsWorkContact;
+
/**
* Bind to a contact cursor in the contact list.
*/
@@ -77,6 +80,8 @@
mRecipientEntry = ContactUtil.createRecipientEntry(displayName,
DisplayNameSources.STRUCTURED_NAME, destination, destinationType, destinationLabel,
contactId, lookupKey, dataId, photoThumbnailUri, isFirstLevel);
+
+ mIsWorkContact = ContactUtil.isEnterpriseContactId(contactId);
}
/**
@@ -84,13 +89,15 @@
* optional styled name & destination for showing bold search match.
*/
public void bind(final RecipientEntry entry, final CharSequence styledName,
- final CharSequence styledDestination, final boolean singleRecipient) {
+ final CharSequence styledDestination, final boolean singleRecipient,
+ final boolean isWorkContact) {
Assert.isTrue(entry.isValid());
mRecipientEntry = entry;
mStyledName = styledName;
mStyledDestination = styledDestination;
mAlphabetHeader = null;
mSingleRecipient = singleRecipient;
+ mIsWorkContact = isWorkContact;
}
public CharSequence getDisplayName() {
@@ -157,4 +164,11 @@
public RecipientEntry getRecipientEntry() {
return mRecipientEntry;
}
+
+ /**
+ * @return whether the contact is in managed profile.
+ */
+ public boolean getIsWorkContact() {
+ return mIsWorkContact;
+ }
}
diff --git a/src/com/android/messaging/ui/animation/PopupTransitionAnimation.java b/src/com/android/messaging/ui/animation/PopupTransitionAnimation.java
index 21529c6..5fc2a19 100644
--- a/src/com/android/messaging/ui/animation/PopupTransitionAnimation.java
+++ b/src/com/android/messaging/ui/animation/PopupTransitionAnimation.java
@@ -79,8 +79,7 @@
mDestRect = new Rect();
mPopupRect = new Rect();
mActionBarRect = new Rect();
- final Activity activity = (Activity) viewToAnimate.getRootView().getContext();
- mActionBarView = activity.getWindow().getDecorView().findViewById(
+ mActionBarView = viewToAnimate.getRootView().findViewById(
android.support.v7.appcompat.R.id.action_bar);
mRectEvaluator = RectEvaluatorCompat.create();
setDuration(UiUtils.MEDIAPICKER_TRANSITION_DURATION);
diff --git a/src/com/android/messaging/ui/contact/ContactDropdownLayouter.java b/src/com/android/messaging/ui/contact/ContactDropdownLayouter.java
index 7df62de..051ebeb 100644
--- a/src/com/android/messaging/ui/contact/ContactDropdownLayouter.java
+++ b/src/com/android/messaging/ui/contact/ContactDropdownLayouter.java
@@ -34,6 +34,7 @@
import com.android.messaging.util.Assert;
import com.android.messaging.util.AvatarUriUtil;
import com.android.messaging.util.ContactRecipientEntryUtils;
+import com.android.messaging.util.ContactUtil;
/**
* An implementation for {@link DropdownChipLayouter}. Layouts the dropdown
@@ -99,8 +100,9 @@
Assert.isTrue(itemView instanceof ContactListItemView);
final ContactListItemView contactListItemView = (ContactListItemView) itemView;
contactListItemView.setImageClickHandlerDisabled(true);
+ boolean isWorkContact = ContactUtil.isEnterpriseContactId(entry.getContactId());
contactListItemView.bind(entry, styledResults[0], styledResults[1],
- mClivHostInterface, (type == AdapterType.SINGLE_RECIPIENT));
+ mClivHostInterface, (type == AdapterType.SINGLE_RECIPIENT), isWorkContact);
return itemView;
}
diff --git a/src/com/android/messaging/ui/contact/ContactListItemView.java b/src/com/android/messaging/ui/contact/ContactListItemView.java
index 6904da6..b0e8957 100644
--- a/src/com/android/messaging/ui/contact/ContactListItemView.java
+++ b/src/com/android/messaging/ui/contact/ContactListItemView.java
@@ -53,6 +53,7 @@
private TextView mAlphabetHeaderTextView;
private ContactIconView mContactIconView;
private ImageView mContactCheckmarkView;
+ private ImageView mWorkProfileIcon;
private HostInterface mHostInterface;
private boolean mShouldShowAlphabetHeader;
@@ -69,6 +70,7 @@
mAlphabetHeaderTextView = (TextView) findViewById(R.id.alphabet_header);
mContactIconView = (ContactIconView) findViewById(R.id.contact_icon);
mContactCheckmarkView = (ImageView) findViewById(R.id.contact_checkmark);
+ mWorkProfileIcon = (ImageView) findViewById(R.id.work_profile_icon);
}
/**
@@ -100,11 +102,12 @@
* @param isSingleRecipient whether this item is shown as the only line item in the single
* recipient drop down from the chips view. If this is the case, we always show the
* contact avatar even if it's not a first-level entry.
+ * @param isWorkContact whether the contact is in managed profile.
*/
public void bind(final RecipientEntry recipientEntry, final CharSequence styledName,
final CharSequence styledDestination, final HostInterface hostInterface,
- final boolean isSingleRecipient) {
- mData.bind(recipientEntry, styledName, styledDestination, isSingleRecipient);
+ final boolean isSingleRecipient, final boolean isWorkContact) {
+ mData.bind(recipientEntry, styledName, styledDestination, isSingleRecipient, isWorkContact);
mHostInterface = hostInterface;
mShouldShowAlphabetHeader = false;
updateViewAppearance();
@@ -152,7 +155,11 @@
mContactDetailsTextView.setVisibility(VISIBLE);
mContactDetailTypeTextView.setVisibility(VISIBLE);
}
-
+ if (mData.getIsWorkContact()) {
+ mWorkProfileIcon.setVisibility(VISIBLE);
+ } else {
+ mWorkProfileIcon.setVisibility(GONE);
+ }
if (mShouldShowAlphabetHeader) {
mAlphabetHeaderTextView.setVisibility(VISIBLE);
mAlphabetHeaderTextView.setText(mData.getAlphabetHeader());
diff --git a/src/com/android/messaging/ui/contact/ContactRecipientAdapter.java b/src/com/android/messaging/ui/contact/ContactRecipientAdapter.java
index 25f422e..1d91241 100644
--- a/src/com/android/messaging/ui/contact/ContactRecipientAdapter.java
+++ b/src/com/android/messaging/ui/contact/ContactRecipientAdapter.java
@@ -22,18 +22,24 @@
import android.text.TextUtils;
import android.text.util.Rfc822Token;
import android.text.util.Rfc822Tokenizer;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
import android.widget.Filter;
+import android.widget.TextView;
import com.android.ex.chips.BaseRecipientAdapter;
import com.android.ex.chips.RecipientAlternatesAdapter;
import com.android.ex.chips.RecipientAlternatesAdapter.RecipientMatchCallback;
import com.android.ex.chips.RecipientEntry;
+import com.android.messaging.R;
import com.android.messaging.util.Assert;
import com.android.messaging.util.Assert.DoesNotRunOnMainThread;
import com.android.messaging.util.BugleGservices;
import com.android.messaging.util.BugleGservicesKeys;
import com.android.messaging.util.ContactRecipientEntryUtils;
import com.android.messaging.util.ContactUtil;
+import com.android.messaging.util.OsUtil;
import com.android.messaging.util.PhoneUtils;
import java.text.Collator;
@@ -53,6 +59,18 @@
* for {@link ContactRecipientAutoCompleteView}
*/
public final class ContactRecipientAdapter extends BaseRecipientAdapter {
+ private static final int WORD_DIRECTORY_HEADER_POS_NONE = -1;
+ /**
+ * Stores the index of work directory header.
+ */
+ private int mWorkDirectoryHeaderPos = WORD_DIRECTORY_HEADER_POS_NONE;
+ private final LayoutInflater mInflater;
+
+ /**
+ * Type of directory entry.
+ */
+ private static final int ENTRY_TYPE_DIRECTORY = RecipientEntry.ENTRY_TYPE_SIZE;
+
public ContactRecipientAdapter(final Context context,
final ContactListItemView.HostInterface clivHost) {
this(context, Integer.MAX_VALUE, QUERY_TYPE_PHONE, clivHost);
@@ -62,6 +80,7 @@
final int queryMode, final ContactListItemView.HostInterface clivHost) {
super(context, preferredMaxResultCount, queryMode);
setPhotoManager(new ContactRecipientPhotoManager(context, clivHost));
+ mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
@Override
@@ -81,6 +100,7 @@
* results.
*/
public class ContactFilter extends Filter {
+
// Used to sort filtered contacts when it has combined results from email and phone.
private final RecipientEntryComparator mComparator = new RecipientEntryComparator();
@@ -95,21 +115,45 @@
* return the merged results.
*/
@DoesNotRunOnMainThread
- private Pair<Cursor, Boolean> getFilteredResultsCursor(final Context context,
- final String searchText) {
+ private CursorResult getFilteredResultsCursor(final String searchText) {
Assert.isNotMainThread();
if (BugleGservices.get().getBoolean(
BugleGservicesKeys.ALWAYS_AUTOCOMPLETE_EMAIL_ADDRESS,
BugleGservicesKeys.ALWAYS_AUTOCOMPLETE_EMAIL_ADDRESS_DEFAULT)) {
- return Pair.create((Cursor) new MergeCursor(new Cursor[] {
- ContactUtil.filterPhones(getContext(), searchText)
- .performSynchronousQuery(),
- ContactUtil.filterEmails(getContext(), searchText)
- .performSynchronousQuery()
- }), false /* the merged cursor is not sorted */);
+
+ final Cursor personalFilterPhonesCursor = ContactUtil
+ .filterPhones(getContext(), searchText).performSynchronousQuery();
+ final Cursor personalFilterEmailsCursor = ContactUtil
+ .filterEmails(getContext(), searchText).performSynchronousQuery();
+ final Cursor personalCursor = new MergeCursor(
+ new Cursor[]{personalFilterEmailsCursor, personalFilterPhonesCursor});
+ final CursorResult cursorResult =
+ new CursorResult(personalCursor, false /* sorted */);
+ if (OsUtil.isAtLeastN()) {
+ // Including enterprise result starting from N.
+ final Cursor enterpriseFilterPhonesCursor = ContactUtil.filterPhonesEnterprise(
+ getContext(), searchText).performSynchronousQuery();
+ final Cursor enterpriseFilterEmailsCursor = ContactUtil.filterEmailsEnterprise(
+ getContext(), searchText).performSynchronousQuery();
+ final Cursor enterpriseCursor = new MergeCursor(
+ new Cursor[]{enterpriseFilterEmailsCursor,
+ enterpriseFilterPhonesCursor});
+ cursorResult.enterpriseCursor = enterpriseCursor;
+ }
+ return cursorResult;
} else {
- return Pair.create(ContactUtil.filterDestination(getContext(), searchText)
- .performSynchronousQuery(), true);
+ final Cursor personalFilterDestinationCursor = ContactUtil
+ .filterDestination(getContext(), searchText).performSynchronousQuery();
+ final CursorResult cursorResult = new CursorResult(personalFilterDestinationCursor,
+ true);
+ if (OsUtil.isAtLeastN()) {
+ // Including enterprise result starting from N.
+ final Cursor enterpriseFilterDestinationCursor = ContactUtil
+ .filterDestinationEnterprise(getContext(), searchText)
+ .performSynchronousQuery();
+ cursorResult.enterpriseCursor = enterpriseFilterDestinationCursor;
+ }
+ return cursorResult;
}
}
@@ -128,44 +172,57 @@
// Query for auto-complete results, since performFiltering() is not done on the
// main thread, perform the cursor loader queries directly.
- final Pair<Cursor, Boolean> filteredResults = getFilteredResultsCursor(getContext(),
- searchText);
- final Cursor cursor = filteredResults.first;
- final boolean sorted = filteredResults.second;
- if (cursor != null) {
- try {
- final List<RecipientEntry> entries = new ArrayList<RecipientEntry>();
- // First check if the constraint is a valid SMS destination. If so, add the
- // destination as a suggestion item to the drop down.
- if (PhoneUtils.isValidSmsMmsDestination(searchText)) {
- entries.add(ContactRecipientEntryUtils
- .constructSendToDestinationEntry(searchText));
- }
+ final CursorResult cursorResult = getFilteredResultsCursor(searchText);
+ final List<RecipientEntry> entries = new ArrayList<>();
- HashSet<Long> existingContactIds = new HashSet<Long>();
- while (cursor.moveToNext()) {
- // Make sure there's only one first-level contact (i.e. contact for which
- // we show the avatar picture and name) for every contact id.
- final long contactId = cursor.getLong(ContactUtil.INDEX_CONTACT_ID);
- final boolean isFirstLevel = !existingContactIds.contains(contactId);
- if (isFirstLevel) {
- existingContactIds.add(contactId);
- }
- entries.add(ContactUtil.createRecipientEntryForPhoneQuery(cursor,
- isFirstLevel));
- }
+ // First check if the constraint is a valid SMS destination. If so, add the
+ // destination as a suggestion item to the drop down.
+ if (PhoneUtils.isValidSmsMmsDestination(searchText)) {
+ entries.add(ContactRecipientEntryUtils
+ .constructSendToDestinationEntry(searchText));
+ }
- if (!sorted) {
- Collections.sort(entries, mComparator);
- }
- results.values = entries;
- results.count = 1;
-
- } finally {
- cursor.close();
+ // Only show work directory header if more than one result in work directory.
+ int workDirectoryHeaderPos = WORD_DIRECTORY_HEADER_POS_NONE;
+ if (cursorResult.enterpriseCursor != null
+ && cursorResult.enterpriseCursor.getCount() > 0) {
+ if (cursorResult.personalCursor != null) {
+ workDirectoryHeaderPos = entries.size();
+ workDirectoryHeaderPos += cursorResult.personalCursor.getCount();
}
}
+
+ final Cursor[] cursors = new Cursor[]{cursorResult.personalCursor,
+ cursorResult.enterpriseCursor};
+ for (Cursor cursor : cursors) {
+ if (cursor != null) {
+ try {
+ final List<RecipientEntry> tempEntries = new ArrayList<>();
+ HashSet<Long> existingContactIds = new HashSet<>();
+ while (cursor.moveToNext()) {
+ // Make sure there's only one first-level contact (i.e. contact for
+ // which we show the avatar picture and name) for every contact id.
+ final long contactId = cursor.getLong(ContactUtil.INDEX_CONTACT_ID);
+ final boolean isFirstLevel = !existingContactIds.contains(contactId);
+ if (isFirstLevel) {
+ existingContactIds.add(contactId);
+ }
+ tempEntries.add(ContactUtil.createRecipientEntryForPhoneQuery(cursor,
+ isFirstLevel));
+ }
+
+ if (!cursorResult.isSorted) {
+ Collections.sort(tempEntries, mComparator);
+ }
+ entries.addAll(tempEntries);
+ } finally {
+ cursor.close();
+ }
+ }
+ }
+ results.values = new ContactReceipientFilterResult(entries, workDirectoryHeaderPos);
+ results.count = 1;
return results;
}
@@ -174,16 +231,20 @@
mCurrentConstraint = constraint;
clearTempEntries();
- if (results.values != null) {
- @SuppressWarnings("unchecked")
- final List<RecipientEntry> entries = (List<RecipientEntry>) results.values;
- updateEntries(entries);
- } else {
- updateEntries(Collections.<RecipientEntry>emptyList());
+ final ContactReceipientFilterResult contactReceipientFilterResult
+ = (ContactReceipientFilterResult) results.values;
+ if (contactReceipientFilterResult != null) {
+ mWorkDirectoryHeaderPos = contactReceipientFilterResult.workDirectoryPos;
+ if (contactReceipientFilterResult.recipientEntries != null) {
+ updateEntries(contactReceipientFilterResult.recipientEntries);
+ } else {
+ updateEntries(Collections.<RecipientEntry>emptyList());
+ }
}
}
private class RecipientEntryComparator implements Comparator<RecipientEntry> {
+
private final Collator mCollator;
public RecipientEntryComparator() {
@@ -237,6 +298,38 @@
}
}
}
+
+ private class CursorResult {
+
+ public final Cursor personalCursor;
+
+ public Cursor enterpriseCursor;
+
+ public final boolean isSorted;
+
+ public CursorResult(Cursor personalCursor, boolean isSorted) {
+ this.personalCursor = personalCursor;
+ this.isSorted = isSorted;
+ }
+ }
+
+ private class ContactReceipientFilterResult {
+ /**
+ * Recipient entries in all directories.
+ */
+ public final List<RecipientEntry> recipientEntries;
+
+ /**
+ * Index of row that showing work directory header.
+ */
+ public final int workDirectoryPos;
+
+ public ContactReceipientFilterResult(List<RecipientEntry> recipientEntries,
+ int workDirectoryPos) {
+ this.recipientEntries = recipientEntries;
+ this.workDirectoryPos = workDirectoryPos;
+ }
+ }
}
/**
@@ -283,4 +376,81 @@
// report matches
callback.matchesFound(recipientEntries);
}
+
+ /**
+ * We handle directory header here and then delegate the work of creating recipient views to
+ * the {@link BaseRecipientAdapter}. Please notice that we need to fix the position
+ * before passing to {@link BaseRecipientAdapter} because it is not aware of the existence of
+ * directory headers.
+ */
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ TextView textView;
+ if (isDirectoryEntry(position)) {
+ if (convertView == null) {
+ textView = (TextView) mInflater.inflate(R.layout.work_directory_header, parent,
+ false);
+ } else {
+ textView = (TextView) convertView;
+ }
+ return textView;
+ }
+ return super.getView(fixPosition(position), convertView, parent);
+ }
+
+ @Override
+ public RecipientEntry getItem(int position) {
+ if (isDirectoryEntry(position)) {
+ return null;
+ }
+ return super.getItem(fixPosition(position));
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return RecipientEntry.ENTRY_TYPE_SIZE + 1;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ if (isDirectoryEntry(position)) {
+ return ENTRY_TYPE_DIRECTORY;
+ }
+ return super.getItemViewType(fixPosition(position));
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ if (isDirectoryEntry(position)) {
+ return false;
+ }
+ return super.isEnabled(fixPosition(position));
+ }
+
+ @Override
+ public int getCount() {
+ return super.getCount() + ((hasWorkDirectoryHeader()) ? 1 : 0);
+ }
+
+ private boolean isDirectoryEntry(int position) {
+ return position == mWorkDirectoryHeaderPos;
+ }
+
+ /**
+ * @return the position of items without counting directory headers.
+ */
+ private int fixPosition(int position) {
+ if (hasWorkDirectoryHeader()) {
+ Assert.isTrue(position != mWorkDirectoryHeaderPos);
+ if (position > mWorkDirectoryHeaderPos) {
+ return position - 1;
+ }
+ }
+ return position;
+ }
+
+ private boolean hasWorkDirectoryHeader() {
+ return mWorkDirectoryHeaderPos != WORD_DIRECTORY_HEADER_POS_NONE;
+ }
+
}
diff --git a/src/com/android/messaging/ui/conversationlist/ShareIntentActivity.java b/src/com/android/messaging/ui/conversationlist/ShareIntentActivity.java
index ef7fcef..83b7be9 100644
--- a/src/com/android/messaging/ui/conversationlist/ShareIntentActivity.java
+++ b/src/com/android/messaging/ui/conversationlist/ShareIntentActivity.java
@@ -34,6 +34,7 @@
import com.android.messaging.util.ContentType;
import com.android.messaging.util.LogUtil;
import com.android.messaging.util.MediaMetadataRetrieverWrapper;
+import com.android.messaging.util.FileUtil;
import java.io.IOException;
import java.util.ArrayList;
@@ -158,8 +159,12 @@
}
private void addSharedImagePartToDraft(final String contentType, final Uri imageUri) {
- mDraftMessage.addPart(PendingAttachmentData.createPendingAttachmentData(contentType,
- imageUri));
+ if (FileUtil.isInPrivateDir(imageUri)) {
+ Assert.fail("Cannot send private file " + imageUri.toString());
+ } else {
+ mDraftMessage.addPart(PendingAttachmentData.createPendingAttachmentData(contentType,
+ imageUri));
+ }
}
@Override
diff --git a/src/com/android/messaging/ui/mediapicker/DocumentImagePicker.java b/src/com/android/messaging/ui/mediapicker/DocumentImagePicker.java
index dff59cf..bb267da 100644
--- a/src/com/android/messaging/ui/mediapicker/DocumentImagePicker.java
+++ b/src/com/android/messaging/ui/mediapicker/DocumentImagePicker.java
@@ -28,9 +28,6 @@
import com.android.messaging.util.FileUtil;
import com.android.messaging.util.ImageUtils;
import com.android.messaging.util.SafeAsyncTask;
-import com.android.messaging.util.UriUtil;
-
-import java.io.File;
/**
* Wraps around the functionalities to allow the user to pick images from the document
@@ -116,8 +113,7 @@
new SafeAsyncTask<Void, Void, String>() {
@Override
protected String doInBackgroundTimed(final Void... params) {
- if (UriUtil.isFileUri(documentUri) &&
- FileUtil.isInDataDir(new File(documentUri.getPath()))) {
+ if (FileUtil.isInPrivateDir(documentUri)) {
// hacker sending private app data. Bail out
if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.ERROR)) {
LogUtil.e(LogUtil.BUGLE_TAG, "Aborting attach of private app data ("
diff --git a/src/com/android/messaging/util/ContactUtil.java b/src/com/android/messaging/util/ContactUtil.java
index 8555889..94af3ba 100644
--- a/src/com/android/messaging/util/ContactUtil.java
+++ b/src/com/android/messaging/util/ContactUtil.java
@@ -274,6 +274,18 @@
}
/**
+ * Get a list of destinations (phone, email) matching the partial destination in work profile.
+ */
+ public static CursorQueryData filterDestinationEnterprise(final Context context,
+ final String destination) {
+ if (shouldFilterForEmail(destination)) {
+ return ContactUtil.filterEmailsEnterprise(context, destination);
+ } else {
+ return ContactUtil.filterPhonesEnterprise(context, destination);
+ }
+ }
+
+ /**
* Get a list of phones matching a search criteria. The search may be on contact name or
* phone number. In case search is on contact name, all matching contact's phone number
* will be returned.
@@ -282,19 +294,29 @@
*/
@VisibleForTesting
public static CursorQueryData filterPhones(final Context context, final String query) {
+ return filterPhonesInternal(context, Phone.CONTENT_FILTER_URI, query, Directory.DEFAULT);
+ }
+
+ /**
+ * Similar to {@link #filterPhones(Context, String)}, but search in work profile instead.
+ */
+ public static CursorQueryData filterPhonesEnterprise(final Context context,
+ final String query) {
+ return filterPhonesInternal(context, Phone.ENTERPRISE_CONTENT_FILTER_URI, query,
+ Directory.ENTERPRISE_DEFAULT);
+ }
+
+ private static CursorQueryData filterPhonesInternal(final Context context,
+ final Uri phoneFilterBaseUri, final String query, final long directoryId) {
if (!ContactUtil.hasReadContactsPermission()) {
return CursorQueryData.getEmptyQueryData();
}
-
- final Uri uri = Phone.CONTENT_FILTER_URI.buildUpon()
- .appendPath(query).appendQueryParameter(
- ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(Directory.DEFAULT))
- .build();
-
- return new CursorQueryData(context, uri, PhoneQuery.PROJECTION, null, null,
+ Uri phoneFilterUri = buildDirectorySearchUri(phoneFilterBaseUri, query, directoryId);
+ return new CursorQueryData(context,
+ phoneFilterUri,
+ PhoneQuery.PROJECTION, null, null,
PhoneQuery.SORT_KEY);
}
-
/**
* Lookup a phone based on a phone number. Supplied phone should be a relatively complete
* phone number for this to succeed. PhoneLookup URI will apply some smartness to do a
@@ -336,17 +358,29 @@
*/
@VisibleForTesting
public static CursorQueryData filterEmails(final Context context, final String query) {
+ return filterEmailsInternal(context, Email.CONTENT_FILTER_URI, query, Directory.DEFAULT);
+ }
+
+ /**
+ * Similar to {@link #filterEmails(Context, String)}, but search in work profile instead.
+ */
+ public static CursorQueryData filterEmailsEnterprise(final Context context,
+ final String query) {
+ return filterEmailsInternal(context, Email.ENTERPRISE_CONTENT_FILTER_URI, query,
+ Directory.ENTERPRISE_DEFAULT);
+ }
+
+ private static CursorQueryData filterEmailsInternal(final Context context,
+ final Uri filterEmailsBaseUri, final String query, final long directoryId) {
if (!ContactUtil.hasReadContactsPermission()) {
return CursorQueryData.getEmptyQueryData();
}
-
- final Uri uri = Email.CONTENT_FILTER_URI.buildUpon()
- .appendPath(query).appendQueryParameter(
- ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(Directory.DEFAULT))
- .build();
-
- return new CursorQueryData(context, uri, EmailQuery.PROJECTION, null, null,
- EmailQuery.SORT_KEY);
+ final Uri filterEmailsUri = buildDirectorySearchUri(filterEmailsBaseUri, query,
+ directoryId);
+ return new CursorQueryData(context,
+ filterEmailsUri,
+ PhoneQuery.PROJECTION, null, null,
+ PhoneQuery.SORT_KEY);
}
/**
@@ -485,25 +519,15 @@
* Returns if a given contact id belongs to managed profile.
*/
public static boolean isEnterpriseContactId(final long contactId) {
- return isWorkProfileSupported()
- && ContactsContract.Contacts.isEnterpriseContactId(contactId);
- }
-
- /**
- * Returns if managed profile is supported.
- */
- public static boolean isWorkProfileSupported() {
- final PackageManager pm = Factory.get().getApplicationContext().getPackageManager();
- return pm.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS);
+ return OsUtil.isAtLeastL() && ContactsContract.Contacts.isEnterpriseContactId(contactId);
}
/**
* Returns Email lookup uri that will query both primary and corp profile
*/
private static Uri getEmailContentLookupUri() {
- if (isWorkProfileSupported() && OsUtil.isAtLeastM()) {
- // TODO: use Email.ENTERPRISE_CONTENT_LOOKUP_URI, which will be available in M SDK API
- return Uri.parse("content://com.android.contacts/data/emails/lookup_enterprise");
+ if (OsUtil.isAtLeastM()) {
+ return Email.ENTERPRISE_CONTENT_LOOKUP_URI;
}
return Email.CONTENT_LOOKUP_URI;
}
@@ -512,8 +536,7 @@
* Returns PhoneLookup URI.
*/
public static Uri getPhoneLookupUri() {
- // Apply it to M only
- if (isWorkProfileSupported() && OsUtil.isAtLeastM()) {
+ if (OsUtil.isAtLeastM()) {
return PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI;
}
return PhoneLookup.CONTENT_FILTER_URI;
@@ -522,4 +545,12 @@
public static boolean hasReadContactsPermission() {
return OsUtil.hasPermission(Manifest.permission.READ_CONTACTS);
}
+
+ private static Uri buildDirectorySearchUri(final Uri uri, final String query,
+ final long directoryId) {
+ return uri.buildUpon()
+ .appendPath(query).appendQueryParameter(
+ ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(directoryId))
+ .build();
+ }
}
diff --git a/src/com/android/messaging/util/FileUtil.java b/src/com/android/messaging/util/FileUtil.java
index b147b25..e35e79b 100644
--- a/src/com/android/messaging/util/FileUtil.java
+++ b/src/com/android/messaging/util/FileUtil.java
@@ -16,8 +16,11 @@
package com.android.messaging.util;
+import android.content.ContentResolver;
import android.content.Context;
+import android.net.Uri;
import android.os.Environment;
+import android.text.TextUtils;
import android.webkit.MimeTypeMap;
import com.android.messaging.Factory;
@@ -117,11 +120,19 @@
}
}
+ private static boolean isFileUri(final Uri uri) {
+ return TextUtils.equals(uri.getScheme(), ContentResolver.SCHEME_FILE);
+ }
+
// Checks if the file is in /data, and don't allow any app to send personal information.
// We're told it's possible to create world readable hardlinks to other apps private data
- // so we ban all /data file uris. b/28793303
- public static boolean isInDataDir(File file) {
- return isSameOrSubDirectory(Environment.getDataDirectory(), file);
+ // so we ban all /data file uris.
+ public static boolean isInPrivateDir(Uri uri) {
+ if (!isFileUri(uri)) {
+ return false;
+ }
+ final File file = new File(uri.getPath());
+ return FileUtil.isSameOrSubDirectory(Environment.getDataDirectory(), file);
}
/**
diff --git a/src/com/android/messaging/util/OsUtil.java b/src/com/android/messaging/util/OsUtil.java
index e45a63c..4890d08 100644
--- a/src/com/android/messaging/util/OsUtil.java
+++ b/src/com/android/messaging/util/OsUtil.java
@@ -22,6 +22,7 @@
import android.os.Build;
import android.os.UserHandle;
import android.os.UserManager;
+import android.support.v4.os.BuildCompat;
import com.android.messaging.Factory;
@@ -41,6 +42,7 @@
private static boolean sIsAtLeastL;
private static boolean sIsAtLeastL_MR1;
private static boolean sIsAtLeastM;
+ private static boolean sIsAtLeastN;
private static Boolean sIsSecondaryUser = null;
@@ -54,6 +56,7 @@
sIsAtLeastL = v >= android.os.Build.VERSION_CODES.LOLLIPOP;
sIsAtLeastL_MR1 = v >= android.os.Build.VERSION_CODES.LOLLIPOP_MR1;
sIsAtLeastM = v >= android.os.Build.VERSION_CODES.M;
+ sIsAtLeastN = BuildCompat.isAtLeastN();
}
/**
@@ -121,6 +124,14 @@
}
/**
+ * @return True if the version of Android that we're running on is at least N
+ * (API level 24).
+ */
+ public static boolean isAtLeastN() {
+ return sIsAtLeastN;
+ }
+
+ /**
* @return The Android API version of the OS that we're currently running on.
*/
public static int getApiVersion() {
diff --git a/tests/Android.mk b/tests/Android.mk
index f3f4752..3bfdb9a 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -24,8 +24,6 @@
LOCAL_INSTRUMENTATION_FOR := messaging
-LOCAL_JACK_ENABLED := disabled
-
# Matching ../Android.mk
LOCAL_SDK_VERSION := current
diff --git a/version.mk b/version.mk
index 7407993..6568cc8 100644
--- a/version.mk
+++ b/version.mk
@@ -100,10 +100,12 @@
version_code_package := $(base_version_major)$(base_version_minor)$(base_version_build)$(base_version_buildtype)$(base_version_arch)$(base_version_density)
# The version name scheme for the package apk is:
+# - For platform builds: M.m.bbb
# - For eng build (t=1): M.m.bbb eng.$(USER)-hh
# - For build server (t=0): M.m.bbb (nnnnnn-hh)
# where nnnnnn is the build number from the build server (no zero-padding)
# On eng builds, the BUILD_NUMBER has the user and timestamp inline
+ifdef TARGET_BUILD_APPS
ifneq "" "$(filter eng.%,$(BUILD_NUMBER))"
git_hash := $(shell git --git-dir $(LOCAL_PATH)/.git log -n 1 --pretty=format:%h)
date_string := $(shell date +%m%d%y_%H%M%S)
@@ -111,6 +113,9 @@
else
version_name_package := $(base_version_major).$(base_version_minor).$(base_version_build) ($(BUILD_NUMBER)-$(base_version_arch)$(base_version_density))
endif
+else # !TARGET_BUILD_APPS
+ version_name_package := $(base_version_major).$(base_version_minor).$(base_version_build)
+endif
# Cleanup the locals
base_version_major :=