Loading src/java/com/android/internal/telephony/SmsController.java +16 −0 Original line number Diff line number Diff line Loading @@ -1117,4 +1117,20 @@ public class SmsController extends ISmsImplBase { // Check if smscAddr is present in FDN list return FdnUtils.isNumberBlockedByFDN(phoneId, smscAddr, defaultCountryIso); } /** * Gets the message size of WAP from the cache. * * @param locationUrl the location to use as a key for looking up the size in the cache. * The locationUrl may or may not have the transactionId appended to the url. * * @return long representing the message size * @throws java.util.NoSuchElementException if the WAP push doesn't exist in the cache * @throws IllegalArgumentException if the locationUrl is empty */ @Override public long getWapMessageSize(@NonNull String locationUrl) { byte[] bytes = locationUrl.getBytes(StandardCharsets.ISO_8859_1); return WapPushCache.getWapMessageSize(bytes); } } No newline at end of file src/java/com/android/internal/telephony/WapPushCache.java 0 → 100644 +172 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.telephony; import android.annotation.NonNull; import android.telephony.Rlog; import com.android.internal.annotations.VisibleForTesting; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.NoSuchElementException; import java.util.concurrent.TimeUnit; /** * Caches WAP push PDU data for retrieval during MMS downloading. * When on a satellite connection, the cached message size will be used to prevent downloading * messages that exceed a threshold. * * The cache uses a circular buffer and will start invalidating the oldest entries after 250 * message sizes have been inserted. * The cache also invalidates entries that have been in the cache for over 14 days. */ public class WapPushCache { private static final String TAG = "WAP PUSH CACHE"; // Because we store each size twice, this represents 250 messages. That limit is chosen so // that the memory footprint of the cache stays reasonably small while still supporting what // we guess will be the vast majority of real use cases. private static final int MAX_CACHE_SIZE = 500; // WAP push PDUs have an expiry property, but we can't be certain that it is set accurately // by the carrier. We will use our own expiry for this cache to keep it small. One example // carrier has an expiry of 7 days so 14 will give us room for those with longer times as well. private static final long CACHE_EXPIRY_TIME = TimeUnit.DAYS.toMillis(14); private static final HashMap<String, CacheEntry> sMessageSizes = new LinkedHashMap<>() { @Override protected boolean removeEldestEntry(Entry<String, CacheEntry> eldest) { return size() > MAX_CACHE_SIZE; } }; @VisibleForTesting public static TelephonyFacade sTelephonyFacade = new TelephonyFacade(); /** * Puts a WAP push PDU's messageSize in the cache. * * The data is stored twice, once using just locationUrl as the key and once * using transactionId appended to the locationUrl. For some carriers, xMS apps * append the transactionId to the location and we need to support lookup using either the * original location or one modified in this way. * * @param locationUrl location of the message used as part of the cache key. * @param transactionId message transaction ID used as part of the cache key. * @param messageSize size of the message to be stored in the cache. */ public static void putWapMessageSize( @NonNull byte[] locationUrl, @NonNull byte[] transactionId, long messageSize ) { long expiry = sTelephonyFacade.getElapsedSinceBootMillis() + CACHE_EXPIRY_TIME; if (messageSize <= 0) { Rlog.e(TAG, "Invalid message size of " + messageSize + ". Not inserting."); return; } synchronized (sMessageSizes) { sMessageSizes.put(Arrays.toString(locationUrl), new CacheEntry(messageSize, expiry)); // concatenate the locationUrl and transactionId byte[] joinedKey = ByteBuffer .allocate(locationUrl.length + transactionId.length) .put(locationUrl) .put(transactionId) .array(); sMessageSizes.put(Arrays.toString(joinedKey), new CacheEntry(messageSize, expiry)); invalidateOldEntries(); } } /** * Remove entries from the cache that are older than CACHE_EXPIRY_TIME */ private static void invalidateOldEntries() { long currentTime = sTelephonyFacade.getElapsedSinceBootMillis(); // We can just remove elements from the start until one is found that does not exceed the // expiry since the elements are in order of insertion. for (Iterator<CacheEntry> it = sMessageSizes.values().iterator(); it.hasNext(); ) { CacheEntry entry = it.next(); if (entry.mExpiry < currentTime) { it.remove(); } else { break; } } } /** * Gets the message size of a WAP from the cache. * * Because we stored the size both using the location+transactionId key and using the * location only key, we should be able to find the size whether the xMS app modified * the location or not. * * @param locationUrl the location to use as a key for looking up the size in the cache. * * @return long representing the message size of the WAP * @throws NoSuchElementException if the WAP doesn't exist in the cache * @throws IllegalArgumentException if the locationUrl is empty */ public static long getWapMessageSize(@NonNull byte[] locationUrl) { if (locationUrl.length == 0) { throw new IllegalArgumentException("Found empty locationUrl"); } CacheEntry entry = sMessageSizes.get(Arrays.toString(locationUrl)); if (entry == null) { throw new NoSuchElementException( "No cached WAP size for locationUrl " + Arrays.toString(locationUrl) ); } return entry.mSize; } /** * Clears all elements from the cache */ @VisibleForTesting public static void clear() { sMessageSizes.clear(); } /** * Returns a count of the number of elements in the cache * @return count of elements */ @VisibleForTesting public static int size() { return sMessageSizes.size(); } private static class CacheEntry { CacheEntry(long size, long expiry) { mSize = size; mExpiry = expiry; } private final long mSize; private final long mExpiry; } } src/java/com/android/internal/telephony/WapPushOverSms.java +7 −0 Original line number Diff line number Diff line Loading @@ -262,6 +262,13 @@ public class WapPushOverSms implements ServiceConnection { if (parsedPdu != null && parsedPdu.getMessageType() == MESSAGE_TYPE_NOTIFICATION_IND) { final NotificationInd nInd = (NotificationInd) parsedPdu; // save the WAP push message size so that if a download request is made for it // while on a satellite connection we can check if the size is under the threshold WapPushCache.putWapMessageSize( nInd.getContentLocation(), nInd.getTransactionId(), nInd.getMessageSize() ); if (nInd.getFrom() != null && BlockChecker.isBlocked(mContext, nInd.getFrom().getString(), null)) { result.statusCode = Intents.RESULT_SMS_HANDLED; Loading tests/telephonytests/src/com/android/internal/telephony/SmsControllerTest.java +42 −0 Original line number Diff line number Diff line Loading @@ -16,7 +16,10 @@ package com.android.internal.telephony; import static junit.framework.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.anyInt; Loading @@ -40,7 +43,9 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.NoSuchElementException; @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper Loading @@ -65,6 +70,7 @@ public class SmsControllerTest extends TelephonyTest { @After public void tearDown() throws Exception { mAdnRecordCache = null; WapPushCache.clear(); super.tearDown(); } Loading Loading @@ -239,4 +245,40 @@ public class SmsControllerTest extends TelephonyTest { verify(mIccSmsInterfaceManager, Mockito.times(0)) .sendText(mCallingPackage, "1234", null, "text", null, null, false, 0L, true); } @Test public void testGetWapMessageSize() { long expectedSize = 100L; String location = "content://mms"; byte[] locationBytes = location.getBytes(StandardCharsets.ISO_8859_1); byte[] transactionId = "123".getBytes(StandardCharsets.ISO_8859_1); WapPushCache.putWapMessageSize(locationBytes, transactionId, expectedSize); long size = mSmsControllerUT.getWapMessageSize(location); assertEquals(expectedSize, size); } @Test public void testGetWapMessageSize_withTransactionIdAppended() { long expectedSize = 100L; byte[] location = "content://mms".getBytes(StandardCharsets.ISO_8859_1); byte[] transactionId = "123".getBytes(StandardCharsets.ISO_8859_1); byte[] joinedKey = new byte[location.length + transactionId.length]; System.arraycopy(location, 0, joinedKey, 0, location.length); System.arraycopy(transactionId, 0, joinedKey, location.length, transactionId.length); String joinedKeyString = new String(joinedKey, StandardCharsets.ISO_8859_1); WapPushCache.putWapMessageSize(location, transactionId, expectedSize); long size = mSmsControllerUT.getWapMessageSize(joinedKeyString); assertEquals(expectedSize, size); } @Test public void testGetWapMessageSize_nonexistentThrows() { assertThrows(NoSuchElementException.class, () -> mSmsControllerUT.getWapMessageSize("content://mms") ); } } No newline at end of file tests/telephonytests/src/com/android/internal/telephony/WapPushCacheTest.java 0 → 100644 +144 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.telephony; import static junit.framework.Assert.assertEquals; import static org.junit.Assert.assertThrows; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.time.Clock; import java.util.NoSuchElementException; import java.util.concurrent.TimeUnit; public class WapPushCacheTest extends TelephonyTest { @Before public void setUp() throws Exception { super.setUp(getClass().getSimpleName()); } @After public void tearDown() throws Exception { WapPushCache.clear(); WapPushCache.sTelephonyFacade = new TelephonyFacade(); super.tearDown(); } @Test public void testGetWapMessageSize() { long expectedSize = 100L; byte[] location = "content://mms".getBytes(); byte[] transactionId = "123".getBytes(); WapPushCache.putWapMessageSize(location, transactionId, expectedSize); long size = WapPushCache.getWapMessageSize(location); assertEquals(expectedSize, size); } @Test public void testGetWapMessageSize_withTransactionIdAppended() { long expectedSize = 100L; byte[] location = "content://mms".getBytes(); byte[] transactionId = "123".getBytes(); byte[] joinedKey = new byte[location.length + transactionId.length]; System.arraycopy(location, 0, joinedKey, 0, location.length); System.arraycopy(transactionId, 0, joinedKey, location.length, transactionId.length); WapPushCache.putWapMessageSize(location, transactionId, expectedSize); long size = WapPushCache.getWapMessageSize(joinedKey); assertEquals(expectedSize, size); } @Test public void testGetWapMessageSize_nonexistentThrows() { assertThrows(NoSuchElementException.class, () -> WapPushCache.getWapMessageSize("content://mms".getBytes()) ); } @Test public void testGetWapMessageSize_emptyLocationUrlThrows() { assertThrows(IllegalArgumentException.class, () -> WapPushCache.getWapMessageSize(new byte[0]) ); } @Test public void testPutWapMessageSize_invalidValuePreventsInsert() { long expectedSize = 0L; byte[] location = "content://mms".getBytes(); byte[] transactionId = "123".getBytes(); WapPushCache.putWapMessageSize(location, transactionId, expectedSize); assertEquals(0, WapPushCache.size()); } @Test public void testPutWapMessageSize_sizeLimitExceeded_oldestEntryRemoved() { long expectedSize = 100L; for (int i = 0; i < 251; i++) { byte[] location = ("" + i).getBytes(); byte[] transactionId = "abc".getBytes(); WapPushCache.putWapMessageSize(location, transactionId, expectedSize); } // assert one of the entries inserted above has been removed assertEquals(500, WapPushCache.size()); // assert last entry added exists assertEquals(expectedSize, WapPushCache.getWapMessageSize("250".getBytes())); // assert the first entry added was removed assertThrows(NoSuchElementException.class, () -> WapPushCache.getWapMessageSize("0".getBytes()) ); } @Test public void testPutWapMessageSize_expiryExceeded_entryRemoved() { long currentTime = Clock.systemUTC().millis(); TelephonyFacade facade = mock(TelephonyFacade.class); when(facade.getElapsedSinceBootMillis()).thenReturn(currentTime); WapPushCache.sTelephonyFacade = facade; long expectedSize = 100L; byte[] transactionId = "abc".getBytes(); byte[] location1 = "old".getBytes(); byte[] location2 = "new".getBytes(); WapPushCache.putWapMessageSize(location1, transactionId, expectedSize); assertEquals(2, WapPushCache.size()); // advance time when(facade.getElapsedSinceBootMillis()) .thenReturn(currentTime + TimeUnit.DAYS.toMillis(14) + 1); WapPushCache.putWapMessageSize(location2, transactionId, expectedSize); assertEquals(2, WapPushCache.size()); assertEquals(expectedSize, WapPushCache.getWapMessageSize(location2)); assertThrows(NoSuchElementException.class, () -> WapPushCache.getWapMessageSize(location1) ); } } Loading
src/java/com/android/internal/telephony/SmsController.java +16 −0 Original line number Diff line number Diff line Loading @@ -1117,4 +1117,20 @@ public class SmsController extends ISmsImplBase { // Check if smscAddr is present in FDN list return FdnUtils.isNumberBlockedByFDN(phoneId, smscAddr, defaultCountryIso); } /** * Gets the message size of WAP from the cache. * * @param locationUrl the location to use as a key for looking up the size in the cache. * The locationUrl may or may not have the transactionId appended to the url. * * @return long representing the message size * @throws java.util.NoSuchElementException if the WAP push doesn't exist in the cache * @throws IllegalArgumentException if the locationUrl is empty */ @Override public long getWapMessageSize(@NonNull String locationUrl) { byte[] bytes = locationUrl.getBytes(StandardCharsets.ISO_8859_1); return WapPushCache.getWapMessageSize(bytes); } } No newline at end of file
src/java/com/android/internal/telephony/WapPushCache.java 0 → 100644 +172 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.telephony; import android.annotation.NonNull; import android.telephony.Rlog; import com.android.internal.annotations.VisibleForTesting; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.NoSuchElementException; import java.util.concurrent.TimeUnit; /** * Caches WAP push PDU data for retrieval during MMS downloading. * When on a satellite connection, the cached message size will be used to prevent downloading * messages that exceed a threshold. * * The cache uses a circular buffer and will start invalidating the oldest entries after 250 * message sizes have been inserted. * The cache also invalidates entries that have been in the cache for over 14 days. */ public class WapPushCache { private static final String TAG = "WAP PUSH CACHE"; // Because we store each size twice, this represents 250 messages. That limit is chosen so // that the memory footprint of the cache stays reasonably small while still supporting what // we guess will be the vast majority of real use cases. private static final int MAX_CACHE_SIZE = 500; // WAP push PDUs have an expiry property, but we can't be certain that it is set accurately // by the carrier. We will use our own expiry for this cache to keep it small. One example // carrier has an expiry of 7 days so 14 will give us room for those with longer times as well. private static final long CACHE_EXPIRY_TIME = TimeUnit.DAYS.toMillis(14); private static final HashMap<String, CacheEntry> sMessageSizes = new LinkedHashMap<>() { @Override protected boolean removeEldestEntry(Entry<String, CacheEntry> eldest) { return size() > MAX_CACHE_SIZE; } }; @VisibleForTesting public static TelephonyFacade sTelephonyFacade = new TelephonyFacade(); /** * Puts a WAP push PDU's messageSize in the cache. * * The data is stored twice, once using just locationUrl as the key and once * using transactionId appended to the locationUrl. For some carriers, xMS apps * append the transactionId to the location and we need to support lookup using either the * original location or one modified in this way. * * @param locationUrl location of the message used as part of the cache key. * @param transactionId message transaction ID used as part of the cache key. * @param messageSize size of the message to be stored in the cache. */ public static void putWapMessageSize( @NonNull byte[] locationUrl, @NonNull byte[] transactionId, long messageSize ) { long expiry = sTelephonyFacade.getElapsedSinceBootMillis() + CACHE_EXPIRY_TIME; if (messageSize <= 0) { Rlog.e(TAG, "Invalid message size of " + messageSize + ". Not inserting."); return; } synchronized (sMessageSizes) { sMessageSizes.put(Arrays.toString(locationUrl), new CacheEntry(messageSize, expiry)); // concatenate the locationUrl and transactionId byte[] joinedKey = ByteBuffer .allocate(locationUrl.length + transactionId.length) .put(locationUrl) .put(transactionId) .array(); sMessageSizes.put(Arrays.toString(joinedKey), new CacheEntry(messageSize, expiry)); invalidateOldEntries(); } } /** * Remove entries from the cache that are older than CACHE_EXPIRY_TIME */ private static void invalidateOldEntries() { long currentTime = sTelephonyFacade.getElapsedSinceBootMillis(); // We can just remove elements from the start until one is found that does not exceed the // expiry since the elements are in order of insertion. for (Iterator<CacheEntry> it = sMessageSizes.values().iterator(); it.hasNext(); ) { CacheEntry entry = it.next(); if (entry.mExpiry < currentTime) { it.remove(); } else { break; } } } /** * Gets the message size of a WAP from the cache. * * Because we stored the size both using the location+transactionId key and using the * location only key, we should be able to find the size whether the xMS app modified * the location or not. * * @param locationUrl the location to use as a key for looking up the size in the cache. * * @return long representing the message size of the WAP * @throws NoSuchElementException if the WAP doesn't exist in the cache * @throws IllegalArgumentException if the locationUrl is empty */ public static long getWapMessageSize(@NonNull byte[] locationUrl) { if (locationUrl.length == 0) { throw new IllegalArgumentException("Found empty locationUrl"); } CacheEntry entry = sMessageSizes.get(Arrays.toString(locationUrl)); if (entry == null) { throw new NoSuchElementException( "No cached WAP size for locationUrl " + Arrays.toString(locationUrl) ); } return entry.mSize; } /** * Clears all elements from the cache */ @VisibleForTesting public static void clear() { sMessageSizes.clear(); } /** * Returns a count of the number of elements in the cache * @return count of elements */ @VisibleForTesting public static int size() { return sMessageSizes.size(); } private static class CacheEntry { CacheEntry(long size, long expiry) { mSize = size; mExpiry = expiry; } private final long mSize; private final long mExpiry; } }
src/java/com/android/internal/telephony/WapPushOverSms.java +7 −0 Original line number Diff line number Diff line Loading @@ -262,6 +262,13 @@ public class WapPushOverSms implements ServiceConnection { if (parsedPdu != null && parsedPdu.getMessageType() == MESSAGE_TYPE_NOTIFICATION_IND) { final NotificationInd nInd = (NotificationInd) parsedPdu; // save the WAP push message size so that if a download request is made for it // while on a satellite connection we can check if the size is under the threshold WapPushCache.putWapMessageSize( nInd.getContentLocation(), nInd.getTransactionId(), nInd.getMessageSize() ); if (nInd.getFrom() != null && BlockChecker.isBlocked(mContext, nInd.getFrom().getString(), null)) { result.statusCode = Intents.RESULT_SMS_HANDLED; Loading
tests/telephonytests/src/com/android/internal/telephony/SmsControllerTest.java +42 −0 Original line number Diff line number Diff line Loading @@ -16,7 +16,10 @@ package com.android.internal.telephony; import static junit.framework.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.anyInt; Loading @@ -40,7 +43,9 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.NoSuchElementException; @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper Loading @@ -65,6 +70,7 @@ public class SmsControllerTest extends TelephonyTest { @After public void tearDown() throws Exception { mAdnRecordCache = null; WapPushCache.clear(); super.tearDown(); } Loading Loading @@ -239,4 +245,40 @@ public class SmsControllerTest extends TelephonyTest { verify(mIccSmsInterfaceManager, Mockito.times(0)) .sendText(mCallingPackage, "1234", null, "text", null, null, false, 0L, true); } @Test public void testGetWapMessageSize() { long expectedSize = 100L; String location = "content://mms"; byte[] locationBytes = location.getBytes(StandardCharsets.ISO_8859_1); byte[] transactionId = "123".getBytes(StandardCharsets.ISO_8859_1); WapPushCache.putWapMessageSize(locationBytes, transactionId, expectedSize); long size = mSmsControllerUT.getWapMessageSize(location); assertEquals(expectedSize, size); } @Test public void testGetWapMessageSize_withTransactionIdAppended() { long expectedSize = 100L; byte[] location = "content://mms".getBytes(StandardCharsets.ISO_8859_1); byte[] transactionId = "123".getBytes(StandardCharsets.ISO_8859_1); byte[] joinedKey = new byte[location.length + transactionId.length]; System.arraycopy(location, 0, joinedKey, 0, location.length); System.arraycopy(transactionId, 0, joinedKey, location.length, transactionId.length); String joinedKeyString = new String(joinedKey, StandardCharsets.ISO_8859_1); WapPushCache.putWapMessageSize(location, transactionId, expectedSize); long size = mSmsControllerUT.getWapMessageSize(joinedKeyString); assertEquals(expectedSize, size); } @Test public void testGetWapMessageSize_nonexistentThrows() { assertThrows(NoSuchElementException.class, () -> mSmsControllerUT.getWapMessageSize("content://mms") ); } } No newline at end of file
tests/telephonytests/src/com/android/internal/telephony/WapPushCacheTest.java 0 → 100644 +144 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.telephony; import static junit.framework.Assert.assertEquals; import static org.junit.Assert.assertThrows; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.time.Clock; import java.util.NoSuchElementException; import java.util.concurrent.TimeUnit; public class WapPushCacheTest extends TelephonyTest { @Before public void setUp() throws Exception { super.setUp(getClass().getSimpleName()); } @After public void tearDown() throws Exception { WapPushCache.clear(); WapPushCache.sTelephonyFacade = new TelephonyFacade(); super.tearDown(); } @Test public void testGetWapMessageSize() { long expectedSize = 100L; byte[] location = "content://mms".getBytes(); byte[] transactionId = "123".getBytes(); WapPushCache.putWapMessageSize(location, transactionId, expectedSize); long size = WapPushCache.getWapMessageSize(location); assertEquals(expectedSize, size); } @Test public void testGetWapMessageSize_withTransactionIdAppended() { long expectedSize = 100L; byte[] location = "content://mms".getBytes(); byte[] transactionId = "123".getBytes(); byte[] joinedKey = new byte[location.length + transactionId.length]; System.arraycopy(location, 0, joinedKey, 0, location.length); System.arraycopy(transactionId, 0, joinedKey, location.length, transactionId.length); WapPushCache.putWapMessageSize(location, transactionId, expectedSize); long size = WapPushCache.getWapMessageSize(joinedKey); assertEquals(expectedSize, size); } @Test public void testGetWapMessageSize_nonexistentThrows() { assertThrows(NoSuchElementException.class, () -> WapPushCache.getWapMessageSize("content://mms".getBytes()) ); } @Test public void testGetWapMessageSize_emptyLocationUrlThrows() { assertThrows(IllegalArgumentException.class, () -> WapPushCache.getWapMessageSize(new byte[0]) ); } @Test public void testPutWapMessageSize_invalidValuePreventsInsert() { long expectedSize = 0L; byte[] location = "content://mms".getBytes(); byte[] transactionId = "123".getBytes(); WapPushCache.putWapMessageSize(location, transactionId, expectedSize); assertEquals(0, WapPushCache.size()); } @Test public void testPutWapMessageSize_sizeLimitExceeded_oldestEntryRemoved() { long expectedSize = 100L; for (int i = 0; i < 251; i++) { byte[] location = ("" + i).getBytes(); byte[] transactionId = "abc".getBytes(); WapPushCache.putWapMessageSize(location, transactionId, expectedSize); } // assert one of the entries inserted above has been removed assertEquals(500, WapPushCache.size()); // assert last entry added exists assertEquals(expectedSize, WapPushCache.getWapMessageSize("250".getBytes())); // assert the first entry added was removed assertThrows(NoSuchElementException.class, () -> WapPushCache.getWapMessageSize("0".getBytes()) ); } @Test public void testPutWapMessageSize_expiryExceeded_entryRemoved() { long currentTime = Clock.systemUTC().millis(); TelephonyFacade facade = mock(TelephonyFacade.class); when(facade.getElapsedSinceBootMillis()).thenReturn(currentTime); WapPushCache.sTelephonyFacade = facade; long expectedSize = 100L; byte[] transactionId = "abc".getBytes(); byte[] location1 = "old".getBytes(); byte[] location2 = "new".getBytes(); WapPushCache.putWapMessageSize(location1, transactionId, expectedSize); assertEquals(2, WapPushCache.size()); // advance time when(facade.getElapsedSinceBootMillis()) .thenReturn(currentTime + TimeUnit.DAYS.toMillis(14) + 1); WapPushCache.putWapMessageSize(location2, transactionId, expectedSize); assertEquals(2, WapPushCache.size()); assertEquals(expectedSize, WapPushCache.getWapMessageSize(location2)); assertThrows(NoSuchElementException.class, () -> WapPushCache.getWapMessageSize(location1) ); } }