1/*
2 * Copyright (C) 2012 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.mail.utils;
18
19import android.app.Fragment;
20import android.app.FragmentManager;
21import android.app.FragmentTransaction;
22import android.os.Bundle;
23import android.os.Parcelable;
24import android.support.v13.app.FragmentCompat;
25import android.support.v13.app.FragmentStatePagerAdapter;
26import android.support.v4.util.SparseArrayCompat;
27import android.support.v4.view.PagerAdapter;
28import android.view.View;
29import android.view.ViewGroup;
30
31import java.util.ArrayList;
32
33/**
34 * Forked from support lib's {@link FragmentStatePagerAdapter}, with some minor
35 * changes that couldn't be accomplished through subclassing:
36 * <ul>
37 * <li>optionally disable stateful behavior when paging (controlled by {@link #mEnableSavedStates}),
38 * for situations where state save/restore when paging is unnecessary</li>
39 * <li>override-able {@link #setItemVisible(Fragment, boolean)} method for subclasses to
40 * add supplemental handling of visibility hints manually on pre-v15 devices</li>
41 * <li>add support to handle data set changes that cause item positions to change</li>
42 * <li>allow read access to existing Fragments by index ({@link #getFragmentAt(int)})</li>
43 * </ul>
44 */
45public abstract class FragmentStatePagerAdapter2 extends PagerAdapter {
46    private static final String TAG = "FragmentStatePagerAdapter";
47    private static final boolean DEBUG = false;
48
49    private final FragmentManager mFragmentManager;
50    private FragmentTransaction mCurTransaction = null;
51
52    private ArrayList<Fragment.SavedState> mSavedState = new ArrayList<Fragment.SavedState>();
53    private SparseArrayCompat<Fragment> mFragments = new SparseArrayCompat<Fragment>();
54    private Fragment mCurrentPrimaryItem = null;
55
56    private boolean mEnableSavedStates;
57
58    public FragmentStatePagerAdapter2(FragmentManager fm) {
59        this(fm, true);
60    }
61
62    public FragmentStatePagerAdapter2(FragmentManager fm, boolean enableSavedStates) {
63        mFragmentManager = fm;
64        mEnableSavedStates = enableSavedStates;
65    }
66
67    /**
68     * Return the Fragment associated with a specified position.
69     */
70    public abstract Fragment getItem(int position);
71
72    @Override
73    public void startUpdate(ViewGroup container) {
74    }
75
76    @Override
77    public Object instantiateItem(ViewGroup container, int position) {
78        // If we already have this item instantiated, there is nothing
79        // to do.  This can happen when we are restoring the entire pager
80        // from its saved state, where the fragment manager has already
81        // taken care of restoring the fragments we previously had instantiated.
82        final Fragment existing = mFragments.get(position);
83        if (existing != null) {
84            return existing;
85        }
86
87        if (mCurTransaction == null) {
88            mCurTransaction = mFragmentManager.beginTransaction();
89        }
90
91        Fragment fragment = getItem(position);
92        if (DEBUG) LogUtils.v(TAG, "Adding item #" + position + ": f=" + fragment);
93        if (mEnableSavedStates && mSavedState.size() > position) {
94            Fragment.SavedState fss = mSavedState.get(position);
95            if (fss != null) {
96                fragment.setInitialSavedState(fss);
97            }
98        }
99        if (fragment != mCurrentPrimaryItem) {
100            setItemVisible(fragment, false);
101        }
102        mFragments.put(position, fragment);
103        mCurTransaction.add(container.getId(), fragment);
104
105        return fragment;
106    }
107
108    @Override
109    public void destroyItem(ViewGroup container, int position, Object object) {
110        Fragment fragment = (Fragment)object;
111
112        if (mCurTransaction == null) {
113            mCurTransaction = mFragmentManager.beginTransaction();
114        }
115        if (DEBUG) LogUtils.v(TAG, "Removing item #" + position + ": f=" + object
116                + " v=" + ((Fragment)object).getView());
117        if (mEnableSavedStates) {
118            while (mSavedState.size() <= position) {
119                mSavedState.add(null);
120            }
121            mSavedState.set(position, mFragmentManager.saveFragmentInstanceState(fragment));
122        }
123        mFragments.delete(position);
124
125        mCurTransaction.remove(fragment);
126    }
127
128    @Override
129    public void setPrimaryItem(ViewGroup container, int position, Object object) {
130        Fragment fragment = (Fragment)object;
131        if (fragment != mCurrentPrimaryItem) {
132            if (mCurrentPrimaryItem != null) {
133                setItemVisible(mCurrentPrimaryItem, false);
134            }
135            if (fragment != null) {
136                setItemVisible(fragment, true);
137            }
138            mCurrentPrimaryItem = fragment;
139        }
140    }
141
142    @Override
143    public void finishUpdate(ViewGroup container) {
144        if (mCurTransaction != null) {
145            mCurTransaction.commitAllowingStateLoss();
146            mCurTransaction = null;
147            mFragmentManager.executePendingTransactions();
148        }
149    }
150
151    @Override
152    public boolean isViewFromObject(View view, Object object) {
153        return ((Fragment)object).getView() == view;
154    }
155
156    @Override
157    public Parcelable saveState() {
158        Bundle state = null;
159        if (mEnableSavedStates && mSavedState.size() > 0) {
160            state = new Bundle();
161            Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()];
162            mSavedState.toArray(fss);
163            state.putParcelableArray("states", fss);
164        }
165        for (int i=0; i<mFragments.size(); i++) {
166            final int pos = mFragments.keyAt(i);
167            final Fragment f = mFragments.valueAt(i);
168            if (state == null) {
169                state = new Bundle();
170            }
171            String key = "f" + pos;
172            mFragmentManager.putFragment(state, key, f);
173        }
174        return state;
175    }
176
177    @Override
178    public void restoreState(Parcelable state, ClassLoader loader) {
179        if (state != null) {
180            Bundle bundle = (Bundle)state;
181            bundle.setClassLoader(loader);
182            mFragments.clear();
183            if (mEnableSavedStates) {
184                Parcelable[] fss = bundle.getParcelableArray("states");
185                mSavedState.clear();
186                if (fss != null) {
187                    for (int i=0; i<fss.length; i++) {
188                        mSavedState.add((Fragment.SavedState)fss[i]);
189                    }
190                }
191            }
192            Iterable<String> keys = bundle.keySet();
193            for (String key: keys) {
194                if (key.startsWith("f")) {
195                    int index = Integer.parseInt(key.substring(1));
196                    Fragment f = mFragmentManager.getFragment(bundle, key);
197                    if (f != null) {
198                        setItemVisible(f, false);
199                        mFragments.put(index, f);
200                    } else {
201                        LogUtils.w(TAG, "Bad fragment at key " + key);
202                    }
203                }
204            }
205        }
206    }
207
208    public void setItemVisible(Fragment item, boolean visible) {
209        FragmentCompat.setMenuVisibility(item, visible);
210        FragmentCompat.setUserVisibleHint(item, visible);
211    }
212
213    @Override
214    public void notifyDataSetChanged() {
215        // update positions in mFragments
216        SparseArrayCompat<Fragment> newFragments =
217                new SparseArrayCompat<Fragment>(mFragments.size());
218        for (int i=0; i<mFragments.size(); i++) {
219            final int oldPos = mFragments.keyAt(i);
220            final Fragment f = mFragments.valueAt(i);
221            final int newPos = getItemPosition(f);
222
223            if (newPos != POSITION_NONE) {
224                final int pos = (newPos >= 0) ? newPos : oldPos;
225                newFragments.put(pos, f);
226            }
227        }
228        mFragments = newFragments;
229
230        super.notifyDataSetChanged();
231    }
232
233    public Fragment getFragmentAt(int position) {
234        return mFragments.get(position);
235    }
236}
237