1/*
2 * Copyright (C) 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
17package com.android.photos;
18
19import android.app.Activity;
20import android.app.Fragment;
21import android.os.Bundle;
22import android.os.Handler;
23import android.util.SparseBooleanArray;
24import android.view.LayoutInflater;
25import android.view.View;
26import android.view.ViewGroup;
27import android.view.animation.AnimationUtils;
28import android.widget.AdapterView;
29import android.widget.GridView;
30import android.widget.ListAdapter;
31import android.widget.TextView;
32
33import com.android.gallery3d.R;
34
35public abstract class MultiSelectGridFragment extends Fragment
36        implements MultiChoiceManager.Delegate, AdapterView.OnItemClickListener {
37
38    final private Handler mHandler = new Handler();
39
40    final private Runnable mRequestFocus = new Runnable() {
41        @Override
42        public void run() {
43            mGrid.focusableViewAvailable(mGrid);
44        }
45    };
46
47    ListAdapter mAdapter;
48    GridView mGrid;
49    TextView mEmptyView;
50    View mProgressContainer;
51    View mGridContainer;
52    CharSequence mEmptyText;
53    boolean mGridShown;
54    MultiChoiceManager.Provider mHost;
55
56    public MultiSelectGridFragment() {
57    }
58
59    /**
60     * Provide default implementation to return a simple grid view. Subclasses
61     * can override to replace with their own layout. If doing so, the returned
62     * view hierarchy <em>must</em> have a GridView whose id is
63     * {@link android.R.id#grid android.R.id.list} and can optionally have a
64     * sibling text view id {@link android.R.id#empty android.R.id.empty} that
65     * is to be shown when the grid is empty.
66     */
67    @Override
68    public View onCreateView(LayoutInflater inflater, ViewGroup container,
69            Bundle savedInstanceState) {
70        return inflater.inflate(R.layout.multigrid_content, container, false);
71    }
72
73    @Override
74    public void onAttach(Activity activity) {
75        super.onAttach(activity);
76        mHost = (MultiChoiceManager.Provider) activity;
77        if (mGrid != null) {
78            mGrid.setMultiChoiceModeListener(mHost.getMultiChoiceManager());
79        }
80    }
81
82    @Override
83    public void onDetach() {
84        super.onDetach();
85        mHost = null;
86    }
87
88    /**
89     * Attach to grid view once the view hierarchy has been created.
90     */
91    @Override
92    public void onViewCreated(View view, Bundle savedInstanceState) {
93        super.onViewCreated(view, savedInstanceState);
94        ensureGrid();
95    }
96
97    /**
98     * Detach from grid view.
99     */
100    @Override
101    public void onDestroyView() {
102        mHandler.removeCallbacks(mRequestFocus);
103        mGrid = null;
104        mGridShown = false;
105        mEmptyView = null;
106        mProgressContainer = mGridContainer = null;
107        super.onDestroyView();
108    }
109
110    /**
111     * This method will be called when an item in the grid is selected.
112     * Subclasses should override. Subclasses can call
113     * getGridView().getItemAtPosition(position) if they need to access the data
114     * associated with the selected item.
115     *
116     * @param g The GridView where the click happened
117     * @param v The view that was clicked within the GridView
118     * @param position The position of the view in the grid
119     * @param id The id of the item that was clicked
120     */
121    public void onGridItemClick(GridView g, View v, int position, long id) {
122    }
123
124    /**
125     * Provide the cursor for the grid view.
126     */
127    public void setAdapter(ListAdapter adapter) {
128        boolean hadAdapter = mAdapter != null;
129        mAdapter = adapter;
130        if (mGrid != null) {
131            mGrid.setAdapter(adapter);
132            if (!mGridShown && !hadAdapter) {
133                // The grid was hidden, and previously didn't have an
134                // adapter. It is now time to show it.
135                setGridShown(true, getView().getWindowToken() != null);
136            }
137        }
138    }
139
140    /**
141     * Set the currently selected grid item to the specified position with the
142     * adapter's data
143     *
144     * @param position
145     */
146    public void setSelection(int position) {
147        ensureGrid();
148        mGrid.setSelection(position);
149    }
150
151    /**
152     * Get the position of the currently selected grid item.
153     */
154    public int getSelectedItemPosition() {
155        ensureGrid();
156        return mGrid.getSelectedItemPosition();
157    }
158
159    /**
160     * Get the cursor row ID of the currently selected grid item.
161     */
162    public long getSelectedItemId() {
163        ensureGrid();
164        return mGrid.getSelectedItemId();
165    }
166
167    /**
168     * Get the activity's grid view widget.
169     */
170    public GridView getGridView() {
171        ensureGrid();
172        return mGrid;
173    }
174
175    /**
176     * The default content for a MultiSelectGridFragment has a TextView that can
177     * be shown when the grid is empty. If you would like to have it shown, call
178     * this method to supply the text it should use.
179     */
180    public void setEmptyText(CharSequence text) {
181        ensureGrid();
182        if (mEmptyView == null) {
183            return;
184        }
185        mEmptyView.setText(text);
186        if (mEmptyText == null) {
187            mGrid.setEmptyView(mEmptyView);
188        }
189        mEmptyText = text;
190    }
191
192    /**
193     * Control whether the grid is being displayed. You can make it not
194     * displayed if you are waiting for the initial data to show in it. During
195     * this time an indeterminate progress indicator will be shown instead.
196     * <p>
197     * Applications do not normally need to use this themselves. The default
198     * behavior of MultiSelectGridFragment is to start with the grid not being
199     * shown, only showing it once an adapter is given with
200     * {@link #setAdapter(ListAdapter)}. If the grid at that point had not been
201     * shown, when it does get shown it will be do without the user ever seeing
202     * the hidden state.
203     *
204     * @param shown If true, the grid view is shown; if false, the progress
205     *            indicator. The initial value is true.
206     */
207    public void setGridShown(boolean shown) {
208        setGridShown(shown, true);
209    }
210
211    /**
212     * Like {@link #setGridShown(boolean)}, but no animation is used when
213     * transitioning from the previous state.
214     */
215    public void setGridShownNoAnimation(boolean shown) {
216        setGridShown(shown, false);
217    }
218
219    /**
220     * Control whether the grid is being displayed. You can make it not
221     * displayed if you are waiting for the initial data to show in it. During
222     * this time an indeterminate progress indicator will be shown instead.
223     *
224     * @param shown If true, the grid view is shown; if false, the progress
225     *            indicator. The initial value is true.
226     * @param animate If true, an animation will be used to transition to the
227     *            new state.
228     */
229    private void setGridShown(boolean shown, boolean animate) {
230        ensureGrid();
231        if (mProgressContainer == null) {
232            throw new IllegalStateException("Can't be used with a custom content view");
233        }
234        if (mGridShown == shown) {
235            return;
236        }
237        mGridShown = shown;
238        if (shown) {
239            if (animate) {
240                mProgressContainer.startAnimation(AnimationUtils.loadAnimation(
241                        getActivity(), android.R.anim.fade_out));
242                mGridContainer.startAnimation(AnimationUtils.loadAnimation(
243                        getActivity(), android.R.anim.fade_in));
244            } else {
245                mProgressContainer.clearAnimation();
246                mGridContainer.clearAnimation();
247            }
248            mProgressContainer.setVisibility(View.GONE);
249            mGridContainer.setVisibility(View.VISIBLE);
250        } else {
251            if (animate) {
252                mProgressContainer.startAnimation(AnimationUtils.loadAnimation(
253                        getActivity(), android.R.anim.fade_in));
254                mGridContainer.startAnimation(AnimationUtils.loadAnimation(
255                        getActivity(), android.R.anim.fade_out));
256            } else {
257                mProgressContainer.clearAnimation();
258                mGridContainer.clearAnimation();
259            }
260            mProgressContainer.setVisibility(View.VISIBLE);
261            mGridContainer.setVisibility(View.GONE);
262        }
263    }
264
265    /**
266     * Get the ListAdapter associated with this activity's GridView.
267     */
268    public ListAdapter getAdapter() {
269        return mGrid.getAdapter();
270    }
271
272    private void ensureGrid() {
273        if (mGrid != null) {
274            return;
275        }
276        View root = getView();
277        if (root == null) {
278            throw new IllegalStateException("Content view not yet created");
279        }
280        if (root instanceof GridView) {
281            mGrid = (GridView) root;
282        } else {
283            View empty = root.findViewById(android.R.id.empty);
284            if (empty != null && empty instanceof TextView) {
285                mEmptyView = (TextView) empty;
286            }
287            mProgressContainer = root.findViewById(R.id.progressContainer);
288            mGridContainer = root.findViewById(R.id.gridContainer);
289            View rawGridView = root.findViewById(android.R.id.list);
290            if (!(rawGridView instanceof GridView)) {
291                throw new RuntimeException(
292                        "Content has view with id attribute 'android.R.id.list' "
293                                + "that is not a GridView class");
294            }
295            mGrid = (GridView) rawGridView;
296            if (mGrid == null) {
297                throw new RuntimeException(
298                        "Your content must have a GridView whose id attribute is " +
299                                "'android.R.id.list'");
300            }
301            if (mEmptyView != null) {
302                mGrid.setEmptyView(mEmptyView);
303            }
304        }
305        mGridShown = true;
306        mGrid.setOnItemClickListener(this);
307        mGrid.setMultiChoiceModeListener(mHost.getMultiChoiceManager());
308        if (mAdapter != null) {
309            ListAdapter adapter = mAdapter;
310            mAdapter = null;
311            setAdapter(adapter);
312        } else {
313            // We are starting without an adapter, so assume we won't
314            // have our data right away and start with the progress indicator.
315            if (mProgressContainer != null) {
316                setGridShown(false, false);
317            }
318        }
319        mHandler.post(mRequestFocus);
320    }
321
322    @Override
323    public Object getItemAtPosition(int position) {
324        return getAdapter().getItem(position);
325    }
326
327    @Override
328    public Object getPathForItemAtPosition(int position) {
329        return getPathForItem(getItemAtPosition(position));
330    }
331
332    @Override
333    public SparseBooleanArray getSelectedItemPositions() {
334        return mGrid.getCheckedItemPositions();
335    }
336
337    @Override
338    public int getSelectedItemCount() {
339        return mGrid.getCheckedItemCount();
340    }
341
342    public abstract Object getPathForItem(Object item);
343
344    @Override
345    public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
346        onGridItemClick((GridView) parent, v, position, id);
347    }
348}
349