From 372e48607ee56042832be9362a052e2b0d4a944f Mon Sep 17 00:00:00 2001 From: Ye Wen Date: Fri, 29 Jan 2016 14:39:34 -0800 Subject: [PATCH 01/14] Change "Touch" to "Tap" b/26749422 Change-Id: I2ca48f7534bf18344a4e6d85470543d23b3dfa5c --- res/values/strings.xml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index ec675ef..58af39b 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -152,7 +152,7 @@ Sending… - Not sent. Touch to try again. + Not sent. Tap to try again. Not sent. Trying again… @@ -171,9 +171,9 @@ Couldn\'t download - Touch to try again + Tap to try again - Touch to download + Tap to download Download or delete @@ -296,7 +296,7 @@ Couldn\'t save audio. Try again. - Touch & hold + Tap & hold ,\u0020 @@ -348,7 +348,7 @@ Notifications turned on - All set. Touch Send again. + All set. Tap Send again. Messaging successfully set as the default SMS app. @@ -378,7 +378,7 @@ Failed message to %s: %s. Time: %s. Message to %s: %s. Time: %s. - Failed message. Touch to retry. + Failed message. Tap to retry. Conversation with %s @@ -905,7 +905,7 @@ Select SIM or edit subject - Touch and hold to record audio + Tap & hold to record audio Start new conversation @@ -931,7 +931,7 @@ Conversation deleted - Conversation deleted. Touch to show a different Messaging conversation + Conversation deleted. Tap to show a different Messaging conversation Blocked From ec2fe3006b51e33505be8a4cb8366d8e03762df4 Mon Sep 17 00:00:00 2001 From: Ye Wen Date: Fri, 5 Feb 2016 15:45:41 -0800 Subject: [PATCH 02/14] Remove "_" variable for Java 8 b/27045502 Change-Id: I7ce2bb262fdc2b64f2c6b3cb629a2ec93450b265 --- src/android/support/v7/mms/pdu/EncodedStringValue.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 @@ public class EncodedStringValue implements Cloneable { } 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 @@ public class EncodedStringValue implements Cloneable { try { ret[i] = new EncodedStringValue(mCharacterSet, temp[i].getBytes()); - } catch (NullPointerException _) { + } catch (NullPointerException e) { // Can't arrive here return null; } From 280a2fbe41f24eed11fcf5e13b803de17525b261 Mon Sep 17 00:00:00 2001 From: Yohann Roussel Date: Wed, 17 Feb 2016 16:30:35 +0100 Subject: [PATCH 03/14] Re-enable Jack for Messaging and its tests Jack was disabled because of bugs in Jack and missing support for testing obfuscated applications. Both are now fixed so Jack can be enabled. Bug: 27215871 (cherry picked from commit bf6bd40faa264ec2727a4b23e6e4b72815ec5518) Change-Id: I73d3c5a3599640932b2305ffe22bb5ef27814ba3 --- Android.mk | 2 -- proguard.flags | 2 +- tests/Android.mk | 2 -- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/Android.mk b/Android.mk index 725c89d..e2ed18b 100644 --- a/Android.mk +++ b/Android.mk @@ -75,8 +75,6 @@ else LOCAL_PROGUARD_FLAG_FILES += proguard-release.flags endif -LOCAL_JACK_ENABLED := disabled - LOCAL_PACKAGE_NAME := messaging LOCAL_CERTIFICATE := platform 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/tests/Android.mk b/tests/Android.mk index f3f4752..3bfdb9a 100644 --- a/tests/Android.mk +++ b/tests/Android.mk @@ -24,8 +24,6 @@ LOCAL_PACKAGE_NAME := messagingtests LOCAL_INSTRUMENTATION_FOR := messaging -LOCAL_JACK_ENABLED := disabled - # Matching ../Android.mk LOCAL_SDK_VERSION := current From 445d7baabc65ffec355e47d0452bafa0f6804923 Mon Sep 17 00:00:00 2001 From: Neil Fuller Date: Tue, 23 Feb 2016 11:31:25 +0000 Subject: [PATCH 04/14] Pin messaging to use Jack with 1.7 language There is a bug in the current version of Jack that causes problems when the default is changed to 1.8. Pinning to 1.7 appears to help. Bug: 27305482 Bug: 26753820 Change-Id: Ia29bf5cb14c754be4c4b453aa89a9d9c369d9e52 --- Android.mk | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Android.mk b/Android.mk index e2ed18b..64f1935 100644 --- a/Android.mk +++ b/Android.mk @@ -81,6 +81,9 @@ LOCAL_CERTIFICATE := platform LOCAL_SDK_VERSION := current +# Workaround for http://b/27305482 +LOCAL_JAVA_LANGUAGE_VERSION := 1.7 + include $(BUILD_PACKAGE) include $(call all-makefiles-under, $(LOCAL_PATH)) From 49528d492f5074a8f8994f4687208f7decc4267c Mon Sep 17 00:00:00 2001 From: Yohann Roussel Date: Fri, 26 Feb 2016 15:23:34 +0000 Subject: [PATCH 05/14] Revert "Pin messaging to use Jack with 1.7 language" This reverts commit 445d7baabc65ffec355e47d0452bafa0f6804923. Bug: 27333568 Bug: 27305482 Bug: 26753820 Change-Id: I44c126c3a54de332c0032e1d7d158f95099bfc03 --- Android.mk | 3 --- 1 file changed, 3 deletions(-) diff --git a/Android.mk b/Android.mk index 64f1935..e2ed18b 100644 --- a/Android.mk +++ b/Android.mk @@ -81,9 +81,6 @@ LOCAL_CERTIFICATE := platform LOCAL_SDK_VERSION := current -# Workaround for http://b/27305482 -LOCAL_JAVA_LANGUAGE_VERSION := 1.7 - include $(BUILD_PACKAGE) include $(call all-makefiles-under, $(LOCAL_PATH)) From 7fe2fc9b2fa68bfcbabddaa96c04972bf5d297ba Mon Sep 17 00:00:00 2001 From: Tom Taylor Date: Mon, 21 Mar 2016 13:44:13 -0700 Subject: [PATCH 06/14] Messaging app ClassCastException (DecorContext -> Activity) when SMSoIMS is initiated Bug 27649619 Change-Id: I1c0de7c14abd255ceb9d61a377db3276c32fede0 --- .../messaging/ui/animation/PopupTransitionAnimation.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 @@ public class PopupTransitionAnimation extends Animation { 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); From 30fb3385393aafd3463811f8e88e04a08b157f67 Mon Sep 17 00:00:00 2001 From: Tavis Bohne Date: Thu, 28 Apr 2016 14:03:37 -0700 Subject: [PATCH 07/14] Messaging doesn't allow sharing of its own files -Previously, Messaging allowed sharing of any file it had permission to reach. This meant that bad apps could share a link to file:///data/data/com.android.messaging/databases/bugle_db and Messaging would happily send all this sensitive information to the target. Worse, a bad app could share a softlink to this file, where the symlink was picture.jpg with the image/jpg type. -Now, when sanitizing attachments, we make sure any filepaths don't lead to any Bugle-specific directories. -getApplicationInfo().dataDir is a symlink to /data/data/com.android.messaging, and appears to be the only directory where we store personal data. -Most apps share as contentUris, including Messaging, so Messaging can still share to itself. Change-Id: Ic464bc1f099029a030793c478aaf88b957d8bad1 Fixes:28076752 --- .../conversationlist/ShareIntentActivity.java | 9 +++++++-- src/com/android/messaging/util/FileUtil.java | 17 +++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/com/android/messaging/ui/conversationlist/ShareIntentActivity.java b/src/com/android/messaging/ui/conversationlist/ShareIntentActivity.java index ef7fcef..396f1da 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.Assert; 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 @@ public class ShareIntentActivity extends BaseBugleActivity implements } private void addSharedImagePartToDraft(final String contentType, final Uri imageUri) { - mDraftMessage.addPart(PendingAttachmentData.createPendingAttachmentData(contentType, - imageUri)); + if (FileUtil.isInPrivateDir(getBaseContext(), imageUri)) { + Assert.fail("Cannot send private file " + imageUri.toString()); + } else { + mDraftMessage.addPart(PendingAttachmentData.createPendingAttachmentData(contentType, + imageUri)); + } } @Override diff --git a/src/com/android/messaging/util/FileUtil.java b/src/com/android/messaging/util/FileUtil.java index 7c47ae9..f8051ed 100644 --- a/src/com/android/messaging/util/FileUtil.java +++ b/src/com/android/messaging/util/FileUtil.java @@ -16,7 +16,10 @@ package com.android.messaging.util; +import android.content.ContentResolver; import android.content.Context; +import android.net.Uri; +import android.text.TextUtils; import android.webkit.MimeTypeMap; import com.android.messaging.Factory; @@ -116,6 +119,20 @@ public class FileUtil { } } + private static boolean isFileUri(final Uri uri) { + return TextUtils.equals(uri.getScheme(), ContentResolver.SCHEME_FILE); + } + + // Checks if the file is in /data/data/com.android.messaging + // The other app folders are either symlinks to this, or hold non-private data like binaries. + public static boolean isInPrivateDir(Context context, Uri uri) { + if (!isFileUri(uri)) { + return false; + } + final File file = new File(uri.getPath()); + return FileUtil.isSameOrSubDirectory(new File(context.getApplicationInfo().dataDir), file); + } + /** * Checks, whether the child directory is the same as, or a sub-directory of the base * directory. From 0a85b81c9f15f72e7284a8ff40f1ba73d7469b58 Mon Sep 17 00:00:00 2001 From: Victor Chang Date: Wed, 11 May 2016 18:07:38 +0100 Subject: [PATCH 08/14] Add isAtLeastN in OsUtil Bug: 28016344 Change-Id: I431b4fed3f2114d1f1b311416a892f608a846e4c --- src/com/android/messaging/util/OsUtil.java | 11 +++++++++++ 1 file changed, 11 insertions(+) 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.content.pm.PackageManager; 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 @@ public class OsUtil { 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 @@ public class OsUtil { 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(); } /** @@ -120,6 +123,14 @@ public class OsUtil { return sIsAtLeastM; } + /** + * @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. */ From b7fc988f231394077c08b5ede4d1a7310da57406 Mon Sep 17 00:00:00 2001 From: Tony Mak Date: Wed, 11 May 2016 17:12:20 +0100 Subject: [PATCH 09/14] Search enterprise directory as well in SMS app TODO: Update the UI to show enterprise result separately (b/26021888) Bug: 28016344, 25977180 (cherry picked from commit 5c621e57256f5068a43f1456f9bab1194f0d27b2) Change-Id: I7130ae5e057ed750a29f64c5497a04b868613579 --- .../ui/contact/ContactRecipientAdapter.java | 51 +++++++++++-- .../android/messaging/util/ContactUtil.java | 74 +++++++++++++++---- 2 files changed, 101 insertions(+), 24 deletions(-) diff --git a/src/com/android/messaging/ui/contact/ContactRecipientAdapter.java b/src/com/android/messaging/ui/contact/ContactRecipientAdapter.java index 25f422e..240f281 100644 --- a/src/com/android/messaging/ui/contact/ContactRecipientAdapter.java +++ b/src/com/android/messaging/ui/contact/ContactRecipientAdapter.java @@ -34,6 +34,7 @@ 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; @@ -101,15 +102,49 @@ public final class ContactRecipientAdapter extends BaseRecipientAdapter { 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(); + Cursor resultCursor; + 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(); + // TODO: Separating enterprise result from personal result (b/26021888) + resultCursor = new MergeCursor( + new Cursor[]{personalFilterEmailsCursor, enterpriseFilterEmailsCursor, + personalFilterPhonesCursor, enterpriseFilterPhonesCursor}); + } else { + resultCursor = new MergeCursor( + new Cursor[]{personalFilterEmailsCursor, personalFilterPhonesCursor}); + } + return Pair.create( + resultCursor, + false /* the merged cursor is not sorted */ + ); } else { - return Pair.create(ContactUtil.filterDestination(getContext(), searchText) - .performSynchronousQuery(), true); + final Cursor personalFilterDestinationCursor = ContactUtil + .filterDestination(getContext(), searchText).performSynchronousQuery(); + Cursor resultCursor; + boolean sorted; + if (OsUtil.isAtLeastN()) { + // Including enterprise result starting from N. + final Cursor enterpriseFilterDestinationCursor = ContactUtil + .filterDestinationEnterprise(getContext(), searchText) + .performSynchronousQuery(); + // TODO: Separating enterprise result from personal result (b/26021888) + resultCursor = new MergeCursor(new Cursor[]{personalFilterDestinationCursor, + enterpriseFilterDestinationCursor}); + sorted = false; + } else { + resultCursor = personalFilterDestinationCursor; + sorted = true; + } + return Pair.create(resultCursor, sorted); } } diff --git a/src/com/android/messaging/util/ContactUtil.java b/src/com/android/messaging/util/ContactUtil.java index 8555889..41a1704 100644 --- a/src/com/android/messaging/util/ContactUtil.java +++ b/src/com/android/messaging/util/ContactUtil.java @@ -273,6 +273,18 @@ public class ContactUtil { } } + /** + * 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 @@ -282,19 +294,29 @@ public class ContactUtil { */ @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 @@ public class ContactUtil { */ @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); } /** @@ -522,4 +556,12 @@ public class ContactUtil { 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(); + } } From 904516077182cce4ce71a51b27a3ab418ed0f225 Mon Sep 17 00:00:00 2001 From: Tony Mak Date: Wed, 11 May 2016 18:21:28 +0100 Subject: [PATCH 10/14] Separate work and personal contacts search result This change allows sms app to show search result of different directories in different partitions. Currently, only personal default and enterprise default directory are searched. BUG=26021888 BUG=28016344 (cherry picked from commit 59666b32c192bd28807ccd2f9153c796668bdf13) Change-Id: I733ceaae806d13661861126ce61c34108ac7e662 --- res/layout/work_directory_header.xml | 24 ++ res/values/dimens.xml | 6 + res/values/strings.xml | 2 + res/values/styles.xml | 10 + .../ui/contact/ContactRecipientAdapter.java | 261 +++++++++++++----- 5 files changed, 240 insertions(+), 63 deletions(-) create mode 100644 res/layout/work_directory_header.xml 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 @@ + + + + + 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 @@ 8dp 24sp + 16dp + 32dp + 18dp + 8dp + 14sp + diff --git a/res/values/strings.xml b/res/values/strings.xml index 58af39b..012d87f 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -973,4 +973,6 @@ %s selected + + Work Profile contacts 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 @@ @android:color/white center + diff --git a/src/com/android/messaging/ui/contact/ContactRecipientAdapter.java b/src/com/android/messaging/ui/contact/ContactRecipientAdapter.java index 240f281..1d91241 100644 --- a/src/com/android/messaging/ui/contact/ContactRecipientAdapter.java +++ b/src/com/android/messaging/ui/contact/ContactRecipientAdapter.java @@ -22,12 +22,17 @@ import android.support.v4.util.Pair; 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; @@ -54,6 +59,18 @@ import java.util.Map; * 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); @@ -63,6 +80,7 @@ public final class ContactRecipientAdapter extends BaseRecipientAdapter { 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 @@ -82,6 +100,7 @@ public final class ContactRecipientAdapter extends BaseRecipientAdapter { * 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(); @@ -96,8 +115,7 @@ public final class ContactRecipientAdapter extends BaseRecipientAdapter { * return the merged results. */ @DoesNotRunOnMainThread - private Pair getFilteredResultsCursor(final Context context, - final String searchText) { + private CursorResult getFilteredResultsCursor(final String searchText) { Assert.isNotMainThread(); if (BugleGservices.get().getBoolean( BugleGservicesKeys.ALWAYS_AUTOCOMPLETE_EMAIL_ADDRESS, @@ -107,44 +125,35 @@ public final class ContactRecipientAdapter extends BaseRecipientAdapter { .filterPhones(getContext(), searchText).performSynchronousQuery(); final Cursor personalFilterEmailsCursor = ContactUtil .filterEmails(getContext(), searchText).performSynchronousQuery(); - Cursor resultCursor; + 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(); - // TODO: Separating enterprise result from personal result (b/26021888) - resultCursor = new MergeCursor( - new Cursor[]{personalFilterEmailsCursor, enterpriseFilterEmailsCursor, - personalFilterPhonesCursor, enterpriseFilterPhonesCursor}); - } else { - resultCursor = new MergeCursor( - new Cursor[]{personalFilterEmailsCursor, personalFilterPhonesCursor}); + final Cursor enterpriseCursor = new MergeCursor( + new Cursor[]{enterpriseFilterEmailsCursor, + enterpriseFilterPhonesCursor}); + cursorResult.enterpriseCursor = enterpriseCursor; } - return Pair.create( - resultCursor, - false /* the merged cursor is not sorted */ - ); + return cursorResult; } else { final Cursor personalFilterDestinationCursor = ContactUtil .filterDestination(getContext(), searchText).performSynchronousQuery(); - Cursor resultCursor; - boolean sorted; + final CursorResult cursorResult = new CursorResult(personalFilterDestinationCursor, + true); if (OsUtil.isAtLeastN()) { // Including enterprise result starting from N. final Cursor enterpriseFilterDestinationCursor = ContactUtil .filterDestinationEnterprise(getContext(), searchText) .performSynchronousQuery(); - // TODO: Separating enterprise result from personal result (b/26021888) - resultCursor = new MergeCursor(new Cursor[]{personalFilterDestinationCursor, - enterpriseFilterDestinationCursor}); - sorted = false; - } else { - resultCursor = personalFilterDestinationCursor; - sorted = true; + cursorResult.enterpriseCursor = enterpriseFilterDestinationCursor; } - return Pair.create(resultCursor, sorted); + return cursorResult; } } @@ -163,44 +172,57 @@ public final class ContactRecipientAdapter extends BaseRecipientAdapter { // Query for auto-complete results, since performFiltering() is not done on the // main thread, perform the cursor loader queries directly. - final Pair filteredResults = getFilteredResultsCursor(getContext(), - searchText); - final Cursor cursor = filteredResults.first; - final boolean sorted = filteredResults.second; - if (cursor != null) { - try { - final List entries = new ArrayList(); - // 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 entries = new ArrayList<>(); - HashSet 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); - } - 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 tempEntries = new ArrayList<>(); + HashSet 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; } @@ -209,16 +231,20 @@ public final class ContactRecipientAdapter extends BaseRecipientAdapter { mCurrentConstraint = constraint; clearTempEntries(); - if (results.values != null) { - @SuppressWarnings("unchecked") - final List entries = (List) results.values; - updateEntries(entries); - } else { - updateEntries(Collections.emptyList()); + final ContactReceipientFilterResult contactReceipientFilterResult + = (ContactReceipientFilterResult) results.values; + if (contactReceipientFilterResult != null) { + mWorkDirectoryHeaderPos = contactReceipientFilterResult.workDirectoryPos; + if (contactReceipientFilterResult.recipientEntries != null) { + updateEntries(contactReceipientFilterResult.recipientEntries); + } else { + updateEntries(Collections.emptyList()); + } } } private class RecipientEntryComparator implements Comparator { + private final Collator mCollator; public RecipientEntryComparator() { @@ -272,6 +298,38 @@ public final class ContactRecipientAdapter extends BaseRecipientAdapter { } } } + + 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 recipientEntries; + + /** + * Index of row that showing work directory header. + */ + public final int workDirectoryPos; + + public ContactReceipientFilterResult(List recipientEntries, + int workDirectoryPos) { + this.recipientEntries = recipientEntries; + this.workDirectoryPos = workDirectoryPos; + } + } } /** @@ -318,4 +376,81 @@ public final class ContactRecipientAdapter extends BaseRecipientAdapter { // 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; + } + } From e2ccf8bff59da2b54fb037557ad93fc4c317c506 Mon Sep 17 00:00:00 2001 From: Tony Mak Date: Wed, 11 May 2016 18:32:25 +0100 Subject: [PATCH 11/14] Cleanup enterprise feature in ContactUtil 1. Remove isWorkProfileSupported checking, api level checking is enough. 2. Replace the hardcoded uri with the corresponding api. BUG=26797244 BUG=28016344 (cherry picked from commit 0d7a1cce157e4ffb26d5fefb9908bbeffcb1d03c) Change-Id: I76edad19dc6a19aa77b523cb960f716fca08944d --- .../android/messaging/util/ContactUtil.java | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/src/com/android/messaging/util/ContactUtil.java b/src/com/android/messaging/util/ContactUtil.java index 41a1704..94af3ba 100644 --- a/src/com/android/messaging/util/ContactUtil.java +++ b/src/com/android/messaging/util/ContactUtil.java @@ -519,25 +519,15 @@ public class ContactUtil { * 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; } @@ -546,8 +536,7 @@ public class ContactUtil { * 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; From c4073ff4ce1a342daad01a99d3fce84c54bc4776 Mon Sep 17 00:00:00 2001 From: Tony Mak Date: Wed, 11 May 2016 18:44:00 +0100 Subject: [PATCH 12/14] Add badge icon for work contact in search result BUG=26021888 BUG=28016344 (cherry picked from commit 6e00763823cd1b27ff88d25d0eb11f8addc3b2e6) Change-Id: If0e2f7b1c58cd064ebfdae8a9b735b391c5009fc --- res/drawable-hdpi/ic_work_profile.png | Bin 0 -> 158 bytes res/drawable-mdpi/ic_work_profile.png | Bin 0 -> 160 bytes res/drawable-xhdpi/ic_work_profile.png | Bin 0 -> 223 bytes res/drawable-xxhdpi/ic_work_profile.png | Bin 0 -> 214 bytes res/drawable-xxxhdpi/ic_work_profile.png | Bin 0 -> 343 bytes res/layout/contact_list_item_view.xml | 33 +++++++++++++----- .../datamodel/data/ContactListItemData.java | 16 ++++++++- .../ui/contact/ContactDropdownLayouter.java | 4 ++- .../ui/contact/ContactListItemView.java | 13 +++++-- 9 files changed, 52 insertions(+), 14 deletions(-) create mode 100644 res/drawable-hdpi/ic_work_profile.png create mode 100644 res/drawable-mdpi/ic_work_profile.png create mode 100644 res/drawable-xhdpi/ic_work_profile.png create mode 100644 res/drawable-xxhdpi/ic_work_profile.png create mode 100644 res/drawable-xxxhdpi/ic_work_profile.png diff --git a/res/drawable-hdpi/ic_work_profile.png b/res/drawable-hdpi/ic_work_profile.png new file mode 100644 index 0000000000000000000000000000000000000000..5c2e457818a53aa89c4646696d07ea2f434203b2 GIT binary patch literal 158 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_+iR8JSjkP60R38n>GmVL1o%28Ni zBejHsv2hQ}9w`T}2|q2~Xq4*L@=Q6z-Kcfvp_IZY&TPj`Gb}73OcSc41chJBJ0`it zps*)lDZ_%OzF-l?4?h|=u(2#z#+D=`fAC9};EHBZ28QKAUChVoLJWWwGI+ZBxvXUv#egTMnrS)K`ABCDTi45rnfOXEVS4!^2|qifdNNU zL#0^2Jl;F<9Lj-fU4k1LW^mOdhJN5)xPUj9Ghj)tRUik0-+@i#Up+EU0j*^4boFyt I=akR{04Az6N&o-= literal 0 HcmV?d00001 diff --git a/res/drawable-xhdpi/ic_work_profile.png b/res/drawable-xhdpi/ic_work_profile.png new file mode 100644 index 0000000000000000000000000000000000000000..b47b485ecb209a2da63c4d186a155d474a2629de GIT binary patch literal 223 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4U%RF5iLn;`P53ss9X8OLYH@V@^ z^(|X=k5o;#R*S&_W(^0cFAfp{ zPSY3_Pq6h!CG>_K{CLSUph9N#9??S#&n`{Ak#pd=(*nM({l|5hG~-z Vv^-fnYa!6Z44$rjF6*2UngBJ8O}zjB literal 0 HcmV?d00001 diff --git a/res/drawable-xxhdpi/ic_work_profile.png b/res/drawable-xxhdpi/ic_work_profile.png new file mode 100644 index 0000000000000000000000000000000000000000..de257e71372a1d4183c00a29207188bffacc94d7 GIT binary patch literal 214 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}b0Dxt=bLAr*{ouWgrfG8ABbu$?__ zVvuOz%+DFGY?x*W&D8kh`S-8>z2}OPD_sqaF1x%m+2%-seB(xmS1KFs@k?4+3iUJ2 z&Swe;aLkd@2@L%5qpf$PuYh%Va?Tv4EleW%XKt@&5%OpVYcOBmCH>yw%cf#RUgf?1 z%rz1UPJRD-cicNTpJ^9ojl2W*0lR>|?Rx(|2IgP-=N4M~R_$!cw&x3kf3tq8*a&ne NgQu&X%Q~loCIEV~QRM&t literal 0 HcmV?d00001 diff --git a/res/drawable-xxxhdpi/ic_work_profile.png b/res/drawable-xxxhdpi/ic_work_profile.png new file mode 100644 index 0000000000000000000000000000000000000000..72292bdb391400e3c00ca54b6eeb0d193c4f865d GIT binary patch literal 343 zcmV-d0jU0oP)JP=1n;CxN-Z;O2jH2iOh#Sf7E46C_n)U$Oi-D z?SLL6=C60l23C1^i~fy;s1!8*u6saN-8cdj%BSfEF$gPdlf@xTD<@;hZ)T pFIp&YQo`Tyo)Zf8r`I%1dj!EX-umIEf@lB$002ovPDHLkV1g - + + + + + + Date: Tue, 17 May 2016 17:36:51 -0700 Subject: [PATCH 13/14] Messenger refuses all file:///data/ uris We've been informed it's possible for an app to create a world readable hardlink in L in its own /data directory that links to another apps private data, including Messenger data. The hardlink bypassed our existing checks. So now we simply refuse all file: uris in the /data/ directory. Other apps shouldn't be sending file uris anyway, and we dont know of any that send file:///data/ uris. Bug: 28793303 Change-Id: I778bb2bcb9e11185357093c59fc1fa3f6caa26a1 --- .../ui/conversationlist/ShareIntentActivity.java | 2 +- src/com/android/messaging/util/FileUtil.java | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/com/android/messaging/ui/conversationlist/ShareIntentActivity.java b/src/com/android/messaging/ui/conversationlist/ShareIntentActivity.java index 396f1da..83b7be9 100644 --- a/src/com/android/messaging/ui/conversationlist/ShareIntentActivity.java +++ b/src/com/android/messaging/ui/conversationlist/ShareIntentActivity.java @@ -159,7 +159,7 @@ public class ShareIntentActivity extends BaseBugleActivity implements } private void addSharedImagePartToDraft(final String contentType, final Uri imageUri) { - if (FileUtil.isInPrivateDir(getBaseContext(), imageUri)) { + if (FileUtil.isInPrivateDir(imageUri)) { Assert.fail("Cannot send private file " + imageUri.toString()); } else { mDraftMessage.addPart(PendingAttachmentData.createPendingAttachmentData(contentType, diff --git a/src/com/android/messaging/util/FileUtil.java b/src/com/android/messaging/util/FileUtil.java index f8051ed..e35e79b 100644 --- a/src/com/android/messaging/util/FileUtil.java +++ b/src/com/android/messaging/util/FileUtil.java @@ -19,6 +19,7 @@ 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; @@ -123,14 +124,15 @@ public class FileUtil { return TextUtils.equals(uri.getScheme(), ContentResolver.SCHEME_FILE); } - // Checks if the file is in /data/data/com.android.messaging - // The other app folders are either symlinks to this, or hold non-private data like binaries. - public static boolean isInPrivateDir(Context context, Uri uri) { + // 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. + public static boolean isInPrivateDir(Uri uri) { if (!isFileUri(uri)) { return false; } final File file = new File(uri.getPath()); - return FileUtil.isSameOrSubDirectory(new File(context.getApplicationInfo().dataDir), file); + return FileUtil.isSameOrSubDirectory(Environment.getDataDirectory(), file); } /** From f1210c127a4288aa53ec8dd3936a0077c63022eb Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Thu, 2 Jun 2016 16:23:08 -0700 Subject: [PATCH 14/14] Bump targetSdkVersion to 24 This will only affect AOSP build. BUG:28621267 Change-Id: I13f47b838c743674d371901bed2bb7b0e2cfcfc2 --- AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 8fe8fae..5ab5910 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -18,7 +18,7 @@ package="com.android.messaging" android:installLocation="internalOnly"> - +