/* * Copyright (C) 2011 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.android.panoramio; import java.io.File; import java.io.IOException; import java.lang.ref.WeakReference; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.concurrent.ExecutionException; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.content.Context; import android.database.DataSetObserver; import android.os.Handler; import android.util.Log; /** * This class is responsible for downloading and parsing the search results for * a particular area. All of the work is done on a separate thread, and progress * is reported back through the DataSetObserver set in * {@link #addObserver(DataSetObserver). State is held in memory by in memory * maintained by a single instance of the ImageManager class. * */ public class ImageManager { private static final String TAG = "Panoramio"; /** * Base URL for Panoramio's web API */ private static final String THUMBNAIL_URL = "//www.panoramio.com/map/get_panoramas.php?" + "order=popularity" + "&set=public" + "&from=0" + "&to=300" + "&miny=%f" + "&minx=%f" + "&maxy=%f" + "&maxx=%f" + "&size=original"; /** * Used to post results back to the UI thread */ private static Handler mHandler = new Handler(); /** * Holds the single instance of a ImageManager that is shared by the * process. */ private static ImageManager sInstance; /** * Holds the images and related data that have been downloaded */ private final ArrayList<PanoramioItem> mImages = new ArrayList<PanoramioItem>(); private int mCurrentPosition; /** * Observers interested in changes to the current search results */ private final ArrayList<WeakReference<DataSetObserver>> mObservers = new ArrayList< WeakReference<DataSetObserver>>(); /** * True if we are in the process of loading */ private static boolean mLoading; private static Context mContext; private volatile static String query; /** * Key for an Intent extra. The value is the zoom level selected by the * user. */ public static final String ZOOM_EXTRA = "zoom"; /** * Key for an Intent extra. The value is the latitude of the center of the * search area chosen by the user. */ public static final String LATITUDE_E6_EXTRA = "latitudeE6"; /** * Key for an Intent extra. The value is the latitude of the center of the * search area chosen by the user. */ public static final String LONGITUDE_E6_EXTRA = "longitudeE6"; /** * Key for an Intent extra. The value is an item to display */ public static final String PANORAMIO_ITEM_EXTRA = "item"; public static ImageManager getInstance(Context c) { if (sInstance == null) { sInstance = new ImageManager(c); } return sInstance; } private ImageManager(Context c) { mContext = c; } /** * @return True if we are still loading content */ public boolean isLoading() { return mLoading; } /** * Clear all downloaded content */ public void clear() { File cacheDir = mContext.getCacheDir(); for (File file : cacheDir.listFiles()) { file.delete(); } cacheDir.delete(); for (PanoramioItem item : mImages) { item.clear(); item = null; } mImages.clear(); notifyInvalidateObservers(); } public PanoramioItem getNext() { if (mCurrentPosition + 1 <= mImages.size() - 1) { mCurrentPosition = mCurrentPosition + 1; return mImages.get(mCurrentPosition); } return null; } public PanoramioItem getPrevious() { if (mCurrentPosition - 1 >= 0) { mCurrentPosition = mCurrentPosition - 1; return mImages.get(mCurrentPosition); } return null; } /** * Add an item to and notify observers of the change. * * @param item The item to add */ private void add(PanoramioItem item) { if (item.getLocation() != query) { return; } mImages.add(item); notifyObservers(); } /** * @return The number of items displayed so far */ public int size() { return mImages.size(); } /** * Gets the item at the specified position */ public PanoramioItem get(int position) { mCurrentPosition = position; if (mImages.size() > position) { return mImages.get(position); } return null; } /** * Adds an observer to be notified when the set of items held by this * ImageManager changes. */ public void addObserver(DataSetObserver observer) { final WeakReference<DataSetObserver> obs = new WeakReference<DataSetObserver>(observer); mObservers.add(obs); } Thread mPrevThread = null; /** * Load a new set of search results for the specified area. * * @param minLong The minimum longitude for the search area * @param maxLong The maximum longitude for the search area * @param minLat The minimum latitude for the search area * @param maxLat The minimum latitude for the search area * @throws JSONException * @throws URISyntaxException * @throws IOException */ public void load(String query) throws IOException, URISyntaxException, JSONException { this.query = query; clear(); mLoading = true; GeoResponse location = null; try { location = new GeoCoderTask().execute(query).get(); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } if (location != null) { mPrevThread = new ImageLoader(location.getMinLatitude(), location.getMinLongitude(), location.getMaxLatitude(), location.getMaxLongitude(), query); mPrevThread.start(); } else { Log.e(TAG, "Geocoder returned no location"); } } /** * Called when something changes in our data set. Cleans up any weak * references that are no longer valid along the way. */ private void notifyObservers() { final ArrayList<WeakReference<DataSetObserver>> observers = mObservers; final int count = observers.size(); for (int i = count - 1; i >= 0; i--) { final WeakReference<DataSetObserver> weak = observers.get(i); final DataSetObserver obs = weak.get(); if (obs != null) { obs.onChanged(); } else { observers.remove(i); } } } /** * Called when something changes in our data set. Cleans up any weak * references that are no longer valid along the way. */ private void notifyInvalidateObservers() { final ArrayList<WeakReference<DataSetObserver>> observers = mObservers; final int count = observers.size(); for (int i = count - 1; i >= 0; i--) { final WeakReference<DataSetObserver> weak = observers.get(i); final DataSetObserver obs = weak.get(); if (obs != null) { obs.onInvalidated(); } else { observers.remove(i); } } } /** * This thread does the actual work of fetching and parsing Panoramio JSON * response data. */ private static class ImageLoader extends Thread { double mMinLong; double mMaxLong; double mMinLat; double mMaxLat; String mQuery; public ImageLoader(double minLatitude, double minLongitude, double maxLatitude, double maxLongitude, String query) { mMinLong = minLongitude; mMaxLong = maxLongitude; mMinLat = minLatitude; mMaxLat = maxLatitude; mQuery = query; } @Override public void run() { String url = THUMBNAIL_URL; url = String.format(url, mMinLat, mMinLong, mMaxLat, mMaxLong); try { final URI uri = new URI("http", url, null); final HttpGet get = new HttpGet(uri); final HttpClient client = new DefaultHttpClient(); final HttpResponse response = client.execute(get); final HttpEntity entity = response.getEntity(); final String str = Utilities.convertStreamToString(entity.getContent()); final JSONObject json = new JSONObject(str); parse(json); } catch (final Exception e) { Log.e(TAG, e.toString()); } } private void parse(JSONObject json) { try { final JSONArray array = json.getJSONArray("photos"); final int count = array.length(); for (int i = 0; i < count; i++) { if (!mQuery.equals(query)) { break; } final boolean done = i == count - 1; final JSONObject obj = array.getJSONObject(i); final long id = obj.getLong("photo_id"); String title = obj.getString("photo_title"); final String owner = obj.getString("owner_name"); final String thumb = obj.getString("photo_file_url"); final String ownerUrl = obj.getString("owner_url"); final String photoUrl = obj.getString("photo_url"); final double latitude = obj.getDouble("latitude"); final double longitude = obj.getDouble("longitude"); final double width = obj.getDouble("width"); final double height = obj.getDouble("height"); if (title == null) { title = mContext.getString(R.string.untitled); } // ignore small pictures, they appear pixelated on Google TV // screen.2000 is a randomly chosen number. if (width < 2000 || height < 2000) { mHandler.post(new Runnable() { public void run() { sInstance.mLoading = !done; sInstance.notifyObservers(); } }); continue; } final PanoramioItem item = new PanoramioItem(mContext, id, thumb, (int) (latitude), (int) (longitude), title, owner, ownerUrl, photoUrl, mQuery, mHandler); item.loadLargeBitmap(); mHandler.post(new Runnable() { public void run() { sInstance.mLoading = !done; sInstance.add(item); } }); } } catch (final JSONException e) { Log.e(TAG, e.toString()); } } } }