Integration with Directory API for autocomplete

The UI changes a bit - there is no separator
between the local contacts and directories.
Will bring the separator back if asked, but
most likely simply as a thick line.

Change-Id: Idfc990deff41b30d63bd8289731694e3d9a00fb6
diff --git a/Android.mk b/Android.mk
index 4e8239b..da7fc5c 100644
--- a/Android.mk
+++ b/Android.mk
@@ -24,7 +24,7 @@
     src/com/android/email/service/IEmailServiceCallback.aidl
 # EXCHANGE-REMOVE-SECTION-END
 
-LOCAL_JAVA_STATIC_LIBRARIES := android-common
+LOCAL_STATIC_JAVA_LIBRARIES := android-common
 
 LOCAL_PACKAGE_NAME := Email
 
diff --git a/src/com/android/exchange/provider/ExchangeDirectoryProvider.java b/src/com/android/exchange/provider/ExchangeDirectoryProvider.java
index 1afe6b1..f4c5d03 100644
--- a/src/com/android/exchange/provider/ExchangeDirectoryProvider.java
+++ b/src/com/android/exchange/provider/ExchangeDirectoryProvider.java
@@ -59,6 +59,7 @@
     private static final int GAL_FILTER = GAL_BASE + 1;
     private static final int GAL_CONTACT = GAL_BASE + 2;
     private static final int GAL_CONTACT_WITH_ID = GAL_BASE + 3;
+    private static final int GAL_EMAIL_FILTER = GAL_BASE + 4;
 
     private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
 
@@ -68,6 +69,7 @@
         sURIMatcher.addURI(EXCHANGE_GAL_AUTHORITY, "contacts/lookup/*/entities", GAL_CONTACT);
         sURIMatcher.addURI(EXCHANGE_GAL_AUTHORITY, "contacts/lookup/*/#/entities",
                 GAL_CONTACT_WITH_ID);
+        sURIMatcher.addURI(EXCHANGE_GAL_AUTHORITY, "data/emails/filter/*", GAL_EMAIL_FILTER);
     }
 
     @Override
@@ -211,7 +213,8 @@
                 return cursor;
             }
 
-            case GAL_FILTER: {
+            case GAL_FILTER:
+            case GAL_EMAIL_FILTER: {
                 String filter = uri.getLastPathSegment();
                 // We should have at least two characters before doing a GAL search
                 if (filter == null || filter.length() < 2) {
diff --git a/src/com/android/exchange/provider/GalEmailAddressAdapter.java b/src/com/android/exchange/provider/GalEmailAddressAdapter.java
deleted file mode 100644
index e0e3f4f..0000000
--- a/src/com/android/exchange/provider/GalEmailAddressAdapter.java
+++ /dev/null
@@ -1,366 +0,0 @@
-/* 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.exchange.provider;
-
-import com.android.email.Email;
-import com.android.email.EmailAddressAdapter;
-import com.android.email.R;
-import com.android.email.provider.EmailContent.Account;
-import com.android.email.provider.EmailContent.HostAuth;
-
-import android.app.Activity;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.MatrixCursor;
-import android.database.MergeCursor;
-import android.net.Uri;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ListView;
-import android.widget.TextView;
-
-/**
- * Email Address adapter that performs asynchronous GAL lookups.
- */
-public class GalEmailAddressAdapter extends EmailAddressAdapter {
-    // DO NOT CHECK IN SET TO TRUE
-    private static final boolean DEBUG_GAL_LOG = false;
-
-    // Don't run GAL query until there are 3 characters typed
-    private static final int MINIMUM_GAL_CONSTRAINT_LENGTH = 3;
-
-    private Activity mActivity;
-    private Account mAccount;
-    private boolean mAccountHasGal;
-    private String mAccountEmailDomain;
-    private LayoutInflater mInflater;
-
-    // Local variables to track status of the search
-    private int mSeparatorDisplayCount;
-    private int mSeparatorTotalCount;
-
-    public GalEmailAddressAdapter(Activity activity) {
-        super(activity);
-        mActivity = activity;
-        mAccount = null;
-        mAccountHasGal = false;
-        mInflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-    }
-
-    /**
-     * Set the account ID when known.  Not used for generic contacts lookup;  Use when
-     * linking lookup to specific account.
-     */
-    @Override
-    public void setAccount(Account account) {
-        mAccount = account;
-        mAccountHasGal = false;
-        int finalSplit = mAccount.mEmailAddress.lastIndexOf('@');
-        mAccountEmailDomain = mAccount.mEmailAddress.substring(finalSplit + 1);
-    }
-
-    /**
-     * Sniff the provided account and if it's EAS, record "mAccounthHasGal".  If not,
-     * clear mAccount so we just ignore it.
-     */
-    private void checkGalAccount(Account account) {
-        HostAuth ha = HostAuth.restoreHostAuthWithId(mActivity, account.mHostAuthKeyRecv);
-        if (ha != null) {
-            if ("eas".equalsIgnoreCase(ha.mProtocol)) {
-                mAccountHasGal = true;
-                return;
-            }
-        }
-        // for any reason, we could not identify a GAL account, so clear mAccount
-        // and we'll never check this again
-        mAccount = null;
-        mAccountHasGal = false;
-    }
-
-    @Override
-    public Cursor runQueryOnBackgroundThread(final CharSequence constraint) {
-        // One time (and not in the UI thread) - check the account and see if it support GAL
-        // If not, clear it so we never bother again
-        if (mAccount != null && mAccountHasGal == false) {
-            checkGalAccount(mAccount);
-        }
-
-        // Get the cursor from ContactsProvider, and set up to exit immediately, returning it
-        Cursor contactsCursor = super.runQueryOnBackgroundThread(constraint);
-        // If we don't have a GAL  account or we don't have a constraint that's long enough,
-        // just return the raw contactsCursor
-        if (!mAccountHasGal || constraint == null) {
-            return contactsCursor;
-        }
-        final String constraintString = constraint.toString().trim();
-        if (constraintString.length() < MINIMUM_GAL_CONSTRAINT_LENGTH) {
-            return contactsCursor;
-        }
-
-        // Strategy for handling dynamic GAL lookup.
-        //  1. Create cursor that we can use now (and update later)
-        //  2. Return it immediately
-        //  3. Spawn a thread that will update the cursor when results arrive or search fails
-
-        final MatrixCursor matrixCursor = new MatrixCursor(ExchangeProvider.GAL_PROJECTION);
-        final MyMergeCursor mergedResultCursor =
-            new MyMergeCursor(new Cursor[] {contactsCursor, matrixCursor});
-        mergedResultCursor.setSeparatorPosition(contactsCursor.getCount());
-        mSeparatorDisplayCount = -1;
-        mSeparatorTotalCount = -1;
-        new Thread(new Runnable() {
-            public void run() {
-                // Uri format is account/constraint
-                Uri galUri =
-                    ExchangeProvider.GAL_URI.buildUpon()
-                        .appendPath(Long.toString(mAccount.mId))
-                        .appendPath(constraintString).build();
-                if (DEBUG_GAL_LOG) {
-                    Log.d(Email.LOG_TAG, "Query: " + galUri);
-                }
-                // Use ExchangeProvider to get the results of the GAL query
-                final Cursor galCursor =
-                    mContentResolver.query(galUri, ExchangeProvider.GAL_PROJECTION,
-                            null, null, null);
-                // There are three result cases to handle here.
-                //  1. matrixCursor is closed - this means the UI no longer cares about us
-                //  2. gal cursor is null or empty - remove separator and exit
-                //  3. gal cursor has results - update separator and add results to matrix cursor
-
-                // Case 1: The merged cursor has already been dropped, (e.g. results superceded)
-                if (mergedResultCursor.isClosed()) {
-                    if (DEBUG_GAL_LOG) {
-                        Log.d(Email.LOG_TAG, "Drop result (cursor closed, bg thread)");
-                    }
-                    return;
-                }
-
-                // Cases 2 & 3 have UI aspects, so do them in the UI thread
-                mActivity.runOnUiThread(new Runnable() {
-                    public void run() {
-                        // Case 1:  (final re-check):  Merged cursor already dropped
-                        if (mergedResultCursor.isClosed()) {
-                            if (DEBUG_GAL_LOG) {
-                                Log.d(Email.LOG_TAG, "Drop result (cursor closed, ui thread)");
-                            }
-                            return;
-                        }
-
-                        // Case 2:  Gal cursor is null or empty
-                        if (galCursor == null || galCursor.getCount() == 0) {
-                            if (DEBUG_GAL_LOG) {
-                                Log.d(Email.LOG_TAG, "Drop empty result");
-                            }
-                            mergedResultCursor.setSeparatorPosition(ListView.INVALID_POSITION);
-                            GalEmailAddressAdapter.this.notifyDataSetChanged();
-                            return;
-                        }
-
-                        // Case 3: Real results
-                        galCursor.moveToPosition(-1);
-                        while (galCursor.moveToNext()) {
-                            MatrixCursor.RowBuilder rb = matrixCursor.newRow();
-                            rb.add(galCursor.getLong(ExchangeProvider.GAL_COLUMN_ID));
-                            rb.add(galCursor.getString(ExchangeProvider.GAL_COLUMN_DISPLAYNAME));
-                            rb.add(galCursor.getString(ExchangeProvider.GAL_COLUMN_DATA));
-                        }
-                        // Replace the separator text with "totals"
-                        mSeparatorDisplayCount = galCursor.getCount();
-                        mSeparatorTotalCount =
-                            galCursor.getExtras().getInt(ExchangeProvider.EXTRAS_TOTAL_RESULTS);
-                        // Notify UI that the cursor changed
-                        if (DEBUG_GAL_LOG) {
-                            Log.d(Email.LOG_TAG, "Notify result, added=" + mSeparatorDisplayCount);
-                        }
-                        GalEmailAddressAdapter.this.notifyDataSetChanged();
-                    }});
-            }}).start();
-        return mergedResultCursor;
-    }
-
-    /*
-     * The following series of overrides insert the separator between contacts & GAL contacts
-     * TODO: extract most of this into a CursorAdapter superclass, and share with AccountFolderList
-     */
-
-    /**
-     * Get the separator position, which is tucked into the cursor to deal with threading.
-     * Result is invalid for any other cursor types (e.g. the raw contacts cursor)
-     */
-    private int getSeparatorPosition() {
-        Cursor c = this.getCursor();
-        if (c instanceof MyMergeCursor) {
-            return ((MyMergeCursor)c).getSeparatorPosition();
-        } else {
-            return ListView.INVALID_POSITION;
-        }
-    }
-
-    /**
-     * Prevents the separator view from recycling into the other views
-     */
-    @Override
-    public int getItemViewType(int position) {
-        if (position == getSeparatorPosition()) {
-            return IGNORE_ITEM_VIEW_TYPE;
-        }
-        return super.getItemViewType(position);
-    }
-
-    /**
-     * Injects the separator view when required
-     */
-    @Override
-    public View getView(int position, View convertView, ViewGroup parent) {
-        // The base class's getView() checks for mDataValid at the beginning, but we don't have
-        // to do that, because if the cursor is invalid getCount() returns 0, in which case this
-        // method wouldn't get called.
-
-        // Handle the separator here - create & bind
-        if (position == getSeparatorPosition()) {
-            View separator;
-            separator = mInflater.inflate(R.layout.recipient_dropdown_separator, parent, false);
-            TextView text1 = (TextView) separator.findViewById(R.id.text1);
-            View progress = separator.findViewById(R.id.progress);
-            String bannerText;
-            if (mSeparatorDisplayCount == -1) {
-                // Display "Searching <account>..."
-                bannerText = mContext.getString(R.string.gal_searching_fmt, mAccountEmailDomain);
-                progress.setVisibility(View.VISIBLE);
-            } else {
-                if (mSeparatorDisplayCount == mSeparatorTotalCount) {
-                    // Display "x results from <account>"
-                    bannerText = mContext.getResources().getQuantityString(
-                            R.plurals.gal_completed_fmt, mSeparatorDisplayCount,
-                            mSeparatorDisplayCount, mAccountEmailDomain);
-                } else {
-                    // Display "First x results from <account>"
-                    bannerText = mContext.getString(R.string.gal_completed_limited_fmt,
-                            mSeparatorDisplayCount, mAccountEmailDomain);
-                }
-                progress.setVisibility(View.GONE);
-            }
-            text1.setText(bannerText);
-            return separator;
-        }
-        return super.getView(getRealPosition(position), convertView, parent);
-    }
-
-    /**
-     * Forces navigation to skip over the separator
-     */
-    @Override
-    public boolean areAllItemsEnabled() {
-        return false;
-    }
-
-    /**
-     * Forces navigation to skip over the separator
-     */
-    @Override
-    public boolean isEnabled(int position) {
-        return position != getSeparatorPosition();
-    }
-
-    /**
-     * Adjusts list count to include separator
-     */
-    @Override
-    public int getCount() {
-        int count = super.getCount();
-        if (getSeparatorPosition() != ListView.INVALID_POSITION) {
-            // Increment for separator, if we have anything to show.
-            count += 1;
-        }
-        return count;
-    }
-
-    /**
-     * Converts list position to cursor position
-     */
-    private int getRealPosition(int pos) {
-        int separatorPosition = getSeparatorPosition();
-        if (separatorPosition == ListView.INVALID_POSITION) {
-            // No separator, identity map
-            return pos;
-        } else if (pos <= separatorPosition) {
-            // Before or at the separator, identity map
-            return pos;
-        } else {
-            // After the separator, remove 1 from the pos to get the real underlying pos
-            return pos - 1;
-        }
-    }
-
-    /**
-     * Returns the item using external position numbering (no separator)
-     */
-    @Override
-    public Object getItem(int pos) {
-        return super.getItem(getRealPosition(pos));
-    }
-
-    /**
-     * Returns the item id using external position numbering (no separator)
-     */
-    @Override
-    public long getItemId(int pos) {
-        if (pos == getSeparatorPosition()) {
-            return View.NO_ID;
-        }
-        return super.getItemId(getRealPosition(pos));
-    }
-
-    /**
-     * Lightweight override of MergeCursor.  Synchronizes "mClosed" / "isClosed()" so we
-     * can safely check if it has been closed, in the threading jumble of our adapter.
-     * Also holds the separator position, so it can be tracked with the cursor itself and avoid
-     * errors when multiple cursors are in flight.
-     */
-    private static class MyMergeCursor extends MergeCursor {
-
-        private int mSeparatorPosition;
-
-        public MyMergeCursor(Cursor[] cursors) {
-            super(cursors);
-            mClosed = false;
-            mSeparatorPosition = ListView.INVALID_POSITION;
-        }
-
-        @Override
-        public synchronized void close() {
-            super.close();
-            if (DEBUG_GAL_LOG) {
-                Log.d(Email.LOG_TAG, "Closing MyMergeCursor");
-            }
-        }
-
-        @Override
-        public synchronized boolean isClosed() {
-            return super.isClosed();
-        }
-
-        void setSeparatorPosition(int newPos) {
-            mSeparatorPosition = newPos;
-        }
-
-        int getSeparatorPosition() {
-            return mSeparatorPosition;
-        }
-    }
-}