1/*
2 * Copyright (C) 2016 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
17package android.car.hardware;
18
19import android.annotation.Nullable;
20import android.annotation.SystemApi;
21import android.car.VehicleAreaType;
22import android.os.Parcel;
23import android.os.Parcelable;
24import android.util.SparseArray;
25
26import java.lang.reflect.Array;
27
28/**
29 * Represents general information about car property such as data type and min/max ranges for car
30 * areas (if applicable). This class supposed to be immutable, parcelable and could be passed over.
31 *
32 * <p>Use {@link CarPropertyConfig#newBuilder} to create an instance of this class.
33 *
34 * @param <T> refer to Parcel#writeValue(Object) to get a list of all supported types. The class
35 * should be visible to framework as default class loader is being used here.
36 *
37 * @hide
38 */
39@SystemApi
40public class CarPropertyConfig<T> implements Parcelable {
41    private final int mPropertyId;
42    private final Class<T> mType;
43    private final int mAreaType;
44    private final SparseArray<AreaConfig<T>> mSupportedAreas;
45
46    private CarPropertyConfig(Class<T> type, int propertyId, int areaType,
47            SparseArray<AreaConfig<T>> supportedAreas) {
48        mPropertyId = propertyId;
49        mType = type;
50        mAreaType = areaType;
51        mSupportedAreas = supportedAreas;
52    }
53
54    public int getPropertyId() { return mPropertyId; }
55    public Class<T> getPropertyType() { return mType; }
56    public @VehicleAreaType.VehicleAreaTypeValue int getAreaType() { return mAreaType; }
57
58    /** Returns true if this property doesn't hold car area-specific configuration */
59    public boolean isGlobalProperty() {
60        return mAreaType == VehicleAreaType.VEHICLE_AREA_TYPE_NONE;
61    }
62
63    public int getAreaCount() {
64        return mSupportedAreas.size();
65    }
66
67    public int[] getAreaIds() {
68        int[] areaIds = new int[mSupportedAreas.size()];
69        for (int i = 0; i < areaIds.length; i++) {
70            areaIds[i] = mSupportedAreas.keyAt(i);
71        }
72        return areaIds;
73    }
74
75    /**
76     * Returns the first areaId.
77     * Throws {@link IllegalStateException} if supported area count not equals to one.
78     * */
79    public int getFirstAndOnlyAreaId() {
80        if (mSupportedAreas.size() != 1) {
81            throw new IllegalStateException("Expected one and only area in this property. Prop: 0x"
82                    + Integer.toHexString(mPropertyId));
83        }
84        return mSupportedAreas.keyAt(0);
85    }
86
87    public boolean hasArea(int areaId) {
88        return mSupportedAreas.indexOfKey(areaId) >= 0;
89    }
90
91    @Nullable
92    public T getMinValue(int areaId) {
93        AreaConfig<T> area = mSupportedAreas.get(areaId);
94        return area == null ? null : area.getMinValue();
95    }
96
97    @Nullable
98    public T getMaxValue(int areaId) {
99        AreaConfig<T> area = mSupportedAreas.get(areaId);
100        return area == null ? null : area.getMaxValue();
101    }
102
103    @Nullable
104    public T getMinValue() {
105        AreaConfig<T> area = mSupportedAreas.valueAt(0);
106        return area == null ? null : area.getMinValue();
107    }
108
109    @Nullable
110    public T getMaxValue() {
111        AreaConfig<T> area = mSupportedAreas.valueAt(0);
112        return area == null ? null : area.getMaxValue();
113    }
114
115    @Override
116    public int describeContents() {
117        return 0;
118    }
119
120    @Override
121    public void writeToParcel(Parcel dest, int flags) {
122        dest.writeInt(mPropertyId);
123        dest.writeString(mType.getName());
124        dest.writeInt(mAreaType);
125        dest.writeInt(mSupportedAreas.size());
126        for (int i = 0; i < mSupportedAreas.size(); i++) {
127            dest.writeInt(mSupportedAreas.keyAt(i));
128            dest.writeParcelable(mSupportedAreas.valueAt(i), flags);
129        }
130    }
131
132    @SuppressWarnings("unchecked")
133    private CarPropertyConfig(Parcel in) {
134        mPropertyId = in.readInt();
135        String className = in.readString();
136        try {
137            mType = (Class<T>) Class.forName(className);
138        } catch (ClassNotFoundException e) {
139            throw new IllegalArgumentException("Class not found: " + className);
140        }
141        mAreaType = in.readInt();
142        int areaSize = in.readInt();
143        mSupportedAreas = new SparseArray<>(areaSize);
144        for (int i = 0; i < areaSize; i++) {
145            int areaId = in.readInt();
146            AreaConfig<T> area = in.readParcelable(getClass().getClassLoader());
147            mSupportedAreas.put(areaId, area);
148        }
149    }
150
151    public static final Creator<CarPropertyConfig> CREATOR = new Creator<CarPropertyConfig>() {
152        @Override
153        public CarPropertyConfig createFromParcel(Parcel in) {
154            return new CarPropertyConfig(in);
155        }
156
157        @Override
158        public CarPropertyConfig[] newArray(int size) {
159            return new CarPropertyConfig[size];
160        }
161    };
162
163    @Override
164    public String toString() {
165        return "CarPropertyConfig{" +
166                "mPropertyId=" + mPropertyId +
167                ", mType=" + mType +
168                ", mAreaType=" + mAreaType +
169                ", mSupportedAreas=" + mSupportedAreas +
170                '}';
171    }
172
173    public static class AreaConfig<T> implements Parcelable {
174        @Nullable private final T mMinValue;
175        @Nullable private final T mMaxValue;
176
177        private AreaConfig(T minValue, T maxValue) {
178            mMinValue = minValue;
179            mMaxValue = maxValue;
180        }
181
182        public static final Parcelable.Creator<AreaConfig<Object>> CREATOR
183                = getCreator(Object.class);
184
185        private static <E> Parcelable.Creator<AreaConfig<E>> getCreator(final Class<E> clazz) {
186            return new Creator<AreaConfig<E>>() {
187                @Override
188                public AreaConfig<E> createFromParcel(Parcel source) {
189                    return new AreaConfig<>(source);
190                }
191
192                @Override @SuppressWarnings("unchecked")
193                public AreaConfig<E>[] newArray(int size) {
194                    return (AreaConfig<E>[]) Array.newInstance(clazz, size);
195                }
196            };
197        }
198
199        @SuppressWarnings("unchecked")
200        private AreaConfig(Parcel in) {
201            mMinValue = (T) in.readValue(getClass().getClassLoader());
202            mMaxValue = (T) in.readValue(getClass().getClassLoader());
203        }
204
205        @Nullable public T getMinValue() { return mMinValue; }
206        @Nullable public T getMaxValue() { return mMaxValue; }
207
208        @Override
209        public int describeContents() {
210            return 0;
211        }
212
213        @Override
214        public void writeToParcel(Parcel dest, int flags) {
215            dest.writeValue(mMinValue);
216            dest.writeValue(mMaxValue);
217        }
218
219        @Override
220        public String toString() {
221            return "CarAreaConfig{" +
222                    "mMinValue=" + mMinValue +
223                    ", mMaxValue=" + mMaxValue +
224                    '}';
225        }
226    }
227
228    public static <T> Builder<T> newBuilder(Class<T> clazz, int propertyId, int areaType,
229            int areaCapacity) {
230        return new Builder<>(clazz, propertyId, areaType, areaCapacity);
231    }
232
233
234    public static <T> Builder<T> newBuilder(Class<T> clazz, int propertyId, int areaType) {
235        return newBuilder(clazz, propertyId, areaType, 0);
236    }
237
238    public static class Builder<T> {
239        private final Class<T> mType;
240        private final int mPropertyId;
241        private final int mAreaType;
242        private final SparseArray<AreaConfig<T>> mAreas;
243
244        private Builder(Class<T> type, int propertyId, int areaType, int areaCapacity) {
245            mType = type;
246            mPropertyId = propertyId;
247            mAreaType = areaType;
248            if (areaCapacity != 0) {
249                mAreas = new SparseArray<>(areaCapacity);
250            } else {
251                mAreas = new SparseArray<>();
252            }
253        }
254
255        public Builder<T> addAreas(int[] areaIds) {
256            for (int id : areaIds) {
257                mAreas.put(id, null);
258            }
259            return this;
260        }
261
262        public Builder<T> addArea(int areaId) {
263            return addAreaConfig(areaId, null, null);
264        }
265
266        public Builder<T> addAreaConfig(int areaId, T min, T max) {
267            if (min == null && max == null) {
268                mAreas.put(areaId, null);
269            } else {
270                mAreas.put(areaId, new AreaConfig<>(min, max));
271            }
272            return this;
273        }
274
275        public CarPropertyConfig<T> build() {
276            return new CarPropertyConfig<>(mType, mPropertyId, mAreaType, mAreas);
277        }
278    }
279}
280