1/*
2 * Copyright (C) 2011 Google Inc.
3 * Licensed to The Android Open Source Project.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *      http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package com.android.ex.photo.adapters;
19
20import android.app.Fragment;
21import android.app.FragmentManager;
22import android.app.FragmentTransaction;
23import android.os.Parcelable;
24import android.support.v4.app.FragmentPagerAdapter;
25import android.support.v4.view.PagerAdapter;
26import android.util.Log;
27import android.util.LruCache;
28import android.view.View;
29
30/**
31 * NOTE: This is a direct copy of {@link FragmentPagerAdapter} with four very important
32 * modifications.
33 * <p>
34 * <ol>
35 * <li>The method {@link #makeFragmentName(int, int)} is declared "protected"
36 * in our class. We need to be able to re-define the fragment's name according to data
37 * only available to sub-classes.</li>
38 * <li>The method {@link #isViewFromObject(View, Object)} has been reimplemented to search
39 * the entire view hierarchy for the given view.</li>
40 * <li>In method {@link #destroyItem(View, int, Object)}, the fragment is detached and
41 * added to a cache. If the fragment is evicted from the cache, it will be deleted.
42 * An album may contain thousands of photos and we want to avoid having thousands of
43 * fragments.</li>
44 * </ol>
45 */
46public abstract class BaseFragmentPagerAdapter extends PagerAdapter {
47    /** The default size of {@link #mFragmentCache} */
48    private static final int DEFAULT_CACHE_SIZE = 5;
49    private static final String TAG = "FragmentPagerAdapter";
50    private static final boolean DEBUG = false;
51
52    private final FragmentManager mFragmentManager;
53    private FragmentTransaction mCurTransaction = null;
54    private Fragment mCurrentPrimaryItem = null;
55    /** A cache to store detached fragments before they are removed  */
56    private LruCache<String, Fragment> mFragmentCache = new FragmentCache(DEFAULT_CACHE_SIZE);
57
58    public BaseFragmentPagerAdapter(FragmentManager fm) {
59        mFragmentManager = fm;
60    }
61
62    /**
63     * Return the Fragment associated with a specified position.
64     */
65    public abstract Fragment getItem(int position);
66
67    @Override
68    public void startUpdate(View container) {
69    }
70
71    @Override
72    public Object instantiateItem(View container, int position) {
73        if (mCurTransaction == null) {
74            mCurTransaction = mFragmentManager.beginTransaction();
75        }
76
77        // Do we already have this fragment?
78        String name = makeFragmentName(container.getId(), position);
79
80        // Remove item from the cache
81        mFragmentCache.remove(name);
82
83        Fragment fragment = mFragmentManager.findFragmentByTag(name);
84        if (fragment != null) {
85            if (DEBUG) Log.v(TAG, "Attaching item #" + position + ": f=" + fragment);
86            mCurTransaction.attach(fragment);
87        } else {
88            fragment = getItem(position);
89            if(fragment == null) {
90                if (DEBUG) Log.e(TAG, "NPE workaround for getItem(). See b/7103023");
91                return null;
92            }
93            if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
94            mCurTransaction.add(container.getId(), fragment,
95                    makeFragmentName(container.getId(), position));
96        }
97        if (fragment != mCurrentPrimaryItem) {
98            fragment.setMenuVisibility(false);
99        }
100
101        return fragment;
102    }
103
104    @Override
105    public void destroyItem(View container, int position, Object object) {
106        if (mCurTransaction == null) {
107            mCurTransaction = mFragmentManager.beginTransaction();
108        }
109        if (DEBUG) Log.v(TAG, "Detaching item #" + position + ": f=" + object
110                + " v=" + ((Fragment)object).getView());
111
112        Fragment fragment = (Fragment) object;
113        String name = fragment.getTag();
114        if (name == null) {
115            // We prefer to get the name directly from the fragment, but, if the fragment is
116            // detached before the add transaction is committed, this could be 'null'. In
117            // that case, generate a name so we can still cache the fragment.
118            name = makeFragmentName(container.getId(), position);
119        }
120
121        mFragmentCache.put(name, fragment);
122        mCurTransaction.detach(fragment);
123    }
124
125    @Override
126    public void setPrimaryItem(View container, int position, Object object) {
127        Fragment fragment = (Fragment) object;
128        if (fragment != mCurrentPrimaryItem) {
129            if (mCurrentPrimaryItem != null) {
130                mCurrentPrimaryItem.setMenuVisibility(false);
131            }
132            if (fragment != null) {
133                fragment.setMenuVisibility(true);
134            }
135            mCurrentPrimaryItem = fragment;
136        }
137
138    }
139
140    @Override
141    public void finishUpdate(View container) {
142        if (mCurTransaction != null) {
143            mCurTransaction.commitAllowingStateLoss();
144            mCurTransaction = null;
145            mFragmentManager.executePendingTransactions();
146        }
147    }
148
149    @Override
150    public boolean isViewFromObject(View view, Object object) {
151        // Ascend the tree to determine if the view is a child of the fragment
152        View root = ((Fragment) object).getView();
153        for (Object v = view; v instanceof View; v = ((View) v).getParent()) {
154            if (v == root) {
155                return true;
156            }
157        }
158        return false;
159    }
160
161    @Override
162    public Parcelable saveState() {
163        return null;
164    }
165
166    @Override
167    public void restoreState(Parcelable state, ClassLoader loader) {
168    }
169
170    /** Creates a name for the fragment */
171    protected String makeFragmentName(int viewId, int index) {
172        return "android:switcher:" + viewId + ":" + index;
173    }
174
175    /**
176     * A cache of detached fragments.
177     */
178    private class FragmentCache extends LruCache<String, Fragment> {
179        public FragmentCache(int size) {
180            super(size);
181        }
182
183        @Override
184        protected void entryRemoved(boolean evicted, String key,
185                Fragment oldValue, Fragment newValue) {
186            // remove the fragment if it's evicted OR it's replaced by a new fragment
187            if (evicted || (newValue != null && oldValue != newValue)) {
188                mCurTransaction.remove(oldValue);
189            }
190        }
191    }
192}
193