1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.android.messaging.ui.photoviewer;
17
18import android.content.Context;
19import android.graphics.drawable.Drawable;
20import android.net.Uri;
21import android.support.rastermill.FrameSequenceDrawable;
22import android.support.v4.content.AsyncTaskLoader;
23
24import com.android.ex.photo.PhotoViewController;
25import com.android.ex.photo.loaders.PhotoBitmapLoaderInterface;
26import com.android.ex.photo.loaders.PhotoBitmapLoaderInterface.BitmapResult;
27import com.android.messaging.datamodel.media.ImageRequestDescriptor;
28import com.android.messaging.datamodel.media.ImageResource;
29import com.android.messaging.datamodel.media.MediaRequest;
30import com.android.messaging.datamodel.media.MediaResourceManager;
31import com.android.messaging.datamodel.media.UriImageRequestDescriptor;
32import com.android.messaging.util.ImageUtils;
33
34/**
35 * Loader for the bitmap of a photo.
36 */
37public class BuglePhotoBitmapLoader extends AsyncTaskLoader<BitmapResult>
38        implements PhotoBitmapLoaderInterface {
39    private String mPhotoUri;
40    private ImageResource mImageResource;
41    // The drawable that is currently "in use" and being presented to the user. This drawable
42    // should never exist without the image resource backing it.
43    private Drawable mDrawable;
44
45    public BuglePhotoBitmapLoader(Context context, String photoUri) {
46        super(context);
47        mPhotoUri = photoUri;
48    }
49
50    @Override
51    public void setPhotoUri(String photoUri) {
52        mPhotoUri = photoUri;
53    }
54
55    @Override
56    public BitmapResult loadInBackground() {
57        final BitmapResult result = new BitmapResult();
58        final Context context = getContext();
59        if (context != null && mPhotoUri != null) {
60            final ImageRequestDescriptor descriptor =
61                    new UriImageRequestDescriptor(Uri.parse(mPhotoUri),
62                            PhotoViewController.sMaxPhotoSize, PhotoViewController.sMaxPhotoSize,
63                            true /* allowCompression */, false /* isStatic */,
64                            false /* cropToCircle */,
65                            ImageUtils.DEFAULT_CIRCLE_BACKGROUND_COLOR /* circleBackgroundColor */,
66                            ImageUtils.DEFAULT_CIRCLE_STROKE_COLOR /* circleStrokeColor */);
67            final MediaRequest<ImageResource> imageRequest =
68                    descriptor.buildSyncMediaRequest(context);
69            final ImageResource imageResource =
70                    MediaResourceManager.get().requestMediaResourceSync(imageRequest);
71            if (imageResource != null) {
72                setImageResource(imageResource);
73                result.status = BitmapResult.STATUS_SUCCESS;
74                result.drawable = mImageResource.getDrawable(context.getResources());
75            } else {
76                releaseImageResource();
77                result.status = BitmapResult.STATUS_EXCEPTION;
78            }
79        } else {
80            result.status = BitmapResult.STATUS_EXCEPTION;
81        }
82        return result;
83    }
84
85    /**
86     * Called when there is new data to deliver to the client. The super class will take care of
87     * delivering it; the implementation here just adds a little more logic.
88     */
89    @Override
90    public void deliverResult(BitmapResult result) {
91        final Drawable drawable = result != null ? result.drawable : null;
92        if (isReset()) {
93            // An async query came in while the loader is stopped. We don't need the result.
94            releaseDrawable(drawable);
95            return;
96        }
97
98        // We are now going to display this drawable so set to mDrawable
99        mDrawable = drawable;
100
101        if (isStarted()) {
102            // If the Loader is currently started, we can immediately deliver its results.
103            super.deliverResult(result);
104        }
105    }
106
107    /**
108     * Handles a request to start the Loader.
109     */
110    @Override
111    protected void onStartLoading() {
112        if (mDrawable != null) {
113            // If we currently have a result available, deliver it
114            // immediately.
115            final BitmapResult result = new BitmapResult();
116            result.status = BitmapResult.STATUS_SUCCESS;
117            result.drawable = mDrawable;
118            deliverResult(result);
119        }
120
121        if (takeContentChanged() || (mImageResource == null)) {
122            // If the data has changed since the last time it was loaded
123            // or is not currently available, start a load.
124            forceLoad();
125        }
126    }
127
128    /**
129     * Handles a request to stop the Loader.
130     */
131    @Override
132    protected void onStopLoading() {
133        // Attempt to cancel the current load task if possible.
134        cancelLoad();
135    }
136
137    /**
138     * Handles a request to cancel a load.
139     */
140    @Override
141    public void onCanceled(BitmapResult result) {
142        super.onCanceled(result);
143
144        // At this point we can release the resources associated with 'drawable' if needed.
145        if (result != null) {
146            releaseDrawable(result.drawable);
147        }
148    }
149
150    /**
151     * Handles a request to completely reset the Loader.
152     */
153    @Override
154    protected void onReset() {
155        super.onReset();
156
157        // Ensure the loader is stopped
158        onStopLoading();
159
160        releaseImageResource();
161    }
162
163    private void releaseDrawable(Drawable drawable) {
164        if (drawable != null && drawable instanceof FrameSequenceDrawable
165                && !((FrameSequenceDrawable) drawable).isDestroyed()) {
166            ((FrameSequenceDrawable) drawable).destroy();
167        }
168
169    }
170
171    private void setImageResource(final ImageResource resource) {
172        if (mImageResource != resource) {
173            // Clear out any information for what is currently used
174            releaseImageResource();
175            mImageResource = resource;
176            // No need to add ref since a ref is already reserved as a result of
177            // requestMediaResourceSync.
178        }
179    }
180
181    private void releaseImageResource() {
182        // If we are getting rid of the imageResource backing the drawable, we must also
183        // destroy the drawable before releasing it.
184        releaseDrawable(mDrawable);
185        mDrawable = null;
186
187        if (mImageResource != null) {
188            mImageResource.release();
189        }
190        mImageResource = null;
191    }
192}