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