diff --git a/emailcommon/src/com/android/emailcommon/utility/Utility.java b/emailcommon/src/com/android/emailcommon/utility/Utility.java index e8259bd64..1861f7267 100644 --- a/emailcommon/src/com/android/emailcommon/utility/Utility.java +++ b/emailcommon/src/com/android/emailcommon/utility/Utility.java @@ -53,6 +53,7 @@ import com.android.emailcommon.provider.EmailContent.Message; import com.android.emailcommon.provider.HostAuth; import com.android.emailcommon.provider.ProviderUnavailableException; import com.android.mail.utils.LogUtils; +import com.google.common.annotations.VisibleForTesting; import java.io.ByteArrayInputStream; import java.io.File; @@ -64,6 +65,7 @@ import java.io.InputStreamReader; import java.io.PrintWriter; import java.io.StringWriter; import java.io.UnsupportedEncodingException; +import java.lang.ThreadLocal; import java.net.URI; import java.net.URISyntaxException; import java.nio.ByteBuffer; @@ -71,8 +73,11 @@ import java.nio.CharBuffer; import java.nio.charset.Charset; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.text.ParseException; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collection; +import java.util.Date; import java.util.GregorianCalendar; import java.util.HashSet; import java.util.Set; @@ -292,41 +297,77 @@ public class Utility { return sb.toString(); } + private static class ThreadLocalDateFormat extends ThreadLocal { + private final String mFormatStr; + + public ThreadLocalDateFormat(String formatStr) { + mFormatStr = formatStr; + } + + @Override + protected SimpleDateFormat initialValue() { + final SimpleDateFormat format = new SimpleDateFormat(mFormatStr); + final GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT")); + format.setCalendar(cal); + return format; + } + + public Date parse(String date) throws ParseException { + return super.get().parse(date); + } + } + /** * Generate a time in milliseconds from a date string that represents a date/time in GMT * @param date string in format 20090211T180303Z (rfc2445, iCalendar). * @return the time in milliseconds (since Jan 1, 1970) */ - public static long parseDateTimeToMillis(String date) { - GregorianCalendar cal = parseDateTimeToCalendar(date); - return cal.getTimeInMillis(); + public static long parseDateTimeToMillis(String date) throws ParseException { + return parseDateTimeToCalendar(date).getTimeInMillis(); } + private static final ThreadLocalDateFormat mFullDateTimeFormat = + new ThreadLocalDateFormat("yyyyMMdd'T'HHmmss'Z'"); + + private static final ThreadLocalDateFormat mAbbrevDateTimeFormat = + new ThreadLocalDateFormat("yyyyMMdd"); + /** * Generate a GregorianCalendar from a date string that represents a date/time in GMT - * @param date string in format 20090211T180303Z (rfc2445, iCalendar). + * @param date string in format 20090211T180303Z (rfc2445, iCalendar), or + * in abbreviated format 20090211. * @return the GregorianCalendar */ - public static GregorianCalendar parseDateTimeToCalendar(String date) { - GregorianCalendar cal = new GregorianCalendar(Integer.parseInt(date.substring(0, 4)), - Integer.parseInt(date.substring(4, 6)) - 1, Integer.parseInt(date.substring(6, 8)), - Integer.parseInt(date.substring(9, 11)), Integer.parseInt(date.substring(11, 13)), - Integer.parseInt(date.substring(13, 15))); - cal.setTimeZone(TimeZone.getTimeZone("GMT")); + @VisibleForTesting + public static GregorianCalendar parseDateTimeToCalendar(String date) throws ParseException { + final GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT")); + if (date.length() <= 8) { + cal.setTime(mAbbrevDateTimeFormat.parse(date)); + } else { + cal.setTime(mFullDateTimeFormat.parse(date)); + } return cal; } + private static final ThreadLocalDateFormat mEmailDateTimeFormat = + new ThreadLocalDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); + + private static final ThreadLocalDateFormat mEmailDateTimeFormatWithMillis = + new ThreadLocalDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + /** * Generate a time in milliseconds from an email date string that represents a date/time in GMT * @param date string in format 2010-02-23T16:00:00.000Z (ISO 8601, rfc3339) * @return the time in milliseconds (since Jan 1, 1970) */ - public static long parseEmailDateTimeToMillis(String date) { - GregorianCalendar cal = new GregorianCalendar(Integer.parseInt(date.substring(0, 4)), - Integer.parseInt(date.substring(5, 7)) - 1, Integer.parseInt(date.substring(8, 10)), - Integer.parseInt(date.substring(11, 13)), Integer.parseInt(date.substring(14, 16)), - Integer.parseInt(date.substring(17, 19))); - cal.setTimeZone(TimeZone.getTimeZone("GMT")); + @VisibleForTesting + public static long parseEmailDateTimeToMillis(String date) throws ParseException { + final GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT")); + if (date.length() <= 20) { + cal.setTime(mEmailDateTimeFormat.parse(date)); + } else { + cal.setTime(mEmailDateTimeFormatWithMillis.parse(date)); + } return cal.getTimeInMillis(); } diff --git a/src/com/android/email/activity/EventViewer.java b/src/com/android/email/activity/EventViewer.java index 7eebeef11..6fd3f787f 100644 --- a/src/com/android/email/activity/EventViewer.java +++ b/src/com/android/email/activity/EventViewer.java @@ -29,6 +29,8 @@ import com.android.emailcommon.mail.PackedString; import com.android.emailcommon.provider.EmailContent.Message; import com.android.emailcommon.utility.Utility; +import java.text.ParseException; + public class EventViewer extends Activity { @Override public void onCreate(Bundle savedInstanceState) { @@ -62,8 +64,13 @@ public class EventViewer extends Activity { if (eventId != -1) { uri = ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, eventId); } else { - long time = - Utility.parseEmailDateTimeToMillis(info.get(MeetingInfo.MEETING_DTSTART)); + long time; + try { + time = Utility.parseEmailDateTimeToMillis(info.get(MeetingInfo.MEETING_DTSTART)); + } catch (ParseException e) { + finish(); + return; + } uri = Uri.parse("content://com.android.calendar/time/" + time); intent.putExtra("VIEW", "DAY"); } diff --git a/tests/src/com/android/email/UtilityLargeTest.java b/tests/src/com/android/email/UtilityLargeTest.java deleted file mode 100644 index 4ab58c136..000000000 --- a/tests/src/com/android/email/UtilityLargeTest.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2010 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.email; - -import android.content.Context; -import android.test.InstrumentationTestCase; -import android.test.suitebuilder.annotation.LargeTest; -import android.test.suitebuilder.annotation.Suppress; - -import com.android.email.provider.ProviderTestUtils; -import com.android.emailcommon.provider.Account; -import com.android.emailcommon.utility.Utility; - -import java.util.ArrayList; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * Large tests for {@link Utility}. - */ -@Suppress -@LargeTest -public class UtilityLargeTest extends InstrumentationTestCase { - private static final int WAIT_UNTIL_TIMEOUT_SECONDS = 10; - - // Isolted Context for providers. - private Context mProviderContext; - - - @Override - protected void setUp() throws Exception { - super.setUp(); - mProviderContext = DBTestHelper.ProviderContextSetupHelper.getProviderContext( - getInstrumentation().getTargetContext()); - } - - - public void testForEachAccount() throws Throwable { - // Create some accounts... - Account acct1 = ProviderTestUtils.setupAccount("acct1", true, mProviderContext); - Account acct2 = ProviderTestUtils.setupAccount("acct2", true, mProviderContext); - - final ArrayList ids = new ArrayList(); // Account id array. - final AtomicBoolean done = new AtomicBoolean(false); - - // Kick ForEachAccount and collect IDs... - // AsyncTask needs to be created on the UI thread. - runTestOnUiThread(new Runnable() { - @Override - public void run() { - new Utility.ForEachAccount(mProviderContext) { - @Override - protected void performAction(long accountId) { - synchronized (ids) { - ids.add(accountId); - } - } - - @Override - protected void onFinished() { - done.set(true); - } - }.execute(); - } - }); - - // Wait until it's done... - TestUtils.waitUntil(new TestUtils.Condition() { - @Override - public boolean isMet() { - return done.get(); - } - }, WAIT_UNTIL_TIMEOUT_SECONDS); - - // Check the collected IDs. - synchronized (ids) { - assertEquals(2, ids.size()); - // ids may not be sorted, so... - assertTrue(ids.contains(acct1.mId)); - assertTrue(ids.contains(acct2.mId)); - } - } -} diff --git a/tests/src/com/android/emailcommon/utility/UtilityMediumTests.java b/tests/src/com/android/emailcommon/utility/UtilityMediumTests.java deleted file mode 100644 index 0eee01e21..000000000 --- a/tests/src/com/android/emailcommon/utility/UtilityMediumTests.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * Copyright (C) 2010 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.emailcommon.utility; - -import android.content.Context; -import android.net.Uri; -import android.test.ProviderTestCase2; -import android.test.suitebuilder.annotation.MediumTest; -import android.test.suitebuilder.annotation.Suppress; - -import com.android.email.provider.EmailProvider; -import com.android.email.provider.ProviderTestUtils; -import com.android.emailcommon.provider.Account; -import com.android.emailcommon.provider.EmailContent; -import com.android.emailcommon.provider.EmailContent.AccountColumns; -import com.android.emailcommon.provider.EmailContent.Attachment; -import com.android.emailcommon.provider.EmailContent.Message; -import com.android.emailcommon.provider.Mailbox; - -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; - -/** - * This is a series of medium tests for the Utility class. These tests must be locally - * complete - no server(s) required. - * - * You can run this entire test case with: - * runtest -c com.android.emailcommon.utility.UtilityMediumTests email - */ -@Suppress -@MediumTest -public class UtilityMediumTests extends ProviderTestCase2 { - - EmailProvider mProvider; - Context mMockContext; - - public UtilityMediumTests() { - super(EmailProvider.class, EmailContent.AUTHORITY); - } - - @Override - public void setUp() throws Exception { - super.setUp(); - mMockContext = getMockContext(); - } - - public void testFindExistingAccount() { - // Create two accounts - Account account1 = ProviderTestUtils.setupAccount("account1", false, mMockContext); - account1.mHostAuthRecv = ProviderTestUtils.setupHostAuth("ha1", -1, false, mMockContext); - account1.mHostAuthSend = ProviderTestUtils.setupHostAuth("ha1", -1, false, mMockContext); - account1.save(mMockContext); - Account account2 = ProviderTestUtils.setupAccount("account2", false, mMockContext); - account2.mHostAuthRecv = ProviderTestUtils.setupHostAuth("ha2", -1, false, mMockContext); - account2.mHostAuthSend = ProviderTestUtils.setupHostAuth("ha2", -1, false, mMockContext); - account2.save(mMockContext); - // Make sure we can find them - Account acct = Utility.findExistingAccount(mMockContext, -1, "address-ha1", "login-ha1"); - assertNotNull(acct); - assertEquals("account1", acct.mDisplayName); - acct = Utility.findExistingAccount(mMockContext, -1, "address-ha2", "login-ha2"); - assertNotNull(acct); - assertEquals("account2", acct.mDisplayName); - // We shouldn't find account - acct = Utility.findExistingAccount(mMockContext, -1, "address-ha3", "login-ha3"); - assertNull(acct); - // Try to find account1, excluding account1 - acct = Utility.findExistingAccount(mMockContext, account1.mId, "address-ha1", "login-ha1"); - assertNull(acct); - - // Make sure we properly handle an underscore in the login name - Account account3 = ProviderTestUtils.setupAccount("account3", false, mMockContext); - account3.mHostAuthRecv = ProviderTestUtils.setupHostAuth("foo_ba", -1, false, mMockContext); - account3.mHostAuthSend = ProviderTestUtils.setupHostAuth("foo_ba", -1, false, mMockContext); - account3.save(mMockContext); - acct = Utility.findExistingAccount(mMockContext, -1, "address-foo_ba", "login-foo.ba"); - assertNull(acct); - acct = Utility.findExistingAccount(mMockContext, -1, "address-foo_ba", "login-foo_ba"); - assertNotNull(acct); - } - - public void testAttachmentExists() throws IOException { - Account account = ProviderTestUtils.setupAccount("account", true, mMockContext); - // We return false with null attachment - assertFalse(Utility.attachmentExists(mMockContext, null)); - - Mailbox mailbox = - ProviderTestUtils.setupMailbox("mailbox", account.mId, true, mMockContext); - Message message = ProviderTestUtils.setupMessage("foo", account.mId, mailbox.mId, false, - true, mMockContext); - Attachment attachment = ProviderTestUtils.setupAttachment(message.mId, "filename.ext", - 69105, true, mMockContext); - attachment.mContentBytes = null; - // With no contentUri, we should return false - assertFalse(Utility.attachmentExists(mMockContext, attachment)); - - attachment.mContentBytes = new byte[0]; - // With contentBytes set, we should return true - assertTrue(Utility.attachmentExists(mMockContext, attachment)); - - attachment.mContentBytes = null; - // Generate a file name in our data directory, and use that for contentUri - File file = mMockContext.getFileStreamPath("test.att"); - // Delete the file if it already exists - if (file.exists()) { - assertTrue(file.delete()); - } - // Should return false, because the file doesn't exist - assertFalse(Utility.attachmentExists(mMockContext, attachment)); - - assertTrue(file.createNewFile()); - // Put something in the file - FileWriter writer = new FileWriter(file); - writer.write("Foo"); - writer.flush(); - writer.close(); - attachment.setContentUri("file://" + file.getAbsolutePath()); - // Now, this should return true - assertTrue(Utility.attachmentExists(mMockContext, attachment)); - } - - public void testBuildLimitOneUri() { - // EmailProvider supports "?limit=" - assertEquals(Uri.parse("content://com.android.mail.provider?limit=1"), - Utility.buildLimitOneUri(Uri.parse("content://com.android.mail.provider"))); - - // Others don't -- so don't add it. - assertEquals(Uri.parse("content://com.android.mail.attachmentprovider"), - Utility.buildLimitOneUri(Uri.parse("content://com.android.mail.attachmentprovider" - ))); - assertEquals(Uri.parse("content://gmail-ls/android@gmail.com"), - Utility.buildLimitOneUri(Uri.parse("content://gmail-ls/android@gmail.com" - ))); - } - - public void testGetFirstRowLong() { - Account account1 = ProviderTestUtils.setupAccount("1", true, mMockContext); - Account account2 = ProviderTestUtils.setupAccount("X1", true, mMockContext); - Account account3 = ProviderTestUtils.setupAccount("X2", true, mMockContext); - - // case 1. Account found - assertEquals((Long) account2.mId, Utility.getFirstRowLong( - mMockContext, Account.CONTENT_URI, EmailContent.ID_PROJECTION, - AccountColumns.DISPLAY_NAME + " like :1", new String[] {"X%"}, - AccountColumns.DISPLAY_NAME, - EmailContent.ID_PROJECTION_COLUMN)); - // different sort order - assertEquals((Long) account3.mId, Utility.getFirstRowLong( - mMockContext, Account.CONTENT_URI, EmailContent.ID_PROJECTION, - AccountColumns.DISPLAY_NAME + " like :1", new String[] {"X%"}, - AccountColumns.DISPLAY_NAME + " desc", - EmailContent.ID_PROJECTION_COLUMN)); - - // case 2. no row found - assertEquals(null, Utility.getFirstRowLong( - mMockContext, Account.CONTENT_URI, EmailContent.ID_PROJECTION, - AccountColumns.DISPLAY_NAME + " like :1", new String[] {"NO SUCH ACCOUNT"}, - null, - EmailContent.ID_PROJECTION_COLUMN)); - - // case 3. no row found with default value - assertEquals((Long) (-1L), Utility.getFirstRowLong( - mMockContext, Account.CONTENT_URI, EmailContent.ID_PROJECTION, - AccountColumns.DISPLAY_NAME + " like :1", new String[] {"NO SUCH ACCOUNT"}, - null, - EmailContent.ID_PROJECTION_COLUMN, -1L)); - } - - public void testGetFirstRowInt() { - Account account1 = ProviderTestUtils.setupAccount("1", true, mMockContext); - Account account2 = ProviderTestUtils.setupAccount("X1", true, mMockContext); - Account account3 = ProviderTestUtils.setupAccount("X2", true, mMockContext); - - // case 1. Account found - assertEquals((Integer)(int) account2.mId, Utility.getFirstRowInt( - mMockContext, Account.CONTENT_URI, EmailContent.ID_PROJECTION, - AccountColumns.DISPLAY_NAME + " like :1", new String[] {"X%"}, - AccountColumns.DISPLAY_NAME, - EmailContent.ID_PROJECTION_COLUMN)); - // different sort order - assertEquals((Integer)(int) account3.mId, Utility.getFirstRowInt( - mMockContext, Account.CONTENT_URI, EmailContent.ID_PROJECTION, - AccountColumns.DISPLAY_NAME + " like :1", new String[] {"X%"}, - AccountColumns.DISPLAY_NAME + " desc", - EmailContent.ID_PROJECTION_COLUMN)); - - // case 2. no row found - assertEquals(null, Utility.getFirstRowInt( - mMockContext, Account.CONTENT_URI, EmailContent.ID_PROJECTION, - AccountColumns.DISPLAY_NAME + " like :1", new String[] {"NO SUCH ACCOUNT"}, - null, - EmailContent.ID_PROJECTION_COLUMN)); - - // case 3. no row found with default value - assertEquals((Integer) (-1), Utility.getFirstRowInt( - mMockContext, Account.CONTENT_URI, EmailContent.ID_PROJECTION, - AccountColumns.DISPLAY_NAME + " like :1", new String[] {"NO SUCH ACCOUNT"}, - null, - EmailContent.ID_PROJECTION_COLUMN, -1)); - } - - public void testGetFirstRowString() { - final String[] DISPLAY_NAME_PROJECTION = new String[] {AccountColumns.DISPLAY_NAME}; - - Account account1 = ProviderTestUtils.setupAccount("1", true, mMockContext); - Account account2 = ProviderTestUtils.setupAccount("X1", true, mMockContext); - Account account3 = ProviderTestUtils.setupAccount("X2", true, mMockContext); - - // case 1. Account found - assertEquals(account2.mDisplayName, Utility.getFirstRowString( - mMockContext, Account.CONTENT_URI, DISPLAY_NAME_PROJECTION, - AccountColumns.DISPLAY_NAME + " like :1", new String[] {"X%"}, - AccountColumns.DISPLAY_NAME, 0)); - - // different sort order - assertEquals(account3.mDisplayName, Utility.getFirstRowString( - mMockContext, Account.CONTENT_URI, DISPLAY_NAME_PROJECTION, - AccountColumns.DISPLAY_NAME + " like :1", new String[] {"X%"}, - AccountColumns.DISPLAY_NAME + " desc", 0)); - - // case 2. no row found - assertEquals(null, Utility.getFirstRowString( - mMockContext, Account.CONTENT_URI, DISPLAY_NAME_PROJECTION, - AccountColumns.DISPLAY_NAME + " like :1", new String[] {"NO SUCH ACCOUNT"}, - null, 0)); - - // case 3. no row found with default value - assertEquals("-", Utility.getFirstRowString( - mMockContext, Account.CONTENT_URI, DISPLAY_NAME_PROJECTION, - AccountColumns.DISPLAY_NAME + " like :1", new String[] {"NO SUCH ACCOUNT"}, - null, 0, "-")); - } -} diff --git a/tests/src/com/android/emailcommon/utility/UtilityTest.java b/tests/src/com/android/emailcommon/utility/UtilityTest.java new file mode 100644 index 000000000..3d08244b4 --- /dev/null +++ b/tests/src/com/android/emailcommon/utility/UtilityTest.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2014 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.emailcommon.utility; + +import android.test.suitebuilder.annotation.SmallTest; +import java.text.ParseException; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.TimeZone; +import junit.framework.TestCase; + +@SmallTest +public class UtilityTest extends TestCase { + private void testParseDateTimesHelper(String date, int year, int month, + int day, int hour, int minute, int second) throws Exception { + GregorianCalendar cal = Utility.parseDateTimeToCalendar(date); + assertEquals(year, cal.get(Calendar.YEAR)); + assertEquals(month, cal.get(Calendar.MONTH) + 1); + assertEquals(day, cal.get(Calendar.DAY_OF_MONTH)); + assertEquals(hour, cal.get(Calendar.HOUR_OF_DAY)); + assertEquals(minute, cal.get(Calendar.MINUTE)); + assertEquals(second, cal.get(Calendar.SECOND)); + } + + @SmallTest + public void testParseDateTimes() throws Exception { + testParseDateTimesHelper("20090211T180303Z", 2009, 2, 11, 18, 3, 3); + testParseDateTimesHelper("20090211", 2009, 2, 11, 0, 0, 0); + try { + testParseDateTimesHelper("200902", 0, 0, 0, 0, 0, 0); + fail("Expected ParseException"); + } catch (ParseException e) { + // expected + } + } + + private void testParseEmailDateTimeHelper(String date, int year, int month, + int day, int hour, int minute, int second, int millis) throws Exception { + GregorianCalendar cal = new GregorianCalendar(year, month - 1, day, + hour, minute, second); + cal.setTimeZone(TimeZone.getTimeZone("GMT")); + long timeInMillis = Utility.parseEmailDateTimeToMillis(date); + assertEquals(cal.getTimeInMillis() + millis, timeInMillis); + } + + @SmallTest + public void testParseEmailDateTime() throws Exception { + testParseEmailDateTimeHelper("2010-02-23T16:01:05.000Z", + 2010, 2, 23, 16, 1, 5, 0); + testParseEmailDateTimeHelper("2009-02-11T18:03:31.123Z", + 2009, 2, 11, 18, 3, 31, 123); + try { + testParseEmailDateTimeHelper("2010-02-23", 1970, 1, 1, 0, 0, 0, 0); + fail("Expected ParseException"); + } catch (ParseException e) { + // expected + } + } +} diff --git a/tests/src/com/android/emailcommon/utility/UtilityUnitTests.java b/tests/src/com/android/emailcommon/utility/UtilityUnitTests.java deleted file mode 100644 index 503cd05a1..000000000 --- a/tests/src/com/android/emailcommon/utility/UtilityUnitTests.java +++ /dev/null @@ -1,421 +0,0 @@ -/* - * Copyright (C) 2008 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.emailcommon.utility; - -import android.content.Context; -import android.database.Cursor; -import android.database.CursorWrapper; -import android.net.Uri; -import android.os.Environment; -import android.test.AndroidTestCase; -import android.test.MoreAsserts; -import android.test.suitebuilder.annotation.SmallTest; -import android.test.suitebuilder.annotation.Suppress; -import android.text.SpannableStringBuilder; -import android.text.TextUtils; -import android.widget.TextView; - -import com.android.email.DBTestHelper; -import com.android.email.TestUtils; -import com.android.email.provider.ProviderTestUtils; -import com.android.emailcommon.provider.Account; -import com.android.emailcommon.provider.EmailContent.Attachment; -import com.android.emailcommon.provider.Mailbox; -import com.android.emailcommon.utility.Utility.NewFileCreator; -import com.android.mail.utils.MatrixCursorWithCachedColumns; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; - -/** - * This is a series of unit tests for the Utility class. These tests must be locally - * complete - no server(s) required. - * - * You can run this entire test case with: - * runtest -c com.android.email.UtilityUnitTests email - */ -@Suppress -@SmallTest -public class UtilityUnitTests extends AndroidTestCase { - - private static byte[] b(int... array) { - return TestUtils.b(array); - } - - public void testToUtf8() { - assertNull(Utility.toUtf8(null)); - MoreAsserts.assertEquals(new byte[] {}, Utility.toUtf8("")); - MoreAsserts.assertEquals(b('a'), Utility.toUtf8("a")); - MoreAsserts.assertEquals(b('A', 'B', 'C'), Utility.toUtf8("ABC")); - MoreAsserts.assertEquals(b(0xE6, 0x97, 0xA5, 0xE6, 0x9C, 0xAC, 0xE8, 0xAA, 0x9E), - Utility.toUtf8("\u65E5\u672C\u8A9E")); - } - - public void testFromUtf8() { - assertNull(Utility.fromUtf8(null)); - assertEquals("", Utility.fromUtf8(new byte[] {})); - assertEquals("a", Utility.fromUtf8(b('a'))); - assertEquals("ABC", Utility.fromUtf8(b('A', 'B', 'C'))); - assertEquals("\u65E5\u672C\u8A9E", - Utility.fromUtf8(b(0xE6, 0x97, 0xA5, 0xE6, 0x9C, 0xAC, 0xE8, 0xAA, 0x9E))); - } - - public void testIsFirstUtf8Byte() { - // 1 byte in UTF-8. - checkIsFirstUtf8Byte("0"); // First 2 bits: 00 - checkIsFirstUtf8Byte("A"); // First 2 bits: 01 - - checkIsFirstUtf8Byte("\u00A2"); // 2 bytes in UTF-8. - checkIsFirstUtf8Byte("\u20AC"); // 3 bytes in UTF-8. - checkIsFirstUtf8Byte("\uD852\uDF62"); // 4 bytes in UTF-8. (surrogate pair) - } - - private void checkIsFirstUtf8Byte(String aChar) { - byte[] bytes = Utility.toUtf8(aChar); - assertTrue("0", Utility.isFirstUtf8Byte(bytes[0])); - for (int i = 1; i < bytes.length; i++) { - assertFalse(Integer.toString(i), Utility.isFirstUtf8Byte(bytes[i])); - } - } - - public void testByteToHex() { - for (int i = 0; i <= 0xFF; i++) { - String hex = Utility.byteToHex((byte) i); - assertEquals("val=" + i, 2, hex.length()); - assertEquals("val=" + i, i, Integer.parseInt(hex, 16)); - } - } - - public void testReplaceBareLfWithCrlf() { - assertEquals("", Utility.replaceBareLfWithCrlf("")); - assertEquals("", Utility.replaceBareLfWithCrlf("\r")); - assertEquals("\r\n", Utility.replaceBareLfWithCrlf("\r\n")); - assertEquals("\r\n", Utility.replaceBareLfWithCrlf("\n")); - assertEquals("\r\n\r\n\r\n", Utility.replaceBareLfWithCrlf("\n\n\n")); - assertEquals("A\r\nB\r\nC\r\nD", Utility.replaceBareLfWithCrlf("A\nB\r\nC\nD")); - } - - public void testGetSmallHash() { - assertEquals("1438642069", Utility.getSmallHash("")); - assertEquals("1354919068", Utility.getSmallHash("abc")); - } - - public void testGetSmallSha1() { - byte[] sha1 = new byte[20]; - - // White box test. Not so great, but to make sure it may detect careless mistakes... - assertEquals(0, Utility.getSmallHashFromSha1(sha1)); - - for (int i = 0; i < sha1.length; i++) { - sha1[i] = (byte) 0xFF; - } - assertEquals(Integer.MAX_VALUE, Utility.getSmallHashFromSha1(sha1)); - - // Boundary check - for (int i = 0; i < 16; i++) { - sha1[19] = (byte) i; - Utility.getSmallHashFromSha1(sha1); - } - } - - public void brokentestCleanUpMimeDate() { - assertNull(Utility.cleanUpMimeDate(null)); - assertEquals("", Utility.cleanUpMimeDate("")); - assertEquals("abc", Utility.cleanUpMimeDate("abc")); - assertEquals("GMT", Utility.cleanUpMimeDate("GMT")); - assertEquals("0000", Utility.cleanUpMimeDate("0000")); - assertEquals("-0000", Utility.cleanUpMimeDate("-0000")); - assertEquals("+1234", Utility.cleanUpMimeDate("GMT+1234")); - assertEquals("-1234", Utility.cleanUpMimeDate("GMT-1234")); - assertEquals("gmt-1234", Utility.cleanUpMimeDate("gmt-1234")); - assertEquals("GMT-123", Utility.cleanUpMimeDate("GMT-123")); - - assertEquals("Thu, 10 Dec 09 15:08:08 -0700", - Utility.cleanUpMimeDate("Thu, 10 Dec 09 15:08:08 GMT-0700")); - assertEquals("Thu, 10 Dec 09 15:08:08 -0700", - Utility.cleanUpMimeDate("Thu, 10 Dec 09 15:08:08 -0700")); - } - - private static class MyNewFileCreator implements NewFileCreator { - private final HashSet mExistingFileNames; - - public MyNewFileCreator(String... fileNames) { - mExistingFileNames = new HashSet(); - for (String f : fileNames) { - mExistingFileNames.add(f); - } - } - - @Override public boolean createNewFile(File f) { - return !mExistingFileNames.contains(f.getAbsolutePath()); - } - } - - public void testCreateUniqueFile() throws Exception { - final MyNewFileCreator noFiles = new MyNewFileCreator(); - - // Case 1: Files don't exist. - checkCreateUniqueFile("/a", noFiles, "/", "a"); - checkCreateUniqueFile("/a.txt", noFiles, "/", "a.txt"); - - checkCreateUniqueFile("/a/b/a", noFiles, "/a/b", "a"); - checkCreateUniqueFile("/a/b/a.txt", noFiles, "/a/b", "a.txt"); - - // Case 2: Files exist already. - final MyNewFileCreator files = new MyNewFileCreator( - "/a", "/a.txt", "/a/b/a", "/a/b/a.txt", - "/a-2.txt", - "/a/b/a-2", "/a/b/a-3", - "/a/b/a-2.txt", "/a/b/a-3.txt", "/a/b/a-4.txt" - ); - - checkCreateUniqueFile("/a-2", files, "/", "a"); - checkCreateUniqueFile("/a-3.txt", files, "/", "a.txt"); - - checkCreateUniqueFile("/a/b/a-4", files, "/a/b", "a"); - checkCreateUniqueFile("/a/b/a-5.txt", files, "/a/b", "a.txt"); - } - - private void checkCreateUniqueFile(String expectedFileName, NewFileCreator nfc, - String dir, String fileName) throws Exception { - assertEquals(expectedFileName, - Utility.createUniqueFileInternal(nfc, new File(dir), fileName).toString()); - } - - /** - * Test that we have the necessary permissions to write to external storage. - */ - public void testExternalStoragePermissions() throws FileNotFoundException, IOException { - File file = null; - try { - // If there's no storage available, this test is moot - if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - return; - } - file = Utility.createUniqueFile(Environment.getExternalStorageDirectory(), - "write-test"); - OutputStream out = new FileOutputStream(file); - out.write(1); - out.close(); - } finally { - try { - if (file != null) { - if (file.exists()) { - file.delete(); - } - } - } catch (Exception e) { - // ignore cleanup error - it still throws the original - } - } - } - - public void testIsPortFieldValid() { - TextView view = new TextView(getContext()); - // null, empty, negative, and non integer strings aren't valid - view.setText(null); - assertFalse(Utility.isPortFieldValid(view)); - view.setText(""); - assertFalse(Utility.isPortFieldValid(view)); - view.setText("-1"); - assertFalse(Utility.isPortFieldValid(view)); - view.setText("1403.75"); - assertFalse(Utility.isPortFieldValid(view)); - view.setText("0"); - assertFalse(Utility.isPortFieldValid(view)); - view.setText("65536"); - assertFalse(Utility.isPortFieldValid(view)); - view.setText("i'm not valid"); - assertFalse(Utility.isPortFieldValid(view)); - // These next values are valid - view.setText("1"); - assertTrue(Utility.isPortFieldValid(view)); - view.setText("65535"); - assertTrue(Utility.isPortFieldValid(view)); - } - - public void testToPrimitiveLongArray() { - assertEquals(0, Utility.toPrimitiveLongArray(createLongCollection()).length); - - final long[] one = Utility.toPrimitiveLongArray(createLongCollection(1)); - assertEquals(1, one.length); - assertEquals(1, one[0]); - - final long[] two = Utility.toPrimitiveLongArray(createLongCollection(3, 4)); - assertEquals(2, two.length); - assertEquals(3, two[0]); - assertEquals(4, two[1]); - } - - public void testToLongSet() { - assertEquals(0, Utility.toLongSet(new long[] {}).size()); - - final Set one = Utility.toLongSet(new long[] {1}); - assertEquals(1, one.size()); - assertTrue(one.contains(1L)); - - final Set two = Utility.toLongSet(new long[] {1, 2}); - assertEquals(2, two.size()); - assertTrue(two.contains(1L)); - assertTrue(two.contains(2L)); - } - - public void testGetContentFileName() throws Exception { - Context providerContext = DBTestHelper.ProviderContextSetupHelper.getProviderContext( - mContext); - - final long ACCOUNT_ID = 1; - final long MESSAGE_ID = 10; - - Account account = ProviderTestUtils.setupAccount("account", true, providerContext); - Mailbox mailbox = ProviderTestUtils.setupMailbox("box", account.mId, true, providerContext); - - // Set up an attachment. - Attachment att = ProviderTestUtils.setupAttachment(mailbox.mId, "name", 123, true, - providerContext); - long attachmentId = att.mId; - Uri uri = AttachmentUtilities.getAttachmentUri(account.mId, attachmentId); - - // Case 1: exists in the provider. - assertEquals("name", Utility.getContentFileName(providerContext, uri)); - - // Case 2: doesn't exist in the provider - Uri notExistUri = AttachmentUtilities.getAttachmentUri(account.mId, 123456789); - String lastPathSegment = notExistUri.getLastPathSegment(); - assertEquals(lastPathSegment, Utility.getContentFileName(providerContext, notExistUri)); - } - - // used by testToPrimitiveLongArray - private static Collection createLongCollection(long... values) { - ArrayList ret = new ArrayList(); - for (long value : values) { - ret.add(value); - } - return ret; - } - - public void testDumpCursor() { - // Just make sure the method won't crash and returns non-empty string. - final Cursor c1 = new MatrixCursorWithCachedColumns(new String[] {"col"}); - final Cursor c2 = new CursorWrapper(c1); - - // Note it's a subclass of CursorWrapper. - final Cursor c3 = new CursorWrapper(c2) { - }; - - assertFalse(TextUtils.isEmpty(Utility.dumpCursor(c1))); - assertFalse(TextUtils.isEmpty(Utility.dumpCursor(c2))); - assertFalse(TextUtils.isEmpty(Utility.dumpCursor(c3))); - assertFalse(TextUtils.isEmpty(Utility.dumpCursor(null))); - - // Test again with closed cursor. - c1.close(); - assertFalse(TextUtils.isEmpty(Utility.dumpCursor(c1))); - assertFalse(TextUtils.isEmpty(Utility.dumpCursor(c2))); - assertFalse(TextUtils.isEmpty(Utility.dumpCursor(c3))); - assertFalse(TextUtils.isEmpty(Utility.dumpCursor(null))); - } - - public void testCloseTraceCursorWrapper() { - final Cursor org = new MatrixCursorWithCachedColumns(new String[] {"col"}); - final Utility.CloseTraceCursorWrapper c = - Utility.CloseTraceCursorWrapper.alwaysCreateForTest(org); - - // Not closed -- no stack trace - assertNull(Utility.CloseTraceCursorWrapper.getTraceIfAvailable(c)); - Utility.CloseTraceCursorWrapper.log(c); // shouldn't crash - - // Close, now stack trace should be available - c.close(); - assertNotNull(Utility.CloseTraceCursorWrapper.getTraceIfAvailable(c)); - Utility.CloseTraceCursorWrapper.log(c); - - // shouldn't crash - Utility.CloseTraceCursorWrapper.log(null); - } - - public void brokentestAppendBold() { - SpannableStringBuilder ssb = new SpannableStringBuilder(); - ssb.append("no"); - - assertEquals(ssb, Utility.appendBold(ssb, "BO")); - - assertEquals("noBO", ssb.toString()); - // TODO check style -- but how? - } - - public void testAreStringsEqual() { - String s1; - String s2; - - s1 = new String("Foo"); - s2 = s1; - assertTrue(Utility.areStringsEqual(s1, s2)); - - s2 = new String("Foo"); - assertTrue(Utility.areStringsEqual(s1, s2)); - - s2 = "Bar"; - assertFalse(Utility.areStringsEqual(s1, s2)); - - s2 = null; - assertFalse(Utility.areStringsEqual(s1, s2)); - - s1 = null; - s2 = "Bar"; - assertFalse(Utility.areStringsEqual(s1, s2)); - - s1 = null; - s2 = null; - assertTrue(Utility.areStringsEqual(s1, s2)); - } - - public void testIsServerNameValid() { - assertTrue(Utility.isServerNameValid("a")); - assertTrue(Utility.isServerNameValid("gmail")); - assertTrue(Utility.isServerNameValid("gmail.com")); - assertTrue(Utility.isServerNameValid("gmail.com.x.y.z")); - assertTrue(Utility.isServerNameValid(" gmail.com.x.y.z ")); - - assertFalse(Utility.isServerNameValid("")); - assertFalse(Utility.isServerNameValid("$")); - assertFalse(Utility.isServerNameValid(" ")); - } - - private static Collection toColleciton(long... values) { - ArrayList ret = new ArrayList(); - for (long v : values) { - ret.add(v); - } - return ret; - } - - public void brokentestBuildInSelection() { - assertEquals("", Utility.buildInSelection("c", null)); - assertEquals("", Utility.buildInSelection("c", toColleciton())); - assertEquals("c in (1)", Utility.buildInSelection("c", toColleciton(1))); - assertEquals("c in (1,2)", Utility.buildInSelection("c", toColleciton(1, 2))); - assertEquals("c in (1,2,-500)", Utility.buildInSelection("c", toColleciton(1, 2, -500))); - } -}