1 /*
2  * Copyright (C) 2017 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.car.storagemonitoring;
18 
19 import android.annotation.NonNull;
20 import android.car.storagemonitoring.WearEstimate;
21 import android.car.storagemonitoring.WearEstimateChange;
22 import android.util.JsonWriter;
23 
24 import org.json.JSONException;
25 import org.json.JSONObject;
26 
27 import java.io.IOException;
28 import java.time.Instant;
29 import java.util.Objects;
30 
31 /**
32  * This class represents a wear estimate record as stored by CarStorageMonitoringService.
33  *
34  * Because it is meant to map 1:1 to on-disk records, it is not directly convertible to a
35  * WearEstimateChange because it does not include information about "acceptable degradation".
36  */
37 public class WearEstimateRecord {
38 
39     private final WearEstimate mOldWearEstimate;
40     private final WearEstimate mNewWearEstimate;
41     private final long mTotalCarServiceUptime;
42     private final Instant mUnixTimestamp;
43 
WearEstimateRecord(@onNull WearEstimate oldWearEstimate, @NonNull WearEstimate newWearEstimate, long totalCarServiceUptime, @NonNull Instant unixTimestamp)44     public WearEstimateRecord(@NonNull WearEstimate oldWearEstimate,
45         @NonNull WearEstimate newWearEstimate,
46         long totalCarServiceUptime,
47         @NonNull Instant unixTimestamp) {
48         mOldWearEstimate = Objects.requireNonNull(oldWearEstimate);
49         mNewWearEstimate = Objects.requireNonNull(newWearEstimate);
50         mTotalCarServiceUptime = totalCarServiceUptime;
51         long unixEpochSeconds = Objects.requireNonNull(unixTimestamp).getEpochSecond();
52         mUnixTimestamp = Instant.ofEpochSecond(unixEpochSeconds);
53     }
54 
WearEstimateRecord(@onNull JSONObject json)55     WearEstimateRecord(@NonNull JSONObject json) throws JSONException {
56         mOldWearEstimate = new WearEstimate(json.getJSONObject("oldWearEstimate"));
57         mNewWearEstimate = new WearEstimate(json.getJSONObject("newWearEstimate"));
58         mTotalCarServiceUptime = json.getLong("totalCarServiceUptime");
59         long unixEpochSeconds = Instant.ofEpochMilli(json.getLong("unixTimestamp"))
60                 .getEpochSecond();
61         mUnixTimestamp = Instant.ofEpochSecond(unixEpochSeconds);
62     }
63 
writeToJson(@onNull JsonWriter jsonWriter)64     void writeToJson(@NonNull JsonWriter jsonWriter) throws IOException {
65         jsonWriter.beginObject();
66         jsonWriter.name("oldWearEstimate"); mOldWearEstimate.writeToJson(jsonWriter);
67         jsonWriter.name("newWearEstimate"); mNewWearEstimate.writeToJson(jsonWriter);
68         jsonWriter.name("totalCarServiceUptime").value(mTotalCarServiceUptime);
69         jsonWriter.name("unixTimestamp").value(mUnixTimestamp.toEpochMilli());
70         jsonWriter.endObject();
71     }
72 
getOldWearEstimate()73     public WearEstimate getOldWearEstimate() {
74         return mOldWearEstimate;
75     }
76 
getNewWearEstimate()77     public WearEstimate getNewWearEstimate() {
78         return mNewWearEstimate;
79     }
80 
getTotalCarServiceUptime()81     public long getTotalCarServiceUptime() {
82         return mTotalCarServiceUptime;
83     }
84 
getUnixTimestamp()85     public Instant getUnixTimestamp() {
86         return mUnixTimestamp;
87     }
88 
toWearEstimateChange(boolean isAcceptableDegradation)89     WearEstimateChange toWearEstimateChange(boolean isAcceptableDegradation) {
90         return new WearEstimateChange(mOldWearEstimate,
91                 mNewWearEstimate, mTotalCarServiceUptime, mUnixTimestamp, isAcceptableDegradation);
92     }
93 
94     @Override
equals(Object other)95     public boolean equals(Object other) {
96         if (other instanceof WearEstimateRecord) {
97             WearEstimateRecord wer = (WearEstimateRecord)other;
98             if (!wer.mOldWearEstimate.equals(mOldWearEstimate)) return false;
99             if (!wer.mNewWearEstimate.equals(mNewWearEstimate)) return false;
100             if (wer.mTotalCarServiceUptime != mTotalCarServiceUptime) return false;
101             return wer.mUnixTimestamp.equals(mUnixTimestamp);
102         }
103         return false;
104     }
105 
106     /**
107      * Checks whether this record tracks the same change as the provided estimate.
108      * That means the two objects have the same values for:
109      * <ul>
110      *  <li>old wear indicators</li>
111      *  <li>new wear indicators</li>
112      *  <li>uptime at event</li>
113      * </ul>
114      */
isSameAs(@onNull WearEstimateChange wearEstimateChange)115     public boolean isSameAs(@NonNull WearEstimateChange wearEstimateChange) {
116         if (!mOldWearEstimate.equals(wearEstimateChange.oldEstimate)) return false;
117         if (!mNewWearEstimate.equals(wearEstimateChange.newEstimate)) return false;
118         return (mTotalCarServiceUptime == wearEstimateChange.uptimeAtChange);
119     }
120 
121     @Override
hashCode()122     public int hashCode() {
123         return Objects.hash(mOldWearEstimate,
124                 mNewWearEstimate, mTotalCarServiceUptime, mUnixTimestamp);
125     }
126 
127     @Override
toString()128     public String toString() {
129         return String.format("WearEstimateRecord {" +
130             "mOldWearEstimate = %s, " +
131             "mNewWearEstimate = %s, " +
132             "mTotalCarServiceUptime = %d, " +
133             "mUnixTimestamp = %s}",
134             mOldWearEstimate, mNewWearEstimate, mTotalCarServiceUptime, mUnixTimestamp);
135     }
136 
137     public static final class Builder {
138         private WearEstimate mOldWearEstimate = null;
139         private WearEstimate mNewWearEstimate = null;
140         private long mTotalCarServiceUptime = -1;
141         private Instant mUnixTimestamp = null;
142 
Builder()143         private Builder() {}
144 
newBuilder()145         public static Builder newBuilder() {
146             return new Builder();
147         }
148 
fromWearEstimate(@onNull WearEstimate wearEstimate)149         public Builder fromWearEstimate(@NonNull WearEstimate wearEstimate) {
150             mOldWearEstimate = Objects.requireNonNull(wearEstimate);
151             return this;
152         }
153 
toWearEstimate(@onNull WearEstimate wearEstimate)154         public Builder toWearEstimate(@NonNull WearEstimate wearEstimate) {
155             mNewWearEstimate = Objects.requireNonNull(wearEstimate);
156             return this;
157         }
158 
atUptime(long uptime)159         public Builder atUptime(long uptime) {
160             if (uptime < 0) {
161                 throw new IllegalArgumentException("uptime must be >= 0");
162             }
163             mTotalCarServiceUptime = uptime;
164             return this;
165         }
166 
atTimestamp(@onNull Instant now)167         public Builder atTimestamp(@NonNull Instant now) {
168             mUnixTimestamp = Objects.requireNonNull(now);
169             return this;
170         }
171 
build()172         public WearEstimateRecord build() {
173             if (mOldWearEstimate == null || mNewWearEstimate == null ||
174                     mTotalCarServiceUptime < 0 || mUnixTimestamp == null) {
175                 throw new IllegalStateException("malformed builder state");
176             }
177             return new WearEstimateRecord(
178                     mOldWearEstimate, mNewWearEstimate, mTotalCarServiceUptime, mUnixTimestamp);
179         }
180     }
181 }
182