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