1bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch/*
2bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch * Copyright (C) 2013 The Android Open Source Project
3bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch *
4bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch * Licensed under the Apache License, Version 2.0 (the "License");
5bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch * you may not use this file except in compliance with the License.
6bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch * You may obtain a copy of the License at
7bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch *
8bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch *      http://www.apache.org/licenses/LICENSE-2.0
9bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch *
10bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch * Unless required by applicable law or agreed to in writing, software
11bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch * distributed under the License is distributed on an "AS IS" BASIS,
12bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch * See the License for the specific language governing permissions and
14bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch * limitations under the License.
15bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch */
16bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch
17bc1a645f26a30fd95e68043b608038537b7c798fAdam Kochpackage com.example.android.contactslist.util;
18bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch
19bc1a645f26a30fd95e68043b608038537b7c798fAdam Kochimport android.annotation.TargetApi;
20bc1a645f26a30fd95e68043b608038537b7c798fAdam Kochimport android.graphics.Bitmap;
21bc1a645f26a30fd95e68043b608038537b7c798fAdam Kochimport android.os.Bundle;
22bc1a645f26a30fd95e68043b608038537b7c798fAdam Kochimport android.support.v4.app.Fragment;
23bc1a645f26a30fd95e68043b608038537b7c798fAdam Kochimport android.support.v4.app.FragmentManager;
24bc1a645f26a30fd95e68043b608038537b7c798fAdam Kochimport android.support.v4.util.LruCache;
25bc1a645f26a30fd95e68043b608038537b7c798fAdam Kochimport android.util.Log;
26bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch
27bc1a645f26a30fd95e68043b608038537b7c798fAdam Kochimport com.example.android.contactslist.BuildConfig;
28bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch
29bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch/**
30bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch * This class holds our bitmap caches (memory and disk).
31bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch */
32bc1a645f26a30fd95e68043b608038537b7c798fAdam Kochpublic class ImageCache {
33bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch    private static final String TAG = "ImageCache";
34bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch    private LruCache<String, Bitmap> mMemoryCache;
35bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch
36bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch    /**
37bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     * Creating a new ImageCache object using the specified parameters.
38bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     *
39bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     * @param memCacheSizePercent The cache size as a percent of available app memory.
40bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     */
41bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch    private ImageCache(float memCacheSizePercent) {
42bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        init(memCacheSizePercent);
43bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch    }
44bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch
45bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch    /**
46bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     * Find and return an existing ImageCache stored in a {@link RetainFragment}, if not found a new
47bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     * one is created using the supplied params and saved to a {@link RetainFragment}.
48bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     *
49bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     * @param fragmentManager The fragment manager to use when dealing with the retained fragment.
50bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     * @param memCacheSizePercent The cache size as a percent of available app memory.
51bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     * @return An existing retained ImageCache object or a new one if one did not exist
52bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     */
53bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch    public static ImageCache getInstance(
54bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch            FragmentManager fragmentManager, float memCacheSizePercent) {
55bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch
56bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        // Search for, or create an instance of the non-UI RetainFragment
57bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        final RetainFragment mRetainFragment = findOrCreateRetainFragment(fragmentManager);
58bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch
59bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        // See if we already have an ImageCache stored in RetainFragment
60bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        ImageCache imageCache = (ImageCache) mRetainFragment.getObject();
61bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch
62bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        // No existing ImageCache, create one and store it in RetainFragment
63bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        if (imageCache == null) {
64bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch            imageCache = new ImageCache(memCacheSizePercent);
65bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch            mRetainFragment.setObject(imageCache);
66bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        }
67bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch
68bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        return imageCache;
69bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch    }
70bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch
71bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch    /**
72bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     * Initialize the cache.
73bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     *
74bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     * @param memCacheSizePercent The cache size as a percent of available app memory.
75bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     */
76bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch    private void init(float memCacheSizePercent) {
77bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        int memCacheSize = calculateMemCacheSize(memCacheSizePercent);
78bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch
79bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        // Set up memory cache
80bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        if (BuildConfig.DEBUG) {
81bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch            Log.d(TAG, "Memory cache created (size = " + memCacheSize + ")");
82bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        }
83bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        mMemoryCache = new LruCache<String, Bitmap>(memCacheSize) {
84bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch            /**
85bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch             * Measure item size in kilobytes rather than units which is more practical
86bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch             * for a bitmap cache
87bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch             */
88bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch            @Override
89bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch            protected int sizeOf(String key, Bitmap bitmap) {
90bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch                final int bitmapSize = getBitmapSize(bitmap) / 1024;
91bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch                return bitmapSize == 0 ? 1 : bitmapSize;
92bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch            }
93bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        };
94bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch    }
95bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch
96bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch    /**
97bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     * Adds a bitmap to both memory and disk cache.
98bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     * @param data Unique identifier for the bitmap to store
99bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     * @param bitmap The bitmap to store
100bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     */
101bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch    public void addBitmapToCache(String data, Bitmap bitmap) {
102bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        if (data == null || bitmap == null) {
103bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch            return;
104bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        }
105bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch
106bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        // Add to memory cache
107bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        if (mMemoryCache != null && mMemoryCache.get(data) == null) {
108bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch            mMemoryCache.put(data, bitmap);
109bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        }
110bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch    }
111bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch
112bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch    /**
113bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     * Get from memory cache.
114bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     *
115bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     * @param data Unique identifier for which item to get
116bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     * @return The bitmap if found in cache, null otherwise
117bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     */
118bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch    public Bitmap getBitmapFromMemCache(String data) {
119bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        if (mMemoryCache != null) {
120bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch            final Bitmap memBitmap = mMemoryCache.get(data);
121bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch            if (memBitmap != null) {
122bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch                if (BuildConfig.DEBUG) {
123bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch                    Log.d(TAG, "Memory cache hit");
124bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch                }
125bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch                return memBitmap;
126bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch            }
127bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        }
128bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        return null;
129bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch    }
130bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch
131bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch    /**
132bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     * Get the size in bytes of a bitmap.
133bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     *
134bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     * @param bitmap The bitmap to calculate the size of.
135bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     * @return size of bitmap in bytes.
136bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     */
137bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch    @TargetApi(12)
138bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch    public static int getBitmapSize(Bitmap bitmap) {
139bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        if (Utils.hasHoneycombMR1()) {
140bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch            return bitmap.getByteCount();
141bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        }
142bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        // Pre HC-MR1
143bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        return bitmap.getRowBytes() * bitmap.getHeight();
144bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch    }
145bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch
146bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch    /**
147bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     * Calculates the memory cache size based on a percentage of the max available VM memory.
148bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     * Eg. setting percent to 0.2 would set the memory cache to one fifth of the available
149bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     * memory. Throws {@link IllegalArgumentException} if percent is < 0.05 or > .8.
150bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     * memCacheSize is stored in kilobytes instead of bytes as this will eventually be passed
151bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     * to construct a LruCache which takes an int in its constructor.
152bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     *
153bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     * This value should be chosen carefully based on a number of factors
154bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     * Refer to the corresponding Android Training class for more discussion:
155bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     * http://developer.android.com/training/displaying-bitmaps/
156bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     *
157bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     * @param percent Percent of available app memory to use to size memory cache.
158bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     */
159bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch    public static int calculateMemCacheSize(float percent) {
160bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        if (percent < 0.05f || percent > 0.8f) {
161bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch            throw new IllegalArgumentException("setMemCacheSizePercent - percent must be "
162bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch                    + "between 0.05 and 0.8 (inclusive)");
163bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        }
164bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        return Math.round(percent * Runtime.getRuntime().maxMemory() / 1024);
165bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch    }
166bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch
167bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch    /**
168bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     * Locate an existing instance of this Fragment or if not found, create and
169bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     * add it using FragmentManager.
170bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     *
171bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     * @param fm The FragmentManager manager to use.
172bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     * @return The existing instance of the Fragment or the new instance if just
173bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     *         created.
174bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     */
175bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch    public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) {
176bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        // Check to see if we have retained the worker fragment.
177bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        RetainFragment mRetainFragment = (RetainFragment) fm.findFragmentByTag(TAG);
178bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch
179bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        // If not retained (or first time running), we need to create and add it.
180bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        if (mRetainFragment == null) {
181bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch            mRetainFragment = new RetainFragment();
182bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch            fm.beginTransaction().add(mRetainFragment, TAG).commitAllowingStateLoss();
183bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        }
184bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch
185bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        return mRetainFragment;
186bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch    }
187bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch
188bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch    /**
189bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     * A simple non-UI Fragment that stores a single Object and is retained over configuration
190bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     * changes. It will be used to retain the ImageCache object.
191bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch     */
192bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch    public static class RetainFragment extends Fragment {
193bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        private Object mObject;
194bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch
195bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        /**
196bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch         * Empty constructor as per the Fragment documentation
197bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch         */
198bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        public RetainFragment() {}
199bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch
200bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        @Override
201bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        public void onCreate(Bundle savedInstanceState) {
202bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch            super.onCreate(savedInstanceState);
203bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch
204bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch            // Make sure this Fragment is retained over a configuration change
205bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch            setRetainInstance(true);
206bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        }
207bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch
208bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        /**
209bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch         * Store a single object in this Fragment.
210bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch         *
211bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch         * @param object The object to store
212bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch         */
213bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        public void setObject(Object object) {
214bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch            mObject = object;
215bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        }
216bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch
217bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        /**
218bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch         * Get the stored object.
219bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch         *
220bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch         * @return The stored object
221bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch         */
222bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        public Object getObject() {
223bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch            return mObject;
224bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch        }
225bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch    }
226bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch
227bc1a645f26a30fd95e68043b608038537b7c798fAdam Koch}
228