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 (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
90            mCurTransaction.add(container.getId(), fragment,
91                    makeFragmentName(container.getId(), position));
92        }
93        if (fragment != mCurrentPrimaryItem) {
94            fragment.setMenuVisibility(false);
95        }
96
97        return fragment;
98    }
99
100    @Override
101    public void destroyItem(View container, int position, Object object) {
102        if (mCurTransaction == null) {
103            mCurTransaction = mFragmentManager.beginTransaction();
104        }
105        if (DEBUG) Log.v(TAG, "Detaching item #" + position + ": f=" + object
106                + " v=" + ((Fragment)object).getView());
107
108        Fragment fragment = (Fragment) object;
109        String name = fragment.getTag();
110        if (name == null) {
111            // We prefer to get the name directly from the fragment, but, if the fragment is
112            // detached before the add transaction is committed, this could be 'null'. In
113            // that case, generate a name so we can still cache the fragment.
114            name = makeFragmentName(container.getId(), position);
115        }
116
117        mFragmentCache.put(name, fragment);
118        mCurTransaction.detach(fragment);
119    }
120
121    @Override
122    public void setPrimaryItem(View container, int position, Object object) {
123        Fragment fragment = (Fragment) object;
124        if (fragment != mCurrentPrimaryItem) {
125            if (mCurrentPrimaryItem != null) {
126                mCurrentPrimaryItem.setMenuVisibility(false);
127            }
128            if (fragment != null) {
129                fragment.setMenuVisibility(true);
130            }
131            mCurrentPrimaryItem = fragment;
132        }
133
134    }
135
136    @Override
137    public void finishUpdate(View container) {
138        if (mCurTransaction != null) {
139            mCurTransaction.commitAllowingStateLoss();
140            mCurTransaction = null;
141            mFragmentManager.executePendingTransactions();
142        }
143    }
144
145    @Override
146    public boolean isViewFromObject(View view, Object object) {
147        // Ascend the tree to determine if the view is a child of the fragment
148        View root = ((Fragment) object).getView();
149        for (Object v = view; v instanceof View; v = ((View) v).getParent()) {
150            if (v == root) {
151                return true;
152            }
153        }
154        return false;
155    }
156
157    @Override
158    public Parcelable saveState() {
159        return null;
160    }
161
162    @Override
163    public void restoreState(Parcelable state, ClassLoader loader) {
164    }
165
166    /** Creates a name for the fragment */
167    protected String makeFragmentName(int viewId, int index) {
168        return "android:switcher:" + viewId + ":" + index;
169    }
170
171    /**
172     * A cache of detached fragments.
173     */
174    private class FragmentCache extends LruCache<String, Fragment> {
175        public FragmentCache(int size) {
176            super(size);
177        }
178
179        @Override
180        protected void entryRemoved(boolean evicted, String key,
181                Fragment oldValue, Fragment newValue) {
182            // remove the fragment if it's evicted OR it's replaced by a new fragment
183            if (evicted || (newValue != null && oldValue != newValue)) {
184                mCurTransaction.remove(oldValue);
185            }
186        }
187    }
188}
189