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 
18 package com.android.messaging.mmslib.util;
19 
20 import android.content.ContentUris;
21 import android.content.UriMatcher;
22 import android.net.Uri;
23 import android.provider.Telephony.Mms;
24 import androidx.collection.SimpleArrayMap;
25 import android.util.Log;
26 import android.util.SparseArray;
27 
28 import java.util.HashSet;
29 
30 public final class PduCache extends AbstractCache<Uri, PduCacheEntry> {
31     private static final String TAG = "PduCache";
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 SparseArray<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 SparseArray<Integer>();
MATCH_TO_MSGBOX_ID_MAP.put(MMS_INBOX, Mms.MESSAGE_BOX_INBOX)68         MATCH_TO_MSGBOX_ID_MAP.put(MMS_INBOX,  Mms.MESSAGE_BOX_INBOX);
MATCH_TO_MSGBOX_ID_MAP.put(MMS_SENT, Mms.MESSAGE_BOX_SENT)69         MATCH_TO_MSGBOX_ID_MAP.put(MMS_SENT,   Mms.MESSAGE_BOX_SENT);
MATCH_TO_MSGBOX_ID_MAP.put(MMS_DRAFTS, Mms.MESSAGE_BOX_DRAFTS)70         MATCH_TO_MSGBOX_ID_MAP.put(MMS_DRAFTS, Mms.MESSAGE_BOX_DRAFTS);
MATCH_TO_MSGBOX_ID_MAP.put(MMS_OUTBOX, Mms.MESSAGE_BOX_OUTBOX)71         MATCH_TO_MSGBOX_ID_MAP.put(MMS_OUTBOX, Mms.MESSAGE_BOX_OUTBOX);
72     }
73 
74     private final SparseArray<HashSet<Uri>> mMessageBoxes;
75     private final SimpleArrayMap<Long, HashSet<Uri>> mThreads;
76     private final HashSet<Uri> mUpdating;
77 
PduCache()78     private PduCache() {
79         mMessageBoxes = new SparseArray<HashSet<Uri>>();
80         mThreads = new SimpleArrayMap<Long, HashSet<Uri>>();
81         mUpdating = new HashSet<Uri>();
82     }
83 
getInstance()84     public static final synchronized 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
put(Uri uri, PduCacheEntry entry)95     public synchronized 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 
setUpdating(Uri uri, boolean updating)120     public synchronized void setUpdating(Uri uri, boolean updating) {
121         if (updating) {
122             mUpdating.add(uri);
123         } else {
124             mUpdating.remove(uri);
125         }
126     }
127 
isUpdating(Uri uri)128     public synchronized boolean isUpdating(Uri uri) {
129         return mUpdating.contains(uri);
130     }
131 
132     @Override
purge(Uri uri)133     public synchronized 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 
purgeSingleEntry(Uri key)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
purgeAll()175     public synchronized 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      */
normalizeKey(Uri uri)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 
purgeByMessageBox(Integer msgBoxId)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.get(msgBoxId);
219             mMessageBoxes.remove(msgBoxId);
220             if (msgBox != null) {
221                 for (Uri key : msgBox) {
222                     mUpdating.remove(key);
223                     PduCacheEntry entry = super.purge(key);
224                     if (entry != null) {
225                         removeFromThreads(key, entry);
226                     }
227                 }
228             }
229         }
230     }
231 
removeFromThreads(Uri key, PduCacheEntry entry)232     private void removeFromThreads(Uri key, PduCacheEntry entry) {
233         HashSet<Uri> thread = mThreads.get(entry.getThreadId());
234         if (thread != null) {
235             thread.remove(key);
236         }
237     }
238 
purgeByThreadId(long threadId)239     private void purgeByThreadId(long threadId) {
240         if (LOCAL_LOGV) {
241             Log.v(TAG, "Purge cache in thread: " + threadId);
242         }
243 
244         HashSet<Uri> thread = mThreads.remove(threadId);
245         if (thread != null) {
246             for (Uri key : thread) {
247                 mUpdating.remove(key);
248                 PduCacheEntry entry = super.purge(key);
249                 if (entry != null) {
250                     removeFromMessageBoxes(key, entry);
251                 }
252             }
253         }
254     }
255 
removeFromMessageBoxes(Uri key, PduCacheEntry entry)256     private void removeFromMessageBoxes(Uri key, PduCacheEntry entry) {
257         HashSet<Uri> msgBox = mThreads.get(Long.valueOf(entry.getMessageBox()));
258         if (msgBox != null) {
259             msgBox.remove(key);
260         }
261     }
262 }
263