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