1/*
2 * Copyright (C) 2008 Esmertec AG.
3 * Copyright (C) 2008 The Android Open Source Project
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *      http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package com.google.android.mms.util;
19
20import android.content.ContentUris;
21import android.content.UriMatcher;
22import android.net.Uri;
23import android.provider.Telephony.Mms;
24import android.util.Log;
25
26import java.util.HashMap;
27import java.util.HashSet;
28
29public final class PduCache extends AbstractCache<Uri, PduCacheEntry> {
30    private static final String TAG = "PduCache";
31    private static final boolean DEBUG = false;
32    private static final boolean LOCAL_LOGV = false;
33
34    private static final int MMS_ALL             = 0;
35    private static final int MMS_ALL_ID          = 1;
36    private static final int MMS_INBOX           = 2;
37    private static final int MMS_INBOX_ID        = 3;
38    private static final int MMS_SENT            = 4;
39    private static final int MMS_SENT_ID         = 5;
40    private static final int MMS_DRAFTS          = 6;
41    private static final int MMS_DRAFTS_ID       = 7;
42    private static final int MMS_OUTBOX          = 8;
43    private static final int MMS_OUTBOX_ID       = 9;
44    private static final int MMS_CONVERSATION    = 10;
45    private static final int MMS_CONVERSATION_ID = 11;
46
47    private static final UriMatcher URI_MATCHER;
48    private static final HashMap<Integer, Integer> MATCH_TO_MSGBOX_ID_MAP;
49
50    private static PduCache sInstance;
51
52    static {
53        URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
54        URI_MATCHER.addURI("mms", null,         MMS_ALL);
55        URI_MATCHER.addURI("mms", "#",          MMS_ALL_ID);
56        URI_MATCHER.addURI("mms", "inbox",      MMS_INBOX);
57        URI_MATCHER.addURI("mms", "inbox/#",    MMS_INBOX_ID);
58        URI_MATCHER.addURI("mms", "sent",       MMS_SENT);
59        URI_MATCHER.addURI("mms", "sent/#",     MMS_SENT_ID);
60        URI_MATCHER.addURI("mms", "drafts",     MMS_DRAFTS);
61        URI_MATCHER.addURI("mms", "drafts/#",   MMS_DRAFTS_ID);
62        URI_MATCHER.addURI("mms", "outbox",     MMS_OUTBOX);
63        URI_MATCHER.addURI("mms", "outbox/#",   MMS_OUTBOX_ID);
64        URI_MATCHER.addURI("mms-sms", "conversations",   MMS_CONVERSATION);
65        URI_MATCHER.addURI("mms-sms", "conversations/#", MMS_CONVERSATION_ID);
66
67        MATCH_TO_MSGBOX_ID_MAP = new HashMap<Integer, Integer>();
68        MATCH_TO_MSGBOX_ID_MAP.put(MMS_INBOX,  Mms.MESSAGE_BOX_INBOX);
69        MATCH_TO_MSGBOX_ID_MAP.put(MMS_SENT,   Mms.MESSAGE_BOX_SENT);
70        MATCH_TO_MSGBOX_ID_MAP.put(MMS_DRAFTS, Mms.MESSAGE_BOX_DRAFTS);
71        MATCH_TO_MSGBOX_ID_MAP.put(MMS_OUTBOX, Mms.MESSAGE_BOX_OUTBOX);
72    }
73
74    private final HashMap<Integer, HashSet<Uri>> mMessageBoxes;
75    private final HashMap<Long, HashSet<Uri>> mThreads;
76    private final HashSet<Uri> mUpdating;
77
78    private PduCache() {
79        mMessageBoxes = new HashMap<Integer, HashSet<Uri>>();
80        mThreads = new HashMap<Long, HashSet<Uri>>();
81        mUpdating = new HashSet<Uri>();
82    }
83
84    synchronized public static final PduCache getInstance() {
85        if (sInstance == null) {
86            if (LOCAL_LOGV) {
87                Log.v(TAG, "Constructing new PduCache instance.");
88            }
89            sInstance = new PduCache();
90        }
91        return sInstance;
92    }
93
94    @Override
95    synchronized public boolean put(Uri uri, PduCacheEntry entry) {
96        int msgBoxId = entry.getMessageBox();
97        HashSet<Uri> msgBox = mMessageBoxes.get(msgBoxId);
98        if (msgBox == null) {
99            msgBox = new HashSet<Uri>();
100            mMessageBoxes.put(msgBoxId, msgBox);
101        }
102
103        long threadId = entry.getThreadId();
104        HashSet<Uri> thread = mThreads.get(threadId);
105        if (thread == null) {
106            thread = new HashSet<Uri>();
107            mThreads.put(threadId, thread);
108        }
109
110        Uri finalKey = normalizeKey(uri);
111        boolean result = super.put(finalKey, entry);
112        if (result) {
113            msgBox.add(finalKey);
114            thread.add(finalKey);
115        }
116        setUpdating(uri, false);
117        return result;
118    }
119
120    synchronized public void setUpdating(Uri uri, boolean updating) {
121        if (updating) {
122            mUpdating.add(uri);
123        } else {
124            mUpdating.remove(uri);
125        }
126    }
127
128    synchronized public boolean isUpdating(Uri uri) {
129        return mUpdating.contains(uri);
130    }
131
132    @Override
133    synchronized public PduCacheEntry purge(Uri uri) {
134        int match = URI_MATCHER.match(uri);
135        switch (match) {
136            case MMS_ALL_ID:
137                return purgeSingleEntry(uri);
138            case MMS_INBOX_ID:
139            case MMS_SENT_ID:
140            case MMS_DRAFTS_ID:
141            case MMS_OUTBOX_ID:
142                String msgId = uri.getLastPathSegment();
143                return purgeSingleEntry(Uri.withAppendedPath(Mms.CONTENT_URI, msgId));
144            // Implicit batch of purge, return null.
145            case MMS_ALL:
146            case MMS_CONVERSATION:
147                purgeAll();
148                return null;
149            case MMS_INBOX:
150            case MMS_SENT:
151            case MMS_DRAFTS:
152            case MMS_OUTBOX:
153                purgeByMessageBox(MATCH_TO_MSGBOX_ID_MAP.get(match));
154                return null;
155            case MMS_CONVERSATION_ID:
156                purgeByThreadId(ContentUris.parseId(uri));
157                return null;
158            default:
159                return null;
160        }
161    }
162
163    private PduCacheEntry purgeSingleEntry(Uri key) {
164        mUpdating.remove(key);
165        PduCacheEntry entry = super.purge(key);
166        if (entry != null) {
167            removeFromThreads(key, entry);
168            removeFromMessageBoxes(key, entry);
169            return entry;
170        }
171        return null;
172    }
173
174    @Override
175    synchronized public void purgeAll() {
176        super.purgeAll();
177
178        mMessageBoxes.clear();
179        mThreads.clear();
180        mUpdating.clear();
181    }
182
183    /**
184     * @param uri The Uri to be normalized.
185     * @return Uri The normalized key of cached entry.
186     */
187    private Uri normalizeKey(Uri uri) {
188        int match = URI_MATCHER.match(uri);
189        Uri normalizedKey = null;
190
191        switch (match) {
192            case MMS_ALL_ID:
193                normalizedKey = uri;
194                break;
195            case MMS_INBOX_ID:
196            case MMS_SENT_ID:
197            case MMS_DRAFTS_ID:
198            case MMS_OUTBOX_ID:
199                String msgId = uri.getLastPathSegment();
200                normalizedKey = Uri.withAppendedPath(Mms.CONTENT_URI, msgId);
201                break;
202            default:
203                return null;
204        }
205
206        if (LOCAL_LOGV) {
207            Log.v(TAG, uri + " -> " + normalizedKey);
208        }
209        return normalizedKey;
210    }
211
212    private void purgeByMessageBox(Integer msgBoxId) {
213        if (LOCAL_LOGV) {
214            Log.v(TAG, "Purge cache in message box: " + msgBoxId);
215        }
216
217        if (msgBoxId != null) {
218            HashSet<Uri> msgBox = mMessageBoxes.remove(msgBoxId);
219            if (msgBox != null) {
220                for (Uri key : msgBox) {
221                    mUpdating.remove(key);
222                    PduCacheEntry entry = super.purge(key);
223                    if (entry != null) {
224                        removeFromThreads(key, entry);
225                    }
226                }
227            }
228        }
229    }
230
231    private void removeFromThreads(Uri key, PduCacheEntry entry) {
232        HashSet<Uri> thread = mThreads.get(entry.getThreadId());
233        if (thread != null) {
234            thread.remove(key);
235        }
236    }
237
238    private void purgeByThreadId(long threadId) {
239        if (LOCAL_LOGV) {
240            Log.v(TAG, "Purge cache in thread: " + threadId);
241        }
242
243        HashSet<Uri> thread = mThreads.remove(threadId);
244        if (thread != null) {
245            for (Uri key : thread) {
246                mUpdating.remove(key);
247                PduCacheEntry entry = super.purge(key);
248                if (entry != null) {
249                    removeFromMessageBoxes(key, entry);
250                }
251            }
252        }
253    }
254
255    private void removeFromMessageBoxes(Uri key, PduCacheEntry entry) {
256        HashSet<Uri> msgBox = mThreads.get(Long.valueOf(entry.getMessageBox()));
257        if (msgBox != null) {
258            msgBox.remove(key);
259        }
260    }
261}
262