1/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.contacts;
18
19import android.content.Context;
20import android.database.Cursor;
21import android.graphics.drawable.Drawable;
22import android.net.Uri;
23import android.provider.ContactsContract.CommonDataKinds.Email;
24import android.provider.ContactsContract.CommonDataKinds.Nickname;
25import android.provider.ContactsContract.CommonDataKinds.Phone;
26import android.provider.ContactsContract.CommonDataKinds.StructuredName;
27import android.provider.ContactsContract.Contacts.Data;
28import android.provider.ContactsContract.RawContacts;
29import android.view.LayoutInflater;
30import android.view.View;
31import android.view.ViewGroup;
32import android.widget.AdapterView;
33import android.widget.ArrayAdapter;
34import android.widget.ImageView;
35import android.widget.ListView;
36import android.widget.TextView;
37
38import com.android.contacts.common.model.AccountTypeManager;
39import com.android.contacts.common.model.account.AccountType;
40
41import java.util.ArrayList;
42import java.util.Collections;
43import java.util.HashMap;
44import java.util.List;
45
46/**
47 * A list view for constituent contacts of an aggregate.  Shows the contact name, source icon
48 * and additional data such as a nickname, email address or phone number, whichever
49 * is available.
50 */
51public class SplitAggregateView extends ListView {
52
53    private static final String TAG = "SplitAggregateView";
54
55    private interface SplitQuery {
56        String[] COLUMNS = new String[] {
57                Data.MIMETYPE, RawContacts.ACCOUNT_TYPE, RawContacts.DATA_SET, Data.RAW_CONTACT_ID,
58                Data.IS_PRIMARY, StructuredName.DISPLAY_NAME, Nickname.NAME, Email.DATA,
59                Phone.NUMBER
60        };
61
62        int MIMETYPE = 0;
63        int ACCOUNT_TYPE = 1;
64        int DATA_SET = 2;
65        int RAW_CONTACT_ID = 3;
66        int IS_PRIMARY = 4;
67        int DISPLAY_NAME = 5;
68        int NICKNAME = 6;
69        int EMAIL = 7;
70        int PHONE = 8;
71    }
72
73    private final Uri mAggregateUri;
74    private OnContactSelectedListener mListener;
75    private AccountTypeManager mAccountTypes;
76
77    /**
78     * Listener interface that gets the contact ID of the user-selected contact.
79     */
80    public interface OnContactSelectedListener {
81        void onContactSelected(long rawContactId);
82    }
83
84    /**
85     * Constructor.
86     */
87    public SplitAggregateView(Context context, Uri aggregateUri) {
88        super(context);
89
90        mAggregateUri = aggregateUri;
91
92        mAccountTypes = AccountTypeManager.getInstance(context);
93
94        final List<RawContactInfo> list = loadData();
95
96        setAdapter(new SplitAggregateAdapter(context, list));
97        setOnItemClickListener(new OnItemClickListener() {
98
99            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
100                mListener.onContactSelected(list.get(position).rawContactId);
101            }
102        });
103    }
104
105    /**
106     * Sets a contact selection listener.
107     */
108    public void setOnContactSelectedListener(OnContactSelectedListener listener) {
109        mListener = listener;
110    }
111
112    /**
113     * Contact information loaded from the content provider.
114     */
115    private static class RawContactInfo implements Comparable<RawContactInfo> {
116        final long rawContactId;
117        String accountType;
118        String dataSet;
119        String name;
120        String phone;
121        String email;
122        String nickname;
123
124        public RawContactInfo(long rawContactId) {
125            this.rawContactId = rawContactId;
126        }
127
128        public String getAdditionalData() {
129            if (nickname != null) {
130                return nickname;
131            }
132
133            if (email != null) {
134                return email;
135            }
136
137            if (phone != null) {
138                return phone;
139            }
140
141            return "";
142        }
143
144        public int compareTo(RawContactInfo another) {
145            String thisAccount = accountType != null ? accountType : "";
146            String thatAccount = another.accountType != null ? another.accountType : "";
147            return thisAccount.compareTo(thatAccount);
148        }
149    }
150
151    /**
152     * Loads data from the content provider, organizes it into {@link RawContactInfo} objects
153     * and returns a sorted list of {@link RawContactInfo}'s.
154     */
155    private List<RawContactInfo> loadData() {
156        HashMap<Long, RawContactInfo> rawContactInfos = new HashMap<Long, RawContactInfo>();
157        Uri dataUri = Uri.withAppendedPath(mAggregateUri, Data.CONTENT_DIRECTORY);
158        Cursor cursor = getContext().getContentResolver().query(dataUri,
159                SplitQuery.COLUMNS, null, null, null);
160        if (cursor == null) {
161            return Collections.emptyList();
162        }
163        try {
164            while (cursor.moveToNext()) {
165                long rawContactId = cursor.getLong(SplitQuery.RAW_CONTACT_ID);
166                RawContactInfo info = rawContactInfos.get(rawContactId);
167                if (info == null) {
168                    info = new RawContactInfo(rawContactId);
169                    rawContactInfos.put(rawContactId, info);
170                    info.accountType = cursor.getString(SplitQuery.ACCOUNT_TYPE);
171                    info.dataSet = cursor.getString(SplitQuery.DATA_SET);
172                }
173
174                String mimetype = cursor.getString(SplitQuery.MIMETYPE);
175                if (StructuredName.CONTENT_ITEM_TYPE.equals(mimetype)) {
176                    loadStructuredName(cursor, info);
177                } else if (Phone.CONTENT_ITEM_TYPE.equals(mimetype)) {
178                    loadPhoneNumber(cursor, info);
179                } else if (Email.CONTENT_ITEM_TYPE.equals(mimetype)) {
180                    loadEmail(cursor, info);
181                } else if (Nickname.CONTENT_ITEM_TYPE.equals(mimetype)) {
182                    loadNickname(cursor, info);
183                }
184            }
185        } finally {
186            cursor.close();
187        }
188
189        List<RawContactInfo> list = new ArrayList<RawContactInfo>(rawContactInfos.values());
190        Collections.sort(list);
191        return list;
192    }
193
194    private void loadStructuredName(Cursor cursor, RawContactInfo info) {
195        info.name = cursor.getString(SplitQuery.DISPLAY_NAME);
196    }
197
198    private void loadNickname(Cursor cursor, RawContactInfo info) {
199        if (info.nickname == null || cursor.getInt(SplitQuery.IS_PRIMARY) != 0) {
200            info.nickname = cursor.getString(SplitQuery.NICKNAME);
201        }
202    }
203
204    private void loadEmail(Cursor cursor, RawContactInfo info) {
205        if (info.email == null || cursor.getInt(SplitQuery.IS_PRIMARY) != 0) {
206            info.email = cursor.getString(SplitQuery.EMAIL);
207        }
208    }
209
210    private void loadPhoneNumber(Cursor cursor, RawContactInfo info) {
211        if (info.phone == null || cursor.getInt(SplitQuery.IS_PRIMARY) != 0) {
212            info.phone = cursor.getString(SplitQuery.PHONE);
213        }
214    }
215
216    private static class SplitAggregateItemCache  {
217        TextView name;
218        TextView additionalData;
219        ImageView sourceIcon;
220    }
221
222    /**
223     * List adapter for the list of {@link RawContactInfo} objects.
224     */
225    private class SplitAggregateAdapter extends ArrayAdapter<RawContactInfo> {
226
227        private LayoutInflater mInflater;
228
229        public SplitAggregateAdapter(Context context, List<RawContactInfo> sources) {
230            super(context, 0, sources);
231            mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
232        }
233
234        @Override
235        public View getView(int position, View convertView, ViewGroup parent) {
236            if (convertView == null) {
237                convertView = mInflater.inflate(R.layout.split_aggregate_list_item, parent, false);
238            }
239
240            SplitAggregateItemCache cache = (SplitAggregateItemCache)convertView.getTag();
241            if (cache == null) {
242                cache = new SplitAggregateItemCache();
243                cache.name = (TextView)convertView.findViewById(R.id.name);
244                cache.additionalData = (TextView)convertView.findViewById(R.id.additionalData);
245                cache.sourceIcon = (ImageView)convertView.findViewById(R.id.sourceIcon);
246                convertView.setTag(cache);
247            }
248
249            final RawContactInfo info = getItem(position);
250            cache.name.setText(info.name);
251            cache.additionalData.setText(info.getAdditionalData());
252
253            Drawable icon = null;
254            AccountType accountType = mAccountTypes.getAccountType(info.accountType, info.dataSet);
255            if (accountType != null) {
256                icon = accountType.getDisplayIcon(getContext());
257            }
258            if (icon != null) {
259                cache.sourceIcon.setImageDrawable(icon);
260            } else {
261                cache.sourceIcon.setImageResource(R.drawable.unknown_source);
262            }
263            return convertView;
264        }
265    }
266}
267