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 package com.android.car.storagemonitoring;
17 
18 import android.annotation.NonNull;
19 import android.car.storagemonitoring.WearEstimateChange;
20 import android.util.JsonWriter;
21 
22 import org.json.JSONArray;
23 import org.json.JSONException;
24 import org.json.JSONObject;
25 
26 import java.io.File;
27 import java.io.IOException;
28 import java.nio.file.Files;
29 import java.time.Duration;
30 import java.util.ArrayList;
31 import java.util.Collections;
32 import java.util.List;
33 import java.util.StringJoiner;
34 
35 /**
36  * This class represents the entire history of flash wear changes as tracked
37  * by CarStorageMonitoringService. It is a set of WearEstimateRecords.
38  *
39  * This is convertible to a list of WearEstimateChanges given policy about what constitutes
40  * acceptable or not acceptable degradation across change events. This policy is subject to
41  * modifications across OS versions, and as such it is not suitable for permanent storage.
42  */
43 public class WearHistory {
44     private final List<WearEstimateRecord> mWearHistory = new ArrayList<>();
45 
WearHistory()46     public WearHistory() {}
47 
WearHistory(@onNull JSONObject jsonObject)48     WearHistory(@NonNull JSONObject jsonObject) throws JSONException {
49         final JSONArray wearHistory = jsonObject.getJSONArray("wearHistory");
50         for (int i = 0; i < wearHistory.length(); ++i) {
51             JSONObject wearRecordJson = wearHistory.getJSONObject(i);
52             WearEstimateRecord wearRecord = new WearEstimateRecord(wearRecordJson);
53             add(wearRecord);
54         }
55     }
56 
fromRecords(@onNull WearEstimateRecord... records)57     public static WearHistory fromRecords(@NonNull WearEstimateRecord... records) {
58         WearHistory wearHistory = new WearHistory();
59         for (int index = 0; index < records.length; index++) {
60             wearHistory.add(records[index]);
61         }
62         return wearHistory;
63     }
64 
fromJson(@onNull File in)65     public static WearHistory fromJson(@NonNull File in) throws IOException, JSONException {
66         JSONObject jsonObject = new JSONObject(new String(Files.readAllBytes(in.toPath())));
67         return new WearHistory(jsonObject);
68     }
69 
writeToJson(@onNull JsonWriter out)70     public void writeToJson(@NonNull JsonWriter out) throws IOException {
71         out.beginObject();
72         out.name("wearHistory").beginArray();
73         for (WearEstimateRecord wearRecord : mWearHistory) {
74             wearRecord.writeToJson(out);
75         }
76         out.endArray();
77         out.endObject();
78     }
79 
add(@onNull WearEstimateRecord record)80     public boolean add(@NonNull WearEstimateRecord record) {
81         if (record != null && mWearHistory.add(record)) {
82             mWearHistory.sort((WearEstimateRecord o1, WearEstimateRecord o2) ->
83                 Long.valueOf(o1.getTotalCarServiceUptime()).compareTo(
84                     o2.getTotalCarServiceUptime()));
85             return true;
86         }
87         return false;
88     }
89 
size()90     public int size() {
91         return mWearHistory.size();
92     }
93 
get(int i)94     public WearEstimateRecord get(int i) {
95         return mWearHistory.get(i);
96     }
97 
getLast()98     public WearEstimateRecord getLast() {
99         return get(size() - 1);
100     }
101 
toWearEstimateChanges( long acceptableHoursPerOnePercentFlashWear)102     public List<WearEstimateChange> toWearEstimateChanges(
103             long acceptableHoursPerOnePercentFlashWear) {
104         // current technology allows us to detect wear in 10% increments
105         final int WEAR_PERCENTAGE_INCREMENT = 10;
106         final long acceptableWearRate = WEAR_PERCENTAGE_INCREMENT *
107                 Duration.ofHours(acceptableHoursPerOnePercentFlashWear).toMillis();
108         final int numRecords = size();
109 
110         if (numRecords == 0) return Collections.emptyList();
111 
112         List<WearEstimateChange> result = new ArrayList<>();
113         result.add(get(0).toWearEstimateChange(true));
114 
115         for (int i = 1; i < numRecords; ++i) {
116             WearEstimateRecord previousRecord = get(i - 1);
117             WearEstimateRecord currentRecord = get(i);
118             final long timeForChange =
119                     currentRecord.getTotalCarServiceUptime() -
120                             previousRecord.getTotalCarServiceUptime();
121             final boolean isAcceptableDegradation = timeForChange >= acceptableWearRate;
122             result.add(currentRecord.toWearEstimateChange(isAcceptableDegradation));
123         }
124 
125         return Collections.unmodifiableList(result);
126     }
127 
128     @Override
equals(Object other)129     public boolean equals(Object other) {
130         if (other instanceof WearHistory) {
131             WearHistory wi = (WearHistory)other;
132             return wi.mWearHistory.equals(mWearHistory);
133         }
134         return false;
135     }
136 
137     @Override
hashCode()138     public int hashCode() {
139         return mWearHistory.hashCode();
140     }
141 
142     @Override
toString()143     public String toString() {
144         StringJoiner stringJoiner = new StringJoiner(",",
145                 "WearHistory[size = " + size() + "] -> ", "");
146         for (int index = 0; index < mWearHistory.size(); index++) {
147             stringJoiner.add(mWearHistory.get(index).toString());
148         }
149         return stringJoiner.toString();
150     }
151 }
152