1/*
2 * Copyright (C) 2010 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.email.service;
18
19import android.content.Context;
20import android.content.Intent;
21
22import com.android.email.AccountTestCase;
23import com.android.email.EmailConnectivityManager;
24import com.android.email.provider.ProviderTestUtils;
25import com.android.email.service.AttachmentDownloadService.DownloadRequest;
26import com.android.email.service.AttachmentDownloadService.DownloadSet;
27import com.android.email.service.EmailServiceUtils.NullEmailService;
28import com.android.emailcommon.provider.Account;
29import com.android.emailcommon.provider.EmailContent.Attachment;
30import com.android.emailcommon.provider.EmailContent.Message;
31import com.android.emailcommon.provider.Mailbox;
32import com.android.emailcommon.service.EmailServiceStatus;
33
34import java.io.File;
35import java.util.Iterator;
36
37/**
38 * Tests of the AttachmentDownloadService
39 *
40 * You can run this entire test case with:
41 *   runtest -c com.android.email.service.AttachmentDownloadServiceTests email
42 */
43public class AttachmentDownloadServiceTests extends AccountTestCase {
44    private AttachmentDownloadService mService;
45    private Context mMockContext;
46    private Account mAccount;
47    private Mailbox mMailbox;
48    private long mAccountId;
49    private long mMailboxId;
50    private AttachmentDownloadService.AccountManagerStub mAccountManagerStub;
51    private MockDirectory mMockDirectory;
52
53    private DownloadSet mDownloadSet;
54
55    @Override
56    public void setUp() throws Exception {
57        super.setUp();
58        mMockContext = getMockContext();
59
60        // Set up an account and mailbox
61        mAccount = ProviderTestUtils.setupAccount("account", false, mMockContext);
62        mAccount.mFlags |= Account.FLAGS_BACKGROUND_ATTACHMENTS;
63        mAccount.save(mMockContext);
64        mAccountId = mAccount.mId;
65
66        mMailbox = ProviderTestUtils.setupMailbox("mailbox", mAccountId, true, mMockContext);
67        mMailboxId = mMailbox.mId;
68
69        // Set up our download service to simulate a running environment
70        // Use the NullEmailService so that the loadAttachment calls become no-ops
71        mService = new AttachmentDownloadService();
72        mService.mContext = mMockContext;
73        mService.addServiceIntentForTest(mAccountId, new Intent(mContext,
74                NullEmailService.class));
75        mAccountManagerStub = new AttachmentDownloadService.AccountManagerStub(null);
76        mService.mAccountManagerStub = mAccountManagerStub;
77        mService.mConnectivityManager = new MockConnectivityManager(mContext, "mock");
78        mDownloadSet = mService.mDownloadSet;
79        mMockDirectory =
80            new MockDirectory(mService.mContext.getCacheDir().getAbsolutePath());
81    }
82
83    @Override
84    public void tearDown() throws Exception {
85        super.tearDown();
86    }
87
88    /**
89     * This test creates attachments and places them in the DownloadSet; we then do various checks
90     * that exercise its functionality.
91     */
92    public void testDownloadSet() {
93        // TODO: Make sure that this doesn't interfere with the "real" ADS that might be running
94        // on device
95        Message message = ProviderTestUtils.setupMessage("message", mAccountId, mMailboxId, false,
96                true, mMockContext);
97        Attachment att1 = ProviderTestUtils.setupAttachment(message.mId, "filename1", 1000,
98                Attachment.FLAG_DOWNLOAD_USER_REQUEST, true, mMockContext);
99        Attachment att2 = ProviderTestUtils.setupAttachment(message.mId, "filename2", 1000,
100                Attachment.FLAG_DOWNLOAD_FORWARD, true, mMockContext);
101        Attachment att3 = ProviderTestUtils.setupAttachment(message.mId, "filename3", 1000,
102                Attachment.FLAG_DOWNLOAD_FORWARD, true, mMockContext);
103        Attachment att4 = ProviderTestUtils.setupAttachment(message.mId, "filename4", 1000,
104                Attachment.FLAG_DOWNLOAD_USER_REQUEST, true, mMockContext);
105        // Indicate that these attachments have changed; they will be added to the queue
106        mDownloadSet.onChange(mMockContext, att1);
107        mDownloadSet.onChange(mMockContext, att2);
108        mDownloadSet.onChange(mMockContext, att3);
109        mDownloadSet.onChange(mMockContext, att4);
110        Iterator<DownloadRequest> iterator = mDownloadSet.descendingIterator();
111        // Check the expected ordering; 1 & 4 are higher priority than 2 & 3
112        // 1 and 3 were created earlier than their priority equals
113        long[] expectedAttachmentIds = new long[] {att1.mId, att4.mId, att2.mId, att3.mId};
114        for (int i = 0; i < expectedAttachmentIds.length; i++) {
115            assertTrue(iterator.hasNext());
116            DownloadRequest req = iterator.next();
117            assertEquals(expectedAttachmentIds[i], req.attachmentId);
118        }
119
120        // Process the queue; attachment 1 should be marked "in progress", and should be in
121        // the in-progress map
122        mDownloadSet.processQueue();
123        DownloadRequest req = mDownloadSet.findDownloadRequest(att1.mId);
124        assertNotNull(req);
125        assertTrue(req.inProgress);
126        assertTrue(mDownloadSet.mDownloadsInProgress.containsKey(att1.mId));
127        // There should also be only one download in progress (testing the per-account limitation)
128        assertEquals(1, mDownloadSet.mDownloadsInProgress.size());
129        // End the "download" with a connection error; we should still have this in the queue,
130        // but it should no longer be in-progress
131        mDownloadSet.endDownload(att1.mId, EmailServiceStatus.CONNECTION_ERROR);
132        assertFalse(req.inProgress);
133        assertEquals(0, mDownloadSet.mDownloadsInProgress.size());
134
135        mDownloadSet.processQueue();
136        // Things should be as they were earlier; att1 should be an in-progress download
137        req = mDownloadSet.findDownloadRequest(att1.mId);
138        assertNotNull(req);
139        assertTrue(req.inProgress);
140        assertTrue(mDownloadSet.mDownloadsInProgress.containsKey(att1.mId));
141        // Successfully download the attachment; there should be no downloads in progress, and
142        // att1 should no longer be in the queue
143        mDownloadSet.endDownload(att1.mId, EmailServiceStatus.SUCCESS);
144        assertEquals(0, mDownloadSet.mDownloadsInProgress.size());
145        assertNull(mDownloadSet.findDownloadRequest(att1.mId));
146
147        // Test dequeue and isQueued
148        assertEquals(3, mDownloadSet.size());
149        mService.dequeue(att2.mId);
150        assertEquals(2, mDownloadSet.size());
151        assertTrue(mService.isQueued(att4.mId));
152        assertTrue(mService.isQueued(att3.mId));
153
154        mDownloadSet.processQueue();
155        // att4 should be the download in progress
156        req = mDownloadSet.findDownloadRequest(att4.mId);
157        assertNotNull(req);
158        assertTrue(req.inProgress);
159        assertTrue(mDownloadSet.mDownloadsInProgress.containsKey(att4.mId));
160    }
161
162    /**
163     * A mock file directory containing a single (Mock)File.  The total space, usable space, and
164     * length of the single file can be set
165     */
166    private static class MockDirectory extends File {
167        private static final long serialVersionUID = 1L;
168        private long mTotalSpace;
169        private long mUsableSpace;
170        private MockFile[] mFiles;
171        private final MockFile mMockFile = new MockFile();
172
173
174        public MockDirectory(String path) {
175            super(path);
176            mFiles = new MockFile[1];
177            mFiles[0] = mMockFile;
178        }
179
180        private void setTotalAndUsableSpace(long total, long usable) {
181            mTotalSpace = total;
182            mUsableSpace = usable;
183        }
184
185        @Override
186        public long getTotalSpace() {
187            return mTotalSpace;
188        }
189
190        @Override
191        public long getUsableSpace() {
192            return mUsableSpace;
193        }
194
195        public void setFileLength(long length) {
196            mMockFile.mLength = length;
197        }
198
199        @Override
200        public File[] listFiles() {
201            return mFiles;
202        }
203    }
204
205    /**
206     * A mock file that reports back a pre-set length
207     */
208    private static class MockFile extends File {
209        private static final long serialVersionUID = 1L;
210        private long mLength = 0;
211
212        public MockFile() {
213            super("_mock");
214        }
215
216        @Override
217        public long length() {
218            return mLength;
219        }
220    }
221
222    private static class MockConnectivityManager extends EmailConnectivityManager {
223        public MockConnectivityManager(Context context, String name) {
224            super(context, name);
225        }
226
227        @Override
228        public void waitForConnectivity() {
229        }
230
231        @Override
232        public boolean isAutoSyncAllowed() {
233            return true;
234        }
235    }
236
237    public void testCanPrefetchForAccount() {
238        // First, test our "global" limits (based on free storage)
239        // Mock storage @ 100 total and 26 available
240        // Note that all file lengths in this test are in arbitrary units
241        mMockDirectory.setTotalAndUsableSpace(100L, 26L);
242        // Mock 2 accounts in total
243        mAccountManagerStub.setNumberOfAccounts(2);
244        // With 26% available, we should be ok to prefetch
245        assertTrue(mService.canPrefetchForAccount(mAccount, mMockDirectory));
246        // Now change to 24 available
247        mMockDirectory.setTotalAndUsableSpace(100L, 24L);
248        // With 24% available, we should NOT be ok to prefetch
249        assertFalse(mService.canPrefetchForAccount(mAccount, mMockDirectory));
250
251        // Now, test per-account storage
252        // Mock storage @ 100 total and 50 available
253        mMockDirectory.setTotalAndUsableSpace(100L, 50L);
254        // Mock a file of length 12, but need to uncache previous amount first
255        mService.mAttachmentStorageMap.remove(mAccountId);
256        mMockDirectory.setFileLength(11);
257        // We can prefetch since 11 < 50/4
258        assertTrue(mService.canPrefetchForAccount(mAccount, mMockDirectory));
259        // Mock a file of length 13, but need to uncache previous amount first
260        mService.mAttachmentStorageMap.remove(mAccountId);
261        mMockDirectory.setFileLength(13);
262        // We can't prefetch since 13 > 50/4
263        assertFalse(mService.canPrefetchForAccount(mAccount, mMockDirectory));
264    }
265
266    public void testCanPrefetchForAccountNoBackgroundDownload() {
267        Account account = ProviderTestUtils.setupAccount("account2", false, mMockContext);
268        account.mFlags &= ~Account.FLAGS_BACKGROUND_ATTACHMENTS;
269        account.save(mMockContext);
270
271        // First, test our "global" limits (based on free storage)
272        // Mock storage @ 100 total and 26 available
273        // Note that all file lengths in this test are in arbitrary units
274        mMockDirectory.setTotalAndUsableSpace(100L, 26L);
275        // Mock 2 accounts in total
276        mAccountManagerStub.setNumberOfAccounts(2);
277
278        // With 26% available, we should be ok to prefetch,
279        // *but* bg download is disabled on the account.
280        assertFalse(mService.canPrefetchForAccount(account, mMockDirectory));
281    }
282}
283