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  */
16 package com.android.messaging.ui.photoviewer;
17 
18 import android.content.Context;
19 import android.graphics.drawable.Drawable;
20 import android.net.Uri;
21 import android.support.rastermill.FrameSequenceDrawable;
22 import androidx.loader.content.AsyncTaskLoader;
23 
24 import com.android.ex.photo.PhotoViewController;
25 import com.android.ex.photo.loaders.PhotoBitmapLoaderInterface;
26 import com.android.ex.photo.loaders.PhotoBitmapLoaderInterface.BitmapResult;
27 import com.android.messaging.datamodel.media.ImageRequestDescriptor;
28 import com.android.messaging.datamodel.media.ImageResource;
29 import com.android.messaging.datamodel.media.MediaRequest;
30 import com.android.messaging.datamodel.media.MediaResourceManager;
31 import com.android.messaging.datamodel.media.UriImageRequestDescriptor;
32 import com.android.messaging.util.ImageUtils;
33 
34 /**
35  * Loader for the bitmap of a photo.
36  */
37 public 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 
BuglePhotoBitmapLoader(Context context, String photoUri)45     public BuglePhotoBitmapLoader(Context context, String photoUri) {
46         super(context);
47         mPhotoUri = photoUri;
48     }
49 
50     @Override
setPhotoUri(String photoUri)51     public void setPhotoUri(String photoUri) {
52         mPhotoUri = photoUri;
53     }
54 
55     @Override
loadInBackground()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
deliverResult(BitmapResult result)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
onStartLoading()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
onStopLoading()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
onCanceled(BitmapResult result)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
onReset()154     protected void onReset() {
155         super.onReset();
156 
157         // Ensure the loader is stopped
158         onStopLoading();
159 
160         releaseImageResource();
161     }
162 
releaseDrawable(Drawable drawable)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 
setImageResource(final ImageResource resource)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 
releaseImageResource()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 }