1 /*
2  * Copyright (C) 2023 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.internal.property;
18 
19 import android.car.hardware.CarPropertyValue;
20 import android.car.hardware.CarPropertyValue.PropertyStatus;
21 
22 import com.android.internal.util.Preconditions;
23 
24 import java.time.Duration;
25 import java.util.Objects;
26 
27 /**
28  * A {@link CarPropertyEventTracker} implementation for continuous property
29  *
30  * @hide
31  */
32 public final class ContCarPropertyEventTracker implements CarPropertyEventTracker{
33     private static final String TAG = "ContCarPropertyEventTracker";
34     private static final float NANOSECONDS_PER_SECOND = Duration.ofSeconds(1).toNanos();
35     // Add a margin so that if an event timestamp is within
36     // 5% of the next timestamp, it will not be dropped.
37     private static final float UPDATE_PERIOD_OFFSET = 0.95f;
38 
39     private final Logger mLogger;
40     private final boolean mEnableVur;
41     private final float mUpdateRateHz;
42     private final float mResolution;
43     private final long mUpdatePeriodNanos;
44     private long mNextUpdateTimeNanos;
45     private CarPropertyValue<?> mCurrentCarPropertyValue;
46     private @PropertyStatus int mCurrentStatus;
47 
ContCarPropertyEventTracker(boolean useSystemLogger, float updateRateHz, boolean enableVur, float resolution)48     public ContCarPropertyEventTracker(boolean useSystemLogger, float updateRateHz,
49             boolean enableVur, float resolution) {
50         mLogger = new Logger(useSystemLogger, TAG);
51         if (mLogger.dbg()) {
52             mLogger.logD(String.format(
53                     "new continuous car property event tracker, updateRateHz: %f, "
54                     + ", enableVur: %b, resolution: %f", updateRateHz, enableVur, resolution));
55         }
56         // updateRateHz should be sanitized before.
57         Preconditions.checkArgument(updateRateHz > 0, "updateRateHz must be a positive number");
58         mUpdateRateHz = updateRateHz;
59         mUpdatePeriodNanos =
60                 (long) ((1.0 / mUpdateRateHz) * NANOSECONDS_PER_SECOND * UPDATE_PERIOD_OFFSET);
61         mEnableVur = enableVur;
62         mResolution = resolution;
63     }
64 
65     @Override
getUpdateRateHz()66     public float getUpdateRateHz() {
67         return mUpdateRateHz;
68     }
69 
70     @Override
getCurrentCarPropertyValue()71     public CarPropertyValue<?> getCurrentCarPropertyValue() {
72         return mCurrentCarPropertyValue;
73     }
74 
sanitizeValueByResolutionInt(Object value)75     private int sanitizeValueByResolutionInt(Object value) {
76         return (int) (Math.round((Integer) value / mResolution) * mResolution);
77     }
78 
sanitizeValueByResolutionLong(Object value)79     private long sanitizeValueByResolutionLong(Object value) {
80         return (long) (Math.round((Long) value / mResolution) * mResolution);
81     }
82 
sanitizeValueByResolutionFloat(Object value)83     private float sanitizeValueByResolutionFloat(Object value) {
84         return Math.round((Float) value / mResolution) * mResolution;
85     }
86 
sanitizeCarPropertyValueByResolution( CarPropertyValue<?> carPropertyValue)87     private CarPropertyValue<?> sanitizeCarPropertyValueByResolution(
88             CarPropertyValue<?> carPropertyValue) {
89         if (mResolution == 0.0f) {
90             return carPropertyValue;
91         }
92 
93         Object value = carPropertyValue.getValue();
94         if (value instanceof Integer) {
95             value = sanitizeValueByResolutionInt(value);
96         } else if (value instanceof Integer[]) {
97             Integer[] array = (Integer[]) value;
98             for (int i = 0; i < array.length; i++) {
99                 array[i] = sanitizeValueByResolutionInt(array[i]);
100             }
101             value = array;
102         } else if (value instanceof Long) {
103             value = sanitizeValueByResolutionLong(value);
104         } else if (value instanceof Long[]) {
105             Long[] array = (Long[]) value;
106             for (int i = 0; i < array.length; i++) {
107                 array[i] = sanitizeValueByResolutionLong(array[i]);
108             }
109             value = array;
110         } else if (value instanceof Float) {
111             value = sanitizeValueByResolutionFloat(value);
112         } else if (value instanceof Float[]) {
113             Float[] array = (Float[]) value;
114             for (int i = 0; i < array.length; i++) {
115                 array[i] = sanitizeValueByResolutionFloat(array[i]);
116             }
117             value = array;
118         }
119         return new CarPropertyValue<>(carPropertyValue.getPropertyId(),
120                 carPropertyValue.getAreaId(),
121                 carPropertyValue.getStatus(),
122                 carPropertyValue.getTimestamp(),
123                 value);
124     }
125 
126     /** Returns true if the client needs to be updated for this event. */
127     @Override
hasUpdate(CarPropertyValue<?> carPropertyValue)128     public boolean hasUpdate(CarPropertyValue<?> carPropertyValue) {
129         if (carPropertyValue.getTimestamp() < mNextUpdateTimeNanos) {
130             if (mLogger.dbg()) {
131                 mLogger.logD(String.format("hasUpdate: Dropping carPropertyValue: %s, "
132                         + "because getTimestamp()=%d < nextUpdateTimeNanos=%d",
133                         carPropertyValue, carPropertyValue.getTimestamp(), mNextUpdateTimeNanos));
134             }
135             return false;
136         }
137         mNextUpdateTimeNanos = carPropertyValue.getTimestamp() + mUpdatePeriodNanos;
138         CarPropertyValue<?> sanitizedCarPropertyValue =
139                 sanitizeCarPropertyValueByResolution(carPropertyValue);
140         int status = sanitizedCarPropertyValue.getStatus();
141         Object value = sanitizedCarPropertyValue.getValue();
142         if (mEnableVur && status == mCurrentStatus && mCurrentCarPropertyValue != null
143                     && Objects.deepEquals(value, mCurrentCarPropertyValue.getValue())) {
144             if (mLogger.dbg()) {
145                 mLogger.logD(String.format("hasUpdate: Dropping carPropertyValue: %s, "
146                                 + "because VUR is enabled and value is the same",
147                         sanitizedCarPropertyValue));
148             }
149             return false;
150         }
151         mCurrentStatus = status;
152         mCurrentCarPropertyValue = sanitizedCarPropertyValue;
153         return true;
154     }
155 }
156