blob: 47c75473d89ce9e2394af85f242f690a1ea505a6 [file] [log] [blame]
Dmitri Plotnikov501b7ea2010-04-07 17:20:49 -07001/*
2 * Copyright (C) 2007 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.ContentUris;
20import android.content.Context;
21import android.content.Intent;
22import android.database.Cursor;
23import android.database.MatrixCursor;
24import android.net.Uri;
25import android.net.Uri.Builder;
26import android.provider.ContactsContract;
27import android.provider.ContactsContract.Contacts;
28import android.provider.ContactsContract.Contacts.AggregationSuggestions;
29import android.text.TextUtils;
30import android.util.Log;
31import android.view.View;
32import android.view.ViewGroup;
33import android.widget.TextView;
34
35/**
36 * An activity that shows a list of contacts that can be joined with the target contact.
37 */
38public class JoinContactActivity extends ContactsListActivity {
39
40 private static final String TAG = "JoinContactActivity";
41
42 /**
43 * The action for the join contact activity.
44 * <p>
45 * Input: extra field {@link #EXTRA_TARGET_CONTACT_ID} is the aggregate ID.
46 * TODO: move to {@link ContactsContract}.
47 */
48 public static final String JOIN_CONTACT = "com.android.contacts.action.JOIN_CONTACT";
49
50 /**
51 * Used with {@link #JOIN_CONTACT} to give it the target for aggregation.
52 * <p>
53 * Type: LONG
54 */
55 public static final String EXTRA_TARGET_CONTACT_ID = "com.android.contacts.action.CONTACT_ID";
56
57 /** Maximum number of suggestions shown for joining aggregates */
58 private static final int MAX_SUGGESTIONS = 4;
59
60 private long mTargetContactId;
61
62 /**
63 * Determines whether we display a list item with the label
64 * "Show all contacts" or actually show all contacts
65 */
66 private boolean mJoinModeShowAllContacts;
67
68 /**
69 * The ID of the special item described above.
70 */
71 private static final long JOIN_MODE_SHOW_ALL_CONTACTS_ID = -2;
72
73 private boolean mLoadingJoinSuggestions;
74
75 private JoinContactListAdapter mAdapter;
76
77 @Override
78 protected void resolveIntent(Intent intent) {
79 mMode = MODE_PICK_CONTACT;
80 mTargetContactId = intent.getLongExtra(EXTRA_TARGET_CONTACT_ID, -1);
81 if (mTargetContactId == -1) {
82 Log.e(TAG, "Intent " + intent.getAction() + " is missing required extra: "
83 + EXTRA_TARGET_CONTACT_ID);
84 setResult(RESULT_CANCELED);
85 finish();
86 }
87 }
88
89 @Override
90 public void initContentView() {
91 setContentView(R.layout.contacts_list_content_join);
92 TextView blurbView = (TextView)findViewById(R.id.join_contact_blurb);
93
94 String blurb = getString(R.string.blurbJoinContactDataWith,
95 getContactDisplayName(mTargetContactId));
96 blurbView.setText(blurb);
97 mJoinModeShowAllContacts = true;
98 mAdapter = new JoinContactListAdapter(this);
99 setupListView(mAdapter);
100 }
101
102 @Override
103 protected void onListItemClick(int position, long id) {
104 if (id == JOIN_MODE_SHOW_ALL_CONTACTS_ID) {
105 mJoinModeShowAllContacts = false;
106 startQuery();
107 } else {
108 final Uri uri = getSelectedUri(position);
109 returnPickerResult(null, null, uri);
110 }
111 }
112
113 @Override
114 protected Uri getUriToQuery() {
115 return getJoinSuggestionsUri(null);
116 }
117
118 /*
119 * TODO: move to a background thread.
120 */
121 private String getContactDisplayName(long contactId) {
122 String contactName = null;
123 Cursor c = getContentResolver().query(
124 ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId),
125 new String[] {Contacts.DISPLAY_NAME}, null, null, null);
126 try {
127 if (c != null && c.moveToFirst()) {
128 contactName = c.getString(0);
129 }
130 } finally {
131 if (c != null) {
132 c.close();
133 }
134 }
135
136 if (contactName == null) {
137 contactName = "";
138 }
139
140 return contactName;
141 }
142
143 private Uri getJoinSuggestionsUri(String filter) {
144 Builder builder = Contacts.CONTENT_URI.buildUpon();
145 builder.appendEncodedPath(String.valueOf(mTargetContactId));
146 builder.appendEncodedPath(AggregationSuggestions.CONTENT_DIRECTORY);
147 if (!TextUtils.isEmpty(filter)) {
148 builder.appendEncodedPath(Uri.encode(filter));
149 }
150 builder.appendQueryParameter("limit", String.valueOf(MAX_SUGGESTIONS));
151 return builder.build();
152 }
153
154 @Override
155 Cursor doFilter(String filter) {
156 throw new UnsupportedOperationException();
157 }
158
159 private Cursor getShowAllContactsLabelCursor(String[] projection) {
160 MatrixCursor matrixCursor = new MatrixCursor(projection);
161 Object[] row = new Object[projection.length];
162 // The only columns we care about is the id
163 row[SUMMARY_ID_COLUMN_INDEX] = JOIN_MODE_SHOW_ALL_CONTACTS_ID;
164 matrixCursor.addRow(row);
165 return matrixCursor;
166 }
167
168 @Override
169 protected void startQuery(Uri uri, String[] projection) {
170 mLoadingJoinSuggestions = true;
171 startQuery(uri, projection, null, null, null);
172 }
173
174 @Override
175 protected void onQueryComplete(Cursor cursor) {
176 // Whenever we get a suggestions cursor, we need to immediately kick off
177 // another query for the complete list of contacts
178 if (cursor != null && mLoadingJoinSuggestions) {
179 mLoadingJoinSuggestions = false;
180 if (cursor.getCount() > 0) {
181 mAdapter.setSuggestionsCursor(cursor);
182 } else {
183 cursor.close();
184 mAdapter.setSuggestionsCursor(null);
185 }
186
187 if (mAdapter.mSuggestionsCursorCount == 0
188 || !mJoinModeShowAllContacts) {
189 startQuery(getContactFilterUri(getTextFilter()),
190 CONTACTS_SUMMARY_PROJECTION,
191 Contacts._ID + " != " + mTargetContactId
192 + " AND " + ContactsContract.Contacts.IN_VISIBLE_GROUP + "=1", null,
193 getSortOrder(CONTACTS_SUMMARY_PROJECTION));
194 return;
195 }
196
197 cursor = getShowAllContactsLabelCursor(CONTACTS_SUMMARY_PROJECTION);
198 }
199
200 super.onQueryComplete(cursor);
201 }
202
Dmitri Plotnikov501b7ea2010-04-07 17:20:49 -0700203 private class JoinContactListAdapter extends ContactItemListAdapter {
204 Cursor mSuggestionsCursor;
205 int mSuggestionsCursorCount;
206
207 public JoinContactListAdapter(Context context) {
208 super(context);
209 }
210
211 public void setSuggestionsCursor(Cursor cursor) {
212 if (mSuggestionsCursor != null) {
213 mSuggestionsCursor.close();
214 }
215 mSuggestionsCursor = cursor;
216 mSuggestionsCursorCount = cursor == null ? 0 : cursor.getCount();
217 }
218
219 private boolean isShowAllContactsItemPosition(int position) {
220 return mJoinModeShowAllContacts
221 && mSuggestionsCursorCount != 0 && position == mSuggestionsCursorCount + 2;
222 }
223
224 @Override
225 public View getView(int position, View convertView, ViewGroup parent) {
226 if (!mDataValid) {
227 throw new IllegalStateException(
228 "this should only be called when the cursor is valid");
229 }
230
231 if (isShowAllContactsItemPosition(position)) {
232 return getLayoutInflater().
233 inflate(R.layout.contacts_list_show_all_item, parent, false);
234 }
235
236 // Handle the separator specially
237 int separatorId = getSeparatorId(position);
238 if (separatorId != 0) {
239 TextView view = (TextView) getLayoutInflater().
240 inflate(R.layout.list_separator, parent, false);
241 view.setText(separatorId);
242 return view;
243 }
244
245 boolean showingSuggestion;
246 Cursor cursor;
247 if (mSuggestionsCursorCount != 0 && position < mSuggestionsCursorCount + 2) {
248 showingSuggestion = true;
249 cursor = mSuggestionsCursor;
250 } else {
251 showingSuggestion = false;
252 cursor = mCursor;
253 }
254
255 int realPosition = getRealPosition(position);
256 if (!cursor.moveToPosition(realPosition)) {
257 throw new IllegalStateException("couldn't move cursor to position " + position);
258 }
259
260 boolean newView;
261 View v;
262 if (convertView == null || convertView.getTag() == null) {
263 newView = true;
264 v = newView(mContext, cursor, parent);
265 } else {
266 newView = false;
267 v = convertView;
268 }
269 bindView(v, mContext, cursor);
270 bindSectionHeader(v, realPosition, !showingSuggestion);
271 return v;
272 }
273
274 @Override
275 public void changeCursor(Cursor cursor) {
276 if (cursor == null) {
277 mAdapter.setSuggestionsCursor(null);
278 }
279
280 super.changeCursor(cursor);
281 }
282 @Override
283 public int getItemViewType(int position) {
284 if (isShowAllContactsItemPosition(position)) {
285 return IGNORE_ITEM_VIEW_TYPE;
286 }
287
288 return super.getItemViewType(position);
289 }
290
291 private int getSeparatorId(int position) {
292 if (mSuggestionsCursorCount != 0) {
293 if (position == 0) {
294 return R.string.separatorJoinAggregateSuggestions;
295 } else if (position == mSuggestionsCursorCount + 1) {
296 return R.string.separatorJoinAggregateAll;
297 }
298 }
299 return 0;
300 }
301
302 @Override
303 public boolean areAllItemsEnabled() {
304 return super.areAllItemsEnabled() && mSuggestionsCursorCount == 0;
305 }
306
307 @Override
308 public boolean isEnabled(int position) {
309 if (position == 0) {
310 return false;
311 }
312
313 if (mSuggestionsCursorCount > 0) {
314 return position != 0 && position != mSuggestionsCursorCount + 1;
315 }
316 return true;
317 }
318
319 @Override
320 public int getCount() {
321 if (!mDataValid) {
322 return 0;
323 }
324 int superCount = super.getCount();
325 if (mSuggestionsCursorCount != 0) {
326 // When showing suggestions, we have 2 additional list items: the "Suggestions"
327 // and "All contacts" headers.
328 return mSuggestionsCursorCount + superCount + 2;
329 }
330 return superCount;
331 }
332
333 private int getRealPosition(int pos) {
334 if (mSuggestionsCursorCount != 0) {
335 // When showing suggestions, we have 2 additional list items: the "Suggestions"
336 // and "All contacts" separators.
337 if (pos < mSuggestionsCursorCount + 2) {
338 // We are in the upper partition (Suggestions). Adjusting for the "Suggestions"
339 // separator.
340 return pos - 1;
341 } else {
342 // We are in the lower partition (All contacts). Adjusting for the size
343 // of the upper partition plus the two separators.
344 return pos - mSuggestionsCursorCount - 2;
345 }
346 } else {
347 // No separator, identity map
348 return pos;
349 }
350 }
351
352 @Override
353 public Object getItem(int pos) {
354 if (mSuggestionsCursorCount != 0 && pos <= mSuggestionsCursorCount) {
355 mSuggestionsCursor.moveToPosition(getRealPosition(pos));
356 return mSuggestionsCursor;
357 } else {
358 int realPosition = getRealPosition(pos);
359 if (realPosition < 0) {
360 return null;
361 }
362 return super.getItem(realPosition);
363 }
364 }
365
366 @Override
367 public long getItemId(int pos) {
368 if (mSuggestionsCursorCount != 0 && pos < mSuggestionsCursorCount + 2) {
369 if (mSuggestionsCursor.moveToPosition(pos - 1)) {
370 return mSuggestionsCursor.getLong(mRowIDColumn);
371 } else {
372 return 0;
373 }
374 }
375 int realPosition = getRealPosition(pos);
376 if (realPosition < 0) {
377 return 0;
378 }
379 return super.getItemId(realPosition);
380 }
381 }
382}