1/*
2 * Copyright (C) 2011 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 */
16package com.android.contacts.list;
17
18import android.app.Activity;
19import android.app.Fragment;
20import android.app.LoaderManager;
21import android.app.LoaderManager.LoaderCallbacks;
22import android.content.CursorLoader;
23import android.content.Loader;
24import android.content.res.Resources;
25import android.database.Cursor;
26import android.graphics.Rect;
27import android.net.Uri;
28import android.os.Bundle;
29import android.util.Log;
30import android.view.LayoutInflater;
31import android.view.View;
32import android.view.ViewGroup;
33import android.widget.ListView;
34import android.widget.TextView;
35import com.android.contacts.R;
36import com.android.contacts.common.ContactPhotoManager;
37import com.android.contacts.common.ContactTileLoaderFactory;
38import com.android.contacts.common.list.ContactTileAdapter;
39import com.android.contacts.common.list.ContactTileAdapter.DisplayType;
40import com.android.contacts.common.list.ContactTileView;
41import com.android.contacts.common.util.ContactListViewUtils;
42import com.android.contacts.common.util.SchedulingUtils;
43
44/**
45 * Fragment containing a list of starred contacts followed by a list of frequently contacted.
46 *
47 * TODO: Make this an abstract class so that the favorites, frequent, and group list functionality
48 * can be separated out. This will make it easier to customize any of those lists if necessary
49 * (i.e. adding header views to the ListViews in the fragment). This work was started
50 * by creating {@link ContactTileFrequentFragment}.
51 */
52public class ContactTileListFragment extends Fragment {
53    private static final String TAG = ContactTileListFragment.class.getSimpleName();
54
55    public interface Listener {
56        void onContactSelected(Uri contactUri, Rect targetRect);
57        void onCallNumberDirectly(String phoneNumber);
58    }
59
60    private Listener mListener;
61    private ContactTileAdapter mAdapter;
62    private DisplayType mDisplayType;
63    private TextView mEmptyView;
64    private ListView mListView;
65
66    private boolean mOptionsMenuHasFrequents;
67
68    @Override
69    public void onAttach(Activity activity) {
70        super.onAttach(activity);
71
72        Resources res = getResources();
73        int columnCount = res.getInteger(R.integer.contact_tile_column_count_in_favorites);
74
75        mAdapter = new ContactTileAdapter(activity, mAdapterListener,
76                columnCount, mDisplayType);
77        mAdapter.setPhotoLoader(ContactPhotoManager.getInstance(activity));
78    }
79
80    @Override
81    public View onCreateView(LayoutInflater inflater, ViewGroup container,
82            Bundle savedInstanceState) {
83        return inflateAndSetupView(inflater, container, savedInstanceState,
84                R.layout.contact_tile_list);
85    }
86
87    protected View inflateAndSetupView(LayoutInflater inflater, ViewGroup container,
88            Bundle savedInstanceState, int layoutResourceId) {
89        View listLayout = inflater.inflate(layoutResourceId, container, false);
90
91        mEmptyView = (TextView) listLayout.findViewById(R.id.contact_tile_list_empty);
92        mListView = (ListView) listLayout.findViewById(R.id.contact_tile_list);
93
94        mListView.setItemsCanFocus(true);
95        mListView.setAdapter(mAdapter);
96        ContactListViewUtils.applyCardPaddingToView(getResources(), mListView, listLayout);
97
98        return listLayout;
99    }
100
101    @Override
102    public void onHiddenChanged(boolean hidden) {
103        super.onHiddenChanged(hidden);
104        if (getActivity() != null && getView() != null && !hidden) {
105            // If the padding was last applied when in a hidden state, it may have been applied
106            // incorrectly. Therefore we need to reapply it.
107            ContactListViewUtils.applyCardPaddingToView(getResources(), mListView, getView());
108        }
109    }
110
111    @Override
112    public void onStart() {
113        super.onStart();
114
115        // initialize the loader for this display type and destroy all others
116        final DisplayType[] loaderTypes = mDisplayType.values();
117        for (int i = 0; i < loaderTypes.length; i++) {
118            if (loaderTypes[i] == mDisplayType) {
119                getLoaderManager().initLoader(mDisplayType.ordinal(), null,
120                        mContactTileLoaderListener);
121            } else {
122                getLoaderManager().destroyLoader(loaderTypes[i].ordinal());
123            }
124        }
125    }
126
127    /**
128     * Returns whether there are any frequents with the side effect of setting the
129     * internal flag mOptionsMenuHasFrequents to the value.  This should be called externally
130     * by the activity that is about to prepare the options menu with the clear frequents
131     * menu item.
132     */
133    public boolean hasFrequents() {
134        mOptionsMenuHasFrequents = internalHasFrequents();
135        return mOptionsMenuHasFrequents;
136    }
137
138    /**
139     * Returns whether there are any frequents.
140     */
141    private boolean internalHasFrequents() {
142        return mAdapter.getNumFrequents() > 0;
143    }
144
145    public void setColumnCount(int columnCount) {
146        mAdapter.setColumnCount(columnCount);
147    }
148
149    public void setDisplayType(DisplayType displayType) {
150        mDisplayType = displayType;
151        mAdapter.setDisplayType(mDisplayType);
152    }
153
154    public void enableQuickContact(boolean enableQuickContact) {
155        mAdapter.enableQuickContact(enableQuickContact);
156    }
157
158    private final LoaderManager.LoaderCallbacks<Cursor> mContactTileLoaderListener =
159            new LoaderCallbacks<Cursor>() {
160
161        @Override
162        public CursorLoader onCreateLoader(int id, Bundle args) {
163            switch (mDisplayType) {
164              case STARRED_ONLY:
165                  return ContactTileLoaderFactory.createStarredLoader(getActivity());
166              case STREQUENT:
167                  return ContactTileLoaderFactory.createStrequentLoader(getActivity());
168              case FREQUENT_ONLY:
169                  return ContactTileLoaderFactory.createFrequentLoader(getActivity());
170              default:
171                  throw new IllegalStateException(
172                      "Unrecognized DisplayType " + mDisplayType);
173            }
174        }
175
176        @Override
177        public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
178            if (data == null || data.isClosed()) {
179                Log.e(TAG, "Failed to load contacts");
180                return;
181            }
182            mAdapter.setContactCursor(data);
183            mEmptyView.setText(getEmptyStateText());
184            mListView.setEmptyView(mEmptyView);
185
186            // invalidate the menu options if needed
187            invalidateOptionsMenuIfNeeded();
188        }
189
190        @Override
191        public void onLoaderReset(Loader<Cursor> loader) {}
192    };
193
194    private boolean isOptionsMenuChanged() {
195        return mOptionsMenuHasFrequents != internalHasFrequents();
196    }
197
198    private void invalidateOptionsMenuIfNeeded() {
199        if (isOptionsMenuChanged()) {
200            getActivity().invalidateOptionsMenu();
201        }
202    }
203
204    private String getEmptyStateText() {
205        String emptyText;
206        switch (mDisplayType) {
207            case STREQUENT:
208            case STARRED_ONLY:
209                emptyText = getString(R.string.listTotalAllContactsZeroStarred);
210                break;
211            case FREQUENT_ONLY:
212            case GROUP_MEMBERS:
213                emptyText = getString(R.string.noContacts);
214                break;
215            default:
216                throw new IllegalArgumentException("Unrecognized DisplayType " + mDisplayType);
217        }
218        return emptyText;
219    }
220
221    public void setListener(Listener listener) {
222        mListener = listener;
223    }
224
225    private ContactTileView.Listener mAdapterListener =
226            new ContactTileView.Listener() {
227        @Override
228        public void onContactSelected(Uri contactUri, Rect targetRect) {
229            if (mListener != null) {
230                mListener.onContactSelected(contactUri, targetRect);
231            }
232        }
233
234        @Override
235        public void onCallNumberDirectly(String phoneNumber) {
236            if (mListener != null) {
237                mListener.onCallNumberDirectly(phoneNumber);
238            }
239        }
240
241        @Override
242        public int getApproximateTileWidth() {
243            return getView().getWidth() / mAdapter.getColumnCount();
244        }
245    };
246}
247