1 /* 2 * Copyright (C) 2010 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 package com.android.contacts.list; 17 18 import android.content.Context; 19 import android.content.CursorLoader; 20 import android.content.Intent; 21 import android.database.Cursor; 22 import android.provider.ContactsContract.Contacts; 23 import android.text.TextUtils; 24 import android.util.Log; 25 import android.view.LayoutInflater; 26 import android.view.View; 27 import android.view.View.OnClickListener; 28 import android.view.ViewGroup; 29 import android.view.accessibility.AccessibilityEvent; 30 import android.widget.Button; 31 import android.widget.FrameLayout; 32 import android.widget.ListView; 33 import android.widget.TextView; 34 35 import com.android.contacts.R; 36 import com.android.contacts.common.list.ContactListAdapter; 37 import com.android.contacts.common.list.ContactListFilter; 38 import com.android.contacts.common.list.ContactListFilterController; 39 import com.android.contacts.common.list.ContactListItemView; 40 import com.android.contacts.common.list.DefaultContactListAdapter; 41 import com.android.contacts.common.list.ProfileAndContactsLoader; 42 import com.android.contacts.editor.ContactEditorFragment; 43 import com.android.contacts.common.util.AccountFilterUtil; 44 45 /** 46 * Fragment containing a contact list used for browsing (as compared to 47 * picking a contact with one of the PICK intents). 48 */ 49 public class DefaultContactBrowseListFragment extends ContactBrowseListFragment { 50 private static final String TAG = DefaultContactBrowseListFragment.class.getSimpleName(); 51 52 private static final int REQUEST_CODE_ACCOUNT_FILTER = 1; 53 54 private TextView mCounterHeaderView; 55 private View mSearchHeaderView; 56 private View mAccountFilterHeader; 57 private FrameLayout mProfileHeaderContainer; 58 private View mProfileHeader; 59 private Button mProfileMessage; 60 private FrameLayout mMessageContainer; 61 private TextView mProfileTitle; 62 private View mSearchProgress; 63 private TextView mSearchProgressText; 64 65 private class FilterHeaderClickListener implements OnClickListener { 66 @Override 67 public void onClick(View view) { 68 AccountFilterUtil.startAccountFilterActivityForResult( 69 DefaultContactBrowseListFragment.this, 70 REQUEST_CODE_ACCOUNT_FILTER, 71 getFilter()); 72 } 73 } 74 private OnClickListener mFilterHeaderClickListener = new FilterHeaderClickListener(); 75 76 public DefaultContactBrowseListFragment() { 77 setPhotoLoaderEnabled(true); 78 setSectionHeaderDisplayEnabled(true); 79 setVisibleScrollbarEnabled(true); 80 } 81 82 @Override 83 public CursorLoader createCursorLoader(Context context) { 84 return new ProfileAndContactsLoader(context); 85 } 86 87 @Override 88 protected void onItemClick(int position, long id) { 89 viewContact(getAdapter().getContactUri(position)); 90 } 91 92 @Override 93 protected ContactListAdapter createListAdapter() { 94 DefaultContactListAdapter adapter = new DefaultContactListAdapter(getContext()); 95 adapter.setSectionHeaderDisplayEnabled(isSectionHeaderDisplayEnabled()); 96 boolean showPhoto = getResources().getBoolean(R.bool.config_browse_list_show_images); 97 adapter.setDisplayPhotos(showPhoto); 98 if (showPhoto) { 99 boolean reverse = getResources().getBoolean(R.bool.config_browse_list_reverse_images); 100 adapter.setPhotoPosition(ContactListItemView.getDefaultPhotoPosition(reverse)); 101 } 102 return adapter; 103 } 104 105 @Override 106 protected View inflateView(LayoutInflater inflater, ViewGroup container) { 107 return inflater.inflate(R.layout.contact_list_content, null); 108 } 109 110 @Override 111 protected void onCreateView(LayoutInflater inflater, ViewGroup container) { 112 super.onCreateView(inflater, container); 113 114 mAccountFilterHeader = getView().findViewById(R.id.account_filter_header_container); 115 mAccountFilterHeader.setOnClickListener(mFilterHeaderClickListener); 116 mCounterHeaderView = (TextView) getView().findViewById(R.id.contacts_count); 117 118 // Create an empty user profile header and hide it for now (it will be visible if the 119 // contacts list will have no user profile). 120 addEmptyUserProfileHeader(inflater); 121 showEmptyUserProfile(false); 122 123 // Putting the header view inside a container will allow us to make 124 // it invisible later. See checkHeaderViewVisibility() 125 FrameLayout headerContainer = new FrameLayout(inflater.getContext()); 126 mSearchHeaderView = inflater.inflate(R.layout.search_header, null, false); 127 headerContainer.addView(mSearchHeaderView); 128 getListView().addHeaderView(headerContainer, null, false); 129 checkHeaderViewVisibility(); 130 131 mSearchProgress = getView().findViewById(R.id.search_progress); 132 mSearchProgressText = (TextView) mSearchHeaderView.findViewById(R.id.totalContactsText); 133 } 134 135 @Override 136 protected void setSearchMode(boolean flag) { 137 super.setSearchMode(flag); 138 checkHeaderViewVisibility(); 139 if (!flag) showSearchProgress(false); 140 } 141 142 /** Show or hide the directory-search progress spinner. */ 143 private void showSearchProgress(boolean show) { 144 mSearchProgress.setVisibility(show ? View.VISIBLE : View.GONE); 145 } 146 147 private void checkHeaderViewVisibility() { 148 if (mCounterHeaderView != null) { 149 mCounterHeaderView.setVisibility(isSearchMode() ? View.GONE : View.VISIBLE); 150 } 151 updateFilterHeaderView(); 152 153 // Hide the search header by default. See showCount(). 154 if (mSearchHeaderView != null) { 155 mSearchHeaderView.setVisibility(View.GONE); 156 } 157 } 158 159 @Override 160 public void setFilter(ContactListFilter filter) { 161 super.setFilter(filter); 162 updateFilterHeaderView(); 163 } 164 165 private void updateFilterHeaderView() { 166 if (mAccountFilterHeader == null) { 167 return; // Before onCreateView -- just ignore it. 168 } 169 final ContactListFilter filter = getFilter(); 170 if (filter != null && !isSearchMode()) { 171 final boolean shouldShowHeader = AccountFilterUtil.updateAccountFilterTitleForPeople( 172 mAccountFilterHeader, filter, false); 173 mAccountFilterHeader.setVisibility(shouldShowHeader ? View.VISIBLE : View.GONE); 174 } else { 175 mAccountFilterHeader.setVisibility(View.GONE); 176 } 177 } 178 179 @Override 180 protected void showCount(int partitionIndex, Cursor data) { 181 if (!isSearchMode() && data != null) { 182 int count = data.getCount(); 183 if (count != 0) { 184 count -= (mUserProfileExists ? 1: 0); 185 String format = getResources().getQuantityText( 186 R.plurals.listTotalAllContacts, count).toString(); 187 // Do not count the user profile in the contacts count 188 if (mUserProfileExists) { 189 getAdapter().setContactsCount(String.format(format, count)); 190 } else { 191 mCounterHeaderView.setText(String.format(format, count)); 192 } 193 } else { 194 ContactListFilter filter = getFilter(); 195 int filterType = filter != null ? filter.filterType 196 : ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS; 197 switch (filterType) { 198 case ContactListFilter.FILTER_TYPE_ACCOUNT: 199 mCounterHeaderView.setText(getString( 200 R.string.listTotalAllContactsZeroGroup, filter.accountName)); 201 break; 202 case ContactListFilter.FILTER_TYPE_WITH_PHONE_NUMBERS_ONLY: 203 mCounterHeaderView.setText(R.string.listTotalPhoneContactsZero); 204 break; 205 case ContactListFilter.FILTER_TYPE_STARRED: 206 mCounterHeaderView.setText(R.string.listTotalAllContactsZeroStarred); 207 break; 208 case ContactListFilter.FILTER_TYPE_CUSTOM: 209 mCounterHeaderView.setText(R.string.listTotalAllContactsZeroCustom); 210 break; 211 default: 212 mCounterHeaderView.setText(R.string.listTotalAllContactsZero); 213 break; 214 } 215 } 216 } else { 217 ContactListAdapter adapter = getAdapter(); 218 if (adapter == null) { 219 return; 220 } 221 222 // In search mode we only display the header if there is nothing found 223 if (TextUtils.isEmpty(getQueryString()) || !adapter.areAllPartitionsEmpty()) { 224 mSearchHeaderView.setVisibility(View.GONE); 225 showSearchProgress(false); 226 } else { 227 mSearchHeaderView.setVisibility(View.VISIBLE); 228 if (adapter.isLoading()) { 229 mSearchProgressText.setText(R.string.search_results_searching); 230 showSearchProgress(true); 231 } else { 232 mSearchProgressText.setText(R.string.listFoundAllContactsZero); 233 mSearchProgressText.sendAccessibilityEvent( 234 AccessibilityEvent.TYPE_VIEW_SELECTED); 235 showSearchProgress(false); 236 } 237 } 238 showEmptyUserProfile(false); 239 } 240 } 241 242 @Override 243 protected void setProfileHeader() { 244 mUserProfileExists = getAdapter().hasProfile(); 245 showEmptyUserProfile(!mUserProfileExists && !isSearchMode()); 246 } 247 248 @Override 249 public void onActivityResult(int requestCode, int resultCode, Intent data) { 250 if (requestCode == REQUEST_CODE_ACCOUNT_FILTER) { 251 if (getActivity() != null) { 252 AccountFilterUtil.handleAccountFilterResult( 253 ContactListFilterController.getInstance(getActivity()), resultCode, data); 254 } else { 255 Log.e(TAG, "getActivity() returns null during Fragment#onActivityResult()"); 256 } 257 } 258 } 259 260 private void showEmptyUserProfile(boolean show) { 261 // Changing visibility of just the mProfileHeader doesn't do anything unless 262 // you change visibility of its children, hence the call to mCounterHeaderView 263 // and mProfileTitle 264 mProfileHeaderContainer.setVisibility(show ? View.VISIBLE : View.GONE); 265 mProfileHeader.setVisibility(show ? View.VISIBLE : View.GONE); 266 mCounterHeaderView.setVisibility(show ? View.VISIBLE : View.GONE); 267 mProfileTitle.setVisibility(show ? View.VISIBLE : View.GONE); 268 mMessageContainer.setVisibility(show ? View.VISIBLE : View.GONE); 269 mProfileMessage.setVisibility(show ? View.VISIBLE : View.GONE); 270 } 271 272 /** 273 * This method creates a pseudo user profile contact. When the returned query doesn't have 274 * a profile, this methods creates 2 views that are inserted as headers to the listview: 275 * 1. A header view with the "ME" title and the contacts count. 276 * 2. A button that prompts the user to create a local profile 277 */ 278 private void addEmptyUserProfileHeader(LayoutInflater inflater) { 279 280 ListView list = getListView(); 281 // Put a header with the "ME" name and a view for the number of contacts 282 // The view is embedded in a frame view since you cannot change the visibility of a 283 // view in a ListView without having a parent view. 284 mProfileHeaderContainer = new FrameLayout(inflater.getContext()); 285 mProfileHeader = inflater.inflate(R.layout.user_profile_header, null, false); 286 mCounterHeaderView = (TextView) mProfileHeader.findViewById(R.id.contacts_count); 287 mProfileTitle = (TextView) mProfileHeader.findViewById(R.id.profile_title); 288 mProfileHeaderContainer.addView(mProfileHeader); 289 list.addHeaderView(mProfileHeaderContainer, null, false); 290 291 // Add a selectable view with a message inviting the user to create a local profile 292 mMessageContainer = new FrameLayout(inflater.getContext()); 293 mProfileMessage = (Button)inflater.inflate(R.layout.user_profile_button, null, false); 294 mMessageContainer.addView(mProfileMessage); 295 list.addHeaderView(mMessageContainer, null, true); 296 297 mProfileMessage.setOnClickListener(new View.OnClickListener() { 298 public void onClick(View v) { 299 Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI); 300 intent.putExtra(ContactEditorFragment.INTENT_EXTRA_NEW_LOCAL_PROFILE, true); 301 startActivity(intent); 302 } 303 }); 304 } 305 } 306