Merge "New interface to pick a contact as attachment" am: 1dc43f0e09

Change-Id: I7e6c871f5ffd303d318936119774ee7ff96fb1c5
This commit is contained in:
Automerger Merge Worker
2020-03-09 22:07:51 +00:00
13 changed files with 296 additions and 46 deletions

View File

@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2020 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"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
android:importantForAccessibility="no" >
<FrameLayout
android:id="@+id/mediapicker_enabled"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
style="@style/ContactPickerHintText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|center"
android:layout_marginTop="16dp"
android:text="@string/contact_picker_hint_text"
android:importantForAccessibility="no" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:scaleType="center"
android:src="@drawable/ic_person_light_large"
android:tint="@color/primary_color"
android:importantForAccessibility="no"
android:contentDescription="@null" />
</FrameLayout>
<!-- This view will hide all other views if the required permission is not granted -->
<TextView
android:id="@+id/missing_permission_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/enable_permission_procedure"
android:contentDescription="@string/enable_permission_procedure_description"
android:background="@android:color/white"
android:gravity="center"
android:visibility="gone" />
</FrameLayout>

View File

@@ -111,6 +111,8 @@
<color name="audio_attachment_timer_text_color">#323232</color>
<color name="audio_progress_bar_color">@color/primary_color</color>
<color name="contact_picker_hint_text_color">#40000000</color>
<color name="notification_sender_text">#9A9A9A</color>
<color name="notification_secondary_text">#FFFFFF</color>
<color name="notification_tertiary_text">#FFFFFF</color>

View File

@@ -100,6 +100,9 @@
<dimen name="audio_picker_text_size">16sp</dimen>
<dimen name="audio_attachment_text_size">14sp</dimen>
<dimen name="audio_progress_bar_height">6dp</dimen>
<dimen name="contact_picker_text_size">16sp</dimen>
<!-- Videos in the message list view should at least be this big in the smallest dimension -->
<dimen name="video_message_min_size">320dp</dimen>
<dimen name="attachment_rounded_corner_radius">3dp</dimen>

View File

@@ -53,6 +53,7 @@
<string name="mediapicker_cameraChooserDescription">Capture pictures or video</string>
<string name="mediapicker_galleryChooserDescription">Choose images from this device</string>
<string name="mediapicker_audioChooserDescription">Record audio</string>
<string name="mediapicker_contactChooserDescription">Choose a contact from this device</string>
<string name="mediapicker_gallery_title">Choose media</string>
<string name="mediapicker_gallery_item_selected_content_description">The media is selected.</string>
<string name="mediapicker_gallery_item_unselected_content_description">The media is unselected.</string>
@@ -61,6 +62,9 @@
<string name="mediapicker_gallery_image_item_description">image <xliff:g id="date">%1$tB %1$te %1$tY %1$tl %1$tM %1$tp</xliff:g></string>
<string name="mediapicker_gallery_image_item_description_no_date">image</string>
<string name="mediapicker_audio_title">Record audio</string>
<string name="mediapicker_contact_title">Choose a contact</string>
<!-- Hint text on the contact picker -->
<string name="contact_picker_hint_text">Click to open contacts list on this device</string>
<string name="action_share">Share</string>

View File

@@ -217,6 +217,11 @@
<item name="android:paddingLeft">16dp</item>
</style>
<style name="ContactPickerHintText">
<item name="android:textSize">@dimen/contact_picker_text_size</item>
<item name="android:textColor">@color/contact_picker_hint_text_color</item>
</style>
<style name="VcardAttachmentSingleStyle">
<item name="android:paddingRight">@dimen/message_text_left_right_padding</item>
<item name="android:paddingLeft">@dimen/message_text_left_right_padding</item>

View File

@@ -47,6 +47,9 @@ public abstract class UIIntents {
// The request code for picking a media from the Document picker.
public static final int REQUEST_PICK_MEDIA_FROM_DOCUMENT_PICKER = 1400;
// The request code for picking a contact card from existing Contacts apps.
public static final int REQUEST_PICK_CONTACT_CARD = 1500;
// Indicates what type of notification this applies to (See BugleNotifications:
// UPDATE_NONE, UPDATE_MESSAGES, UPDATE_ERRORS, UPDATE_ALL)
public static final String UI_INTENT_EXTRA_NOTIFICATIONS_UPDATE = "notifications_update";
@@ -166,12 +169,19 @@ public abstract class UIIntents {
public abstract void launchAddContactActivity(final Context context, final String destination);
/**
* Launch an activity to show the document picker to pick an image/video.
* Launch an activity to show the document picker to pick an image/video/audio.
*
* @param fragment the requesting fragment
*/
public abstract void launchDocumentImagePicker(final Fragment fragment);
/**
* Launch an activity to show the contacts list to pick one.
*
* @param fragment the requesting fragment
*/
public abstract void launchContactCardPicker(final Fragment fragment);
/**
* Launch an activity to show people & options for a given conversation.
*/

View File

@@ -243,6 +243,19 @@ public class UIIntentsImpl extends UIIntents {
fragment.startActivityForResult(intent, REQUEST_PICK_MEDIA_FROM_DOCUMENT_PICKER);
}
@Override
public void launchContactCardPicker(final Fragment fragment) {
final Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType(Contacts.CONTENT_TYPE);
try {
fragment.startActivityForResult(intent, REQUEST_PICK_CONTACT_CARD);
} catch (final ActivityNotFoundException ex) {
LogUtil.w(LogUtil.BUGLE_TAG, "Couldn't find activity:", ex);
UiUtils.showToastAtBottom(R.string.activity_not_found_message);
}
}
@Override
public void launchPeopleAndOptionsActivity(final Activity activity,
final String conversationId) {

View File

@@ -0,0 +1,144 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.messaging.ui.mediapicker;
import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract.Contacts;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.android.messaging.R;
import com.android.messaging.datamodel.data.PendingAttachmentData;
import com.android.messaging.ui.UIIntents;
import com.android.messaging.util.ContactUtil;
import com.android.messaging.util.ContentType;
import com.android.messaging.util.SafeAsyncTask;
/**
* Chooser which allows the user to select an existing contact from contacts apps on this device.
* Note that this chooser requires the Manifest.permission.READ_CONTACTS which is one of the miminum
* set of permissions for this app. Thus no case to request READ_CONTACTS permission on it actually.
*/
class ContactMediaChooser extends MediaChooser {
private View mEnabledView;
private View mMissingPermissionView;
ContactMediaChooser(final MediaPicker mediaPicker) {
super(mediaPicker);
}
@Override
public int getSupportedMediaTypes() {
return MediaPicker.MEDIA_TYPE_VCARD;
}
@Override
public int getIconResource() {
return R.drawable.ic_person_light;
}
@Override
public int getIconDescriptionResource() {
return R.string.mediapicker_contactChooserDescription;
}
@Override
int getActionBarTitleResId() {
return R.string.mediapicker_contact_title;
}
@Override
protected View createView(final ViewGroup container) {
final LayoutInflater inflater = getLayoutInflater();
final View view =
inflater.inflate(
R.layout.mediapicker_contact_chooser,
container /* root */,
false /* attachToRoot */);
mEnabledView = view.findViewById(R.id.mediapicker_enabled);
mMissingPermissionView = view.findViewById(R.id.missing_permission_view);
mEnabledView.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(final View v) {
// Launch an external picker to pick a contact as attachment.
UIIntents.get().launchContactCardPicker(mMediaPicker);
}
});
return view;
}
@Override
protected void setSelected(final boolean selected) {
super.setSelected(selected);
if (selected && !ContactUtil.hasReadContactsPermission()) {
mMediaPicker.requestPermissions(
new String[] {Manifest.permission.READ_CONTACTS},
MediaPicker.READ_CONTACT_PERMISSION_REQUEST_CODE);
}
}
@Override
protected void onRequestPermissionsResult(
final int requestCode, final String permissions[], final int[] grantResults) {
if (requestCode == MediaPicker.READ_CONTACT_PERMISSION_REQUEST_CODE) {
final boolean permissionGranted = grantResults[0] == PackageManager.PERMISSION_GRANTED;
mEnabledView.setVisibility(permissionGranted ? View.VISIBLE : View.GONE);
mMissingPermissionView.setVisibility(permissionGranted ? View.GONE : View.VISIBLE);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == UIIntents.REQUEST_PICK_CONTACT_CARD
&& resultCode == Activity.RESULT_OK) {
Uri contactUri = data.getData();
if (contactUri != null) {
String lookupKey = null;
try (final Cursor c = getContext().getContentResolver().query(
contactUri,
new String[] {Contacts.LOOKUP_KEY},
null,
null,
null)) {
if (c != null) {
c.moveToFirst();
lookupKey = c.getString(0);
}
}
final Uri vCardUri = Uri.withAppendedPath(Contacts.CONTENT_VCARD_URI, lookupKey);
if (vCardUri != null) {
SafeAsyncTask.executeOnThreadPool(new Runnable() {
@Override
public void run() {
final PendingAttachmentData pendingItem =
PendingAttachmentData.createPendingAttachmentData(
ContentType.TEXT_VCARD.toLowerCase(), vCardUri);
mMediaPicker.dispatchPendingItemAdded(pendingItem);
}
});
}
}
}
}
}

View File

@@ -15,7 +15,6 @@
*/
package com.android.messaging.ui.mediapicker;
import android.app.Activity;
import android.app.Fragment;
import android.content.Intent;
import android.net.Uri;
@@ -79,30 +78,27 @@ public class DocumentImagePicker {
* Must be called from the fragment/activity's onActivityResult().
*/
public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
if (requestCode == UIIntents.REQUEST_PICK_MEDIA_FROM_DOCUMENT_PICKER
&& resultCode == Activity.RESULT_OK) {
// Sometimes called after media item has been picked from the document picker.
String url = data.getStringExtra(EXTRA_PHOTO_URL);
// Sometimes called after media item has been picked from the document picker.
String url = data.getStringExtra(EXTRA_PHOTO_URL);
if (url == null) {
// we're using the builtin photo picker which supplies the return
// url as it's "data"
url = data.getDataString();
if (url == null) {
// we're using the builtin photo picker which supplies the return
// url as it's "data"
url = data.getDataString();
if (url == null) {
final Bundle extras = data.getExtras();
if (extras != null) {
final Uri uri = (Uri) extras.getParcelable(Intent.EXTRA_STREAM);
if (uri != null) {
url = uri.toString();
}
final Bundle extras = data.getExtras();
if (extras != null) {
final Uri uri = (Uri) extras.getParcelable(Intent.EXTRA_STREAM);
if (uri != null) {
url = uri.toString();
}
}
}
}
// Guard against null uri cases for when the activity returns a null/invalid intent.
if (url != null) {
final Uri uri = Uri.parse(url);
prepareDocumentForAttachment(uri);
}
// Guard against null uri cases for when the activity returns a null/invalid intent.
if (url != null) {
final Uri uri = Uri.parse(url);
prepareDocumentForAttachment(uri);
}
}

View File

@@ -17,6 +17,8 @@
package com.android.messaging.ui.mediapicker;
import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.database.MatrixCursor;
@@ -35,6 +37,9 @@ import com.android.messaging.datamodel.data.GalleryGridItemData;
import com.android.messaging.datamodel.data.MediaPickerData;
import com.android.messaging.datamodel.data.MessagePartData;
import com.android.messaging.datamodel.data.MediaPickerData.MediaPickerDataListener;
import com.android.messaging.datamodel.data.PendingAttachmentData;
import com.android.messaging.ui.UIIntents;
import com.android.messaging.ui.mediapicker.DocumentImagePicker.SelectionListener;
import com.android.messaging.util.Assert;
import com.android.messaging.util.OsUtil;
@@ -47,9 +52,21 @@ class GalleryMediaChooser extends MediaChooser implements
private GalleryGridView mGalleryGridView;
private View mMissingPermissionView;
/** Handles picking a media from the document picker. */
private DocumentImagePicker mDocumentImagePicker;
GalleryMediaChooser(final MediaPicker mediaPicker) {
super(mediaPicker);
mAdapter = new GalleryGridAdapter(Factory.get().getApplicationContext(), null);
mDocumentImagePicker = new DocumentImagePicker(mMediaPicker,
new SelectionListener() {
@Override
public void onDocumentSelected(final PendingAttachmentData data) {
if (mBindingRef.isBound()) {
mMediaPicker.dispatchPendingItemAdded(data);
}
}
});
}
@Override
@@ -148,7 +165,8 @@ class GalleryMediaChooser extends MediaChooser implements
@Override
public void onDocumentPickerItemClicked() {
mMediaPicker.launchDocumentPicker();
// Launch an external picker to pick item from document picker as attachment.
mDocumentImagePicker.launchPicker();
}
@Override
@@ -230,4 +248,13 @@ class GalleryMediaChooser extends MediaChooser implements
mGalleryGridView.setVisibility(granted ? View.VISIBLE : View.GONE);
mMissingPermissionView.setVisibility(granted ? View.GONE : View.VISIBLE);
}
@Override
protected void onActivityResult(
final int requestCode, final int resultCode, final Intent data) {
if (requestCode == UIIntents.REQUEST_PICK_MEDIA_FROM_DOCUMENT_PICKER
&& resultCode == Activity.RESULT_OK) {
mDocumentImagePicker.onActivityResult(requestCode, resultCode, data);
}
}
}

View File

@@ -18,6 +18,7 @@ package com.android.messaging.ui.mediapicker;
import android.app.FragmentManager;
import android.content.Context;
import android.content.Intent;
import androidx.appcompat.app.ActionBar;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -203,6 +204,9 @@ abstract class MediaChooser extends BasePagerViewHolder
public void stopTouchHandling() {
}
protected void onActivityResult(
final int requestCode, final int resultCode, final Intent data) {}
@Override
public int getConversationSelfSubId() {
return mMediaPicker.getConversationSelfSubId();

View File

@@ -16,7 +16,6 @@
package com.android.messaging.ui.mediapicker;
import android.app.Activity;
import android.app.Fragment;
import android.content.Context;
@@ -48,7 +47,6 @@ import com.android.messaging.datamodel.data.PendingAttachmentData;
import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageSubscriptionDataProvider;
import com.android.messaging.ui.BugleActionBarActivity;
import com.android.messaging.ui.FixedViewPagerAdapter;
import com.android.messaging.ui.mediapicker.DocumentImagePicker.SelectionListener;
import com.android.messaging.util.AccessibilityUtil;
import com.android.messaging.util.Assert;
import com.android.messaging.util.UiUtils;
@@ -159,9 +157,6 @@ public class MediaPicker extends Fragment implements DraftMessageSubscriptionDat
@VisibleForTesting
final Binding<MediaPickerData> mBinding = BindingBase.createBinding(this);
/** Handles picking a media from the document picker. */
private DocumentImagePicker mDocumentImagePicker;
/** Provides subscription-related data to access per-subscription configurations. */
private DraftMessageSubscriptionDataProvider mSubscriptionDataProvider;
@@ -179,6 +174,7 @@ public class MediaPicker extends Fragment implements DraftMessageSubscriptionDat
new CameraMediaChooser(this),
new GalleryMediaChooser(this),
new AudioMediaChooser(this),
new ContactMediaChooser(this),
};
mOpen = false;
@@ -203,15 +199,6 @@ public class MediaPicker extends Fragment implements DraftMessageSubscriptionDat
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding.getData().init(getLoaderManager());
mDocumentImagePicker = new DocumentImagePicker(this,
new SelectionListener() {
@Override
public void onDocumentSelected(final PendingAttachmentData data) {
if (mBinding.isBound()) {
dispatchPendingItemAdded(data);
}
}
});
}
@Override
@@ -295,7 +282,7 @@ public class MediaPicker extends Fragment implements DraftMessageSubscriptionDat
@Override
public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
super.onActivityResult(requestCode, resultCode, data);
mDocumentImagePicker.onActivityResult(requestCode, resultCode, data);
mSelectedChooser.onActivityResult(requestCode, resultCode, data);
}
@Override
@@ -710,13 +697,6 @@ public class MediaPicker extends Fragment implements DraftMessageSubscriptionDat
mPagerAdapter.resetState();
}
/**
* Launch an external picker to pick item from document picker as attachment.
*/
public void launchDocumentPicker() {
mDocumentImagePicker.launchPicker();
}
public ImmutableBindingRef<MediaPickerData> getMediaPickerDataBinding() {
return BindingBase.createBindingReference(mBinding);
}
@@ -725,6 +705,7 @@ public class MediaPicker extends Fragment implements DraftMessageSubscriptionDat
protected static final int LOCATION_PERMISSION_REQUEST_CODE = 2;
protected static final int RECORD_AUDIO_PERMISSION_REQUEST_CODE = 3;
protected static final int GALLERY_PERMISSION_REQUEST_CODE = 4;
protected static final int READ_CONTACT_PERMISSION_REQUEST_CODE = 5;
@Override
public void onRequestPermissionsResult(

View File

@@ -103,7 +103,7 @@ public class MediaPickerTest extends FragmentTestCase<MediaPicker> {
final View view = mediaPicker.getView();
assertNotNull(view);
final ViewGroup tabStrip = (ViewGroup) view.findViewById(R.id.mediapicker_tabstrip);
assertEquals(tabStrip.getChildCount(), 3);
assertEquals(tabStrip.getChildCount(), 4);
for (int i = 0; i < tabStrip.getChildCount(); i++) {
final ImageButton tabButton = (ImageButton) tabStrip.getChildAt(i);
assertEquals(View.VISIBLE, tabButton.getVisibility());
@@ -120,7 +120,7 @@ public class MediaPickerTest extends FragmentTestCase<MediaPicker> {
final View view = mediaPicker.getView();
assertNotNull(view);
final ViewGroup tabStrip = (ViewGroup) view.findViewById(R.id.mediapicker_tabstrip);
assertEquals(tabStrip.getChildCount(), 3);
assertEquals(tabStrip.getChildCount(), 4);
for (int i = 0; i < tabStrip.getChildCount(); i++) {
final ImageButton tabButton = (ImageButton) tabStrip.getChildAt(i);
assertEquals(i == 0, tabButton.isSelected());