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
17package com.android.mms.util;
18
19import java.util.Set;
20
21import android.content.Context;
22import android.net.Uri;
23import android.util.Log;
24
25import com.android.mms.LogTag;
26import com.android.mms.model.SlideshowModel;
27import com.google.android.mms.MmsException;
28import com.google.android.mms.pdu.GenericPdu;
29import com.google.android.mms.pdu.MultimediaMessagePdu;
30import com.google.android.mms.pdu.PduPersister;
31import com.google.android.mms.util.PduCache;
32import 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 */
53public class PduLoaderManager extends BackgroundLoaderManager {
54    private static final String TAG = "Mms:PduLoaderManager";
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
65    public PduLoaderManager(final Context context) {
66        super(context);
67
68        mSlideshowCache = new SimpleCache<Uri, SlideshowModel>(8, 16, 0.75f, true);
69        mPduCache = PduCache.getInstance();
70        mPduPersister = PduPersister.getPduPersister(context);
71        mContext = context;
72    }
73
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
132    public void clear() {
133        super.clear();
134
135        synchronized(mPduCache) {
136            mPduCache.purgeAll();
137        }
138        mSlideshowCache.clear();
139    }
140
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
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
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} */
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
230        public PduLoaded(GenericPdu pdu, SlideshowModel slideshow) {
231            mPdu = pdu;
232            mSlideshow = slideshow;
233        }
234    }
235}
236