Merge "Allow adding/replacing a photo from contact card."
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 11b9cde..48fe520 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -544,7 +544,13 @@
<data android:mimeType="image/*" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
- />
+ </activity>
+
+ <!-- Internal photo selection activity -->
+ <activity android:name=".activities.PhotoSelectionActivity"
+ android:theme="@style/Theme.PhotoSelector"
+ android:launchMode="singleTop"
+ android:windowSoftInputMode="stateUnchanged">
</activity>
<!-- Interstitial activity that shows a phone disambig dialog -->
diff --git a/res/layout-sw580dp/detail_header_contact_without_updates.xml b/res/layout-sw580dp/detail_header_contact_without_updates.xml
index 9261f11..0f0e3c2 100644
--- a/res/layout-sw580dp/detail_header_contact_without_updates.xml
+++ b/res/layout-sw580dp/detail_header_contact_without_updates.xml
@@ -40,9 +40,7 @@
android:layout_height="match_parent"
android:orientation="horizontal">
- <ImageView
- android:id="@+id/photo"
- android:scaleType="centerCrop"
+ <include layout="@layout/photo_selector_view"
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="2" />
diff --git a/res/layout-sw680dp-w1000dp/contact_detail_fragment.xml b/res/layout-sw680dp-w1000dp/contact_detail_fragment.xml
index ecf8130..f70c770 100644
--- a/res/layout-sw680dp-w1000dp/contact_detail_fragment.xml
+++ b/res/layout-sw680dp-w1000dp/contact_detail_fragment.xml
@@ -38,12 +38,12 @@
android:layout_width="match_parent"
android:layout_height="0dip">
- <ImageView android:id="@+id/photo"
- android:scaleType="centerCrop"
+ <include android:id="@+id/static_photo_container"
+ layout="@layout/photo_selector_view"
android:layout_width="@dimen/detail_contact_photo_size"
android:layout_height="@dimen/detail_contact_photo_size"
android:layout_marginTop="@dimen/detail_contact_photo_margin"
- android:layout_marginRight="@dimen/detail_contact_photo_margin"/>
+ android:layout_marginRight="@dimen/detail_contact_photo_margin" />
<ListView android:id="@android:id/list"
android:layout_width="0dip"
diff --git a/res/layout-sw680dp-w1000dp/detail_header_contact_with_updates.xml b/res/layout-sw680dp-w1000dp/detail_header_contact_with_updates.xml
index dfba659..7758a21 100644
--- a/res/layout-sw680dp-w1000dp/detail_header_contact_with_updates.xml
+++ b/res/layout-sw680dp-w1000dp/detail_header_contact_with_updates.xml
@@ -27,9 +27,7 @@
android:paddingBottom="8dip"
android:orientation="horizontal">
- <ImageView
- android:id="@+id/photo"
- android:scaleType="centerCrop"
+ <include layout="@layout/photo_selector_view"
android:layout_width="@dimen/detail_contact_photo_size"
android:layout_height="@dimen/detail_contact_photo_size" />
diff --git a/res/layout-w470dp/contact_detail_fragment.xml b/res/layout-w470dp/contact_detail_fragment.xml
index 415bb56..166610e 100644
--- a/res/layout-w470dp/contact_detail_fragment.xml
+++ b/res/layout-w470dp/contact_detail_fragment.xml
@@ -37,12 +37,12 @@
android:layout_above="@id/contact_quick_fix"
android:layout_height="match_parent" >
- <ImageView android:id="@+id/photo"
- android:scaleType="centerCrop"
+ <include android:id="@+id/static_photo_container"
+ layout="@layout/photo_selector_view"
android:layout_width="128dip"
android:layout_height="128dip"
android:layout_marginLeft="@dimen/detail_contact_photo_margin"
- android:layout_marginTop="@dimen/detail_contact_photo_margin"/>
+ android:layout_marginTop="@dimen/detail_contact_photo_margin" />
<ListView android:id="@android:id/list"
android:layout_width="0dip"
diff --git a/res/layout/detail_header_contact_without_updates.xml b/res/layout/detail_header_contact_without_updates.xml
index 2de7711..6422445 100644
--- a/res/layout/detail_header_contact_without_updates.xml
+++ b/res/layout/detail_header_contact_without_updates.xml
@@ -26,9 +26,7 @@
android:layout_height="wrap_content"
ex:ratio="0.5"
ex:direction="widthToHeight">
- <ImageView
- android:id="@+id/photo"
- android:scaleType="centerCrop"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
+
+ <include layout="@layout/photo_selector_view" />
+
</view>
\ No newline at end of file
diff --git a/res/layout/photo_selector_view.xml b/res/layout/photo_selector_view.xml
new file mode 100644
index 0000000..0006559
--- /dev/null
+++ b/res/layout/photo_selector_view.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!--
+ View for displaying photos that show a photo selector when clicked.
+-->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <ImageView
+ android:id="@+id/photo"
+ android:scaleType="centerCrop"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ <View
+ android:id="@+id/photo_touch_intercept_overlay"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="?android:attr/selectableItemBackground"
+ android:visibility="gone" />
+
+</FrameLayout>
\ No newline at end of file
diff --git a/res/layout/photoselection_activity.xml b/res/layout/photoselection_activity.xml
new file mode 100644
index 0000000..75f729b
--- /dev/null
+++ b/res/layout/photoselection_activity.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:ex="http://schemas.android.com/apk/res/com.android.contacts"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <View
+ android:id="@+id/backdrop"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="#000000" />
+ <view
+ android:id="@+id/photo"
+ class="com.android.contacts.detail.TransformableImageView"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:scaleType="centerCrop" />
+</FrameLayout>
\ No newline at end of file
diff --git a/res/values/styles.xml b/res/values/styles.xml
index ece881e..9434104 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -314,6 +314,15 @@
<item name="android:background">@color/quickcontact_tab_indicator</item>
</style>
+ <style name="Theme.PhotoSelector" parent="@android:style/Theme.Holo.Light">
+ <item name="android:windowBackground">@android:color/transparent</item>
+ <item name="android:windowFrame">@null</item>
+ <item name="android:windowContentOverlay">@null</item>
+ <item name="android:windowAnimationStyle">@null</item>
+ <item name="android:backgroundDimEnabled">false</item>
+ <item name="android:windowIsTranslucent">true</item>
+ <item name="android:windowNoTitle">true</item>
+ </style>
<style name="GroupMembershipSizeTextAppearance" parent="@android:style/TextAppearance.Small"/>
diff --git a/src/com/android/contacts/ContactLoader.java b/src/com/android/contacts/ContactLoader.java
index c711b6c..1cba29b 100644
--- a/src/com/android/contacts/ContactLoader.java
+++ b/src/com/android/contacts/ContactLoader.java
@@ -301,8 +301,10 @@
return mRequestedUri;
}
- @VisibleForTesting
- /*package*/ long getId() {
+ /**
+ * Returns the contact ID.
+ */
+ public long getId() {
return mId;
}
diff --git a/src/com/android/contacts/activities/AttachPhotoActivity.java b/src/com/android/contacts/activities/AttachPhotoActivity.java
index a697c29..8d4cb5d 100644
--- a/src/com/android/contacts/activities/AttachPhotoActivity.java
+++ b/src/com/android/contacts/activities/AttachPhotoActivity.java
@@ -156,7 +156,7 @@
Bitmap photo = extras.getParcelable("data");
if (photo != null) {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
- photo.compress(Bitmap.CompressFormat.JPEG, 75, stream);
+ photo.compress(Bitmap.CompressFormat.PNG, 100, stream);
final ContentValues imageValues = new ContentValues();
imageValues.put(Photo.PHOTO, stream.toByteArray());
diff --git a/src/com/android/contacts/activities/ContactDetailActivity.java b/src/com/android/contacts/activities/ContactDetailActivity.java
index b949176..b353a0b 100644
--- a/src/com/android/contacts/activities/ContactDetailActivity.java
+++ b/src/com/android/contacts/activities/ContactDetailActivity.java
@@ -74,7 +74,7 @@
private Handler mHandler = new Handler();
@Override
- public void onCreate(Bundle savedState) {
+ protected void onCreate(Bundle savedState) {
super.onCreate(savedState);
if (PhoneCapabilityTester.isUsingTwoPanes(this)) {
// This activity must not be shown. We have to select the contact in the
diff --git a/src/com/android/contacts/activities/PhotoSelectionActivity.java b/src/com/android/contacts/activities/PhotoSelectionActivity.java
new file mode 100644
index 0000000..73a85eb
--- /dev/null
+++ b/src/com/android/contacts/activities/PhotoSelectionActivity.java
@@ -0,0 +1,474 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.contacts.activities;
+
+import com.android.contacts.ContactSaveService;
+import com.android.contacts.R;
+import com.android.contacts.detail.PhotoSelectionHandler;
+import com.android.contacts.editor.PhotoActionPopup;
+import com.android.contacts.model.EntityDeltaList;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.view.View;
+import android.view.ViewGroup.MarginLayoutParams;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+import android.widget.FrameLayout.LayoutParams;
+import android.widget.ImageView;
+
+import java.io.File;
+
+/**
+ * Popup activity for choosing a contact photo within the Contacts app.
+ */
+public class PhotoSelectionActivity extends Activity {
+
+ /** Number of ms for the animation to expand the photo. */
+ private static final int PHOTO_EXPAND_DURATION = 100;
+
+ /** Number of ms for the animation to contract the photo on activity exit. */
+ private static final int PHOTO_CONTRACT_DURATION = 50;
+
+ /** Number of ms for the animation to hide the backdrop on finish. */
+ private static final int BACKDROP_FADEOUT_DURATION = 100;
+
+ private static final String KEY_CURRENT_PHOTO_FILE = "currentphotofile";
+
+ private static final String KEY_SUB_ACTIVITY_IN_PROGRESS = "subinprogress";
+
+ /** Intent extra to get the photo bitmap. */
+ public static final String PHOTO_BITMAP = "photo_bitmap";
+
+ /** Intent extra to get the entity delta list. */
+ public static final String ENTITY_DELTA_LIST = "entity_delta_list";
+
+ /** Intent extra to indicate whether the contact is the user's profile. */
+ public static final String IS_PROFILE = "is_profile";
+
+ /** Intent extra to indicate whether the contact is from a directory (non-editable). */
+ public static final String IS_DIRECTORY_CONTACT = "is_directory_contact";
+
+ /**
+ * Intent extra to indicate whether the photo should be animated to show the full contents of
+ * the photo (on a larger portion of the screen) when clicked. If unspecified or false, the
+ * photo will not move from its original location.
+ */
+ public static final String EXPAND_PHOTO = "expand_photo";
+
+ /** Source bounds of the image that was clicked on. */
+ private Rect mSourceBounds;
+
+ /** The photo bitmap. */
+ private Bitmap mPhotoBitmap;
+
+ /** Entity delta list of the contact. */
+ private EntityDeltaList mState;
+
+ /** Whether the contact is the user's profile. */
+ private boolean mIsProfile;
+
+ /** Whether the contact is from a directory. */
+ private boolean mIsDirectoryContact;
+
+ /** Whether to animate the photo to an expanded view covering more of the screen. */
+ private boolean mExpandPhoto;
+
+ /** The semi-transparent backdrop. */
+ private View mBackdrop;
+
+ /** The photo view. */
+ private ImageView mPhotoView;
+
+ /** The photo handler attached to this activity, if any. */
+ private PhotoHandler mPhotoHandler;
+
+ /** Animator to expand the photo out to full size. */
+ private ObjectAnimator mPhotoAnimator;
+
+ /** Listener for the animation. */
+ private AnimatorListenerAdapter mAnimationListener;
+
+ /** Whether a change in layout of the photo has occurred that has no animation yet. */
+ private boolean mAnimationPending;
+
+ /** Prior position of the image (for animating). */
+ Rect mOriginalPos = new Rect();
+
+ /** Layout params for the photo view before we started animating. */
+ private LayoutParams mPhotoStartParams;
+
+ /** Layout params for the photo view after we finished animating. */
+ private LayoutParams mPhotoEndParams;
+
+ /** Whether a sub-activity is currently in progress. */
+ private boolean mSubActivityInProgress;
+
+ /**
+ * A photo result received by the activity, persisted across activity lifecycle.
+ */
+ private PendingPhotoResult mPendingPhotoResult;
+
+ /**
+ * The photo file being interacted with, if any. Saved/restored between activity instances.
+ */
+ private File mCurrentPhotoFile;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.photoselection_activity);
+ if (savedInstanceState != null) {
+ String fileName = savedInstanceState.getString(KEY_CURRENT_PHOTO_FILE);
+ if (fileName != null) {
+ mCurrentPhotoFile = new File(fileName);
+ }
+ mSubActivityInProgress = savedInstanceState.getBoolean(KEY_SUB_ACTIVITY_IN_PROGRESS);
+ }
+
+ // Pull data out of the intent.
+ final Intent intent = getIntent();
+ mPhotoBitmap = intent.getParcelableExtra(PHOTO_BITMAP);
+ mState = (EntityDeltaList) intent.getParcelableExtra(ENTITY_DELTA_LIST);
+ mIsProfile = intent.getBooleanExtra(IS_PROFILE, false);
+ mIsDirectoryContact = intent.getBooleanExtra(IS_DIRECTORY_CONTACT, false);
+ mExpandPhoto = intent.getBooleanExtra(EXPAND_PHOTO, false);
+
+ mBackdrop = findViewById(R.id.backdrop);
+ mPhotoView = (ImageView) findViewById(R.id.photo);
+ mSourceBounds = intent.getSourceBounds();
+
+ // Fade in the background.
+ animateInBackground();
+
+ // Dismiss the dialog on clicking the backdrop.
+ mBackdrop.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ finish();
+ }
+ });
+
+ // Wait until the layout pass to show the photo, so that the source bounds will match up.
+ OnGlobalLayoutListener globalLayoutListener = new OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ displayPhoto();
+ mBackdrop.getViewTreeObserver().removeGlobalOnLayoutListener(this);
+ }
+ };
+ mBackdrop.getViewTreeObserver().addOnGlobalLayoutListener(globalLayoutListener);
+ }
+
+ @Override
+ public void finish() {
+ if (!mSubActivityInProgress) {
+ closePhotoAndFinish();
+ } else {
+ activityFinish();
+ }
+ }
+
+ /**
+ * Builds a well-formed intent for invoking this activity.
+ * @param context The context.
+ * @param photoBitmap The bitmap of the current photo.
+ * @param photoBounds The pixel bounds of the current photo.
+ * @param delta The entity delta list for the contact.
+ * @param isProfile Whether the contact is the user's profile.
+ * @param isDirectoryContact Whether the contact comes from a directory (non-editable).
+ * @param expandPhotoOnClick Whether the photo should be expanded on click or not (generally,
+ * this should be true for phones, and false for tablets).
+ * @return An intent that can be used to invoke the photo selection activity.
+ */
+ public static Intent buildIntent(Context context, Bitmap photoBitmap, Rect photoBounds,
+ EntityDeltaList delta, boolean isProfile, boolean isDirectoryContact,
+ boolean expandPhotoOnClick) {
+ Intent intent = new Intent(context, PhotoSelectionActivity.class);
+ intent.putExtra(PHOTO_BITMAP, photoBitmap);
+ intent.setSourceBounds(photoBounds);
+ intent.putExtra(ENTITY_DELTA_LIST, (Parcelable) delta);
+ intent.putExtra(IS_PROFILE, isProfile);
+ intent.putExtra(IS_DIRECTORY_CONTACT, isDirectoryContact);
+ intent.putExtra(EXPAND_PHOTO, expandPhotoOnClick);
+ return intent;
+ }
+
+ private void activityFinish() {
+ super.finish();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (mPhotoAnimator != null) {
+ mPhotoAnimator.cancel();
+ mPhotoAnimator = null;
+ }
+ if (mPhotoHandler != null) {
+ mPhotoHandler.destroy();
+ mPhotoHandler = null;
+ }
+ }
+
+ private void displayPhoto() {
+ if (mPhotoBitmap != null) {
+ final int[] pos = new int[2];
+ mBackdrop.getLocationOnScreen(pos);
+ LayoutParams layoutParams = new LayoutParams(mSourceBounds.width(),
+ mSourceBounds.height());
+ mOriginalPos.left = mSourceBounds.left - pos[0];
+ mOriginalPos.top = mSourceBounds.top - pos[1];
+ mOriginalPos.right = mOriginalPos.left + mSourceBounds.width();
+ mOriginalPos.bottom = mOriginalPos.top + mSourceBounds.height();
+ layoutParams.setMargins(mOriginalPos.left, mOriginalPos.top, mOriginalPos.right,
+ mOriginalPos.bottom);
+ mPhotoStartParams = layoutParams;
+ mPhotoView.setLayoutParams(layoutParams);
+ mPhotoView.requestLayout();
+
+ mPhotoView.setImageBitmap(mPhotoBitmap);
+ mPhotoView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ if (mAnimationPending) {
+ mAnimationPending = false;
+ PropertyValuesHolder pvhLeft =
+ PropertyValuesHolder.ofInt("left", mOriginalPos.left, left);
+ PropertyValuesHolder pvhTop =
+ PropertyValuesHolder.ofInt("top", mOriginalPos.top, top);
+ PropertyValuesHolder pvhRight =
+ PropertyValuesHolder.ofInt("right", mOriginalPos.right, right);
+ PropertyValuesHolder pvhBottom =
+ PropertyValuesHolder.ofInt("bottom", mOriginalPos.bottom, bottom);
+ ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(mPhotoView,
+ pvhLeft, pvhTop, pvhRight, pvhBottom).setDuration(
+ PHOTO_EXPAND_DURATION);
+ if (mAnimationListener != null) {
+ anim.addListener(mAnimationListener);
+ }
+ anim.start();
+ }
+ }
+ });
+ attachPhotoHandler();
+ }
+ }
+
+ private LayoutParams getPhotoEndParams() {
+ if (mPhotoEndParams == null) {
+ mPhotoEndParams = new LayoutParams(mPhotoStartParams);
+ if (mExpandPhoto) {
+ Rect bounds = new Rect();
+ mBackdrop.getDrawingRect(bounds);
+ if (bounds.height() > bounds.width()) {
+ //Take up full width.
+ mPhotoEndParams.width = bounds.width();
+ mPhotoEndParams.height = bounds.width();
+ } else {
+ // Take up full height, leaving space for the popup.
+ mPhotoEndParams.height = bounds.height() - 150;
+ mPhotoEndParams.width = bounds.height() - 150;
+ }
+ mPhotoEndParams.topMargin = 0;
+ mPhotoEndParams.leftMargin = 0;
+ mPhotoEndParams.bottomMargin = mPhotoEndParams.height;
+ mPhotoEndParams.rightMargin = mPhotoEndParams.width;
+ }
+ }
+ return mPhotoEndParams;
+ }
+
+ private void animatePhotoOpen() {
+ mAnimationListener = new AnimatorListenerAdapter() {
+ private void capturePhotoPos() {
+ mPhotoView.requestLayout();
+ mOriginalPos.left = mPhotoView.getLeft();
+ mOriginalPos.top = mPhotoView.getTop();
+ mOriginalPos.right = mPhotoView.getRight();
+ mOriginalPos.bottom = mPhotoView.getBottom();
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ capturePhotoPos();
+ if (mPhotoHandler != null) {
+ mPhotoHandler.onClick(mPhotoView);
+ }
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ capturePhotoPos();
+ }
+ };
+ animatePhoto(getPhotoEndParams());
+ }
+
+ private void closePhotoAndFinish() {
+ mAnimationListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ // After the photo animates down, fade it away and finish.
+ ObjectAnimator anim = ObjectAnimator.ofFloat(
+ mPhotoView, "alpha", 0f).setDuration(PHOTO_CONTRACT_DURATION);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ activityFinish();
+ }
+ });
+ anim.start();
+ }
+ };
+
+ // TODO: This won't animate in the right way if the rotation has changed since the activity
+ // was first started.
+ animatePhoto(mPhotoStartParams);
+ animateAwayBackground();
+ }
+
+ private void animatePhoto(MarginLayoutParams to) {
+ // Cancel any existing animation.
+ if (mPhotoAnimator != null) {
+ mPhotoAnimator.cancel();
+ }
+
+ mPhotoView.setLayoutParams(to);
+ mAnimationPending = true;
+ mPhotoView.requestLayout();
+ }
+
+ private void animateInBackground() {
+ ObjectAnimator.ofFloat(mBackdrop, "alpha", 0, 0.5f).setDuration(
+ PHOTO_EXPAND_DURATION).start();
+ }
+
+ private void animateAwayBackground() {
+ ObjectAnimator.ofFloat(mBackdrop, "alpha", 0f).setDuration(
+ BACKDROP_FADEOUT_DURATION).start();
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ if (mCurrentPhotoFile != null) {
+ outState.putString(KEY_CURRENT_PHOTO_FILE, mCurrentPhotoFile.toString());
+ }
+ outState.putBoolean(KEY_SUB_ACTIVITY_IN_PROGRESS, mSubActivityInProgress);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (mPhotoHandler != null) {
+ mSubActivityInProgress = false;
+ if (mPhotoHandler.handlePhotoActivityResult(requestCode, resultCode, data)) {
+ // Clear out any pending photo result.
+ mPendingPhotoResult = null;
+ } else {
+ // User returning to the photo selection activity. Re-display options.
+ mPhotoHandler.onClick(mPhotoView);
+ }
+ } else {
+ // Create a pending photo result to be handled when the photo handler is created.
+ mPendingPhotoResult = new PendingPhotoResult(requestCode, resultCode, data);
+ }
+ }
+
+ private void attachPhotoHandler() {
+ mPhotoHandler = new PhotoHandler(this, mPhotoView,
+ PhotoActionPopup.MODE_NO_PHOTO, mState);
+ if (mPendingPhotoResult != null) {
+ mPhotoHandler.handlePhotoActivityResult(mPendingPhotoResult.mRequestCode,
+ mPendingPhotoResult.mResultCode, mPendingPhotoResult.mData);
+ mPendingPhotoResult = null;
+ } else {
+ animatePhotoOpen();
+ }
+ }
+
+ private final class PhotoHandler extends PhotoSelectionHandler {
+ private PhotoHandler(Context context, View photoView, int photoMode,
+ EntityDeltaList state) {
+ super(context, photoView, photoMode, mIsDirectoryContact, state);
+ setListener(new PhotoListener(context, mIsProfile));
+ }
+
+ private final class PhotoListener extends PhotoActionListener {
+ private final Context mContext;
+ private final boolean mIsProfile;
+ private PhotoListener(Context context, boolean isProfile) {
+ mContext = context;
+ mIsProfile = isProfile;
+ }
+
+ @Override
+ public void startTakePhotoActivity(Intent intent, int requestCode, File photoFile) {
+ mSubActivityInProgress = true;
+ mCurrentPhotoFile = photoFile;
+ startActivityForResult(intent, requestCode);
+ }
+
+ @Override
+ public void startPickFromGalleryActivity(Intent intent, int requestCode) {
+ mSubActivityInProgress = true;
+ startActivityForResult(intent, requestCode);
+ }
+
+ @Override
+ public void onPhotoSelected(Bitmap bitmap) {
+ EntityDeltaList delta = getDeltaForAttachingPhotoToContact(bitmap);
+ Intent intent = ContactSaveService.createSaveContactIntent(mContext, delta,
+ "", 0, mIsProfile, PhotoSelectionActivity.class,
+ ContactEditorActivity.ACTION_SAVE_COMPLETED);
+ startService(intent);
+ finish();
+ }
+
+ @Override
+ public File getCurrentPhotoFile() {
+ return mCurrentPhotoFile;
+ }
+
+ @Override
+ public void onPhotoSelectionDismissed() {
+ if (!mSubActivityInProgress) {
+ finish();
+ }
+ }
+ }
+ }
+
+ private static class PendingPhotoResult {
+ private int mRequestCode;
+ private int mResultCode;
+ private Intent mData;
+ private PendingPhotoResult(int requestCode, int resultCode, Intent data) {
+ mRequestCode = requestCode;
+ mResultCode = resultCode;
+ mData = data;
+ }
+ }
+}
diff --git a/src/com/android/contacts/detail/CarouselTab.java b/src/com/android/contacts/detail/CarouselTab.java
index 677f0ad..cdcf6b2 100644
--- a/src/com/android/contacts/detail/CarouselTab.java
+++ b/src/com/android/contacts/detail/CarouselTab.java
@@ -73,8 +73,9 @@
@Override
public void disableTouchInterceptor() {
- // This shouldn't be called because there is no need to disable the touch interceptor if
- // there is no content within the tab that needs to be clicked.
+ if (mTouchInterceptLayer != null) {
+ mTouchInterceptLayer.setVisibility(View.GONE);
+ }
}
@Override
diff --git a/src/com/android/contacts/detail/ContactDetailDisplayUtils.java b/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
index b81cebf..588e6ff 100644
--- a/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
+++ b/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
@@ -20,6 +20,8 @@
import com.android.contacts.ContactLoader.Result;
import com.android.contacts.ContactPhotoManager;
import com.android.contacts.R;
+import com.android.contacts.activities.PhotoSelectionActivity;
+import com.android.contacts.model.EntityDeltaList;
import com.android.contacts.preference.ContactsPreferences;
import com.android.contacts.util.ContactBadgeUtil;
import com.android.contacts.util.HtmlUtils;
@@ -27,19 +29,23 @@
import com.android.contacts.util.StreamItemPhotoEntry;
import com.google.common.annotations.VisibleForTesting;
+import android.app.Activity;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Entity;
import android.content.Entity.NamedContentValues;
+import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.content.res.Resources.NotFoundException;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.os.Parcelable;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.Organization;
import android.provider.ContactsContract.Data;
@@ -51,6 +57,7 @@
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.AlphaAnimation;
@@ -192,11 +199,20 @@
/**
* Sets the contact photo to display in the given {@link ImageView}. If bitmap is null, the
* default placeholder image is shown.
+ * @param context The context.
+ * @param contactData The contact loader result.
+ * @param photoView The photo view that will host the image and act as the basis for the
+ * photo selector.
+ * @param expandPhotoOnClick Whether the photo should be expanded to fill more of the screen
+ * when clicked.
+ * @return The onclick listener for the photo. When clicked, a photo selection activity will
+ * be launched.
*/
- public static void setPhoto(Context context, Result contactData, ImageView photoView) {
+ public static OnClickListener setPhoto(Context context, Result contactData,
+ ImageView photoView, boolean expandPhotoOnClick) {
if (contactData.isLoadingPhoto()) {
photoView.setImageBitmap(null);
- return;
+ return null;
}
byte[] photo = contactData.getPhotoBinaryData();
Bitmap bitmap = photo != null ? BitmapFactory.decodeByteArray(photo, 0, photo.length)
@@ -209,6 +225,52 @@
photoView.startAnimation(animation);
}
photoView.setImageBitmap(bitmap);
+
+ // Set up the photo to display a full-screen photo selection activity when clicked.
+ OnClickListener clickListener = new PhotoClickListener(context, contactData, bitmap,
+ expandPhotoOnClick);
+ photoView.setOnClickListener(clickListener);
+ return clickListener;
+ }
+
+ private static final class PhotoClickListener implements OnClickListener {
+
+ private final Context mContext;
+ private final Result mContactData;
+ private final Bitmap mPhotoBitmap;
+ private final boolean mExpandPhotoOnClick;
+ public PhotoClickListener(Context context, Result contactData, Bitmap photoBitmap,
+ boolean expandPhotoOnClick) {
+ mContext = context;
+ mContactData = contactData;
+ mPhotoBitmap = photoBitmap;
+ mExpandPhotoOnClick = expandPhotoOnClick;
+ }
+
+ @Override
+ public void onClick(View v) {
+ // Assemble the intent.
+ EntityDeltaList delta = EntityDeltaList.fromIterator(
+ mContactData.getEntities().iterator());
+
+ // Find location and bounds of target view, adjusting based on the
+ // assumed local density.
+ final float appScale =
+ mContext.getResources().getCompatibilityInfo().applicationScale;
+ final int[] pos = new int[2];
+ v.getLocationOnScreen(pos);
+
+ final Rect rect = new Rect();
+ rect.left = (int) (pos[0] * appScale + 0.5f);
+ rect.top = (int) (pos[1] * appScale + 0.5f);
+ rect.right = (int) ((pos[0] + v.getWidth()) * appScale + 0.5f);
+ rect.bottom = (int) ((pos[1] + v.getHeight()) * appScale + 0.5f);
+
+ Intent photoSelectionIntent = PhotoSelectionActivity.buildIntent(mContext,
+ mPhotoBitmap, rect, delta, mContactData.isUserProfile(),
+ mContactData.isDirectoryEntry(), mExpandPhotoOnClick);
+ mContext.startActivity(photoSelectionIntent);
+ }
}
/**
diff --git a/src/com/android/contacts/detail/ContactDetailFragment.java b/src/com/android/contacts/detail/ContactDetailFragment.java
index e74f481..577062e 100644
--- a/src/com/android/contacts/detail/ContactDetailFragment.java
+++ b/src/com/android/contacts/detail/ContactDetailFragment.java
@@ -40,8 +40,8 @@
import com.android.contacts.util.Constants;
import com.android.contacts.util.DataStatus;
import com.android.contacts.util.DateUtils;
-import com.android.contacts.util.StructuredPostalUtils;
import com.android.contacts.util.PhoneCapabilityTester;
+import com.android.contacts.util.StructuredPostalUtils;
import com.android.contacts.widget.TransitionAnimationView;
import com.android.internal.telephony.ITelephony;
import com.google.common.annotations.VisibleForTesting;
@@ -147,7 +147,8 @@
private Listener mListener;
private ContactLoader.Result mContactData;
- private ImageView mStaticPhotoView;
+ private ViewGroup mStaticPhotoContainer;
+ private View mPhotoTouchOverlay;
private ListView mListView;
private ViewAdapter mAdapter;
private Uri mPrimaryPhoneUri = null;
@@ -162,7 +163,8 @@
private final QuickFix[] mPotentialQuickFixes = new QuickFix[] {
new MakeLocalCopyQuickFix(),
- new AddToMyContactsQuickFix() };
+ new AddToMyContactsQuickFix()
+ };
/**
* Device capability: Set during buildEntries and used in the long-press context menu
@@ -280,7 +282,8 @@
mInflater = inflater;
- mStaticPhotoView = (ImageView) mView.findViewById(R.id.photo);
+ mStaticPhotoContainer = (ViewGroup) mView.findViewById(R.id.static_photo_container);
+ mPhotoTouchOverlay = mView.findViewById(R.id.photo_touch_intercept_overlay);
mListView = (ListView) mView.findViewById(android.R.id.list);
mListView.setScrollBarStyle(ListView.SCROLLBARS_OUTSIDE_OVERLAY);
@@ -442,16 +445,22 @@
mContactHasSocialUpdates = !mContactData.getStreamItems().isEmpty();
// Setup the photo if applicable
- if (mStaticPhotoView != null) {
- // The presence of a static photo view is not sufficient to determine whether or not
- // we should show the photo. Check the mShowStaticPhoto flag which can be set by an
+ if (mStaticPhotoContainer != null) {
+ // The presence of a static photo container is not sufficient to determine whether or
+ // not we should show the photo. Check the mShowStaticPhoto flag which can be set by an
// outside class depending on screen size, layout, and whether the contact has social
// updates or not.
if (mShowStaticPhoto) {
- mStaticPhotoView.setVisibility(View.VISIBLE);
- ContactDetailDisplayUtils.setPhoto(mContext, mContactData, mStaticPhotoView);
+ mStaticPhotoContainer.setVisibility(View.VISIBLE);
+ ImageView photoView = (ImageView) mStaticPhotoContainer.findViewById(R.id.photo);
+ OnClickListener listener = ContactDetailDisplayUtils.setPhoto(mContext,
+ mContactData, photoView, !PhoneCapabilityTester.isUsingTwoPanes(mContext));
+ if (mPhotoTouchOverlay != null) {
+ mPhotoTouchOverlay.setVisibility(View.VISIBLE);
+ mPhotoTouchOverlay.setOnClickListener(listener);
+ }
} else {
- mStaticPhotoView.setVisibility(View.GONE);
+ mStaticPhotoContainer.setVisibility(View.GONE);
}
}
@@ -1371,10 +1380,11 @@
/**
* Cache of the children views for a view that displays a header view entry.
*/
- private static class HeaderViewCache {
+ private static class HeaderViewCache implements ViewOverlay {
public final TextView displayNameView;
public final TextView companyView;
public final ImageView photoView;
+ public final View photoOverlayView;
public final CheckBox starredView;
public final int layoutResourceId;
@@ -1382,9 +1392,30 @@
displayNameView = (TextView) view.findViewById(R.id.name);
companyView = (TextView) view.findViewById(R.id.company);
photoView = (ImageView) view.findViewById(R.id.photo);
+ photoOverlayView = view.findViewById(R.id.photo_touch_intercept_overlay);
starredView = (CheckBox) view.findViewById(R.id.star);
layoutResourceId = layoutResourceInflated;
}
+
+ @Override
+ public void setAlphaLayerValue(float alpha) {
+ // Nothing to do.
+ }
+
+ @Override
+ public void enableTouchInterceptor(OnClickListener clickListener) {
+ if (photoOverlayView != null) {
+ photoOverlayView.setVisibility(View.VISIBLE);
+ photoOverlayView.setOnClickListener(clickListener);
+ }
+ }
+
+ @Override
+ public void disableTouchInterceptor() {
+ if (photoOverlayView != null) {
+ photoOverlayView.setVisibility(View.GONE);
+ }
+ }
}
/**
@@ -1498,7 +1529,10 @@
// Set the photo if it should be displayed
if (viewCache.photoView != null) {
- ContactDetailDisplayUtils.setPhoto(mContext, mContactData, viewCache.photoView);
+ OnClickListener listener = ContactDetailDisplayUtils.setPhoto(mContext,
+ mContactData, viewCache.photoView,
+ !PhoneCapabilityTester.isUsingTwoPanes(mContext));
+ viewCache.enableTouchInterceptor(listener);
}
// Set the starred state if it should be displayed
diff --git a/src/com/android/contacts/detail/ContactDetailTabCarousel.java b/src/com/android/contacts/detail/ContactDetailTabCarousel.java
index 045e900..e13d3c8 100644
--- a/src/com/android/contacts/detail/ContactDetailTabCarousel.java
+++ b/src/com/android/contacts/detail/ContactDetailTabCarousel.java
@@ -18,7 +18,9 @@
import com.android.contacts.ContactLoader;
import com.android.contacts.R;
+import com.android.contacts.util.PhoneCapabilityTester;
+import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.util.AttributeSet;
@@ -50,6 +52,8 @@
private ImageView mPhotoView;
private TextView mStatusView;
private ImageView mStatusPhotoView;
+ private boolean mHasPhoto;
+ private OnClickListener mPhotoClickListener;
private Listener mListener;
@@ -153,7 +157,11 @@
private final OnClickListener mAboutTabTouchInterceptListener = new OnClickListener() {
@Override
public void onClick(View v) {
- mListener.onTabSelected(TAB_INDEX_ABOUT);
+ if (mCurrentTab == TAB_INDEX_ABOUT && mPhotoClickListener != null) {
+ mPhotoClickListener.onClick(v);
+ } else {
+ mListener.onTabSelected(TAB_INDEX_ABOUT);
+ }
}
};
@@ -256,9 +264,11 @@
case TAB_INDEX_ABOUT:
mAboutTab.showSelectedState();
mUpdatesTab.showDeselectedState();
+ mUpdatesTab.enableTouchInterceptor(mUpdatesTabTouchInterceptListener);
break;
case TAB_INDEX_UPDATES:
mUpdatesTab.showSelectedState();
+ mUpdatesTab.disableTouchInterceptor();
mAboutTab.showDeselectedState();
break;
default:
@@ -275,10 +285,12 @@
if (contactData == null) {
return;
}
+ mHasPhoto = contactData.getPhotoUri() != null;
// TODO: Move this into the {@link CarouselTab} class when the updates fragment code is more
// finalized
- ContactDetailDisplayUtils.setPhoto(mContext, contactData, mPhotoView);
+ mPhotoClickListener = ContactDetailDisplayUtils.setPhoto(mContext, contactData, mPhotoView,
+ !PhoneCapabilityTester.isUsingTwoPanes(mContext));
ContactDetailDisplayUtils.setSocialSnippet(mContext, contactData, mStatusView,
mStatusPhotoView);
}
diff --git a/src/com/android/contacts/detail/PhotoSelectionHandler.java b/src/com/android/contacts/detail/PhotoSelectionHandler.java
new file mode 100644
index 0000000..72397c0
--- /dev/null
+++ b/src/com/android/contacts/detail/PhotoSelectionHandler.java
@@ -0,0 +1,365 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.contacts.detail;
+
+import com.android.contacts.R;
+import com.android.contacts.editor.PhotoActionPopup;
+import com.android.contacts.model.AccountType;
+import com.android.contacts.model.AccountTypeManager;
+import com.android.contacts.model.EntityDelta;
+import com.android.contacts.model.EntityDelta.ValuesDelta;
+import com.android.contacts.model.EntityDeltaList;
+import com.android.contacts.model.EntityModifier;
+
+import android.app.Activity;
+import android.content.ActivityNotFoundException;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.media.MediaScannerConnection;
+import android.net.Uri;
+import android.os.Environment;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.provider.ContactsContract.DisplayPhoto;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.MediaStore;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.ListPopupWindow;
+import android.widget.PopupWindow.OnDismissListener;
+import android.widget.Toast;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * Handles displaying a photo selection popup for a given photo view and dealing with the results
+ * that come back.
+ */
+public class PhotoSelectionHandler implements OnClickListener {
+
+ private static final String TAG = PhotoSelectionHandler.class.getSimpleName();
+
+ private static final File PHOTO_DIR = new File(
+ Environment.getExternalStorageDirectory() + "/DCIM/Camera");
+
+ private static final String PHOTO_DATE_FORMAT = "'IMG'_yyyyMMdd_HHmmss";
+
+ private static final int REQUEST_CODE_CAMERA_WITH_DATA = 1001;
+ private static final int REQUEST_CODE_PHOTO_PICKED_WITH_DATA = 1002;
+
+ private final Context mContext;
+ private final View mPhotoView;
+ private final int mPhotoMode;
+ private final int mPhotoPickSize;
+ private final EntityDeltaList mState;
+ private final boolean mIsDirectoryContact;
+ private ListPopupWindow mPopup;
+ private AccountType mWritableAccount;
+ private PhotoActionListener mListener;
+
+ public PhotoSelectionHandler(Context context, View photoView, int photoMode,
+ boolean isDirectoryContact, EntityDeltaList state) {
+ mContext = context;
+ mPhotoView = photoView;
+ mPhotoMode = photoMode;
+ mIsDirectoryContact = isDirectoryContact;
+ mState = state;
+ mPhotoPickSize = getPhotoPickSize();
+ }
+
+ public void destroy() {
+ if (mPopup != null) {
+ mPopup.dismiss();
+ }
+ }
+
+ public PhotoActionListener getListener() {
+ return mListener;
+ }
+
+ public void setListener(PhotoActionListener listener) {
+ mListener = listener;
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (mListener != null) {
+ if (getWritableEntityIndex() != -1) {
+ mPopup = PhotoActionPopup.createPopupMenu(
+ mContext, mPhotoView, mListener, mPhotoMode);
+ mPopup.setOnDismissListener(new OnDismissListener() {
+ @Override
+ public void onDismiss() {
+ mListener.onPhotoSelectionDismissed();
+ }
+ });
+ mPopup.show();
+ }
+ }
+ }
+
+ /**
+ * Attempts to handle the given activity result. Returns whether this handler was able to
+ * process the result successfully.
+ * @param requestCode The request code.
+ * @param resultCode The result code.
+ * @param data The intent that was returned.
+ * @return Whether the handler was able to process the result.
+ */
+ public boolean handlePhotoActivityResult(int requestCode, int resultCode, Intent data) {
+ if (resultCode == Activity.RESULT_OK) {
+ switch (requestCode) {
+ case REQUEST_CODE_PHOTO_PICKED_WITH_DATA: {
+ Bitmap bitmap = data.getParcelableExtra("data");
+ mListener.onPhotoSelected(bitmap);
+ return true;
+ }
+ case REQUEST_CODE_CAMERA_WITH_DATA: {
+ doCropPhoto(mListener.getCurrentPhotoFile());
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Return the index of the first entity in the contact data that belongs to a contact-writable
+ * account, or -1 if no such entity exists.
+ */
+ private int getWritableEntityIndex() {
+ // Directory entries are non-writable.
+ if (mIsDirectoryContact) {
+ return -1;
+ }
+
+ // Find the first writable entity.
+ int entityIndex = 0;
+ for (EntityDelta delta : mState) {
+ ContentValues entityValues = delta.getValues().getCompleteValues();
+ String type = entityValues.getAsString(RawContacts.ACCOUNT_TYPE);
+ String dataSet = entityValues.getAsString(RawContacts.DATA_SET);
+ AccountType accountType = AccountTypeManager.getInstance(mContext).getAccountType(
+ type, dataSet);
+ if (accountType.areContactsWritable()) {
+ mWritableAccount = accountType;
+ return entityIndex;
+ }
+ entityIndex++;
+ }
+ return -1;
+ }
+
+ /**
+ * Utility method to retrieve the entity delta for attaching the given bitmap to the contact.
+ * This will attach the photo to the first contact-writable account that provided data to the
+ * contact. It is the caller's responsibility to apply the delta.
+ * @param bitmap The photo to use.
+ * @return An entity delta list that can be applied to associate the bitmap with the contact,
+ * or null if the photo could not be parsed or none of the accounts associated with the
+ * contact are writable.
+ */
+ public EntityDeltaList getDeltaForAttachingPhotoToContact(Bitmap bitmap) {
+ // Find the first writable entity.
+ int writableEntityIndex = getWritableEntityIndex();
+ if (writableEntityIndex != -1) {
+ // Convert the photo to a byte array.
+ final int size = bitmap.getWidth() * bitmap.getHeight() * 4;
+ final ByteArrayOutputStream out = new ByteArrayOutputStream(size);
+
+ try {
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
+ out.flush();
+ out.close();
+ } catch (IOException e) {
+ Log.w(TAG, "Unable to serialize photo: " + e.toString());
+ return null;
+ }
+
+ // Note - guaranteed to have contact data if we have a writable entity index.
+ EntityDelta delta = mState.get(writableEntityIndex);
+ ValuesDelta child = EntityModifier.ensureKindExists(
+ delta, mWritableAccount, Photo.CONTENT_ITEM_TYPE);
+ child.put(Photo.PHOTO, out.toByteArray());
+ child.setFromTemplate(false);
+ child.put(Photo.IS_SUPER_PRIMARY, 1);
+
+ return mState;
+ }
+ return null;
+ }
+
+ /**
+ * Sends a newly acquired photo to Gallery for cropping
+ */
+ private void doCropPhoto(File f) {
+ try {
+ // Add the image to the media store
+ MediaScannerConnection.scanFile(
+ mContext,
+ new String[] { f.getAbsolutePath() },
+ new String[] { null },
+ null);
+
+ // Launch gallery to crop the photo
+ final Intent intent = getCropImageIntent(Uri.fromFile(f));
+ mListener.startPickFromGalleryActivity(intent, REQUEST_CODE_PHOTO_PICKED_WITH_DATA);
+ } catch (Exception e) {
+ Log.e(TAG, "Cannot crop image", e);
+ Toast.makeText(mContext, R.string.photoPickerNotFoundText, Toast.LENGTH_LONG).show();
+ }
+ }
+
+ private String getPhotoFileName() {
+ Date date = new Date(System.currentTimeMillis());
+ SimpleDateFormat dateFormat = new SimpleDateFormat(PHOTO_DATE_FORMAT);
+ return dateFormat.format(date) + ".jpg";
+ }
+
+ private int getPhotoPickSize() {
+ // Note that this URI is safe to call on the UI thread.
+ Cursor c = mContext.getContentResolver().query(DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI,
+ new String[]{DisplayPhoto.DISPLAY_MAX_DIM}, null, null, null);
+ try {
+ c.moveToFirst();
+ return c.getInt(0);
+ } finally {
+ c.close();
+ }
+ }
+
+ /**
+ * Constructs an intent for picking a photo from Gallery, cropping it and returning the bitmap.
+ */
+ private Intent getPhotoPickIntent() {
+ Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);
+ intent.setType("image/*");
+ intent.putExtra("crop", "true");
+ intent.putExtra("aspectX", 1);
+ intent.putExtra("aspectY", 1);
+ intent.putExtra("outputX", mPhotoPickSize);
+ intent.putExtra("outputY", mPhotoPickSize);
+ intent.putExtra("return-data", true);
+ return intent;
+ }
+
+ /**
+ * Constructs an intent for image cropping.
+ */
+ private Intent getCropImageIntent(Uri photoUri) {
+ Intent intent = new Intent("com.android.camera.action.CROP");
+ intent.setDataAndType(photoUri, "image/*");
+ intent.putExtra("crop", "true");
+ intent.putExtra("aspectX", 1);
+ intent.putExtra("aspectY", 1);
+ intent.putExtra("outputX", mPhotoPickSize);
+ intent.putExtra("outputY", mPhotoPickSize);
+ intent.putExtra("return-data", true);
+ return intent;
+ }
+
+ /**
+ * Constructs an intent for capturing a photo and storing it in a temporary file.
+ */
+ public static Intent getTakePhotoIntent(File f) {
+ Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE, null);
+ intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(f));
+ return intent;
+ }
+
+ public abstract class PhotoActionListener implements PhotoActionPopup.Listener {
+ @Override
+ public void onUseAsPrimaryChosen() {
+ // No default implementation.
+ }
+
+ @Override
+ public void onRemovePictureChosen() {
+ // No default implementation.
+ }
+
+ @Override
+ public void onTakePhotoChosen() {
+ try {
+ // Launch camera to take photo for selected contact
+ PHOTO_DIR.mkdirs();
+ File photoFile = new File(PHOTO_DIR, getPhotoFileName());
+ startTakePhotoActivity(getTakePhotoIntent(photoFile),
+ REQUEST_CODE_CAMERA_WITH_DATA, photoFile);
+ } catch (ActivityNotFoundException e) {
+ Toast.makeText(mContext, R.string.photoPickerNotFoundText,
+ Toast.LENGTH_LONG).show();
+ }
+ }
+
+ @Override
+ public void onPickFromGalleryChosen() {
+ try {
+ // Launch picker to choose photo for selected contact
+ final Intent intent = getPhotoPickIntent();
+ startPickFromGalleryActivity(intent, REQUEST_CODE_PHOTO_PICKED_WITH_DATA);
+ } catch (ActivityNotFoundException e) {
+ Toast.makeText(mContext, R.string.photoPickerNotFoundText,
+ Toast.LENGTH_LONG).show();
+ }
+ }
+
+ /**
+ * Should initiate an activity to take a photo using the camera.
+ * @param intent The image capture intent.
+ * @param requestCode The request code to use, suitable for handling by
+ * {@link PhotoSelectionHandler#handlePhotoActivityResult(int, int, Intent)}.
+ * @param photoFile The file path that will be used to store the photo. This is generally
+ * what should be returned by
+ * {@link PhotoSelectionHandler.PhotoActionListener#getCurrentPhotoFile()}.
+ */
+ public abstract void startTakePhotoActivity(Intent intent, int requestCode, File photoFile);
+
+ /**
+ * Should initiate an activity pick a photo from the gallery.
+ * @param intent The image capture intent.
+ * @param requestCode The request code to use, suitable for handling by
+ * {@link PhotoSelectionHandler#handlePhotoActivityResult(int, int, Intent)}.
+ */
+ public abstract void startPickFromGalleryActivity(Intent intent, int requestCode);
+
+ /**
+ * Called when the user has completed selection of a photo.
+ * @param bitmap The selected and cropped photo.
+ */
+ public abstract void onPhotoSelected(Bitmap bitmap);
+
+ /**
+ * Gets the current photo file that is being interacted with. It is the activity or
+ * fragment's responsibility to maintain this in saved state, since this handler instance
+ * will not survive rotation.
+ */
+ public abstract File getCurrentPhotoFile();
+
+ /**
+ * Called when the photo selection dialog is dismissed.
+ */
+ public abstract void onPhotoSelectionDismissed();
+ }
+}
diff --git a/src/com/android/contacts/detail/TransformableImageView.java b/src/com/android/contacts/detail/TransformableImageView.java
new file mode 100644
index 0000000..6edc42b
--- /dev/null
+++ b/src/com/android/contacts/detail/TransformableImageView.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.contacts.detail;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageView;
+
+/**
+ * Extension to ImageView that handles cropping during resize animations.
+ */
+public class TransformableImageView extends ImageView {
+
+ public TransformableImageView(Context context) {
+ super(context);
+ }
+
+ public TransformableImageView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public TransformableImageView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ int saveCount = canvas.getSaveCount();
+ canvas.save();
+ canvas.translate(mPaddingLeft, mPaddingTop);
+ Matrix drawMatrix = new Matrix();
+ int dwidth = getDrawable().getIntrinsicWidth();
+ int dheight = getDrawable().getIntrinsicHeight();
+
+ int vwidth = getWidth() - mPaddingLeft - mPaddingRight;
+ int vheight = getHeight() - mPaddingTop - mPaddingBottom;
+ float scale;
+ float dx = 0, dy = 0;
+
+ if (dwidth * vheight > vwidth * dheight) {
+ scale = (float) vheight / (float) dheight;
+ dx = (vwidth - dwidth * scale) * 0.5f;
+ } else {
+ scale = (float) vwidth / (float) dwidth;
+ dy = (vheight - dheight * scale) * 0.5f;
+ }
+
+ drawMatrix.setScale(scale, scale);
+ drawMatrix.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));
+ canvas.concat(drawMatrix);
+ getDrawable().draw(canvas);
+ canvas.restoreToCount(saveCount);
+ }
+}
diff --git a/src/com/android/contacts/editor/ContactEditorFragment.java b/src/com/android/contacts/editor/ContactEditorFragment.java
index da5237f..5448005 100644
--- a/src/com/android/contacts/editor/ContactEditorFragment.java
+++ b/src/com/android/contacts/editor/ContactEditorFragment.java
@@ -23,6 +23,7 @@
import com.android.contacts.activities.ContactEditorAccountsChangedActivity;
import com.android.contacts.activities.ContactEditorActivity;
import com.android.contacts.activities.JoinContactActivity;
+import com.android.contacts.detail.PhotoSelectionHandler;
import com.android.contacts.editor.AggregationSuggestionEngine.Suggestion;
import com.android.contacts.editor.Editor.EditorListener;
import com.android.contacts.model.AccountType;
@@ -44,7 +45,6 @@
import android.app.Fragment;
import android.app.LoaderManager;
import android.app.LoaderManager.LoaderCallbacks;
-import android.content.ActivityNotFoundException;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
@@ -56,10 +56,8 @@
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Rect;
-import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.Bundle;
-import android.os.Environment;
import android.os.SystemClock;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Event;
@@ -67,11 +65,9 @@
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.DisplayPhoto;
import android.provider.ContactsContract.Groups;
import android.provider.ContactsContract.Intents;
import android.provider.ContactsContract.RawContacts;
-import android.provider.MediaStore;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -87,11 +83,9 @@
import android.widget.Toast;
import java.io.File;
-import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
-import java.util.Date;
import java.util.List;
public class ContactEditorFragment extends Fragment implements
@@ -192,26 +186,19 @@
}
private static final int REQUEST_CODE_JOIN = 0;
- private static final int REQUEST_CODE_CAMERA_WITH_DATA = 1;
- private static final int REQUEST_CODE_PHOTO_PICKED_WITH_DATA = 2;
- private static final int REQUEST_CODE_ACCOUNTS_CHANGED = 3;
+ private static final int REQUEST_CODE_ACCOUNTS_CHANGED = 1;
private Bitmap mPhoto = null;
private long mRawContactIdRequestingPhoto = -1;
private long mRawContactIdRequestingPhotoAfterLoad = -1;
+ private PhotoSelectionHandler mPhotoSelectionHandler;
private final EntityDeltaComparator mComparator = new EntityDeltaComparator();
- private static final File PHOTO_DIR = new File(
- Environment.getExternalStorageDirectory() + "/DCIM/Camera");
-
private Cursor mGroupMetaData;
private File mCurrentPhotoFile;
- // Height/width (in pixels) to request for the photo - queried from the provider.
- private int mPhotoPickSize;
-
private Context mContext;
private String mAction;
private Uri mLookupUri;
@@ -322,7 +309,6 @@
super.onAttach(activity);
mContext = activity;
mEditorUtils = ContactEditorUtils.getInstance(mContext);
- loadPhotoPickSize();
}
@Override
@@ -729,8 +715,9 @@
editor.setState(entity, type, mViewIdGenerator, isEditingUserProfile());
- editor.getPhotoEditor().setEditorListener(
- new PhotoEditorListener(editor, type.areContactsWritable()));
+ // Set up the photo handler.
+ bindPhotoHandler(editor, type, mState);
+
if (editor instanceof RawContactEditorView) {
final RawContactEditorView rawContactEditor = (RawContactEditorView) editor;
EditorListener listener = new EditorListener() {
@@ -776,7 +763,32 @@
// Activity can be null if we have been detached from the Activity
final Activity activity = getActivity();
if (activity != null) activity.invalidateOptionsMenu();
+ }
+ private void bindPhotoHandler(BaseRawContactEditorView editor, AccountType type,
+ EntityDeltaList state) {
+ final int mode;
+ if (type.areContactsWritable()) {
+ if (editor.hasSetPhoto()) {
+ if (hasMoreThanOnePhoto()) {
+ mode = PhotoActionPopup.MODE_PHOTO_ALLOW_PRIMARY;
+ } else {
+ mode = PhotoActionPopup.MODE_PHOTO_DISALLOW_PRIMARY;
+ }
+ } else {
+ mode = PhotoActionPopup.MODE_NO_PHOTO;
+ }
+ } else {
+ if (editor.hasSetPhoto() && hasMoreThanOnePhoto()) {
+ mode = PhotoActionPopup.MODE_READ_ONLY_ALLOW_PRIMARY;
+ } else {
+ // Read-only and either no photo or the only photo ==> no options
+ return;
+ }
+ }
+ mPhotoSelectionHandler = new PhotoHandler(mContext, editor, mode, state);
+ editor.getPhotoEditor().setEditorListener(
+ (PhotoHandler.PhotoEditorListener) mPhotoSelectionHandler.getListener());
}
private void bindGroupMetaData() {
@@ -926,32 +938,6 @@
return save(SaveMode.JOIN);
}
- private void loadPhotoPickSize() {
- Cursor c = mContext.getContentResolver().query(DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI,
- new String[]{DisplayPhoto.DISPLAY_MAX_DIM}, null, null, null);
- try {
- c.moveToFirst();
- mPhotoPickSize = c.getInt(0);
- } finally {
- c.close();
- }
- }
-
- /**
- * Constructs an intent for picking a photo from Gallery, cropping it and returning the bitmap.
- */
- public Intent getPhotoPickIntent() {
- Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);
- intent.setType("image/*");
- intent.putExtra("crop", "true");
- intent.putExtra("aspectX", 1);
- intent.putExtra("aspectY", 1);
- intent.putExtra("outputX", mPhotoPickSize);
- intent.putExtra("outputY", mPhotoPickSize);
- intent.putExtra("return-data", true);
- return intent;
- }
-
/**
* Check if our internal {@link #mState} is valid, usually checked before
* performing user actions.
@@ -961,61 +947,6 @@
}
/**
- * Create a file name for the icon photo using current time.
- */
- private String getPhotoFileName() {
- Date date = new Date(System.currentTimeMillis());
- SimpleDateFormat dateFormat = new SimpleDateFormat("'IMG'_yyyyMMdd_HHmmss");
- return dateFormat.format(date) + ".jpg";
- }
-
- /**
- * Constructs an intent for capturing a photo and storing it in a temporary file.
- */
- public static Intent getTakePickIntent(File f) {
- Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE, null);
- intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(f));
- return intent;
- }
-
- /**
- * Sends a newly acquired photo to Gallery for cropping
- */
- protected void doCropPhoto(File f) {
- try {
- // Add the image to the media store
- MediaScannerConnection.scanFile(
- mContext,
- new String[] { f.getAbsolutePath() },
- new String[] { null },
- null);
-
- // Launch gallery to crop the photo
- final Intent intent = getCropImageIntent(Uri.fromFile(f));
- mStatus = Status.SUB_ACTIVITY;
- startActivityForResult(intent, REQUEST_CODE_PHOTO_PICKED_WITH_DATA);
- } catch (Exception e) {
- Log.e(TAG, "Cannot crop image", e);
- Toast.makeText(mContext, R.string.photoPickerNotFoundText, Toast.LENGTH_LONG).show();
- }
- }
-
- /**
- * Constructs an intent for image cropping.
- */
- public Intent getCropImageIntent(Uri photoUri) {
- Intent intent = new Intent("com.android.camera.action.CROP");
- intent.setDataAndType(photoUri, "image/*");
- intent.putExtra("crop", "true");
- intent.putExtra("aspectX", 1);
- intent.putExtra("aspectY", 1);
- intent.putExtra("outputX", mPhotoPickSize);
- intent.putExtra("outputY", mPhotoPickSize);
- intent.putExtra("return-data", true);
- return intent;
- }
-
- /**
* Saves or creates the contact based on the mode, and if successful
* finishes the activity.
*/
@@ -1591,27 +1522,13 @@
mStatus = Status.EDITING;
}
- switch (requestCode) {
- case REQUEST_CODE_PHOTO_PICKED_WITH_DATA: {
- // Ignore failed requests
- if (resultCode != Activity.RESULT_OK) return;
- // As we are coming back to this view, the editor will be reloaded automatically,
- // which will cause the photo that is set here to disappear. To prevent this,
- // we remember to set a flag which is interpreted after loading.
- // This photo is set here already to reduce flickering.
- mPhoto = data.getParcelableExtra("data");
- setPhoto(mRawContactIdRequestingPhoto, mPhoto);
- mRawContactIdRequestingPhotoAfterLoad = mRawContactIdRequestingPhoto;
- mRawContactIdRequestingPhoto = -1;
+ // See if the photo selection handler handles this result.
+ if (mPhotoSelectionHandler != null && mPhotoSelectionHandler.handlePhotoActivityResult(
+ requestCode, resultCode, data)) {
+ return;
+ }
- break;
- }
- case REQUEST_CODE_CAMERA_WITH_DATA: {
- // Ignore failed requests
- if (resultCode != Activity.RESULT_OK) return;
- doCropPhoto(mCurrentPhotoFile);
- break;
- }
+ switch (requestCode) {
case REQUEST_CODE_JOIN: {
// Ignore failed requests
if (resultCode != Activity.RESULT_OK) return;
@@ -1770,111 +1687,97 @@
save(SaveMode.SPLIT);
}
- private final class PhotoEditorListener
- implements EditorListener, PhotoActionPopup.Listener {
- private final BaseRawContactEditorView mEditor;
- private final boolean mAccountWritable;
-
- private PhotoEditorListener(BaseRawContactEditorView editor, boolean accountWritable) {
- mEditor = editor;
- mAccountWritable = accountWritable;
+ /**
+ * Custom photo handler for the editor. The inner listener that this creates also has a
+ * reference to the editor and acts as an {@link EditorListener}, and uses that editor to hold
+ * state information in several of the listener methods.
+ */
+ private final class PhotoHandler extends PhotoSelectionHandler {
+ public PhotoHandler(Context context, BaseRawContactEditorView editor, int photoMode,
+ EntityDeltaList state) {
+ super(context, editor.getPhotoEditor(), photoMode, false, state);
+ setListener(new PhotoEditorListener(editor));
}
- @Override
- public void onRequest(int request) {
- if (!hasValidState()) return;
+ private final class PhotoEditorListener extends PhotoSelectionHandler.PhotoActionListener
+ implements EditorListener {
+ private final BaseRawContactEditorView mEditor;
- if (request == EditorListener.REQUEST_PICK_PHOTO) {
- // Determine mode
- final int mode;
- if (mAccountWritable) {
- if (mEditor.hasSetPhoto()) {
- if (hasMoreThanOnePhoto()) {
- mode = PhotoActionPopup.MODE_PHOTO_ALLOW_PRIMARY;
- } else {
- mode = PhotoActionPopup.MODE_PHOTO_DISALLOW_PRIMARY;
- }
- } else {
- mode = PhotoActionPopup.MODE_NO_PHOTO;
- }
- } else {
- if (mEditor.hasSetPhoto() && hasMoreThanOnePhoto()) {
- mode = PhotoActionPopup.MODE_READ_ONLY_ALLOW_PRIMARY;
- } else {
- // Read-only and either no photo or the only photo ==> no options
- return;
- }
- }
- PhotoActionPopup.createPopupMenu(mContext, mEditor.getPhotoEditor(), this, mode)
- .show();
+ private PhotoEditorListener(BaseRawContactEditorView editor) {
+ mEditor = editor;
}
- }
- @Override
- public void onDeleteRequested(Editor removedEditor) {
- // The picture cannot be deleted, it can only be removed, which is handled by
- // onRemovePictureChosen()
- }
+ @Override
+ public void onRequest(int request) {
+ if (!hasValidState()) return;
- /**
- * User has chosen to set the selected photo as the (super) primary photo
- */
- @Override
- public void onUseAsPrimaryChosen() {
- // Set the IsSuperPrimary for each editor
- int count = mContent.getChildCount();
- for (int i = 0; i < count; i++) {
- final View childView = mContent.getChildAt(i);
- if (childView instanceof BaseRawContactEditorView) {
- final BaseRawContactEditorView editor = (BaseRawContactEditorView) childView;
- final PhotoEditorView photoEditor = editor.getPhotoEditor();
- photoEditor.setSuperPrimary(editor == mEditor);
+ if (request == EditorListener.REQUEST_PICK_PHOTO) {
+ onClick(mEditor.getPhotoEditor());
}
}
- }
- /**
- * User has chosen to remove a picture
- */
- @Override
- public void onRemovePictureChosen() {
- mEditor.setPhotoBitmap(null);
- }
-
- /**
- * Launches Camera to take a picture and store it in a file.
- */
- @Override
- public void onTakePhotoChosen() {
- mRawContactIdRequestingPhoto = mEditor.getRawContactId();
- try {
- // Launch camera to take photo for selected contact
- PHOTO_DIR.mkdirs();
- mCurrentPhotoFile = new File(PHOTO_DIR, getPhotoFileName());
- final Intent intent = getTakePickIntent(mCurrentPhotoFile);
-
- mStatus = Status.SUB_ACTIVITY;
- startActivityForResult(intent, REQUEST_CODE_CAMERA_WITH_DATA);
- } catch (ActivityNotFoundException e) {
- Toast.makeText(mContext, R.string.photoPickerNotFoundText,
- Toast.LENGTH_LONG).show();
+ @Override
+ public void onDeleteRequested(Editor removedEditor) {
+ // The picture cannot be deleted, it can only be removed, which is handled by
+ // onRemovePictureChosen()
}
- }
- /**
- * Launches Gallery to pick a photo.
- */
- @Override
- public void onPickFromGalleryChosen() {
- mRawContactIdRequestingPhoto = mEditor.getRawContactId();
- try {
- // Launch picker to choose photo for selected contact
- final Intent intent = getPhotoPickIntent();
+ /**
+ * User has chosen to set the selected photo as the (super) primary photo
+ */
+ @Override
+ public void onUseAsPrimaryChosen() {
+ // Set the IsSuperPrimary for each editor
+ int count = mContent.getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View childView = mContent.getChildAt(i);
+ if (childView instanceof BaseRawContactEditorView) {
+ final BaseRawContactEditorView editor =
+ (BaseRawContactEditorView) childView;
+ final PhotoEditorView photoEditor = editor.getPhotoEditor();
+ photoEditor.setSuperPrimary(editor == mEditor);
+ }
+ }
+ }
+
+ /**
+ * User has chosen to remove a picture
+ */
+ @Override
+ public void onRemovePictureChosen() {
+ mEditor.setPhotoBitmap(null);
+ }
+
+ @Override
+ public void startTakePhotoActivity(Intent intent, int requestCode, File photoFile) {
+ mRawContactIdRequestingPhoto = mEditor.getRawContactId();
mStatus = Status.SUB_ACTIVITY;
- startActivityForResult(intent, REQUEST_CODE_PHOTO_PICKED_WITH_DATA);
- } catch (ActivityNotFoundException e) {
- Toast.makeText(mContext, R.string.photoPickerNotFoundText,
- Toast.LENGTH_LONG).show();
+ mCurrentPhotoFile = photoFile;
+ startActivityForResult(intent, requestCode);
+ }
+
+ @Override
+ public void startPickFromGalleryActivity(Intent intent, int requestCode) {
+ mRawContactIdRequestingPhoto = mEditor.getRawContactId();
+ mStatus = Status.SUB_ACTIVITY;
+ startActivityForResult(intent, requestCode);
+ }
+
+ @Override
+ public void onPhotoSelected(Bitmap bitmap) {
+ setPhoto(mRawContactIdRequestingPhoto, bitmap);
+ mRawContactIdRequestingPhotoAfterLoad = mRawContactIdRequestingPhoto;
+ mRawContactIdRequestingPhoto = -1;
+ }
+
+ @Override
+ public File getCurrentPhotoFile() {
+ return mCurrentPhotoFile;
+ }
+
+ @Override
+ public void onPhotoSelectionDismissed() {
+ // Nothing to do.
}
}
}
diff --git a/src/com/android/contacts/editor/PhotoActionPopup.java b/src/com/android/contacts/editor/PhotoActionPopup.java
index cca6f9d..029212f 100644
--- a/src/com/android/contacts/editor/PhotoActionPopup.java
+++ b/src/com/android/contacts/editor/PhotoActionPopup.java
@@ -76,8 +76,6 @@
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
final ChoiceListItem choice = choices.get(position);
- listPopupWindow.dismiss();
-
switch (choice.getId()) {
case ChoiceListItem.ID_USE_AS_PRIMARY:
listener.onUseAsPrimaryChosen();
@@ -92,6 +90,8 @@
listener.onPickFromGalleryChosen();
break;
}
+
+ listPopupWindow.dismiss();
}
};
diff --git a/src/com/android/contacts/model/EntityModifier.java b/src/com/android/contacts/model/EntityModifier.java
index 289ca54..ef5d304 100644
--- a/src/com/android/contacts/model/EntityModifier.java
+++ b/src/com/android/contacts/model/EntityModifier.java
@@ -103,19 +103,28 @@
/**
* Ensure that at least one of the given {@link DataKind} exists in the
* given {@link EntityDelta} state, and try creating one if none exist.
+ * @return The child (either newly created or the first existing one), or null if the
+ * account doesn't support this {@link DataKind}.
*/
- public static void ensureKindExists(
+ public static ValuesDelta ensureKindExists(
EntityDelta state, AccountType accountType, String mimeType) {
final DataKind kind = accountType.getKindForMimetype(mimeType);
final boolean hasChild = state.getMimeEntriesCount(mimeType, true) > 0;
- if (!hasChild && kind != null) {
- // Create child when none exists and valid kind
- final ValuesDelta child = insertChild(state, kind);
- if (kind.mimeType.equals(Photo.CONTENT_ITEM_TYPE)) {
- child.setFromTemplate(true);
+ if (kind != null) {
+ if (hasChild) {
+ // Return the first entry.
+ return state.getMimeEntries(mimeType).get(0);
+ } else {
+ // Create child when none exists and valid kind
+ final ValuesDelta child = insertChild(state, kind);
+ if (kind.mimeType.equals(Photo.CONTENT_ITEM_TYPE)) {
+ child.setFromTemplate(true);
+ }
+ return child;
}
}
+ return null;
}
/**