Smooth scrolling to selection after new/edit/copy

Change-Id: I8dd5702c6dc3a46b35fea1a3e851238f6e8a38f0
diff --git a/src/com/android/contacts/activities/ContactBrowserActivity.java b/src/com/android/contacts/activities/ContactBrowserActivity.java
index 6a68547..55e9e37 100644
--- a/src/com/android/contacts/activities/ContactBrowserActivity.java
+++ b/src/com/android/contacts/activities/ContactBrowserActivity.java
@@ -75,6 +75,7 @@
 
     private static final int SUBACTIVITY_NEW_CONTACT = 2;
     private static final int SUBACTIVITY_DISPLAY_GROUP = 3;
+    private static final int SUBACTIVITY_EDIT_CONTACT = 4;
 
     private DialogManager mDialogManager = new DialogManager(this);
 
@@ -190,6 +191,7 @@
             }
             mListFragment.setSelectedContactUri(uri);
             setupContactDetailFragment(uri);
+            mListFragment.scrollToSelectedContact();
         }
     }
 
@@ -281,6 +283,10 @@
             return;
         }
 
+        // If we are closing the editor, it's a good idea to scroll the list
+        // to the contact we have just finished editing.
+        boolean scrollToSelection = mEditorFragment != null;
+
         // No editor here
         closeEditorFragment(true);
 
@@ -308,6 +314,9 @@
                     .replace(R.id.detail_container, mEmptyFragment)
                     .commit();
         }
+        if (scrollToSelection) {
+            mListFragment.scrollToSelectedContact();
+        }
     }
 
     private void setupContactEditorFragment(Uri contactLookupUri) {
@@ -470,7 +479,7 @@
                 if (extras != null) {
                     intent.putExtras(extras);
                 }
-                startActivity(intent);
+                startActivityForResult(intent, SUBACTIVITY_EDIT_CONTACT);
             }
         }
 
@@ -511,7 +520,8 @@
 
         @Override
         public void onEditRequested(Uri contactLookupUri) {
-            startActivity(new Intent(Intent.ACTION_EDIT, contactLookupUri));
+            startActivityForResult(
+                    new Intent(Intent.ACTION_EDIT, contactLookupUri), SUBACTIVITY_EDIT_CONTACT);
         }
 
         @Override
@@ -715,6 +725,11 @@
     @Override
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
         switch (requestCode) {
+            case SUBACTIVITY_EDIT_CONTACT: {
+                mListFragment.scrollToSelectedContact();
+                break;
+            }
+
             case SUBACTIVITY_NEW_CONTACT: {
                 if (resultCode == RESULT_OK && mContactContentDisplayed) {
                     final Uri newContactUri = data.getData();
diff --git a/src/com/android/contacts/list/ContactBrowseListFragment.java b/src/com/android/contacts/list/ContactBrowseListFragment.java
index fe82129..2dfe10d 100644
--- a/src/com/android/contacts/list/ContactBrowseListFragment.java
+++ b/src/com/android/contacts/list/ContactBrowseListFragment.java
@@ -16,6 +16,7 @@
 package com.android.contacts.list;
 
 import com.android.contacts.R;
+import com.android.contacts.widget.ListViewUtils;
 
 import android.app.LoaderManager.LoaderCallbacks;
 import android.content.CursorLoader;
@@ -27,6 +28,7 @@
 import android.provider.ContactsContract.Contacts;
 import android.provider.ContactsContract.Directory;
 import android.text.TextUtils;
+import android.widget.ListView;
 
 /**
  * Fragment containing a contact list used for browsing (as compared to
@@ -42,6 +44,7 @@
     private Uri mSelectedContactUri;
     private long mSelectedContactDirectoryId;
     private String mSelectedContactLookupKey;
+    private boolean mScrollToSelectionRequested;
 
     private OnContactBrowserActionListener mListener;
 
@@ -229,4 +232,34 @@
         super.finish();
         mListener.onFinishAction();
     }
+
+    public void scrollToSelectedContact() {
+        mScrollToSelectionRequested = true;
+        scrollToSelectedContactIfNeeded();
+    }
+
+    @Override
+    protected void completeRestoreInstanceState() {
+        super.completeRestoreInstanceState();
+        scrollToSelectedContactIfNeeded();
+    }
+
+    private void scrollToSelectedContactIfNeeded() {
+        if (!mScrollToSelectionRequested) {
+            return;
+        }
+
+        ContactListAdapter adapter = getAdapter();
+        if (adapter == null) {
+            return;
+        }
+
+        int position = adapter.getSelectedContactPosition();
+        if (position != -1) {
+            mScrollToSelectionRequested = false;
+            ListView listView = getListView();
+            ListViewUtils.smartSmoothScrollToPosition(
+                    listView, position + listView.getHeaderViewsCount());
+        }
+    }
 }
diff --git a/src/com/android/contacts/list/ContactEntryListFragment.java b/src/com/android/contacts/list/ContactEntryListFragment.java
index 235f8f3..ca5a9a8 100644
--- a/src/com/android/contacts/list/ContactEntryListFragment.java
+++ b/src/com/android/contacts/list/ContactEntryListFragment.java
@@ -228,7 +228,8 @@
         outState.putParcelable(KEY_REQUEST, mRequest);
 
         if (mListView != null) {
-            outState.putParcelable(KEY_LIST_STATE, mListView.onSaveInstanceState());
+            mListState = mListView.onSaveInstanceState();
+            outState.putParcelable(KEY_LIST_STATE, mListState);
         }
     }
 
diff --git a/src/com/android/contacts/list/ContactListAdapter.java b/src/com/android/contacts/list/ContactListAdapter.java
index c6ceb18..f2b0da7 100644
--- a/src/com/android/contacts/list/ContactListAdapter.java
+++ b/src/com/android/contacts/list/ContactListAdapter.java
@@ -228,4 +228,48 @@
         view.showSnippet(cursor, CONTACT_SNIPPET_MIMETYPE_COLUMN_INDEX,
                 CONTACT_SNIPPET_DATA1_COLUMN_INDEX, CONTACT_SNIPPET_DATA4_COLUMN_INDEX);
     }
+
+    public int getSelectedContactPosition() {
+        if (mSelectedContactLookupKey == null) {
+            return -1;
+        }
+
+        Cursor cursor = null;
+        int partitionIndex = -1;
+        int partitionCount = getPartitionCount();
+        for (int i = 0; i < partitionCount; i++) {
+            DirectoryPartition partition = (DirectoryPartition) getPartition(i);
+            if (partition.getDirectoryId() == mSelectedContactDirectoryId) {
+                partitionIndex = i;
+                break;
+            }
+        }
+        if (partitionIndex == -1) {
+            return -1;
+        }
+
+        cursor = getCursor(partitionIndex);
+        if (cursor == null) {
+            return -1;
+        }
+
+        cursor.moveToPosition(-1);      // Reset cursor
+        int offset = -1;
+        while (cursor.moveToNext()) {
+            String lookupKey = cursor.getString(CONTACT_LOOKUP_KEY_COLUMN_INDEX);
+            if (mSelectedContactLookupKey.equals(lookupKey)) {
+                offset = cursor.getPosition();
+                break;
+            }
+        }
+        if (offset == -1) {
+            return -1;
+        }
+
+        int position = getPositionForPartition(partitionIndex) + offset;
+        if (hasHeader(partitionIndex)) {
+            position++;
+        }
+        return position;
+    }
 }
diff --git a/src/com/android/contacts/views/editor/ContactEditorFragment.java b/src/com/android/contacts/views/editor/ContactEditorFragment.java
index 26f5375..2d4af4f 100644
--- a/src/com/android/contacts/views/editor/ContactEditorFragment.java
+++ b/src/com/android/contacts/views/editor/ContactEditorFragment.java
@@ -668,6 +668,11 @@
             return false;
         }
 
+        // If we are about to close the editor - there is no need to refresh the data
+        if (saveMode == SaveMode.CLOSE) {
+            getLoaderManager().stopLoader(LOADER_DATA);
+        }
+
         mStatus = Status.SAVING;
         final PersistTask task = new PersistTask(this, saveMode);
         task.execute(mState);
diff --git a/src/com/android/contacts/widget/ListViewUtils.java b/src/com/android/contacts/widget/ListViewUtils.java
new file mode 100644
index 0000000..4003122
--- /dev/null
+++ b/src/com/android/contacts/widget/ListViewUtils.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2010 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.widget;
+
+import android.widget.ListView;
+
+/**
+ * Utility methods for working with ListView.
+ */
+public final class ListViewUtils {
+
+    /**
+     * Brings the specified position to view by performing a jump-scroll maneuver:
+     * first it jumps to some position near the one requested and then does a smooth
+     * scroll to the requested position.  This creates an impression of full smooth
+     * scrolling without actually traversing the entire list.
+     */
+    public static void smartSmoothScrollToPosition(final ListView listView, final int position) {
+        listView.post(new Runnable() {
+
+            @Override
+            public void run() {
+                int firstPosition = listView.getFirstVisiblePosition();
+                int lastPosition = listView.getLastVisiblePosition();
+                if (position >= firstPosition && position <= lastPosition) {
+                    return; // Already on screen
+                }
+
+                // We will first position the list a couple of screens before or after
+                // the new selection and then scroll smoothly to it.
+                int twoScreens = (lastPosition - firstPosition) * 2;
+                int preliminaryPosition;
+                if (position < firstPosition) {
+                    preliminaryPosition = position + twoScreens;
+                    if (preliminaryPosition >= listView.getCount()) {
+                        preliminaryPosition = listView.getCount() - 1;
+                    }
+                } else {
+                    preliminaryPosition = position - twoScreens;
+                    if (preliminaryPosition < 0) {
+                        preliminaryPosition = 0;
+                    }
+                }
+                listView.setSelection(preliminaryPosition);
+                scrollToFinalPosition(listView, position);
+            }
+        });
+    }
+
+    private static void scrollToFinalPosition(final ListView listView, final int position) {
+        // Position the element at about 1/3 of the list height
+        final int offset = (int) (listView.getHeight() * 0.33);
+        listView.post(new Runnable() {
+
+            @Override
+            public void run() {
+                listView.smoothScrollToPositionFromTop(position, offset);
+            }
+        });
+    }
+}