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 android.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.text.format.DateFormat;
22 import android.util.ArraySet;
23 import android.util.Slog;
24 import android.util.proto.ProtoInputStream;
25 import android.util.proto.ProtoOutputStream;
26 
27 import com.android.server.people.PeopleEventProto;
28 
29 import java.io.IOException;
30 import java.lang.annotation.Retention;
31 import java.lang.annotation.RetentionPolicy;
32 import java.util.Objects;
33 import java.util.Set;
34 
35 /** An event representing the interaction with a specific conversation or app. */
36 public class Event {
37 
38     private static final String TAG = Event.class.getSimpleName();
39 
40     public static final int TYPE_SHORTCUT_INVOCATION = 1;
41 
42     public static final int TYPE_NOTIFICATION_POSTED = 2;
43 
44     public static final int TYPE_NOTIFICATION_OPENED = 3;
45 
46     public static final int TYPE_SHARE_TEXT = 4;
47 
48     public static final int TYPE_SHARE_IMAGE = 5;
49 
50     public static final int TYPE_SHARE_VIDEO = 6;
51 
52     public static final int TYPE_SHARE_OTHER = 7;
53 
54     public static final int TYPE_SMS_OUTGOING = 8;
55 
56     public static final int TYPE_SMS_INCOMING = 9;
57 
58     public static final int TYPE_CALL_OUTGOING = 10;
59 
60     public static final int TYPE_CALL_INCOMING = 11;
61 
62     public static final int TYPE_CALL_MISSED = 12;
63 
64     public static final int TYPE_IN_APP_CONVERSATION = 13;
65 
66     @IntDef(prefix = { "TYPE_" }, value = {
67             TYPE_SHORTCUT_INVOCATION,
68             TYPE_NOTIFICATION_POSTED,
69             TYPE_NOTIFICATION_OPENED,
70             TYPE_SHARE_TEXT,
71             TYPE_SHARE_IMAGE,
72             TYPE_SHARE_VIDEO,
73             TYPE_SHARE_OTHER,
74             TYPE_SMS_OUTGOING,
75             TYPE_SMS_INCOMING,
76             TYPE_CALL_OUTGOING,
77             TYPE_CALL_INCOMING,
78             TYPE_CALL_MISSED,
79             TYPE_IN_APP_CONVERSATION,
80     })
81     @Retention(RetentionPolicy.SOURCE)
82     public @interface EventType {}
83 
84     public static final Set<Integer> NOTIFICATION_EVENT_TYPES = new ArraySet<>();
85     public static final Set<Integer> SHARE_EVENT_TYPES = new ArraySet<>();
86     public static final Set<Integer> SMS_EVENT_TYPES = new ArraySet<>();
87     public static final Set<Integer> CALL_EVENT_TYPES = new ArraySet<>();
88     public static final Set<Integer> ALL_EVENT_TYPES = new ArraySet<>();
89 
90     static {
91         NOTIFICATION_EVENT_TYPES.add(TYPE_NOTIFICATION_POSTED);
92         NOTIFICATION_EVENT_TYPES.add(TYPE_NOTIFICATION_OPENED);
93 
94         SHARE_EVENT_TYPES.add(TYPE_SHARE_TEXT);
95         SHARE_EVENT_TYPES.add(TYPE_SHARE_IMAGE);
96         SHARE_EVENT_TYPES.add(TYPE_SHARE_VIDEO);
97         SHARE_EVENT_TYPES.add(TYPE_SHARE_OTHER);
98 
99         SMS_EVENT_TYPES.add(TYPE_SMS_INCOMING);
100         SMS_EVENT_TYPES.add(TYPE_SMS_OUTGOING);
101 
102         CALL_EVENT_TYPES.add(TYPE_CALL_INCOMING);
103         CALL_EVENT_TYPES.add(TYPE_CALL_OUTGOING);
104         CALL_EVENT_TYPES.add(TYPE_CALL_MISSED);
105 
106         ALL_EVENT_TYPES.add(TYPE_SHORTCUT_INVOCATION);
107         ALL_EVENT_TYPES.add(TYPE_IN_APP_CONVERSATION);
108         ALL_EVENT_TYPES.addAll(NOTIFICATION_EVENT_TYPES);
109         ALL_EVENT_TYPES.addAll(SHARE_EVENT_TYPES);
110         ALL_EVENT_TYPES.addAll(SMS_EVENT_TYPES);
111         ALL_EVENT_TYPES.addAll(CALL_EVENT_TYPES);
112     }
113 
114     private final long mTimestamp;
115 
116     private final int mType;
117 
118     private final int mDurationSeconds;
119 
Event(long timestamp, @EventType int type)120     Event(long timestamp, @EventType int type) {
121         mTimestamp = timestamp;
122         mType = type;
123         mDurationSeconds = 0;
124     }
125 
Event(@onNull Builder builder)126     private Event(@NonNull Builder builder) {
127         mTimestamp = builder.mTimestamp;
128         mType = builder.mType;
129         mDurationSeconds = builder.mDurationSeconds;
130     }
131 
getTimestamp()132     public long getTimestamp() {
133         return mTimestamp;
134     }
135 
getType()136     public @EventType int getType() {
137         return mType;
138     }
139 
140     /**
141      * Gets the duration of the event in seconds. It is only available for these events:
142      * <ul>
143      *     <li>{@link #TYPE_CALL_INCOMING}
144      *     <li>{@link #TYPE_CALL_OUTGOING}
145      *     <li>{@link #TYPE_IN_APP_CONVERSATION}
146      * </ul>
147      * <p>For the other event types, it always returns {@code 0}.
148      */
getDurationSeconds()149     public int getDurationSeconds() {
150         return mDurationSeconds;
151     }
152 
153     /** Writes field members to {@link ProtoOutputStream}. */
writeToProto(@onNull ProtoOutputStream protoOutputStream)154     void writeToProto(@NonNull ProtoOutputStream protoOutputStream) {
155         protoOutputStream.write(PeopleEventProto.EVENT_TYPE, mType);
156         protoOutputStream.write(PeopleEventProto.TIME, mTimestamp);
157         protoOutputStream.write(PeopleEventProto.DURATION, mDurationSeconds);
158     }
159 
160     /** Reads from {@link ProtoInputStream} and constructs {@link Event}. */
161     @NonNull
readFromProto(@onNull ProtoInputStream protoInputStream)162     static Event readFromProto(@NonNull ProtoInputStream protoInputStream) throws IOException {
163         Event.Builder builder = new Event.Builder();
164         while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
165             switch (protoInputStream.getFieldNumber()) {
166                 case (int) PeopleEventProto.EVENT_TYPE:
167                     builder.setType(protoInputStream.readInt(PeopleEventProto.EVENT_TYPE));
168                     break;
169                 case (int) PeopleEventProto.TIME:
170                     builder.setTimestamp(protoInputStream.readLong(PeopleEventProto.TIME));
171                     break;
172                 case (int) PeopleEventProto.DURATION:
173                     builder.setDurationSeconds(protoInputStream.readInt(PeopleEventProto.DURATION));
174                     break;
175                 default:
176                     Slog.w(TAG, "Could not read undefined field: "
177                             + protoInputStream.getFieldNumber());
178             }
179         }
180         return builder.build();
181     }
182 
183     @Override
equals(Object obj)184     public boolean equals(Object obj) {
185         if (this == obj) {
186             return true;
187         }
188         if (!(obj instanceof Event)) {
189             return false;
190         }
191         Event other = (Event) obj;
192         return mTimestamp == other.mTimestamp
193                 && mType == other.mType
194                 && mDurationSeconds == other.mDurationSeconds;
195     }
196 
197     @Override
hashCode()198     public int hashCode() {
199         return Objects.hash(mTimestamp, mType, mDurationSeconds);
200     }
201 
202     @Override
toString()203     public String toString() {
204         StringBuilder sb = new StringBuilder();
205         sb.append("Event {");
206         sb.append("timestamp=").append(DateFormat.format("yyyy-MM-dd HH:mm:ss", mTimestamp));
207         sb.append(", type=").append(mType);
208         if (mDurationSeconds > 0) {
209             sb.append(", durationSeconds=").append(mDurationSeconds);
210         }
211         sb.append("}");
212         return sb.toString();
213     }
214 
215     /** Builder class for {@link Event} objects. */
216     static class Builder {
217 
218         private long mTimestamp;
219 
220         private int mType;
221 
222         private int mDurationSeconds;
223 
Builder()224         private Builder() {}
225 
Builder(long timestamp, @EventType int type)226         Builder(long timestamp, @EventType int type) {
227             mTimestamp = timestamp;
228             mType = type;
229         }
230 
setDurationSeconds(int durationSeconds)231         Builder setDurationSeconds(int durationSeconds) {
232             mDurationSeconds = durationSeconds;
233             return this;
234         }
235 
setTimestamp(long timestamp)236         private Builder setTimestamp(long timestamp) {
237             mTimestamp = timestamp;
238             return this;
239         }
240 
setType(int type)241         private Builder setType(int type) {
242             mType = type;
243             return this;
244         }
245 
build()246         Event build() {
247             return new Event(this);
248         }
249     }
250 }
251