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