1 /*
2  * Copyright (C) 2022 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.safetycenter.persistence;
18 
19 import static android.os.Build.VERSION_CODES.TIRAMISU;
20 
21 import androidx.annotation.Nullable;
22 import androidx.annotation.RequiresApi;
23 
24 import java.time.Instant;
25 import java.util.Objects;
26 
27 /**
28  * Data class containing all the identifiers and metadata of a safety source issue that should be
29  * persisted.
30  */
31 @RequiresApi(TIRAMISU)
32 public final class PersistedSafetyCenterIssue {
33     private final String mKey;
34     private final Instant mFirstSeenAt;
35     @Nullable private final Instant mDismissedAt;
36     private final int mDismissCount;
37     @Nullable private final Instant mNotificationDismissedAt;
38 
PersistedSafetyCenterIssue( String key, Instant firstSeenAt, @Nullable Instant dismissedAt, int dismissCount, @Nullable Instant notificationDismissedAt)39     private PersistedSafetyCenterIssue(
40             String key,
41             Instant firstSeenAt,
42             @Nullable Instant dismissedAt,
43             int dismissCount,
44             @Nullable Instant notificationDismissedAt) {
45         mKey = key;
46         mFirstSeenAt = firstSeenAt;
47         mDismissedAt = dismissedAt;
48         mDismissCount = dismissCount;
49         mNotificationDismissedAt = notificationDismissedAt;
50     }
51 
52     /** The unique key for a safety source issue. */
getKey()53     public String getKey() {
54         return mKey;
55     }
56 
57     /** The instant when this issue was first seen. */
getFirstSeenAt()58     public Instant getFirstSeenAt() {
59         return mFirstSeenAt;
60     }
61 
62     /** The instant when this issue was dismissed, {@code null} if the issue is not dismissed. */
63     @Nullable
getDismissedAt()64     public Instant getDismissedAt() {
65         return mDismissedAt;
66     }
67 
68     /** The number of times this issue was dismissed. */
getDismissCount()69     public int getDismissCount() {
70         return mDismissCount;
71     }
72 
73     /**
74      * The instant when the notification for this issue was dismissed, {@code null} if the issue's
75      * notification is not dismissed.
76      */
77     @Nullable
getNotificationDismissedAt()78     public Instant getNotificationDismissedAt() {
79         return mNotificationDismissedAt;
80     }
81 
82     @Override
equals(Object o)83     public boolean equals(Object o) {
84         if (this == o) return true;
85         if (!(o instanceof PersistedSafetyCenterIssue)) return false;
86         PersistedSafetyCenterIssue that = (PersistedSafetyCenterIssue) o;
87         return Objects.equals(mKey, that.mKey)
88                 && Objects.equals(mFirstSeenAt, that.mFirstSeenAt)
89                 && Objects.equals(mDismissedAt, that.mDismissedAt)
90                 && mDismissCount == that.mDismissCount
91                 && Objects.equals(mNotificationDismissedAt, that.mNotificationDismissedAt);
92     }
93 
94     @Override
hashCode()95     public int hashCode() {
96         return Objects.hash(
97                 mKey, mFirstSeenAt, mDismissedAt, mDismissCount, mNotificationDismissedAt);
98     }
99 
100     @Override
toString()101     public String toString() {
102         return "PersistedSafetyCenterIssue{"
103                 + "mKey="
104                 + mKey
105                 + ", mFirstSeenAt="
106                 + mFirstSeenAt
107                 + ", mDismissedAt="
108                 + mDismissedAt
109                 + ", mDismissCount="
110                 + mDismissCount
111                 + ", mNotificationDismissedAt="
112                 + mNotificationDismissedAt
113                 + '}';
114     }
115 
116     /** Builder class for {@link PersistedSafetyCenterIssue}. */
117     public static final class Builder {
118         @Nullable private String mKey;
119         @Nullable private Instant mFirstSeenAt;
120         @Nullable private Instant mDismissedAt;
121         private int mDismissCount = 0;
122         @Nullable private Instant mNotificationDismissedAt;
123 
124         /** Creates a {@link Builder} for a {@link PersistedSafetyCenterIssue}. */
Builder()125         public Builder() {}
126 
127         /** The unique key for a safety source issue. */
setKey(@ullable String key)128         public Builder setKey(@Nullable String key) {
129             mKey = key;
130             return this;
131         }
132 
133         /** The instant when this issue was first seen. */
setFirstSeenAt(@ullable Instant firstSeenAt)134         public Builder setFirstSeenAt(@Nullable Instant firstSeenAt) {
135             mFirstSeenAt = firstSeenAt;
136             return this;
137         }
138 
139         /** The instant when this issue was dismissed. */
setDismissedAt(@ullable Instant dismissedAt)140         public Builder setDismissedAt(@Nullable Instant dismissedAt) {
141             mDismissedAt = dismissedAt;
142             return this;
143         }
144 
145         /** The number of times this issue was dismissed. */
setDismissCount(int dismissCount)146         public Builder setDismissCount(int dismissCount) {
147             if (dismissCount < 0) {
148                 throw new IllegalArgumentException("Dismiss count cannot be negative");
149             }
150             mDismissCount = dismissCount;
151             return this;
152         }
153 
154         /** The instant when this issue's notification was dismissed. */
setNotificationDismissedAt(@ullable Instant notificationDismissedAt)155         public Builder setNotificationDismissedAt(@Nullable Instant notificationDismissedAt) {
156             mNotificationDismissedAt = notificationDismissedAt;
157             return this;
158         }
159 
160         /**
161          * Creates the {@link PersistedSafetyCenterIssue} defined by this {@link Builder}.
162          *
163          * <p>Throws an {@link IllegalStateException} if any constraint is violated.
164          */
build()165         public PersistedSafetyCenterIssue build() {
166             validateRequiredAttribute(mKey, "key");
167             validateRequiredAttribute(mFirstSeenAt, "firstSeenAt");
168 
169             if (mDismissedAt != null && mDismissCount == 0) {
170                 throw new IllegalStateException(
171                         "dismissCount cannot be 0 if dismissedAt is present");
172             }
173             if (mDismissCount > 0 && mDismissedAt == null) {
174                 throw new IllegalStateException(
175                         "dismissedAt must be present if dismissCount is greater than 0");
176             }
177 
178             return new PersistedSafetyCenterIssue(
179                     mKey, mFirstSeenAt, mDismissedAt, mDismissCount, mNotificationDismissedAt);
180         }
181     }
182 
validateRequiredAttribute(@ullable Object attribute, String name)183     private static void validateRequiredAttribute(@Nullable Object attribute, String name) {
184         if (attribute == null) {
185             throw new IllegalStateException("Required attribute " + name + " missing");
186         }
187     }
188 }
189