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