1 /*
2  * Copyright (C) 2012 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 
17 package com.android.mms.util;
18 
19 import java.util.Set;
20 
21 import android.content.Context;
22 import android.net.Uri;
23 import android.util.Log;
24 
25 import com.android.mms.LogTag;
26 import com.android.mms.model.SlideshowModel;
27 import com.google.android.mms.MmsException;
28 import com.google.android.mms.pdu.GenericPdu;
29 import com.google.android.mms.pdu.MultimediaMessagePdu;
30 import com.google.android.mms.pdu.PduPersister;
31 import com.google.android.mms.util.PduCache;
32 import com.google.android.mms.util.PduCacheEntry;
33 
34 /**
35  * Primary {@link PduLoaderManager} implementation used by {@link MessagingApplication}.
36  * <p>
37  * Public methods should only be used from a single thread (typically the UI
38  * thread). Callbacks will be invoked on the thread where the PduLoaderManager
39  * was instantiated.
40  * <p>
41  * Uses a thread-pool ExecutorService instead of AsyncTasks since clients may
42  * request lots of pdus around the same time, and AsyncTask may reject tasks
43  * in that case and has no way of bounding the number of threads used by those
44  * tasks.
45  * <p>
46  * PduLoaderManager is used to asynchronously load mms pdu's and then build a slideshow model
47  * from that loaded pdu. Then it will call the passed in callback with the result. This class
48  * uses the PduCache built into the mms framework. It also manages a local cache of slideshow
49  * models. The slideshow cache uses SoftReferences to hang onto the slideshow.
50  *
51  * Based on BooksImageManager by Virgil King.
52  */
53 public class PduLoaderManager extends BackgroundLoaderManager {
54     private static final String TAG = LogTag.TAG;
55 
56     private static final boolean DEBUG_DISABLE_CACHE = false;
57     private static final boolean DEBUG_DISABLE_PDUS = false;
58     private static final boolean DEBUG_LONG_WAIT = false;
59 
60     private static PduCache mPduCache;
61     private final PduPersister mPduPersister;
62     private final SimpleCache<Uri, SlideshowModel> mSlideshowCache;
63     private final Context mContext;
64 
PduLoaderManager(final Context context)65     public PduLoaderManager(final Context context) {
66         super(context);
67 
68         mSlideshowCache = new SimpleCache<Uri, SlideshowModel>(8, 16, 0.75f, false);
69         mPduCache = PduCache.getInstance();
70         mPduPersister = PduPersister.getPduPersister(context);
71         mContext = context;
72     }
73 
getPdu(Uri uri, boolean requestSlideshow, final ItemLoadedCallback<PduLoaded> callback)74     public ItemLoadedFuture getPdu(Uri uri, boolean requestSlideshow,
75             final ItemLoadedCallback<PduLoaded> callback) {
76         if (uri == null) {
77             throw new NullPointerException();
78         }
79 
80         PduCacheEntry cacheEntry = null;
81         synchronized(mPduCache) {
82             if (!mPduCache.isUpdating(uri)) {
83                 cacheEntry = mPduCache.get(uri);
84             }
85         }
86         final SlideshowModel slideshow = (requestSlideshow && !DEBUG_DISABLE_CACHE) ?
87                 mSlideshowCache.get(uri) : null;
88 
89         final boolean slideshowExists = (!requestSlideshow || slideshow != null);
90         final boolean pduExists = (cacheEntry != null && cacheEntry.getPdu() != null);
91         final boolean taskExists = mPendingTaskUris.contains(uri);
92         final boolean newTaskRequired = (!pduExists || !slideshowExists) && !taskExists;
93         final boolean callbackRequired = (callback != null);
94 
95         if (pduExists && slideshowExists) {
96             if (callbackRequired) {
97                 PduLoaded pduLoaded = new PduLoaded(cacheEntry.getPdu(), slideshow);
98                 callback.onItemLoaded(pduLoaded, null);
99             }
100             return new NullItemLoadedFuture();
101         }
102 
103         if (callbackRequired) {
104             addCallback(uri, callback);
105         }
106 
107         if (newTaskRequired) {
108             mPendingTaskUris.add(uri);
109             Runnable task = new PduTask(uri, requestSlideshow);
110             mExecutor.execute(task);
111         }
112         return new ItemLoadedFuture() {
113             private boolean mIsDone;
114 
115             public void cancel(Uri uri) {
116                 cancelCallback(callback);
117                 removePdu(uri);     // the pdu and/or slideshow might be half loaded. Make sure
118                                     // we load fresh the next time this uri is requested.
119             }
120 
121             public void setIsDone(boolean done) {
122                 mIsDone = done;
123             }
124 
125             public boolean isDone() {
126                 return mIsDone;
127             }
128         };
129     }
130 
131     @Override
clear()132     public void clear() {
133         super.clear();
134 
135         synchronized(mPduCache) {
136             mPduCache.purgeAll();
137         }
138         mSlideshowCache.clear();
139     }
140 
removePdu(Uri uri)141     public void removePdu(Uri uri) {
142         if (Log.isLoggable(TAG, Log.DEBUG)) {
143             Log.d(TAG, "removePdu: " + uri);
144         }
145         if (uri != null) {
146             synchronized(mPduCache) {
147                 mPduCache.purge(uri);
148             }
149             mSlideshowCache.remove(uri);
150         }
151     }
152 
getTag()153     public String getTag() {
154         return TAG;
155     }
156 
157     public class PduTask implements Runnable {
158         private final Uri mUri;
159         private final boolean mRequestSlideshow;
160 
PduTask(Uri uri, boolean requestSlideshow)161         public PduTask(Uri uri, boolean requestSlideshow) {
162             if (uri == null) {
163                 throw new NullPointerException();
164             }
165             mUri = uri;
166             mRequestSlideshow = requestSlideshow;
167         }
168 
169         /** {@inheritDoc} */
run()170         public void run() {
171             if (DEBUG_DISABLE_PDUS) {
172                 return;
173             }
174             if (DEBUG_LONG_WAIT) {
175                 try {
176                     Thread.sleep(10000);
177                 } catch (InterruptedException e) {
178                 }
179             }
180             GenericPdu pdu = null;
181             SlideshowModel slideshow = null;
182             Throwable exception = null;
183             try {
184                 pdu = mPduPersister.load(mUri);
185                 if (pdu != null && mRequestSlideshow) {
186                     slideshow = SlideshowModel.createFromPduBody(mContext,
187                             ((MultimediaMessagePdu)pdu).getBody());
188                 }
189             } catch (final MmsException e) {
190                 Log.e(TAG, "MmsException loading uri: " + mUri, e);
191                 exception = e;
192             }
193             final GenericPdu resultPdu = pdu;
194             final SlideshowModel resultSlideshow = slideshow;
195             final Throwable resultException = exception;
196             mCallbackHandler.post(new Runnable() {
197                 public void run() {
198                     final Set<ItemLoadedCallback> callbacks = mCallbacks.get(mUri);
199                     if (callbacks != null) {
200                         // Make a copy so that the callback can unregister itself
201                         for (final ItemLoadedCallback<PduLoaded> callback : asList(callbacks)) {
202                             if (Log.isLoggable(TAG, Log.DEBUG)) {
203                                 Log.d(TAG, "Invoking pdu callback " + callback);
204                             }
205                             PduLoaded pduLoaded = new PduLoaded(resultPdu, resultSlideshow);
206                             callback.onItemLoaded(pduLoaded, resultException);
207                         }
208                     }
209                     // Add the slideshow to the soft cache if the load succeeded
210                     if (resultSlideshow != null) {
211                         mSlideshowCache.put(mUri, resultSlideshow);
212                     }
213 
214                     mCallbacks.remove(mUri);
215                     mPendingTaskUris.remove(mUri);
216 
217                     if (Log.isLoggable(LogTag.PDU_CACHE, Log.DEBUG)) {
218                         Log.d(TAG, "Pdu task for " + mUri + "exiting; " + mPendingTaskUris.size()
219                                 + " remain");
220                     }
221                 }
222             });
223         }
224     }
225 
226     public static class PduLoaded {
227         public final GenericPdu mPdu;
228         public final SlideshowModel mSlideshow;
229 
PduLoaded(GenericPdu pdu, SlideshowModel slideshow)230         public PduLoaded(GenericPdu pdu, SlideshowModel slideshow) {
231             mPdu = pdu;
232             mSlideshow = slideshow;
233         }
234     }
235 }
236