1 /*
2  * Copyright 2013 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 
17 package com.example.android.content.loadercursor;
18 
19 import android.app.ListFragment;
20 import android.app.LoaderManager;
21 import android.content.CursorLoader;
22 import android.content.Loader;
23 import android.database.Cursor;
24 import android.net.Uri;
25 import android.os.Bundle;
26 import android.provider.ContactsContract.Contacts;
27 import android.text.TextUtils;
28 import android.view.Menu;
29 import android.view.MenuInflater;
30 import android.view.MenuItem;
31 import android.view.View;
32 import android.widget.ListView;
33 import android.widget.SearchView;
34 import android.widget.SearchView.OnCloseListener;
35 import android.widget.SearchView.OnQueryTextListener;
36 import android.widget.SimpleCursorAdapter;
37 import android.widget.Toast;
38 
39 /**
40  * A {@link ListFragment} that shows the use of a {@link LoaderManager} to
41  * display a list of contacts accessed through a {@link Cursor}.
42  */
43 public class CursorLoaderListFragment extends ListFragment implements
44         LoaderManager.LoaderCallbacks<Cursor> {
45 
46     // This is the Adapter being used to display the list's data.
47     SimpleCursorAdapter mAdapter;
48 
49     // The SearchView for doing filtering.
50     SearchView mSearchView;
51 
52     // If non-null, this is the current filter the user has provided.
53     String mCurFilter;
54 
55     @Override
onActivityCreated(Bundle savedInstanceState)56     public void onActivityCreated(Bundle savedInstanceState) {
57         super.onActivityCreated(savedInstanceState);
58 
59         // Give some text to display if there is no data. In a real
60         // application this would come from a resource.
61         setEmptyText("No phone numbers");
62 
63         // We have a menu item to show in action bar.
64         setHasOptionsMenu(true);
65 
66         /*
67          * Create an empty adapter we will use to display the loaded data. The
68          * simple_list_item_2 layout contains two rows on top of each other
69          * (text1 and text2) that will show the contact's name and status.
70          */
71         mAdapter = new SimpleCursorAdapter(getActivity(),
72                 android.R.layout.simple_list_item_2, null,
73                 new String[] {
74                         Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS
75                 },
76                 new int[] {
77                         android.R.id.text1, android.R.id.text2
78                 }, 0);
79         setListAdapter(mAdapter);
80 
81         // Start out with a progress indicator.
82         setListShown(false);
83 
84         // BEGIN_INCLUDE(getloader)
85         // Prepare the loader. Either re-connect with an existing one,
86         // or start a new one.
87         getLoaderManager().initLoader(0, null, this);
88         // END_INCLUDE(getloader)
89     }
90 
91     @Override
onCreateOptionsMenu(Menu menu, MenuInflater inflater)92     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
93 
94         inflater.inflate(R.menu.main, menu);
95 
96         // Get the search item and update its action view
97         MenuItem item = menu.findItem(R.id.action_search);
98         mSearchView = (SearchView) item.getActionView();
99         mSearchView.setOnQueryTextListener(queryListener);
100         mSearchView.setOnCloseListener(closeListener);
101         mSearchView.setIconifiedByDefault(true);
102     }
103 
104     /**
105      * The {@link OnCloseListener} called when the SearchView is closed. Resets
106      * the query field.
107      */
108     private SearchView.OnCloseListener closeListener = new SearchView.OnCloseListener() {
109 
110         @Override
111         public boolean onClose() {
112             // Restore the SearchView if a query was entered
113             if (!TextUtils.isEmpty(mSearchView.getQuery())) {
114                 mSearchView.setQuery(null, true);
115             }
116             return true;
117         }
118     };
119 
120     /**
121      * The {@link OnQueryTextListener} that is called when text in the
122      * {@link SearchView} is changed. Updates the query filter and triggers a
123      * reload of the {@link LoaderManager} to update the displayed list.
124      */
125     private OnQueryTextListener queryListener = new OnQueryTextListener() {
126 
127         /**
128          * Called when the action bar search text has changed. Update the search
129          * filter, and restart the loader to do a new query with this filter.
130          *
131          * @param newText the new content of the query text field
132          * @return true, the action has been handled.
133          */
134         public boolean onQueryTextChange(String newText) {
135 
136             String newFilter = !TextUtils.isEmpty(newText) ? newText : null;
137 
138             // Don't do anything if the filter hasn't actually changed.
139             // Prevents restarting the loader when restoring state.
140             if (mCurFilter == null && newFilter == null) {
141                 return true;
142             }
143             if (mCurFilter != null && mCurFilter.equals(newFilter)) {
144                 return true;
145             }
146 
147             // Restart the Loader.
148             // #onCreateLoader uses the value of mCurFilter as a filter when
149             // creating the query for the Loader.
150             mCurFilter = newFilter;
151             getLoaderManager().restartLoader(0, null, CursorLoaderListFragment.this);
152 
153             return true;
154         }
155 
156         @Override
157         public boolean onQueryTextSubmit(String query) {
158             // Don't care about this.
159             return true;
160         }
161     };
162 
163     /**
164      * An item has been clicked in the {@link ListView}. Display a toast with
165      * the tapped item's id.
166      */
167     @Override
onListItemClick(ListView l, View v, int position, long id)168     public void onListItemClick(ListView l, View v, int position, long id) {
169         Toast.makeText(getActivity(), "Item clicked: " + id, Toast.LENGTH_LONG).show();
170     }
171 
172     // These are the Contacts rows that we will retrieve.
173     static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
174             Contacts._ID,
175             Contacts.DISPLAY_NAME,
176             Contacts.CONTACT_STATUS,
177             Contacts.LOOKUP_KEY,
178     };
179 
180     // BEGIN_INCLUDE(oncreateloader)
onCreateLoader(int id, Bundle args)181     public Loader<Cursor> onCreateLoader(int id, Bundle args) {
182         // This is called when a new Loader needs to be created. This
183         // sample only has one Loader, so we don't care about the ID.
184         // First, pick the base URI to use depending on whether we are
185         // currently filtering.
186         Uri baseUri;
187         if (mCurFilter != null) {
188             baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
189                     Uri.encode(mCurFilter));
190         } else {
191             baseUri = Contacts.CONTENT_URI;
192         }
193 
194         // Now create and return a CursorLoader that will take care of
195         // creating a Cursor for the data being displayed.
196         String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
197                 + Contacts.HAS_PHONE_NUMBER + "=1) AND ("
198                 + Contacts.DISPLAY_NAME + " != '' ))";
199         String order = Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC";
200 
201         return new CursorLoader(getActivity(), baseUri,
202                 CONTACTS_SUMMARY_PROJECTION, select, null, order);
203     }
204 
205     // END_INCLUDE(oncreateloader)
206 
207     // BEGIN_INCLUDE(onloadfinished)
onLoadFinished(Loader<Cursor> loader, Cursor data)208     public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
209         // Swap the new cursor in. (The framework will take care of closing the
210         // old cursor once we return.)
211         mAdapter.swapCursor(data);
212 
213         // The list should now be shown.
214         if (isResumed()) {
215             setListShown(true);
216         } else {
217             setListShownNoAnimation(true);
218         }
219     }
220 
221     // END_INCLUDE(onloadfinished)
222 
223     // BEGIN_INCLUDE(onloaderreset)
onLoaderReset(Loader<Cursor> loader)224     public void onLoaderReset(Loader<Cursor> loader) {
225         // This is called when the last Cursor provided to onLoadFinished()
226         // above is about to be closed. We need to make sure we are no
227         // longer using it.
228         mAdapter.swapCursor(null);
229     }
230     // END_INCLUDE(onloaderreset)
231 
232 }
233