Allow rotation in Flickr sample.
diff --git a/library/src/main/java/com/bumptech/glide/manager/LifecycleRequestManager.java b/library/src/main/java/com/bumptech/glide/manager/LifecycleRequestManager.java
index c3d80a0..4871d21 100644
--- a/library/src/main/java/com/bumptech/glide/manager/LifecycleRequestManager.java
+++ b/library/src/main/java/com/bumptech/glide/manager/LifecycleRequestManager.java
@@ -2,11 +2,18 @@
 
 import com.bumptech.glide.request.Request;
 
-import java.util.HashSet;
+import java.util.Collections;
 import java.util.Set;
+import java.util.WeakHashMap;
 
 public class LifecycleRequestManager implements RequestManager {
-    private final Set<Request> requests = new HashSet<Request>();
+    // Most requests will be for views and will therefore be held strongly (and safely) by the view via the tag.
+    // However, a user can always pass in a different type of target which may end up not being strongly referenced even
+    // though the user still would like the request to finish. Weak references are therefore only really functional in
+    // this context for view targets. Despite the side affects, WeakReferences are still essentially required. A user
+    // can always make repeated requests into targets other than views, or use an activity manager in a fragment pager
+    // where holding strong references would steadily leak bitmaps and/or views.
+    private final Set<Request> requests = Collections.newSetFromMap(new WeakHashMap<Request, Boolean>());
 
     @Override
     public void addRequest(Request request) {
diff --git a/library/src/test/java/com/bumptech/glide/manager/RequestManagerRetrieverTest.java b/library/src/test/java/com/bumptech/glide/manager/RequestManagerRetrieverTest.java
index a18a5c8..b58cb1d 100644
--- a/library/src/test/java/com/bumptech/glide/manager/RequestManagerRetrieverTest.java
+++ b/library/src/test/java/com/bumptech/glide/manager/RequestManagerRetrieverTest.java
@@ -141,7 +141,7 @@
     }
 
     @Test(expected = IllegalArgumentException.class)
-    public void testDoesNotThrowIfActivityDestroyed() {
+    public void testThrowsIfActivityDestroyed() {
         DefaultRetrieverHarness harness = new DefaultRetrieverHarness();
         harness.getController().pause().stop().destroy();
         harness.doGet();
diff --git a/samples/flickr/AndroidManifest.xml b/samples/flickr/AndroidManifest.xml
index 5fbd3dc..49fce32 100644
--- a/samples/flickr/AndroidManifest.xml
+++ b/samples/flickr/AndroidManifest.xml
@@ -15,7 +15,7 @@
       <activity android:name=".FlickrSearchActivity"
         android:label="@string/app_name"
         android:launchMode="singleTask"
-        android:screenOrientation="portrait">
+        android:windowSoftInputMode="stateHidden|adjustResize" >
         <intent-filter>
             <action android:name="android.intent.action.MAIN" />
             <category android:name="android.intent.category.LAUNCHER" />
diff --git a/samples/flickr/res/layout/flickr_search_activity.xml b/samples/flickr/res/layout/flickr_search_activity.xml
index fe172fd..df9d5f1 100644
--- a/samples/flickr/res/layout/flickr_search_activity.xml
+++ b/samples/flickr/res/layout/flickr_search_activity.xml
@@ -21,7 +21,6 @@
         android:singleLine="true"
         android:inputType="text"
         android:imeOptions="actionSearch" />
-        <!-- android:background="@drawable/stretch_field_comments" /> -->
       <Button
         android:id="@+id/search"
         android:layout_width="wrap_content"
diff --git a/samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/FlickrPhotoGrid.java b/samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/FlickrPhotoGrid.java
index e50970f..3d5e30e 100644
--- a/samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/FlickrPhotoGrid.java
+++ b/samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/FlickrPhotoGrid.java
@@ -18,12 +18,15 @@
 import java.util.List;
 
 public class FlickrPhotoGrid extends SherlockFragment implements PhotoViewer {
+    private static final String STATE_POSITION_INDEX = "state_position_index";
+
     private static final String IMAGE_SIZE_KEY = "image_size";
     private static final String PRELOAD_KEY = "preload";
 
     private PhotoAdapter adapter;
     private List<Photo> currentPhotos;
     private int photoSize;
+    private GridView grid;
 
     public static FlickrPhotoGrid newInstance(int size, int preloadCount) {
         FlickrPhotoGrid photoGrid = new FlickrPhotoGrid();
@@ -40,19 +43,34 @@
         photoSize = args.getInt(IMAGE_SIZE_KEY);
 
         final View result = inflater.inflate(R.layout.flickr_photo_grid, container, false);
-        final GridView grid = (GridView) result.findViewById(R.id.images);
+        grid = (GridView) result.findViewById(R.id.images);
         grid.setColumnWidth(photoSize);
         final FlickrPreloader preloader = new FlickrPreloader(getActivity(), args.getInt(PRELOAD_KEY));
         grid.setOnScrollListener(preloader);
         adapter = new PhotoAdapter();
         grid.setAdapter(adapter);
-        if (currentPhotos != null)
+        if (currentPhotos != null) {
             adapter.setPhotos(currentPhotos);
+        }
+
+        if (savedInstanceState != null) {
+            int index = savedInstanceState.getInt(STATE_POSITION_INDEX);
+            grid.setSelection(index);
+        }
 
         return result;
     }
 
     @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        if (grid != null) {
+            int index = grid.getFirstVisiblePosition();
+            outState.putInt(STATE_POSITION_INDEX, index);
+        }
+    }
+
+    @Override
     public void onPhotosUpdated(List<Photo> photos) {
         currentPhotos = photos;
         if (adapter != null)
diff --git a/samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/FlickrPhotoList.java b/samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/FlickrPhotoList.java
index 7a8dedc..102e9b0 100644
--- a/samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/FlickrPhotoList.java
+++ b/samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/FlickrPhotoList.java
@@ -19,9 +19,12 @@
 import java.util.List;
 
 public class FlickrPhotoList extends SherlockFragment implements PhotoViewer {
+    private static final String STATE_POSITION_INDEX = "state_position_index";
+    private static final String STATE_POSITION_OFFSET = "state_position_offset";
     private FlickrPhotoListAdapter adapter;
     private List<Photo> currentPhotos;
     private FlickrListPreloader preloader;
+    private ListView list;
 
     public static FlickrPhotoList newInstance() {
         return new FlickrPhotoList();
@@ -38,7 +41,7 @@
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
         final View result = inflater.inflate(R.layout.flickr_photo_list, container, false);
-        ListView list = (ListView) result.findViewById(R.id.flickr_photo_list);
+        list = (ListView) result.findViewById(R.id.flickr_photo_list);
         adapter = new FlickrPhotoListAdapter();
         list.setAdapter(adapter);
         preloader = new FlickrListPreloader(getActivity(), 5);
@@ -46,9 +49,28 @@
         if (currentPhotos != null) {
             adapter.setPhotos(currentPhotos);
         }
+
+        if (savedInstanceState != null) {
+            int index = savedInstanceState.getInt(STATE_POSITION_INDEX);
+            int offset = savedInstanceState.getInt(STATE_POSITION_OFFSET);
+            list.setSelectionFromTop(index, offset);
+        }
+
         return result;
     }
 
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        if (list != null) {
+            int index = list.getFirstVisiblePosition();
+            View topView = list.getChildAt(0);
+            int offset = topView != null ? topView.getTop() : 0;
+            outState.putInt(STATE_POSITION_INDEX, index);
+            outState.putInt(STATE_POSITION_OFFSET, offset);
+        }
+    }
+
     private static class ViewHolder {
         private final TextView titleText;
         private final ImageView imageView;
diff --git a/samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/FlickrSearchActivity.java b/samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/FlickrSearchActivity.java
index 919fb96..d8062a2 100644
--- a/samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/FlickrSearchActivity.java
+++ b/samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/FlickrSearchActivity.java
@@ -7,6 +7,7 @@
 import android.support.v4.app.FragmentPagerAdapter;
 import android.support.v4.app.FragmentTransaction;
 import android.support.v4.view.ViewPager;
+import android.text.TextUtils;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.View;
@@ -30,14 +31,17 @@
 
 public class FlickrSearchActivity extends SherlockFragmentActivity {
     private static final String TAG = "FlickrSearchActivity";
+    private static final String STATE_SEARCH_STRING = "state_search_string";
 
-    private int searchCount = 0;
     private EditText searchText;
     private View searching;
     private TextView searchTerm;
     private Set<PhotoViewer> photoViewers = new HashSet<PhotoViewer>();
     private List<Photo> currentPhotos = new ArrayList<Photo>();
     private View searchLoading;
+    private String currentSearchString;
+    private final SearchListener searchListener = new SearchListener();
+
     private enum Page {
         SMALL,
         MEDIUM,
@@ -122,6 +126,28 @@
         }
 
         pager.setAdapter(new FlickrPagerAdapter(getSupportFragmentManager()));
+
+        Api.get(Glide.get(this).getRequestQueue()).registerSearchListener(searchListener);
+        if (savedInstanceState != null) {
+            String savedSearchString = savedInstanceState.getString(STATE_SEARCH_STRING);
+            if (!TextUtils.isEmpty(savedSearchString)) {
+                executeSearch(savedSearchString);
+            }
+        }
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        if (!TextUtils.isEmpty(currentSearchString)) {
+            outState.putString(STATE_SEARCH_STRING, currentSearchString);
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        Api.get(Glide.get(this).getRequestQueue()).unregisterSearchListener(searchListener);
     }
 
     @Override
@@ -137,46 +163,23 @@
     }
 
     private void executeSearch() {
-        final String searchString = searchText.getText().toString();
+        String searchString = searchText.getText().toString();
         searchText.getText().clear();
+        executeSearch(searchString);
+    }
 
-        if ("".equals(searchString.trim())) return;
+    private void executeSearch(String searchString) {
+        currentSearchString = searchString;
 
-        final int currentSearch = ++searchCount;
+        if (TextUtils.isEmpty(searchString)) {
+            return;
+        }
 
         searching.setVisibility(View.VISIBLE);
         searchLoading.setVisibility(View.VISIBLE);
-        searchTerm.setText(getString(R.string.searching_for, searchString));
+        searchTerm.setText(getString(R.string.searching_for, currentSearchString));
 
-        Api.get(Glide.get(this).getRequestQueue()).search(searchString, new Api.SearchCallback() {
-            @Override
-            public void onSearchCompleted(List<Photo> photos) {
-                if (currentSearch != searchCount) return;
-
-                if (Log.isLoggable(TAG, Log.DEBUG)) {
-                    Log.d(TAG, "Search completed, got " + photos.size() + " results");
-                }
-                searching.setVisibility(View.INVISIBLE);
-
-                for (PhotoViewer viewer : photoViewers) {
-                    viewer.onPhotosUpdated(photos);
-                }
-
-                currentPhotos = photos;
-            }
-
-            @Override
-            public void onSearchFailed(Exception e) {
-                if (currentSearch != searchCount) return;
-
-                if (Log.isLoggable(TAG, Log.ERROR)) {
-                    Log.e(TAG, "Search failed", e);
-                }
-                searching.setVisibility(View.VISIBLE);
-                searchLoading.setVisibility(View.INVISIBLE);
-                searchTerm.setText(getString(R.string.search_failed, searchString));
-            }
-        });
+        Api.get(Glide.get(this).getRequestQueue()).search(currentSearchString);
     }
 
     private static class TabListener implements ActionBar.TabListener {
@@ -198,6 +201,40 @@
         public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) { }
     }
 
+    private class SearchListener implements Api.SearchListener {
+        @Override
+        public void onSearchCompleted(String searchString, List<Photo> photos) {
+            if (!TextUtils.equals(currentSearchString, searchString)) {
+                return;
+            }
+
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Search completed, got " + photos.size() + " results");
+            }
+            searching.setVisibility(View.INVISIBLE);
+
+            for (PhotoViewer viewer : photoViewers) {
+                viewer.onPhotosUpdated(photos);
+            }
+
+            currentPhotos = photos;
+        }
+
+        @Override
+        public void onSearchFailed(String searchString, Exception e) {
+            if (!TextUtils.equals(currentSearchString, searchString)) {
+                return;
+            }
+
+            if (Log.isLoggable(TAG, Log.ERROR)) {
+                Log.e(TAG, "Search failed", e);
+            }
+            searching.setVisibility(View.VISIBLE);
+            searchLoading.setVisibility(View.INVISIBLE);
+            searchTerm.setText(getString(R.string.search_failed, currentSearchString));
+        }
+    }
+
     private class FlickrPagerAdapter extends FragmentPagerAdapter {
 
         public FlickrPagerAdapter(FragmentManager fm) {
diff --git a/samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/api/Api.java b/samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/api/Api.java
index 2fb95c9..908deaa 100644
--- a/samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/api/Api.java
+++ b/samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/api/Api.java
@@ -1,5 +1,6 @@
 package com.bumptech.glide.samples.flickr.api;
 
+import android.text.TextUtils;
 import android.util.Log;
 import com.android.volley.DefaultRetryPolicy;
 import com.android.volley.Request;
@@ -14,8 +15,10 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 public class Api {
     private static Api API;
@@ -41,8 +44,6 @@
         Collections.sort(SORTED_SIZE_KEYS);
     }
 
-    private final RequestQueue requestQueue;
-
     private static String getSizeKey(int width, int height) {
         final int largestEdge = Math.max(width, height);
 
@@ -56,30 +57,14 @@
         return result;
     }
 
-    public interface SearchCallback {
-        public void onSearchCompleted(List<Photo> photos);
-        public void onSearchFailed(Exception e);
-    }
-
-    public static Api get(RequestQueue requestQueue) {
-        if (API == null) {
-            API = new Api(requestQueue);
-        }
-        return API;
-    }
-
-    protected Api(RequestQueue requestQueue) {
-        this.requestQueue = requestQueue;
+    public static String getCacheableUrl(Photo photo) {
+        return String.format(CACHEABLE_PHOTO_URL, photo.farm, photo.server, photo.id, photo.secret);
     }
 
     public static String getPhotoURL(Photo photo, int width, int height) {
         return getPhotoUrl(photo, getSizeKey(width, height));
     }
 
-    public static String getCacheableUrl(Photo photo) {
-        return String.format(CACHEABLE_PHOTO_URL, photo.farm, photo.server, photo.id, photo.secret);
-    }
-
     private static String getUrlForMethod(String method) {
         return String.format(SIGNED_API_URL, method);
     }
@@ -92,7 +77,42 @@
         return photo.getPartialUrl() + sizeKey + ".jpg";
     }
 
-    public void search(String text, final SearchCallback cb) {
+    public interface SearchListener {
+        public void onSearchCompleted(String searchString, List<Photo> photos);
+        public void onSearchFailed(String searchString, Exception e);
+    }
+
+    public static Api get(RequestQueue requestQueue) {
+        if (API == null) {
+            API = new Api(requestQueue);
+        }
+        return API;
+    }
+
+    private final RequestQueue requestQueue;
+    private final Set<SearchListener> searchListeners = new HashSet<SearchListener>();
+    private SearchResult lastSearchResult;
+
+    protected Api(RequestQueue requestQueue) {
+        this.requestQueue = requestQueue;
+    }
+
+    public void registerSearchListener(SearchListener searchListener) {
+        searchListeners.add(searchListener);
+    }
+
+    public void unregisterSearchListener(SearchListener searchListener) {
+        searchListeners.remove(searchListener);
+    }
+
+    public void search(final String text) {
+        if (lastSearchResult != null && TextUtils.equals(lastSearchResult.searchString, text)) {
+            for (SearchListener listener : searchListeners) {
+                listener.onSearchCompleted(lastSearchResult.searchString, lastSearchResult.results);
+            }
+            return;
+        }
+
         StringRequest request = new StringRequest(Request.Method.GET, getSearchUrl(text),
                 new Response.Listener<String>() {
             @Override
@@ -105,9 +125,14 @@
                     for (int i = 0; i < photos.length(); i++) {
                         results.add(new Photo(photos.getJSONObject(i)));
                     }
-                    cb.onSearchCompleted(results);
+                    lastSearchResult = new SearchResult(text, results);
+                    for (SearchListener listener : searchListeners) {
+                        listener.onSearchCompleted(text, results);
+                    }
                 } catch (JSONException e) {
-                    cb.onSearchFailed(e);
+                    for (SearchListener listener : searchListeners) {
+                        listener.onSearchFailed(text, e);
+                    }
                     if (Log.isLoggable(TAG, Log.ERROR)) {
                         Log.e(TAG, "Search failed response=" + response, e);
                     }
@@ -116,11 +141,25 @@
         }, new Response.ErrorListener() {
             @Override
             public void onErrorResponse(VolleyError error) {
-                cb.onSearchFailed(error);
+                for (SearchListener listener : searchListeners) {
+                    listener.onSearchFailed(text, error);
+                }
             }
         });
         request.setRetryPolicy(new DefaultRetryPolicy(DefaultRetryPolicy.DEFAULT_TIMEOUT_MS, 3,
                 DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
         requestQueue.add(request);
     }
+
+    private static class SearchResult {
+        public final String searchString;
+        public final List<Photo> results;
+
+        public SearchResult(String searchString, List<Photo> results) {
+
+            this.searchString = searchString;
+            this.results = results;
+        }
+
+    }
 }