1 /*
2  * Copyright (C) 2019 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 
17 package android.dropboxmanager.cts;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertTrue;
21 import static org.junit.Assert.fail;
22 
23 import android.content.BroadcastReceiver;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.os.DropBoxManager;
29 import android.os.SystemClock;
30 import android.platform.test.annotations.AppModeFull;
31 import android.provider.Settings;
32 import android.support.test.uiautomator.UiDevice;
33 import android.util.Log;
34 
35 import androidx.test.InstrumentationRegistry;
36 import androidx.test.filters.LargeTest;
37 import androidx.test.runner.AndroidJUnit4;
38 
39 import org.junit.After;
40 import org.junit.Before;
41 import org.junit.Test;
42 import org.junit.runner.RunWith;
43 
44 import java.io.IOException;
45 import java.text.MessageFormat;
46 import java.util.ArrayList;
47 import java.util.List;
48 import java.util.concurrent.CountDownLatch;
49 import java.util.concurrent.TimeUnit;
50 
51 import com.android.compatibility.common.util.AmUtils;
52 import com.android.compatibility.common.util.SystemUtil;
53 import com.android.internal.annotations.GuardedBy;
54 
55 /**
56  * Tests DropBox entry management
57  */
58 @LargeTest
59 @RunWith(AndroidJUnit4.class)
60 public class DropBoxTests {
61     private static final String ENABLED_TAG = "DropBoxTestsEnabledTag";
62     private static final String LOW_PRIORITY_TAG = "DropBoxTestsLowPriorityTag";
63     private static final String ANOTHER_LOW_PRIORITY_TAG = "AnotherDropBoxTestsLowPriorityTag";
64     private static final long BROADCAST_RATE_LIMIT = 1000L;
65     private static final long BROADCAST_DELAY_ALLOWED_ERROR = 200L;
66 
67     private static final String SET_RATE_LIMIT_SHELL_COMMAND = "cmd dropbox set-rate-limit {0}";
68     private static final String ADD_LOW_PRIORITY_SHELL_COMMAND =
69             "cmd dropbox add-low-priority {0}";
70     private static final String RESTORE_DEFAULTS_SHELL_COMMAND = "cmd dropbox restore-defaults";
71 
72     private Context mContext;
73     private DropBoxManager mDropBoxManager;
74 
75     private CountDownLatch mEnabledTagLatch = new CountDownLatch(0);
76     private CountDownLatch mLowPriorityTagLatch = new CountDownLatch(0);
77     private CountDownLatch mAnotherLowPriorityTagLatch = new CountDownLatch(0);
78 
79     private ArrayList<DropBoxEntryAddedData> mEnabledBuffer;
80     private ArrayList<DropBoxEntryAddedData> mLowPriorityBuffer;
81     private ArrayList<DropBoxEntryAddedData> mAnotherLowPriorityBuffer;
82 
83     public static class DropBoxEntryAddedData {
84         String tag;
85         long time;
86         int droppedCount;
87         long received;
88     }
89 
90     private final BroadcastReceiver mDropBoxEntryAddedReceiver = new BroadcastReceiver() {
91         @Override
92         public void onReceive(Context context, Intent intent) {
93             final DropBoxEntryAddedData data = new DropBoxEntryAddedData();
94             data.tag = intent.getStringExtra(DropBoxManager.EXTRA_TAG);
95             data.time = intent.getLongExtra(DropBoxManager.EXTRA_TIME, 0);
96             data.droppedCount = intent.getIntExtra(DropBoxManager.EXTRA_DROPPED_COUNT, 0);
97             data.received = SystemClock.elapsedRealtime();
98             if (ENABLED_TAG.equals(data.tag)) {
99                 mEnabledBuffer.add(data);
100                 mEnabledTagLatch.countDown();
101             } else if (LOW_PRIORITY_TAG.equals(data.tag)) {
102                 mLowPriorityBuffer.add(data);
103                 mLowPriorityTagLatch.countDown();
104             } else if (ANOTHER_LOW_PRIORITY_TAG.equals(data.tag)) {
105                 mAnotherLowPriorityBuffer.add(data);
106                 mAnotherLowPriorityTagLatch.countDown();
107             }
108         }
109     };
110 
111     @Before
setUp()112     public void setUp() throws Exception {
113         mContext = InstrumentationRegistry.getTargetContext();
114         mDropBoxManager = mContext.getSystemService(DropBoxManager.class);
115 
116         AmUtils.waitForBroadcastIdle();
117 
118         final IntentFilter intentFilter = new IntentFilter();
119         intentFilter.addAction(DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED);
120         mContext.registerReceiver(mDropBoxEntryAddedReceiver, intentFilter);
121 
122         setTagLowPriority(LOW_PRIORITY_TAG);
123         setTagLowPriority(ANOTHER_LOW_PRIORITY_TAG);
124 
125         setBroadcastRateLimitSetting(BROADCAST_RATE_LIMIT);
126     }
127 
128     @After
tearDown()129     public void tearDown() throws Exception {
130         mContext.unregisterReceiver(mDropBoxEntryAddedReceiver);
131 
132         // Restore dropbox defaults
133         restoreDropboxDefaults();
134     }
135 
sendExcessiveDropBoxEntries(String tag, int count, long delayPerEntry)136     private void sendExcessiveDropBoxEntries(String tag, int count, long delayPerEntry)
137             throws Exception {
138         int i = 0;
139         mDropBoxManager.addText(tag, String.valueOf(i++));
140         for (; i < count; i++) {
141             Thread.sleep(delayPerEntry);
142             mDropBoxManager.addText(tag, String.valueOf(i));
143         }
144     }
145 
146     /**
147      * A single DropBox entry for a low priority tag should have their
148      * ACTION_DROPBOX_ENTRY_ADDED broadcasts delayed
149      */
150     @Test
testLowPrioritySingleEntry()151     public void testLowPrioritySingleEntry() throws Exception {
152         final int nLowPriorityEntries = 1;
153 
154         mLowPriorityTagLatch = new CountDownLatch(nLowPriorityEntries);
155         mLowPriorityBuffer = new ArrayList(nLowPriorityEntries);
156 
157         final long startTime = SystemClock.elapsedRealtime();
158         mDropBoxManager.addText(LOW_PRIORITY_TAG, "test");
159 
160         assertTrue(mLowPriorityTagLatch.await(BROADCAST_RATE_LIMIT * 3 / 2,
161                 TimeUnit.MILLISECONDS));
162         final long endTime = SystemClock.elapsedRealtime();
163 
164         assertEqualsWithinDelta("Broadcast not received at expected time", BROADCAST_RATE_LIMIT,
165                 endTime - startTime, BROADCAST_DELAY_ALLOWED_ERROR);
166 
167         assertEquals("A single broadcast should be sent for a single low priority dropbox entry",
168                 1, mLowPriorityBuffer.size());
169         DropBoxEntryAddedData data = mLowPriorityBuffer.get(0);
170         assertEquals("Dropped broadcast count should be 0",
171                 0, data.droppedCount);
172     }
173 
174     /**
175      * Many contemporary DropBox entries for a low priority tag should have their
176      * ACTION_DROPBOX_ENTRY_ADDED broadcasts collapsed into one broadcast
177      */
178     @Test
testLowPriorityRapidEntryLimiting()179     public void testLowPriorityRapidEntryLimiting() throws Exception {
180         final int nLowPriorityEntries = 10;
181 
182         mLowPriorityTagLatch = new CountDownLatch(1);
183         mLowPriorityBuffer = new ArrayList(nLowPriorityEntries * 2);
184 
185         // add several low priority entries in quick sucession
186         final long startTime = SystemClock.elapsedRealtime();
187         sendExcessiveDropBoxEntries(LOW_PRIORITY_TAG, nLowPriorityEntries, 0);
188         assertTrue(mLowPriorityTagLatch.await(BROADCAST_RATE_LIMIT * 3 / 2,
189                 TimeUnit.MILLISECONDS));
190         final long endTime = SystemClock.elapsedRealtime();
191 
192         assertEqualsWithinDelta("Broadcast not received at expected time", BROADCAST_RATE_LIMIT,
193                 endTime - startTime, BROADCAST_DELAY_ALLOWED_ERROR);
194 
195         assertEquals("Many low priority dropbox entries within the rate limit period should " +
196                      "result in 1 broadcast", 1, mLowPriorityBuffer.size());
197         DropBoxEntryAddedData data = mLowPriorityBuffer.get(0);
198         assertEquals("All but one of the low priority broadcasts should have been dropped",
199                 nLowPriorityEntries - 1, data.droppedCount);
200     }
201 
202     /**
203      * Many DropBox entries for a low priority tag should have their
204      * ACTION_DROPBOX_ENTRY_ADDED broadcasts collapsed into a few broadcast
205      */
206     @Test
testLowPrioritySustainedRapidEntryLimiting()207     public void testLowPrioritySustainedRapidEntryLimiting() throws Exception {
208         final int nLowPriorityEntries = 10;
209 
210         mLowPriorityTagLatch = new CountDownLatch(2);
211         mLowPriorityBuffer = new ArrayList(nLowPriorityEntries * 2);
212 
213         // add several low priority entries across the rate limit period
214         final long startTime = SystemClock.elapsedRealtime();
215         sendExcessiveDropBoxEntries(LOW_PRIORITY_TAG, nLowPriorityEntries,
216                 BROADCAST_RATE_LIMIT * 3 / 2 / nLowPriorityEntries);
217         assertTrue(mLowPriorityTagLatch.await(BROADCAST_RATE_LIMIT * 5 / 2,
218                 TimeUnit.MILLISECONDS));
219         final long endTime = SystemClock.elapsedRealtime();
220 
221         assertEqualsWithinDelta("Broadcast not received at expected time",
222                 BROADCAST_RATE_LIMIT * 2, endTime - startTime, BROADCAST_DELAY_ALLOWED_ERROR * 2);
223 
224         assertEquals("Many low priority dropbox entries across two rate limit periods should " +
225                 "result in 2 broadcasts", 2, mLowPriorityBuffer.size());
226         DropBoxEntryAddedData data = mLowPriorityBuffer.get(0);
227         int droppedCount = data.droppedCount;
228         data = mLowPriorityBuffer.get(1);
229         droppedCount += data.droppedCount;
230         assertEquals("All but two of the low priority broadcasts should have been dropped",
231                 nLowPriorityEntries - 2, droppedCount);
232     }
233 
234     /**
235      * Many contemporary DropBox entries from multiple low priority tag should have their
236      * ACTION_DROPBOX_ENTRY_ADDED broadcasts collapsed into seperate broadcasts per tag.
237      * Different tags should not interfer with each others' broadcasts
238      */
239     @Test
testMultipleLowPriorityRateLimiting()240     public void testMultipleLowPriorityRateLimiting() throws Exception {
241         final int nLowPriorityEntries = 10;
242         final int nOtherEntries = 10;
243 
244         mLowPriorityTagLatch = new CountDownLatch(1);
245         mLowPriorityBuffer = new ArrayList(nLowPriorityEntries * 2);
246         mAnotherLowPriorityTagLatch = new CountDownLatch(1);
247         mAnotherLowPriorityBuffer = new ArrayList(nOtherEntries * 2);
248 
249         final long delayTime = BROADCAST_RATE_LIMIT / 2;
250 
251         // add several low priority entries across multiple tags
252         final long firstEntryTime = SystemClock.elapsedRealtime();
253         sendExcessiveDropBoxEntries(LOW_PRIORITY_TAG, nLowPriorityEntries, 0);
254         Thread.sleep(delayTime);
255         final long startTime = SystemClock.elapsedRealtime();
256         sendExcessiveDropBoxEntries(ANOTHER_LOW_PRIORITY_TAG, nOtherEntries, 0);
257         assertTrue(mAnotherLowPriorityTagLatch.await(BROADCAST_RATE_LIMIT * 3 / 2,
258                 TimeUnit.MILLISECONDS));
259         final long endTime = SystemClock.elapsedRealtime();
260 
261         assertEqualsWithinDelta("Broadcast not received at expected time", BROADCAST_RATE_LIMIT,
262                 endTime - startTime, BROADCAST_DELAY_ALLOWED_ERROR);
263 
264         assertEquals("Many low priority dropbox entries within the rate limit period should " +
265                 "result in 1 broadcast for " + LOW_PRIORITY_TAG, 1, mLowPriorityBuffer.size());
266         assertEquals("Many low priority dropbox entries within the rate limit period should " +
267                 "result in 1 broadcastfor " + ANOTHER_LOW_PRIORITY_TAG, 1,
268                 mAnotherLowPriorityBuffer.size());
269         DropBoxEntryAddedData data = mLowPriorityBuffer.get(0);
270         DropBoxEntryAddedData anotherData = mAnotherLowPriorityBuffer.get(0);
271         assertEquals("All but one of the low priority broadcasts should have been dropped for " +
272                         LOW_PRIORITY_TAG, nLowPriorityEntries - 1, data.droppedCount);
273         assertEquals("All but one of the low priority broadcasts should have been dropped for " +
274                         ANOTHER_LOW_PRIORITY_TAG, nOtherEntries - 1, anotherData.droppedCount);
275 
276         final long startTimeDelta = startTime - firstEntryTime;
277         final long receivedTimeDelta = anotherData.received - data.received;
278         final long errorMargin = receivedTimeDelta - startTimeDelta;
279 
280         // Received time delta should be around start time delta (20% margin of error)
281         if (errorMargin < -startTimeDelta / 5  || errorMargin > startTimeDelta / 5 ) {
282             fail("Multiple low priority entry tags interfered with each others delayed broadcast" +
283                     "\nstartTimeDelta = " + String.valueOf(startTimeDelta) +
284                     "\nreceivedTimeDelta = " + String.valueOf(receivedTimeDelta));
285         }
286     }
287 
288     /**
289      * Broadcasts for regular priority DropBox entries should not be throttled and they should not
290      * interfere with the throttling of low priority Dropbox entry broadcasts.
291      */
292     @Test
testLowPriorityRateLimitingWithEnabledEntries()293     public void testLowPriorityRateLimitingWithEnabledEntries() throws Exception {
294 
295         final int nLowPriorityEntries = 10;
296         final int nEnabledEntries = 10;
297 
298         mLowPriorityTagLatch = new CountDownLatch(1);
299         mLowPriorityBuffer = new ArrayList(nLowPriorityEntries * 2);
300         mEnabledTagLatch = new CountDownLatch(nEnabledEntries);
301         mEnabledBuffer = new ArrayList(nEnabledEntries * 2);
302 
303         final long startTimeDelta = BROADCAST_RATE_LIMIT / 2;
304 
305         final long startTime = SystemClock.elapsedRealtime();
306         // add several low priority and enabled entries
307         sendExcessiveDropBoxEntries(LOW_PRIORITY_TAG, nLowPriorityEntries, 0);
308         sendExcessiveDropBoxEntries(ENABLED_TAG, nEnabledEntries, 0);
309         assertTrue(mLowPriorityTagLatch.await(BROADCAST_RATE_LIMIT * 3 / 2,
310                 TimeUnit.MILLISECONDS));
311         final long endTime = SystemClock.elapsedRealtime();
312 
313         assertEqualsWithinDelta("Broadcast not received at expected time", BROADCAST_RATE_LIMIT,
314                 endTime - startTime, BROADCAST_DELAY_ALLOWED_ERROR);
315 
316         assertEquals("Broadcasts for enabled tags should not be limited", nEnabledEntries,
317                 mEnabledBuffer.size());
318 
319         assertEquals("Many low priority dropbox entries within the rate limit period should " +
320                 "result in 1 broadcast for " + LOW_PRIORITY_TAG, 1, mLowPriorityBuffer.size());
321         DropBoxEntryAddedData data = mLowPriorityBuffer.get(0);
322         assertEquals("All but one of the low priority broadcasts should have been dropped " +
323                 LOW_PRIORITY_TAG, nLowPriorityEntries - 1, data.droppedCount);
324 
325         for (int i = 0; i < nEnabledEntries; i++) {
326             DropBoxEntryAddedData enabledData = mEnabledBuffer.get(i);
327             assertEquals("Enabled tag broadcasts should not be dropped", 0,
328                     enabledData.droppedCount);
329         }
330     }
331 
setTagLowPriority(String tag)332     private void setTagLowPriority(String tag) throws IOException {
333         final String putCmd = MessageFormat.format(ADD_LOW_PRIORITY_SHELL_COMMAND, tag);
334         SystemUtil.runShellCommand(putCmd);
335     }
336 
setBroadcastRateLimitSetting(long period)337     private void setBroadcastRateLimitSetting(long period) throws IOException {
338         final String putCmd = MessageFormat.format(SET_RATE_LIMIT_SHELL_COMMAND,
339                 String.valueOf(period));
340         SystemUtil.runShellCommand(putCmd);
341     }
342 
restoreDropboxDefaults()343     private void restoreDropboxDefaults() throws IOException {
344         SystemUtil.runShellCommand(RESTORE_DEFAULTS_SHELL_COMMAND);
345     }
346 
assertEqualsWithinDelta(String msg, long expected, long actual, long delta)347     private void assertEqualsWithinDelta(String msg, long expected, long actual, long delta) {
348         if (expected - actual > delta || actual - expected > delta) {
349             assertEquals(msg, expected, actual);
350         }
351     }
352 }
353