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