1 /*
2  * Copyright (C) 2017 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * 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, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */
16 
17 package com.google.android.mobly.snippet.event;
18 
19 import com.google.android.mobly.snippet.util.Log;
20 import java.util.Deque;
21 import java.util.HashMap;
22 import java.util.Locale;
23 import java.util.Map;
24 import java.util.concurrent.LinkedBlockingDeque;
25 
26 /**
27  * Manage the event queue.
28  *
29  * <p>EventCache APIs interact with the SnippetEvent cache - a data structure that holds {@link
30  * SnippetEvent} objects posted from snippet classes. The SnippetEvent cache provides a useful means
31  * of recording background events (such as sensor data) when the phone is busy with foreground
32  * activities.
33  */
34 public class EventCache {
35     private static final String EVENT_DEQUE_ID_TEMPLATE = "%s|%s";
36     private static final int EVENT_DEQUE_MAX_SIZE = 1024;
37 
38     // A Map with each value being the queue for a particular type of event, and the key being the
39     // unique ID of the queue. The ID is composed of a callback ID and an event's name.
40     private final Map<String, LinkedBlockingDeque<SnippetEvent>> mEventDeques = new HashMap<>();
41 
42     private static volatile EventCache mEventCache;
43 
EventCache()44     private EventCache() {}
45 
getInstance()46     public static EventCache getInstance() {
47         if (mEventCache == null) {
48             synchronized (EventCache.class) {
49                 if (mEventCache == null) {
50                     mEventCache = new EventCache();
51                 }
52             }
53         }
54         return mEventCache;
55     }
56 
getQueueId(String callbackId, String name)57     public static String getQueueId(String callbackId, String name) {
58         return String.format(Locale.US, EVENT_DEQUE_ID_TEMPLATE, callbackId, name);
59     }
60 
getEventDeque(String qId)61     public LinkedBlockingDeque<SnippetEvent> getEventDeque(String qId) {
62         synchronized (mEventDeques) {
63             LinkedBlockingDeque<SnippetEvent> eventDeque = mEventDeques.get(qId);
64             if (eventDeque == null) {
65                 eventDeque = new LinkedBlockingDeque<>(EVENT_DEQUE_MAX_SIZE);
66                 mEventDeques.put(qId, eventDeque);
67             }
68             return eventDeque;
69         }
70     }
71 
72     /**
73      * Post an {@link SnippetEvent} object to the Event cache.
74      *
75      * <p>Snippet classes should use this method to post events. If EVENT_DEQUE_MAX_SIZE is reached,
76      * the oldest elements will be retired until the new event could be posted.
77      *
78      * @param snippetEvent The snippetEvent to post to {@link EventCache}.
79      */
postEvent(SnippetEvent snippetEvent)80     public void postEvent(SnippetEvent snippetEvent) {
81         String qId = getQueueId(snippetEvent.getCallbackId(), snippetEvent.getName());
82         Deque<SnippetEvent> q = getEventDeque(qId);
83         synchronized (q) {
84             while (!q.offer(snippetEvent)) {
85                 SnippetEvent retiredEvent = q.removeFirst();
86                 Log.v(
87                         String.format(
88                                 Locale.US,
89                                 "Retired event %s due to deque reaching the size limit (%s).",
90                                 retiredEvent,
91                                 EVENT_DEQUE_MAX_SIZE));
92             }
93         }
94         Log.v(String.format(Locale.US, "Posted event(%s)", qId));
95     }
96 
97     /** Clears all cached events. */
clearAll()98     public void clearAll() {
99         synchronized (mEventDeques) {
100             mEventDeques.clear();
101         }
102     }
103 }
104