Android Open Source - Robook Image Downloader






From Project

Back to project page Robook.

License

The source code is released under:

MIT License

If you think the Android project Robook listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/**
 * Copyright 2010-present Facebook.//from   w w  w.j a  va  2s  .com
 *
 * 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.facebook.internal;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.Looper;
import com.facebook.FacebookException;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

public class ImageDownloader {
    private static final int DOWNLOAD_QUEUE_MAX_CONCURRENT = WorkQueue.DEFAULT_MAX_CONCURRENT;
    private static final int CACHE_READ_QUEUE_MAX_CONCURRENT = 2;
    private static Handler handler;
    private static WorkQueue downloadQueue = new WorkQueue(DOWNLOAD_QUEUE_MAX_CONCURRENT);
    private static WorkQueue cacheReadQueue = new WorkQueue(CACHE_READ_QUEUE_MAX_CONCURRENT);

    private static final Map<RequestKey, DownloaderContext> pendingRequests = new HashMap<RequestKey, DownloaderContext>();

    /**
     * Downloads the image specified in the passed in request.
     * If a callback is specified, it is guaranteed to be invoked on the calling thread.
     * @param request Request to process
     */
    public static void downloadAsync(ImageRequest request) {
        if (request == null) {
            return;
        }

        // NOTE: This is the ONLY place where the original request's Url is read. From here on,
        // we will keep track of the Url separately. This is because we might be dealing with a
        // redirect response and the Url might change. We can't create our own new ImageRequests
        // for these changed Urls since the caller might be doing some book-keeping with the request's
        // object reference. So we keep the old references and just map them to new urls in the downloader
        RequestKey key = new RequestKey(request.getImageUri(), request.getCallerTag());
        synchronized (pendingRequests) {
            DownloaderContext downloaderContext = pendingRequests.get(key);
            if (downloaderContext != null) {
                downloaderContext.request = request;
                downloaderContext.isCancelled = false;
                downloaderContext.workItem.moveToFront();
            } else {
                enqueueCacheRead(request, key, request.isCachedRedirectAllowed());
            }
        }
    }

    public static boolean cancelRequest(ImageRequest request) {
        boolean cancelled = false;
        RequestKey key = new RequestKey(request.getImageUri(), request.getCallerTag());
        synchronized (pendingRequests) {
            DownloaderContext downloaderContext = pendingRequests.get(key);
            if (downloaderContext != null) {
                // If we were able to find the request in our list of pending requests, then we will
                // definitely be able to prevent an ImageResponse from being issued. This is regardless
                // of whether a cache-read or network-download is underway for this request.
                cancelled = true;

                if (downloaderContext.workItem.cancel()) {
                    pendingRequests.remove(key);
                } else {
                    // May be attempting a cache-read right now. So keep track of the cancellation
                    // to prevent network calls etc
                    downloaderContext.isCancelled = true;
                }
            }
        }

        return cancelled;
    }

    public static void prioritizeRequest(ImageRequest request) {
        RequestKey key = new RequestKey(request.getImageUri(), request.getCallerTag());
        synchronized (pendingRequests) {
            DownloaderContext downloaderContext = pendingRequests.get(key);
            if (downloaderContext != null) {
                downloaderContext.workItem.moveToFront();
            }
        }
    }

    public static void clearCache(Context context) {
        ImageResponseCache.clearCache(context);
        UrlRedirectCache.clearCache(context);
    }

    private static void enqueueCacheRead(ImageRequest request, RequestKey key, boolean allowCachedRedirects) {
        enqueueRequest(
                request,
                key,
                cacheReadQueue,
                new CacheReadWorkItem(request.getContext(), key, allowCachedRedirects));
    }

    private static void enqueueDownload(ImageRequest request, RequestKey key) {
        enqueueRequest(
                request,
                key,
                downloadQueue,
                new DownloadImageWorkItem(request.getContext(), key));
    }

    private static void enqueueRequest(
            ImageRequest request,
            RequestKey key,
            WorkQueue workQueue,
            Runnable workItem) {
        synchronized (pendingRequests) {
            DownloaderContext downloaderContext = new DownloaderContext();
            downloaderContext.request = request;
            pendingRequests.put(key, downloaderContext);

            // The creation of the WorkItem should be done after the pending request has been registered.
            // This is necessary since the WorkItem might kick off right away and attempt to retrieve
            // the request's DownloaderContext prior to it being ready for access.
            //
            // It is also necessary to hold on to the lock until after the workItem is created, since
            // calls to cancelRequest or prioritizeRequest might come in and expect a registered
            // request to have a workItem available as well.
            downloaderContext.workItem = workQueue.addActiveWorkItem(workItem);
        }
    }

    private static void issueResponse(
            RequestKey key,
            final Exception error,
            final Bitmap bitmap,
            final boolean isCachedRedirect) {
        // Once the old downloader context is removed, we are thread-safe since this is the
        // only reference to it
        DownloaderContext completedRequestContext = removePendingRequest(key);
        if (completedRequestContext != null && !completedRequestContext.isCancelled) {
            final ImageRequest request = completedRequestContext.request;
            final ImageRequest.Callback callback = request.getCallback();
            if (callback != null) {
                getHandler().post(new Runnable() {
                    @Override
                    public void run() {
                        ImageResponse response = new ImageResponse(
                                request,
                                error,
                                isCachedRedirect,
                                bitmap);
                        callback.onCompleted(response);
                    }
                });
            }
        }
    }

    private static void readFromCache(RequestKey key, Context context, boolean allowCachedRedirects) {
        InputStream cachedStream = null;
        boolean isCachedRedirect = false;
        if (allowCachedRedirects) {
            URI redirectUri = UrlRedirectCache.getRedirectedUri(context, key.uri);
            if (redirectUri != null) {
                cachedStream = ImageResponseCache.getCachedImageStream(redirectUri, context);
                isCachedRedirect = cachedStream != null;
            }
        }

        if (!isCachedRedirect) {
            cachedStream = ImageResponseCache.getCachedImageStream(key.uri, context);
        }

        if (cachedStream != null) {
            // We were able to find a cached image.
            Bitmap bitmap = BitmapFactory.decodeStream(cachedStream);
            Utility.closeQuietly(cachedStream);
            issueResponse(key, null, bitmap, isCachedRedirect);
        } else {
            // Once the old downloader context is removed, we are thread-safe since this is the
            // only reference to it
            DownloaderContext downloaderContext = removePendingRequest(key);
            if (downloaderContext != null && !downloaderContext.isCancelled) {
                enqueueDownload(downloaderContext.request, key);
            }
        }
    }

    private static void download(RequestKey key, Context context) {
        HttpURLConnection connection = null;
        InputStream stream = null;
        Exception error = null;
        Bitmap bitmap = null;
        boolean issueResponse = true;

        try {
            URL url = new URL(key.uri.toString());
            connection = (HttpURLConnection) url.openConnection();
            connection.setInstanceFollowRedirects(false);

            switch (connection.getResponseCode()) {
                case HttpURLConnection.HTTP_MOVED_PERM:
                case HttpURLConnection.HTTP_MOVED_TEMP:
                    // redirect. So we need to perform further requests
                    issueResponse = false;

                    String redirectLocation = connection.getHeaderField("location");
                    if (!Utility.isNullOrEmpty(redirectLocation)) {
                        URI redirectUri = new URI(redirectLocation);
                        UrlRedirectCache.cacheUriRedirect(context, key.uri, redirectUri);

                        // Once the old downloader context is removed, we are thread-safe since this is the
                        // only reference to it
                        DownloaderContext downloaderContext = removePendingRequest(key);
                        if (downloaderContext != null && !downloaderContext.isCancelled) {
                            enqueueCacheRead(
                                    downloaderContext.request,
                                    new RequestKey(redirectUri, key.tag),
                                    false);
                        }
                    }
                    break;

                case HttpURLConnection.HTTP_OK:
                    // image should be available
                    stream = ImageResponseCache.interceptAndCacheImageStream(context, connection);
                    bitmap = BitmapFactory.decodeStream(stream);
                    break;

                default:
                    stream = connection.getErrorStream();
                    InputStreamReader reader = new InputStreamReader(stream);
                    char[] buffer = new char[128];
                    int bufferLength;
                    StringBuilder errorMessageBuilder = new StringBuilder();
                    while ((bufferLength = reader.read(buffer, 0, buffer.length)) > 0) {
                        errorMessageBuilder.append(buffer, 0, bufferLength);
                    }
                    Utility.closeQuietly(reader);

                    error = new FacebookException(errorMessageBuilder.toString());
                    break;
            }
        } catch (IOException e) {
            error = e;
        } catch (URISyntaxException e) {
            error = e;
        } finally {
            Utility.closeQuietly(stream);
            Utility.disconnectQuietly(connection);
        }

        if (issueResponse) {
            issueResponse(key, error, bitmap, false);
        }
    }

    private static synchronized Handler getHandler() {
        if (handler == null) {
            handler = new Handler(Looper.getMainLooper());
        }
        return handler;
    }

    private static DownloaderContext removePendingRequest(RequestKey key) {
        synchronized (pendingRequests) {
            return pendingRequests.remove(key);
        }
    }

    private static class RequestKey {
        private static final int HASH_SEED = 29; // Some random prime number
        private static final int HASH_MULTIPLIER = 37; // Some random prime number

        URI uri;
        Object tag;

        RequestKey(URI url, Object tag) {
            this.uri = url;
            this.tag = tag;
        }

        @Override
        public int hashCode() {
            int result = HASH_SEED;

            result = (result * HASH_MULTIPLIER) + uri.hashCode();
            result = (result * HASH_MULTIPLIER) + tag.hashCode();

            return result;
        }

        @Override
        public boolean equals(Object o) {
            boolean isEqual = false;

            if (o != null && o instanceof RequestKey) {
                RequestKey compareTo = (RequestKey)o;
                isEqual = compareTo.uri == uri && compareTo.tag == tag;
            }

            return isEqual;
        }
    }

    private static class DownloaderContext {
        WorkQueue.WorkItem workItem;
        ImageRequest request;
        boolean isCancelled;
    }

    private static class CacheReadWorkItem implements Runnable {
        private Context context;
        private RequestKey key;
        private boolean allowCachedRedirects;

        CacheReadWorkItem(Context context, RequestKey key, boolean allowCachedRedirects) {
            this.context = context;
            this.key = key;
            this.allowCachedRedirects = allowCachedRedirects;
        }

        @Override
        public void run() {
            readFromCache(key, context, allowCachedRedirects);
        }
    }

    private static class DownloadImageWorkItem implements Runnable {
        private Context context;
        private RequestKey key;

        DownloadImageWorkItem(Context context, RequestKey key) {
            this.context = context;
            this.key = key;
        }

        @Override
        public void run() {
            download(key, context);
        }

    }
}




Java Source Code List

com.facebook.AccessTokenSource.java
com.facebook.AccessToken.java
com.facebook.AppEventsConstants.java
com.facebook.AppEventsLogger.java
com.facebook.AppLinkData.java
com.facebook.AuthorizationClient.java
com.facebook.BoltsMeasurementEventListener.java
com.facebook.FacebookAppLinkResolver.java
com.facebook.FacebookAuthorizationException.java
com.facebook.FacebookBroadcastReceiver.java
com.facebook.FacebookDialogException.java
com.facebook.FacebookException.java
com.facebook.FacebookGraphObjectException.java
com.facebook.FacebookOperationCanceledException.java
com.facebook.FacebookRequestError.java
com.facebook.FacebookSdkVersion.java
com.facebook.FacebookServiceException.java
com.facebook.FacebookTimeSpentData.java
com.facebook.GetTokenClient.java
com.facebook.HttpMethod.java
com.facebook.InsightsLogger.java
com.facebook.LegacyHelper.java
com.facebook.LoggingBehavior.java
com.facebook.LoginActivity.java
com.facebook.NativeAppCallAttachmentStore.java
com.facebook.NativeAppCallContentProvider.java
com.facebook.NonCachingTokenCachingStrategy.java
com.facebook.ProgressNoopOutputStream.java
com.facebook.ProgressOutputStream.java
com.facebook.RequestAsyncTask.java
com.facebook.RequestBatch.java
com.facebook.RequestOutputStream.java
com.facebook.RequestProgress.java
com.facebook.Request.java
com.facebook.Response.java
com.facebook.SessionDefaultAudience.java
com.facebook.SessionLoginBehavior.java
com.facebook.SessionState.java
com.facebook.Session.java
com.facebook.Settings.java
com.facebook.SharedPreferencesTokenCachingStrategy.java
com.facebook.TestSession.java
com.facebook.TokenCachingStrategy.java
com.facebook.UiLifecycleHelper.java
com.facebook.android.AsyncFacebookRunner.java
com.facebook.android.DialogError.java
com.facebook.android.FacebookError.java
com.facebook.android.Facebook.java
com.facebook.android.FbDialog.java
com.facebook.android.Util.java
com.facebook.internal.AnalyticsEvents.java
com.facebook.internal.AttributionIdentifiers.java
com.facebook.internal.CacheableRequestBatch.java
com.facebook.internal.FileLruCache.java
com.facebook.internal.ImageDownloader.java
com.facebook.internal.ImageRequest.java
com.facebook.internal.ImageResponseCache.java
com.facebook.internal.ImageResponse.java
com.facebook.internal.Logger.java
com.facebook.internal.NativeProtocol.java
com.facebook.internal.PlatformServiceClient.java
com.facebook.internal.ServerProtocol.java
com.facebook.internal.SessionAuthorizationType.java
com.facebook.internal.SessionTracker.java
com.facebook.internal.UrlRedirectCache.java
com.facebook.internal.Utility.java
com.facebook.internal.Validate.java
com.facebook.internal.WorkQueue.java
com.facebook.internal.package-info.java
com.facebook.model.CreateGraphObject.java
com.facebook.model.GraphLocation.java
com.facebook.model.GraphMultiResult.java
com.facebook.model.GraphObjectList.java
com.facebook.model.GraphObject.java
com.facebook.model.GraphPlace.java
com.facebook.model.GraphUser.java
com.facebook.model.JsonUtil.java
com.facebook.model.OpenGraphAction.java
com.facebook.model.OpenGraphObject.java
com.facebook.model.PropertyName.java
com.facebook.widget.FacebookDialog.java
com.facebook.widget.FacebookFragment.java
com.facebook.widget.FriendPickerFragment.java
com.facebook.widget.GraphObjectAdapter.java
com.facebook.widget.GraphObjectCursor.java
com.facebook.widget.GraphObjectPagingLoader.java
com.facebook.widget.LoginButton.java
com.facebook.widget.PickerFragment.java
com.facebook.widget.PlacePickerFragment.java
com.facebook.widget.ProfilePictureView.java
com.facebook.widget.SimpleGraphObjectCursor.java
com.facebook.widget.ToolTipPopup.java
com.facebook.widget.UserSettingsFragment.java
com.facebook.widget.WebDialog.java
unipg.dmi.robook.AbstractAdkActivity.java
unipg.dmi.robook.MainActivity.java
unipg.dmi.robook.Preview.java
unipg.dmi.robook.UpdaterService.java
unipg.dmi.robook.prefs.java