11cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein/*
21cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein * Copyright (C) 2011 Google Inc.
31cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein * Licensed to The Android Open Source Project.
41cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein *
51cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein * Licensed under the Apache License, Version 2.0 (the "License");
61cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein * you may not use this file except in compliance with the License.
71cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein * You may obtain a copy of the License at
81cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein *
91cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein *      http://www.apache.org/licenses/LICENSE-2.0
101cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein *
111cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein * Unless required by applicable law or agreed to in writing, software
121cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein * distributed under the License is distributed on an "AS IS" BASIS,
131cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
141cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein * See the License for the specific language governing permissions and
151cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein * limitations under the License.
161cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein */
171cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
181cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sappersteinpackage com.android.ex.photo.adapters;
191cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
201cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sappersteinimport android.app.Fragment;
211cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sappersteinimport android.app.FragmentManager;
221cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sappersteinimport android.app.FragmentTransaction;
231cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sappersteinimport android.os.Parcelable;
241cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sappersteinimport android.support.v4.app.FragmentPagerAdapter;
251cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sappersteinimport android.support.v4.view.PagerAdapter;
261cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sappersteinimport android.util.Log;
271cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sappersteinimport android.util.LruCache;
281cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sappersteinimport android.view.View;
291cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
301cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein/**
311cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein * NOTE: This is a direct copy of {@link FragmentPagerAdapter} with four very important
321cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein * modifications.
331cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein * <p>
341cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein * <ol>
351cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein * <li>The method {@link #makeFragmentName(int, int)} is declared "protected"
361cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein * in our class. We need to be able to re-define the fragment's name according to data
371cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein * only available to sub-classes.</li>
381cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein * <li>The method {@link #isViewFromObject(View, Object)} has been reimplemented to search
391cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein * the entire view hierarchy for the given view.</li>
401cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein * <li>In method {@link #destroyItem(View, int, Object)}, the fragment is detached and
411cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein * added to a cache. If the fragment is evicted from the cache, it will be deleted.
421cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein * An album may contain thousands of photos and we want to avoid having thousands of
431cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein * fragments.</li>
441cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein * </ol>
451cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein */
461cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sappersteinpublic abstract class BaseFragmentPagerAdapter extends PagerAdapter {
471cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /** The default size of {@link #mFragmentCache} */
481cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private static final int DEFAULT_CACHE_SIZE = 5;
491cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private static final String TAG = "FragmentPagerAdapter";
501cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private static final boolean DEBUG = false;
511cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
521cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private final FragmentManager mFragmentManager;
531cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private FragmentTransaction mCurTransaction = null;
541cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private Fragment mCurrentPrimaryItem = null;
551cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /** A cache to store detached fragments before they are removed  */
561cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private LruCache<String, Fragment> mFragmentCache = new FragmentCache(DEFAULT_CACHE_SIZE);
571cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
581cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    public BaseFragmentPagerAdapter(FragmentManager fm) {
591cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        mFragmentManager = fm;
601cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
611cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
621cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /**
631cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * Return the Fragment associated with a specified position.
641cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     */
651cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    public abstract Fragment getItem(int position);
661cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
671cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    @Override
681cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    public void startUpdate(View container) {
691cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
701cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
711cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    @Override
721cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    public Object instantiateItem(View container, int position) {
731cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        if (mCurTransaction == null) {
741cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mCurTransaction = mFragmentManager.beginTransaction();
751cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
761cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
771cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        // Do we already have this fragment?
781cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        String name = makeFragmentName(container.getId(), position);
791cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
801cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        // Remove item from the cache
811cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        mFragmentCache.remove(name);
821cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
831cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        Fragment fragment = mFragmentManager.findFragmentByTag(name);
841cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        if (fragment != null) {
851cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            if (DEBUG) Log.v(TAG, "Attaching item #" + position + ": f=" + fragment);
861cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mCurTransaction.attach(fragment);
871cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        } else {
881cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            fragment = getItem(position);
891cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
901cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mCurTransaction.add(container.getId(), fragment,
911cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                    makeFragmentName(container.getId(), position));
921cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
931cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        if (fragment != mCurrentPrimaryItem) {
941cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            fragment.setMenuVisibility(false);
951cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
961cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
971cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        return fragment;
981cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
991cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
1001cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    @Override
1011cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    public void destroyItem(View container, int position, Object object) {
1021cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        if (mCurTransaction == null) {
1031cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mCurTransaction = mFragmentManager.beginTransaction();
1041cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
1051cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        if (DEBUG) Log.v(TAG, "Detaching item #" + position + ": f=" + object
1061cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                + " v=" + ((Fragment)object).getView());
1071cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
1081cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        Fragment fragment = (Fragment) object;
1091cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        String name = fragment.getTag();
1101cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        if (name == null) {
1111cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            // We prefer to get the name directly from the fragment, but, if the fragment is
1121cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            // detached before the add transaction is committed, this could be 'null'. In
1131cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            // that case, generate a name so we can still cache the fragment.
1141cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            name = makeFragmentName(container.getId(), position);
1151cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
1161cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
1171cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        mFragmentCache.put(name, fragment);
1181cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        mCurTransaction.detach(fragment);
1191cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
1201cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
1211cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    @Override
1221cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    public void setPrimaryItem(View container, int position, Object object) {
1231cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        Fragment fragment = (Fragment) object;
1241cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        if (fragment != mCurrentPrimaryItem) {
1251cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            if (mCurrentPrimaryItem != null) {
1261cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                mCurrentPrimaryItem.setMenuVisibility(false);
1271cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            }
1281cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            if (fragment != null) {
1291cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                fragment.setMenuVisibility(true);
1301cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            }
1311cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mCurrentPrimaryItem = fragment;
1321cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
1331cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
1341cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
1351cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
1361cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    @Override
1371cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    public void finishUpdate(View container) {
1381cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        if (mCurTransaction != null) {
1391cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mCurTransaction.commitAllowingStateLoss();
1401cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mCurTransaction = null;
1411cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            mFragmentManager.executePendingTransactions();
1421cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
1431cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
1441cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
1451cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    @Override
1461cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    public boolean isViewFromObject(View view, Object object) {
1471cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        // Ascend the tree to determine if the view is a child of the fragment
1481cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        View root = ((Fragment) object).getView();
1491cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        for (Object v = view; v instanceof View; v = ((View) v).getParent()) {
1501cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            if (v == root) {
1511cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                return true;
1521cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            }
1531cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
1541cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        return false;
1551cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
1561cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
1571cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    @Override
1581cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    public Parcelable saveState() {
1591cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        return null;
1601cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
1611cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
1621cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    @Override
1631cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    public void restoreState(Parcelable state, ClassLoader loader) {
1641cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
1651cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
1661cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /** Creates a name for the fragment */
1671cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    protected String makeFragmentName(int viewId, int index) {
1681cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        return "android:switcher:" + viewId + ":" + index;
1691cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
1701cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
1711cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    /**
1721cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     * A cache of detached fragments.
1731cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein     */
1741cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    private class FragmentCache extends LruCache<String, Fragment> {
1751cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        public FragmentCache(int size) {
1761cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            super(size);
1771cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
1781cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein
1791cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        @Override
1801cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        protected void entryRemoved(boolean evicted, String key,
1811cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                Fragment oldValue, Fragment newValue) {
1821cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            // remove the fragment if it's evicted OR it's replaced by a new fragment
1831cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            if (evicted || (newValue != null && oldValue != newValue)) {
1841cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein                mCurTransaction.remove(oldValue);
1851cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            }
1861cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein        }
1871cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein    }
1881cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein}
189