ExchangeDirectoryProvider.java revision 469544cd1b9652c446c96b97f4abbdb65d7e06aa
15aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank/*
25aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank * Copyright (C) 2010 The Android Open Source Project
35aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank *
45aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank * Licensed under the Apache License, Version 2.0 (the "License");
55aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank * you may not use this file except in compliance with the License.
65aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank * You may obtain a copy of the License at
75aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank *
85aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank *      http://www.apache.org/licenses/LICENSE-2.0
95aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank *
105aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank * Unless required by applicable law or agreed to in writing, software
115aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank * distributed under the License is distributed on an "AS IS" BASIS,
125aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
135aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank * See the License for the specific language governing permissions and
145aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank * limitations under the License.
155aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank */
165aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank
175aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blankpackage com.android.exchange.provider;
185aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank
191f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikovimport com.android.email.R;
201f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikovimport com.android.email.VendorPolicyLoader;
21d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikovimport com.android.email.mail.PackedString;
225aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blankimport com.android.email.provider.EmailContent.Account;
235aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blankimport com.android.exchange.EasSyncService;
24385a0be662509754e687bcfa9813208b050bf951Marc Blankimport com.android.exchange.ExchangeService;
255aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blankimport com.android.exchange.provider.GalResult.GalData;
265aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank
271f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikovimport android.accounts.AccountManager;
285aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blankimport android.content.ContentProvider;
295aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blankimport android.content.ContentValues;
305aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blankimport android.content.UriMatcher;
315aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blankimport android.database.Cursor;
325aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blankimport android.database.MatrixCursor;
335aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blankimport android.net.Uri;
345aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blankimport android.os.Binder;
35469544cd1b9652c446c96b97f4abbdb65d7e06aaMarc Blankimport android.provider.ContactsContract;
365aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blankimport android.provider.ContactsContract.CommonDataKinds;
37469544cd1b9652c446c96b97f4abbdb65d7e06aaMarc Blankimport android.provider.ContactsContract.Contacts;
38469544cd1b9652c446c96b97f4abbdb65d7e06aaMarc Blankimport android.provider.ContactsContract.Directory;
39469544cd1b9652c446c96b97f4abbdb65d7e06aaMarc Blankimport android.provider.ContactsContract.RawContacts;
40d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikovimport android.provider.ContactsContract.CommonDataKinds.Email;
41d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikovimport android.provider.ContactsContract.CommonDataKinds.Phone;
42d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikovimport android.provider.ContactsContract.CommonDataKinds.StructuredName;
43d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikovimport android.provider.ContactsContract.Contacts.Data;
44d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikovimport android.text.TextUtils;
45d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov
46d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikovimport java.util.HashMap;
47d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikovimport java.util.List;
485aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank
495aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank/**
505aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank * ExchangeDirectoryProvider provides real-time data from the Exchange server; at the moment, it is
515aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank * used solely to provide GAL (Global Address Lookup) service to email address adapters
525aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank */
535aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blankpublic class ExchangeDirectoryProvider extends ContentProvider {
54eb9cccf5c94b043baecb03d06738f7eedef03378Marc Blank    public static final String EXCHANGE_GAL_AUTHORITY = "com.android.exchange.directory.provider";
555aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank
56d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov    private static final int DEFAULT_CONTACT_ID = 1;
57469544cd1b9652c446c96b97f4abbdb65d7e06aaMarc Blank    private static final int DEFAULT_LOOKUP_LIMIT = 20;
58d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov
595aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank    private static final int GAL_BASE = 0;
601f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov    private static final int GAL_DIRECTORIES = GAL_BASE;
611f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov    private static final int GAL_FILTER = GAL_BASE + 1;
621f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov    private static final int GAL_CONTACT = GAL_BASE + 2;
631f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov    private static final int GAL_CONTACT_WITH_ID = GAL_BASE + 3;
64f6e831704c7df2b841760931469b1e995b957cd9Dmitri Plotnikov    private static final int GAL_EMAIL_FILTER = GAL_BASE + 4;
655aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank
665aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank    private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
675aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank
685aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank    static {
691f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov        sURIMatcher.addURI(EXCHANGE_GAL_AUTHORITY, "directories", GAL_DIRECTORIES);
705aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank        sURIMatcher.addURI(EXCHANGE_GAL_AUTHORITY, "contacts/filter/*", GAL_FILTER);
711f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov        sURIMatcher.addURI(EXCHANGE_GAL_AUTHORITY, "contacts/lookup/*/entities", GAL_CONTACT);
72d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov        sURIMatcher.addURI(EXCHANGE_GAL_AUTHORITY, "contacts/lookup/*/#/entities",
73d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                GAL_CONTACT_WITH_ID);
74f6e831704c7df2b841760931469b1e995b957cd9Dmitri Plotnikov        sURIMatcher.addURI(EXCHANGE_GAL_AUTHORITY, "data/emails/filter/*", GAL_EMAIL_FILTER);
755aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank    }
765aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank
775aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank    @Override
785aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank    public boolean onCreate() {
795aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank        return true;
805aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank    }
815aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank
82d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov    static class GalProjection {
83d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov        final int size;
84d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov        final HashMap<String, Integer> columnMap = new HashMap<String, Integer>();
85d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov
86d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov        GalProjection(String[] projection) {
87d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            size = projection.length;
88d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            for (int i = 0; i < projection.length; i++) {
89d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                columnMap.put(projection[i], i);
90d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            }
91d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov        }
92d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov    }
93d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov
94d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov    static class GalContactRow {
95d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov        private final GalProjection mProjection;
96d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov        private Object[] row;
97d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov        static long dataId = 1;
98d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov
99d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov        GalContactRow(GalProjection projection, long contactId, String lookupKey,
100d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                String accountName, String displayName) {
101d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            this.mProjection = projection;
102d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            row = new Object[projection.size];
103d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov
104d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            put(Contacts.Entity.CONTACT_ID, contactId);
105d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov
106d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            // We only have one raw contact per aggregate, so they can have the same ID
107d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            put(Contacts.Entity.RAW_CONTACT_ID, contactId);
108d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            put(Contacts.Entity.DATA_ID, dataId++);
109d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov
110d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            put(Contacts.DISPLAY_NAME, displayName);
111d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov
112d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            // TODO alternative display name
113d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            put(Contacts.DISPLAY_NAME_ALTERNATIVE, displayName);
114d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov
115d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            put(RawContacts.ACCOUNT_TYPE, com.android.email.Email.EXCHANGE_ACCOUNT_MANAGER_TYPE);
116d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            put(RawContacts.ACCOUNT_NAME, accountName);
117d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            put(RawContacts.RAW_CONTACT_IS_READ_ONLY, 1);
118d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            put(Data.IS_READ_ONLY, 1);
119d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov        }
120d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov
121d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov        Object[] getRow () {
122d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            return row;
123d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov        }
124d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov
125d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov        void put(String columnName, Object value) {
126d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            Integer integer = mProjection.columnMap.get(columnName);
127d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            if (integer != null) {
128d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                row[integer] = value;
129d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            } else {
130d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                System.out.println("Unsupported column: " + columnName);
131d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            }
132d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov        }
133d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov
134d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov        static void addEmailAddress(MatrixCursor cursor, GalProjection galProjection,
135d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                long contactId, String lookupKey, String accountName, String displayName,
136d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                String address) {
137d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            if (!TextUtils.isEmpty(address)) {
138d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                GalContactRow r = new GalContactRow(
139d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                        galProjection, contactId, lookupKey, accountName, displayName);
140d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                r.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
141d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                r.put(Email.TYPE, Email.TYPE_WORK);
142d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                r.put(Email.ADDRESS, address);
143d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                cursor.addRow(r.getRow());
144d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            }
145d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov        }
146d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov
147d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov        static void addPhoneRow(MatrixCursor cursor, GalProjection projection, long contactId,
148d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                String lookupKey, String accountName, String displayName, int type, String number) {
149d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            if (!TextUtils.isEmpty(number)) {
150d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                GalContactRow r = new GalContactRow(
151d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                        projection, contactId, lookupKey, accountName, displayName);
152d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                r.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
153d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                r.put(Phone.TYPE, type);
154d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                r.put(Phone.NUMBER, number);
155d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                cursor.addRow(r.getRow());
156d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            }
157d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov        }
158d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov
159d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov        public static void addNameRow(MatrixCursor cursor, GalProjection galProjection,
160d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                long contactId, String lookupKey, String accountName, String displayName,
161d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                String firstName, String lastName) {
162d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            GalContactRow r = new GalContactRow(
163d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                    galProjection, contactId, lookupKey, accountName, displayName);
164d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            r.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
165d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            r.put(StructuredName.GIVEN_NAME, firstName);
166d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            r.put(StructuredName.FAMILY_NAME, lastName);
167d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            r.put(StructuredName.DISPLAY_NAME, displayName);
168d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            cursor.addRow(r.getRow());
169d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov        }
170d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov    }
171d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov
1725aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank    @Override
1735aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
1745aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank            String sortOrder) {
1755aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank        int match = sURIMatcher.match(uri);
176d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov        MatrixCursor cursor;
177d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov        Object[] row;
178d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov        PackedString ps;
179d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov        String lookupKey;
180d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov
1815aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank        switch (match) {
1821f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov            case GAL_DIRECTORIES: {
1831f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov                // Assuming that GAL can be used with all exchange accounts
1841f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov                android.accounts.Account[] accounts = AccountManager.get(getContext())
1851f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov                        .getAccountsByType(com.android.email.Email.EXCHANGE_ACCOUNT_MANAGER_TYPE);
1861f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov                cursor = new MatrixCursor(projection);
1871f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov                if (accounts != null) {
1881f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov                    for (android.accounts.Account account : accounts) {
1891f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov                        row = new Object[projection.length];
1901f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov
1911f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov                        for (int i = 0; i < projection.length; i++) {
1921f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov                            String column = projection[i];
1931f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov                            if (column.equals(Directory.ACCOUNT_NAME)) {
1941f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov                                row[i] = account.name;
1951f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov                            } else if (column.equals(Directory.ACCOUNT_TYPE)) {
1961f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov                                row[i] = account.type;
1971f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov                            } else if (column.equals(Directory.TYPE_RESOURCE_ID)) {
1981f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov                                if (VendorPolicyLoader.getInstance(getContext())
1991f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov                                        .useAlternateExchangeStrings()) {
2001f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov                                    row[i] = R.string.exchange_name_alternate;
2011f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov                                } else {
2021f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov                                    row[i] = R.string.exchange_name;
2031f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov                                }
2041f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov                            } else if (column.equals(Directory.DISPLAY_NAME)) {
2051f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov                                row[i] = account.name;
2061f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov                            } else if (column.equals(Directory.EXPORT_SUPPORT)) {
2071f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov                                row[i] = Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY;
2081f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov                            } else if (column.equals(Directory.SHORTCUT_SUPPORT)) {
2091f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov                                row[i] = Directory.SHORTCUT_SUPPORT_FULL;
2101f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov                            }
2111f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov                        }
2121f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov                        cursor.addRow(row);
2131f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov                    }
2141f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov                }
2151f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov                return cursor;
2161f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov            }
2171f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov
218f6e831704c7df2b841760931469b1e995b957cd9Dmitri Plotnikov            case GAL_FILTER:
219f6e831704c7df2b841760931469b1e995b957cd9Dmitri Plotnikov            case GAL_EMAIL_FILTER: {
2205aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank                String filter = uri.getLastPathSegment();
2215aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank                // We should have at least two characters before doing a GAL search
2225aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank                if (filter == null || filter.length() < 2) {
2235aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank                    return null;
2245aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank                }
2251f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov
2261f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov                String accountName = uri.getQueryParameter(RawContacts.ACCOUNT_NAME);
2271f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov                if (accountName == null) {
2281f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov                    return null;
2291f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov                }
2301f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov
231469544cd1b9652c446c96b97f4abbdb65d7e06aaMarc Blank                // Enforce a limit on the number of lookup responses
232469544cd1b9652c446c96b97f4abbdb65d7e06aaMarc Blank                String limitString = uri.getQueryParameter(ContactsContract.LIMIT_PARAM_KEY);
233469544cd1b9652c446c96b97f4abbdb65d7e06aaMarc Blank                int limit = DEFAULT_LOOKUP_LIMIT;
234469544cd1b9652c446c96b97f4abbdb65d7e06aaMarc Blank                if (limitString != null) {
235469544cd1b9652c446c96b97f4abbdb65d7e06aaMarc Blank                    try {
236469544cd1b9652c446c96b97f4abbdb65d7e06aaMarc Blank                        limit = Integer.parseInt(limitString);
237469544cd1b9652c446c96b97f4abbdb65d7e06aaMarc Blank                    } catch (NumberFormatException e) {
238469544cd1b9652c446c96b97f4abbdb65d7e06aaMarc Blank                        limit = 0;
239469544cd1b9652c446c96b97f4abbdb65d7e06aaMarc Blank                    }
240469544cd1b9652c446c96b97f4abbdb65d7e06aaMarc Blank                    if (limit <= 0) {
241469544cd1b9652c446c96b97f4abbdb65d7e06aaMarc Blank                        throw new IllegalArgumentException("Limit not valid: " + limitString);
242469544cd1b9652c446c96b97f4abbdb65d7e06aaMarc Blank                    }
243469544cd1b9652c446c96b97f4abbdb65d7e06aaMarc Blank                }
244469544cd1b9652c446c96b97f4abbdb65d7e06aaMarc Blank
245385a0be662509754e687bcfa9813208b050bf951Marc Blank                Account account = ExchangeService.getAccountByName(accountName);
2461f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov                if (account == null) {
2471f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov                    return null;
2481f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov                }
2491f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov
2505aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank                long callingId = Binder.clearCallingIdentity();
2515aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank                try {
2525aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank                    // Get results from the Exchange account
2535aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank                    GalResult galResult = EasSyncService.searchGal(getContext(), account.mId,
254469544cd1b9652c446c96b97f4abbdb65d7e06aaMarc Blank                            filter, limit);
2555aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank                    if (galResult != null) {
2565aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank                        return buildGalResultCursor(projection, galResult);
2575aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank                    }
2585aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank                } finally {
2595aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank                    Binder.restoreCallingIdentity(callingId);
2605aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank                }
2615aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank                break;
262d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            }
263d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov
264d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            case GAL_CONTACT:
265d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            case GAL_CONTACT_WITH_ID: {
2661f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov                String accountName = uri.getQueryParameter(RawContacts.ACCOUNT_NAME);
2671f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov                if (accountName == null) {
2681f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov                    return null;
2691f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov                }
2701f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov
271d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                GalProjection galProjection = new GalProjection(projection);
272d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                cursor = new MatrixCursor(projection);
273d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                // Handle the decomposition of the key into rows suitable for CP2
2741f4b000ebe72fe9e71082031f48d0133914bda2dDmitri Plotnikov                List<String> pathSegments = uri.getPathSegments();
275d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                lookupKey = pathSegments.get(2);
276d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                long contactId = (match == GAL_CONTACT_WITH_ID)
277d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                        ? Long.parseLong(pathSegments.get(3))
278d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                        : DEFAULT_CONTACT_ID;
279d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                ps = new PackedString(lookupKey);
280d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                String displayName = ps.get(GalData.DISPLAY_NAME);
281d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                GalContactRow.addEmailAddress(cursor, galProjection, contactId, lookupKey,
282d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                        accountName, displayName, ps.get(GalData.EMAIL_ADDRESS));
283d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                GalContactRow.addPhoneRow(cursor, galProjection, contactId, accountName,
284d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                        displayName, displayName, Phone.TYPE_HOME, ps.get(GalData.HOME_PHONE));
285d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                GalContactRow.addPhoneRow(cursor, galProjection, contactId, accountName,
286d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                        displayName, displayName, Phone.TYPE_WORK, ps.get(GalData.WORK_PHONE));
287d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                GalContactRow.addPhoneRow(cursor, galProjection, contactId, accountName,
288d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                        displayName, displayName, Phone.TYPE_MOBILE, ps.get(GalData.MOBILE_PHONE));
289d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                GalContactRow.addNameRow(cursor, galProjection, contactId, accountName, displayName,
290d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                        ps.get(GalData.FIRST_NAME), ps.get(GalData.LAST_NAME), displayName);
291d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                return cursor;
292d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            }
2935aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank        }
2945aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank
2955aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank        return null;
2965aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank    }
2975aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank
2985aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank    /*package*/ Cursor buildGalResultCursor(String[] projection, GalResult galResult) {
2995aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank        int displayNameIndex = -1;
300d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov        int alternateDisplayNameIndex = -1;;
3015aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank        int emailIndex = -1;
302d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov        int idIndex = -1;
303d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov        int lookupIndex = -1;
3045aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank
3055aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank        for (int i = 0; i < projection.length; i++) {
3065aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank            String column = projection[i];
3075aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank            if (Contacts.DISPLAY_NAME.equals(column) ||
3085aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank                    Contacts.DISPLAY_NAME_PRIMARY.equals(column)) {
3095aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank                displayNameIndex = i;
3105aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank            } else if (Contacts.DISPLAY_NAME_ALTERNATIVE.equals(column)) {
311d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                alternateDisplayNameIndex = i;
3125aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank            } else if (CommonDataKinds.Email.ADDRESS.equals(column)) {
3135aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank                emailIndex = i;
314d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            } else if (Contacts._ID.equals(column)) {
315d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                idIndex = i;
316d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            } else if (Contacts.LOOKUP_KEY.equals(column)) {
317d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                lookupIndex = i;
3185aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank            }
3195aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank        }
3205aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank
3215aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank        Object[] row = new Object[projection.length];
3225aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank
3235aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank        /*
3245aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank         * ContactsProvider will ensure that every request has a non-null projection.
3255aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank         */
3265aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank        MatrixCursor cursor = new MatrixCursor(projection);
3275aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank        int count = galResult.galData.size();
3285aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank        for (int i = 0; i < count; i++) {
3295aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank            GalData galDataRow = galResult.galData.get(i);
330d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            String firstName = galDataRow.get(GalData.FIRST_NAME);
331d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            String lastName = galDataRow.get(GalData.LAST_NAME);
332d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            String displayName = galDataRow.get(GalData.DISPLAY_NAME);
333d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            // If we don't have a display name, try to create one using first and last name
334d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            if (displayName == null) {
335d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                if (firstName != null && lastName != null) {
336d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                    displayName = firstName + " " + lastName;
337d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                } else if (firstName != null) {
338d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                    displayName = firstName;
339d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                } else if (lastName != null) {
340d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                    displayName = lastName;
341d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                }
342d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            }
343d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            galDataRow.put(GalData.DISPLAY_NAME, displayName);
344d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov
3455aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank            if (displayNameIndex != -1) {
346d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                row[displayNameIndex] = displayName;
347d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            }
348d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            if (alternateDisplayNameIndex != -1) {
349d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                // Try to create an alternate display name, using first and last name
350d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                // TODO: Check with Contacts team to make sure we're using this properly
351d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                if (firstName != null && lastName != null) {
352d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                    row[alternateDisplayNameIndex] = lastName + " " + firstName;
353d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                } else {
354d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                    row[alternateDisplayNameIndex] = displayName;
355d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                }
3565aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank            }
3575aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank            if (emailIndex != -1) {
358d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                row[emailIndex] = galDataRow.get(GalData.EMAIL_ADDRESS);
359d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            }
360d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            if (idIndex != -1) {
361d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                row[idIndex] = i + 1;  // Let's be 1 based
362d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            }
363d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov            if (lookupIndex != -1) {
364d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                // We use the packed string as our lookup key; it contains ALL of the gal data
365d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                // We do this because we are not able to provide a stable id to ContactsProvider
366d41eaba645dcb4573c5dc33077fc87799ef06195Dmitri Plotnikov                row[lookupIndex] = Uri.encode(galDataRow.toPackedString());
3675aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank            }
3685aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank            cursor.addRow(row);
3695aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank        }
3705aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank        return cursor;
3715aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank    }
3725aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank
3735aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank    @Override
3745aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank    public String getType(Uri uri) {
3755aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank        int match = sURIMatcher.match(uri);
3765aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank        switch (match) {
3775aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank            case GAL_FILTER:
3785aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank                return Contacts.CONTENT_ITEM_TYPE;
3795aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank        }
3805aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank        return null;
3815aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank    }
3825aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank
3835aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank    @Override
3845aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank    public int delete(Uri uri, String selection, String[] selectionArgs) {
3855aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank        throw new UnsupportedOperationException();
3865aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank    }
3875aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank
3885aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank    @Override
3895aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank    public Uri insert(Uri uri, ContentValues values) {
3905aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank        throw new UnsupportedOperationException();
3915aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank    }
3925aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank
3935aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank    @Override
3945aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
3955aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank        throw new UnsupportedOperationException();
3965aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank    }
3975aec61dc15c62cff0cf55a3d6cb483f9e338230aMarc Blank}
398