1 /*
2  * Copyright (C) 2020 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 com.android.server.people.data;
18 
19 import static com.android.server.people.data.TestUtils.timestamp;
20 
21 import static org.junit.Assert.assertEquals;
22 import static org.junit.Assert.assertTrue;
23 
24 import android.content.Context;
25 import android.os.FileUtils;
26 import android.text.format.DateUtils;
27 
28 import androidx.test.InstrumentationRegistry;
29 
30 import com.google.android.collect.Lists;
31 import com.google.android.collect.Sets;
32 
33 import org.junit.After;
34 import org.junit.Before;
35 import org.junit.Test;
36 import org.junit.runner.RunWith;
37 import org.junit.runners.JUnit4;
38 
39 import java.io.File;
40 import java.util.List;
41 import java.util.Map;
42 
43 @RunWith(JUnit4.class)
44 public final class EventHistoryImplTest {
45     private static final long CURRENT_TIMESTAMP = timestamp("01-30 18:50");
46 
47     private static final Event E1 = new Event(timestamp("01-06 05:26"),
48             Event.TYPE_NOTIFICATION_OPENED);
49     private static final Event E2 = new Event(timestamp("01-27 18:41"),
50             Event.TYPE_NOTIFICATION_OPENED);
51     private static final Event E3 = new Event(timestamp("01-30 03:06"),
52             Event.TYPE_SHARE_IMAGE);
53     private static final Event E4 = new Event(timestamp("01-30 16:14"),
54             Event.TYPE_SMS_INCOMING);
55     private static final Event E5 = new Event(timestamp("01-30 18:30"),
56             Event.TYPE_SMS_INCOMING);
57 
58     private static final EventIndex.Injector EVENT_INDEX_INJECTOR = new EventIndex.Injector() {
59         @Override
60         long currentTimeMillis() {
61             return CURRENT_TIMESTAMP;
62         }
63     };
64     private static final EventHistoryImpl.Injector EVENT_HISTORY_INJECTOR =
65             new EventHistoryImpl.Injector() {
66                 @Override
67                 EventIndex createEventIndex() {
68                     return new EventIndex(EVENT_INDEX_INJECTOR);
69                 }
70 
71                 @Override
72                 long currentTimeMillis() {
73                     return CURRENT_TIMESTAMP;
74                 }
75             };
76 
77     private EventHistoryImpl mEventHistory;
78     private File mCacheDir;
79     private File mFile;
80     private MockScheduledExecutorService mMockScheduledExecutorService;
81 
82     @Before
setUp()83     public void setUp() {
84         Context ctx = InstrumentationRegistry.getContext();
85         mCacheDir = ctx.getCacheDir();
86         mFile = new File(mCacheDir, "testdir");
87         mMockScheduledExecutorService = new MockScheduledExecutorService();
88         mEventHistory = new EventHistoryImpl(EVENT_HISTORY_INJECTOR, mFile,
89                 mMockScheduledExecutorService);
90     }
91 
92     @After
tearDown()93     public void tearDown() {
94         FileUtils.deleteContentsAndDir(mFile);
95     }
96 
97     @Test
testNoEvents()98     public void testNoEvents() {
99         EventIndex eventIndex = mEventHistory.getEventIndex(Event.ALL_EVENT_TYPES);
100         assertTrue(eventIndex.isEmpty());
101 
102         List<Event> events = mEventHistory.queryEvents(Event.ALL_EVENT_TYPES, 0L, 999L);
103         assertTrue(events.isEmpty());
104     }
105 
106     @Test
testMultipleEvents()107     public void testMultipleEvents() {
108         mEventHistory.addEvent(E1);
109         mEventHistory.addEvent(E2);
110         mEventHistory.addEvent(E3);
111         mEventHistory.addEvent(E4);
112 
113         EventIndex eventIndex = mEventHistory.getEventIndex(Event.ALL_EVENT_TYPES);
114         assertEquals(4, eventIndex.getActiveTimeSlots().size());
115 
116         List<Event> events = mEventHistory.queryEvents(Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE);
117         assertEquals(4, events.size());
118     }
119 
120     @Test
testQuerySomeEventTypes()121     public void testQuerySomeEventTypes() {
122         mEventHistory.addEvent(E1);
123         mEventHistory.addEvent(E2);
124         mEventHistory.addEvent(E3);
125         mEventHistory.addEvent(E4);
126 
127         EventIndex eventIndex = mEventHistory.getEventIndex(Event.NOTIFICATION_EVENT_TYPES);
128         assertEquals(2, eventIndex.getActiveTimeSlots().size());
129 
130         List<Event> events = mEventHistory.queryEvents(
131                 Event.NOTIFICATION_EVENT_TYPES, 0L, Long.MAX_VALUE);
132         assertEquals(2, events.size());
133     }
134 
135     @Test
testQuerySingleEventType()136     public void testQuerySingleEventType() {
137         mEventHistory.addEvent(E1);
138         mEventHistory.addEvent(E2);
139         mEventHistory.addEvent(E3);
140         mEventHistory.addEvent(E4);
141 
142         EventIndex eventIndex = mEventHistory.getEventIndex(Event.TYPE_SHARE_IMAGE);
143         assertEquals(1, eventIndex.getActiveTimeSlots().size());
144 
145         List<Event> events = mEventHistory.queryEvents(
146                 Sets.newArraySet(Event.TYPE_SHARE_IMAGE), 0L, Long.MAX_VALUE);
147         assertEquals(1, events.size());
148     }
149 
150     @Test
testPersistenceAndRestoration()151     public void testPersistenceAndRestoration() {
152         mEventHistory.addEvent(E1);
153         mEventHistory.addEvent(E2);
154         mEventHistory.addEvent(E3);
155         mEventHistory.addEvent(E4);
156         mEventHistory.addEvent(E5);
157 
158         // futures of events and event index flush.
159         long futuresExecuted = mMockScheduledExecutorService.fastForwardTime(
160                 3L * DateUtils.MINUTE_IN_MILLIS);
161         assertEquals(2, futuresExecuted);
162 
163         EventIndex indexBeforePowerOff = mEventHistory.getEventIndex(Event.ALL_EVENT_TYPES);
164 
165         resetAndLoadEventHistory();
166 
167         List<Event> events = mEventHistory.queryEvents(Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE);
168         assertEquals(2, events.size());
169         assertTrue(events.containsAll(Lists.newArrayList(E4, E5)));
170 
171         EventIndex indexAfterPowerOff = mEventHistory.getEventIndex(Event.ALL_EVENT_TYPES);
172         assertEquals(indexBeforePowerOff, indexAfterPowerOff);
173     }
174 
175     @Test
testMimicDevicePowerOff()176     public void testMimicDevicePowerOff() {
177         mEventHistory.addEvent(E1);
178         mEventHistory.addEvent(E2);
179         mEventHistory.addEvent(E3);
180         mEventHistory.addEvent(E4);
181         mEventHistory.addEvent(E5);
182         mEventHistory.saveToDisk();
183 
184         EventIndex indexBeforePowerOff = mEventHistory.getEventIndex(Event.ALL_EVENT_TYPES);
185 
186         // Ensure that futures were cancelled and the immediate flush occurred.
187         assertEquals(0, mMockScheduledExecutorService.getFutures().size());
188 
189         // Expect to see 2 executes from #saveToDisk, one for events and another for index.
190         assertEquals(2, mMockScheduledExecutorService.getExecutes().size());
191 
192         resetAndLoadEventHistory();
193 
194         List<Event> events = mEventHistory.queryEvents(Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE);
195         assertEquals(2, events.size());
196         assertTrue(events.containsAll(Lists.newArrayList(E4, E5)));
197 
198         EventIndex indexAfterPowerOff = mEventHistory.getEventIndex(Event.ALL_EVENT_TYPES);
199         assertEquals(indexBeforePowerOff, indexAfterPowerOff);
200     }
201 
202     @Test
testOnDestroy()203     public void testOnDestroy() {
204         mEventHistory.addEvent(E1);
205         mEventHistory.addEvent(E2);
206         mEventHistory.addEvent(E3);
207         mEventHistory.addEvent(E4);
208         mEventHistory.addEvent(E5);
209         mEventHistory.saveToDisk();
210 
211         mEventHistory.onDestroy();
212 
213         List<Event> events = mEventHistory.queryEvents(Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE);
214         assertTrue(events.isEmpty());
215 
216         EventIndex index = mEventHistory.getEventIndex(Event.ALL_EVENT_TYPES);
217         assertTrue(index.isEmpty());
218     }
219 
220     @Test
testEventHistoriesImplFromDisk()221     public void testEventHistoriesImplFromDisk() {
222         mEventHistory.addEvent(E1);
223         mEventHistory.addEvent(E2);
224         mEventHistory.addEvent(E3);
225         mEventHistory.addEvent(E4);
226         mEventHistory.addEvent(E5);
227         mEventHistory.saveToDisk();
228 
229         EventIndex indexBefore = mEventHistory.getEventIndex(Event.ALL_EVENT_TYPES);
230 
231         Map<String, EventHistoryImpl> map = EventHistoryImpl.eventHistoriesImplFromDisk(
232                 EVENT_HISTORY_INJECTOR, mCacheDir, mMockScheduledExecutorService);
233         assertEquals(1, map.size());
234         assertTrue(map.containsKey("testdir"));
235 
236         List<Event> events = map.get("testdir").queryEvents(Event.ALL_EVENT_TYPES, 0L,
237                 Long.MAX_VALUE);
238         assertEquals(2, events.size());
239         assertTrue(events.containsAll(Lists.newArrayList(E4, E5)));
240 
241         EventIndex indexAfter = map.get("testdir").getEventIndex(Event.ALL_EVENT_TYPES);
242         assertEquals(indexBefore, indexAfter);
243     }
244 
resetAndLoadEventHistory()245     private void resetAndLoadEventHistory() {
246         mEventHistory = new EventHistoryImpl(EVENT_HISTORY_INJECTOR, mFile,
247                 mMockScheduledExecutorService);
248         mEventHistory.loadFromDisk();
249     }
250 }
251