blob: 528353db8201417d134586cd86b5737a3ebecc9a [file] [log] [blame]
Keun young Park9a91efb2019-11-15 18:10:47 -08001/*
2 * Copyright (C) 2019 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 com.android.car;
18
19import android.annotation.NonNull;
20import android.car.Car;
21import android.car.Car.FeaturerRequestEnum;
22import android.car.CarFeatures;
23import android.content.Context;
24import android.os.Build;
25import android.os.Handler;
26import android.os.HandlerThread;
27import android.util.AtomicFile;
Felipe Leme176a5fd2021-01-20 15:48:33 -080028import android.util.IndentingPrintWriter;
Mark Tabry614e06e2020-03-15 03:30:01 -070029import android.util.Pair;
Eric Jeongbd5fb562020-12-21 13:49:40 -080030import android.util.Slog;
Keun young Park9a91efb2019-11-15 18:10:47 -080031
32import com.android.internal.annotations.GuardedBy;
Keun young Park1fd33fe2019-12-19 18:25:14 -080033import com.android.internal.annotations.VisibleForTesting;
Keun young Park9a91efb2019-11-15 18:10:47 -080034
35import java.io.BufferedReader;
36import java.io.BufferedWriter;
37import java.io.File;
38import java.io.FileInputStream;
39import java.io.FileNotFoundException;
40import java.io.FileOutputStream;
41import java.io.IOException;
42import java.io.InputStreamReader;
43import java.io.OutputStreamWriter;
Keun young Park9a91efb2019-11-15 18:10:47 -080044import java.nio.charset.StandardCharsets;
45import java.util.ArrayList;
46import java.util.Arrays;
Mark Tabry614e06e2020-03-15 03:30:01 -070047import java.util.Collection;
Keun young Park9a91efb2019-11-15 18:10:47 -080048import java.util.Collections;
49import java.util.HashSet;
50import java.util.List;
51
52/**
53 * Component controlling the feature of car.
54 */
55public final class CarFeatureController implements CarServiceBase {
56
Mayank Garg72c71d22021-02-03 23:54:45 -080057 private static final String TAG = CarLog.tagFor(CarFeatureController.class);
Keun young Park9a91efb2019-11-15 18:10:47 -080058
59 // Use HaseSet for better search performance. Memory consumption is fixed and it not an issue.
60 // Should keep alphabetical order under each bucket.
61 // Update CarFeatureTest as well when this is updated.
62 private static final HashSet<String> MANDATORY_FEATURES = new HashSet<>(Arrays.asList(
63 Car.APP_FOCUS_SERVICE,
64 Car.AUDIO_SERVICE,
65 Car.BLUETOOTH_SERVICE,
Yuncheol Heo592145a2021-09-28 18:23:22 -070066 Car.CAR_ACTIVITY_SERVICE,
Keun young Park9a91efb2019-11-15 18:10:47 -080067 Car.CAR_BUGREPORT_SERVICE,
Yuncheol Heoe1ad3532021-02-11 19:34:14 -080068 Car.CAR_DEVICE_POLICY_SERVICE,
Keun young Park9a91efb2019-11-15 18:10:47 -080069 Car.CAR_DRIVING_STATE_SERVICE,
Keun young Park401479c2020-02-19 14:15:51 -080070 Car.CAR_INPUT_SERVICE,
Keun young Park9a91efb2019-11-15 18:10:47 -080071 Car.CAR_MEDIA_SERVICE,
Keun young Park9a91efb2019-11-15 18:10:47 -080072 Car.CAR_OCCUPANT_ZONE_SERVICE,
73 Car.CAR_USER_SERVICE,
Keun young Parkc6d80af2020-01-17 18:14:28 -080074 Car.CAR_UX_RESTRICTION_SERVICE,
Yuncheol Heoe1ad3532021-02-11 19:34:14 -080075 Car.CAR_WATCHDOG_SERVICE,
Keun young Park9a91efb2019-11-15 18:10:47 -080076 Car.INFO_SERVICE,
77 Car.PACKAGE_SERVICE,
78 Car.POWER_SERVICE,
79 Car.PROJECTION_SERVICE,
80 Car.PROPERTY_SERVICE,
81 Car.TEST_SERVICE,
Keun young Parkc6d80af2020-01-17 18:14:28 -080082 // All items below here are deprecated, but still should be supported
Keun young Park9a91efb2019-11-15 18:10:47 -080083 Car.CABIN_SERVICE,
84 Car.HVAC_SERVICE,
Keun young Parkc6d80af2020-01-17 18:14:28 -080085 Car.SENSOR_SERVICE,
86 Car.VENDOR_EXTENSION_SERVICE
Keun young Park9a91efb2019-11-15 18:10:47 -080087 ));
88
89 private static final HashSet<String> OPTIONAL_FEATURES = new HashSet<>(Arrays.asList(
Keun young Parkc6d80af2020-01-17 18:14:28 -080090 CarFeatures.FEATURE_CAR_USER_NOTICE_SERVICE,
Yuncheol Heo7ac86e82020-12-02 09:12:35 -080091 Car.CLUSTER_HOME_SERVICE,
Yuncheol Heo9a4eb7c2020-05-01 13:41:45 -070092 Car.CAR_NAVIGATION_SERVICE,
Keun young Parkc6d80af2020-01-17 18:14:28 -080093 Car.DIAGNOSTIC_SERVICE,
Michael Kellerac2ed202020-01-30 14:11:16 -080094 Car.OCCUPANT_AWARENESS_SERVICE,
Keun young Park9a91efb2019-11-15 18:10:47 -080095 Car.STORAGE_MONITORING_SERVICE,
Yuncheol Heoe1ad3532021-02-11 19:34:14 -080096 Car.VEHICLE_MAP_SERVICE,
Rui Qiuf6668202021-04-08 15:37:20 -070097 Car.CAR_TELEMETRY_SERVICE,
Yuncheol Heoe1ad3532021-02-11 19:34:14 -080098 // All items below here are deprecated, but still could be supported
99 Car.CAR_INSTRUMENT_CLUSTER_SERVICE
Keun young Park9a91efb2019-11-15 18:10:47 -0800100 ));
101
Keun young Parkae34db62021-06-23 15:10:20 -0700102 // This is a feature still under development and cannot be enabled in user build.
103 private static final HashSet<String> NON_USER_ONLY_FEATURES = new HashSet<>(Arrays.asList(
Selim Gurun268640d2021-06-30 18:58:27 -0700104 Car.CAR_EVS_SERVICE,
105 Car.CAR_TELEMETRY_SERVICE
Keun young Parkae34db62021-06-23 15:10:20 -0700106 ));
107
Mark Tabry614e06e2020-03-15 03:30:01 -0700108 // Features that depend on another feature being enabled (i.e. legacy API support).
109 // For example, VMS_SUBSCRIBER_SERVICE will be enabled if VEHICLE_MAP_SERVICE is enabled
110 // and disabled if VEHICLE_MAP_SERVICE is disabled.
111 private static final List<Pair<String, String>> SUPPORT_FEATURES = Arrays.asList(
112 Pair.create(Car.VEHICLE_MAP_SERVICE, Car.VMS_SUBSCRIBER_SERVICE)
113 );
114
Keun young Park9a91efb2019-11-15 18:10:47 -0800115 private static final String FEATURE_CONFIG_FILE_NAME = "car_feature_config.txt";
116
Mayank Garg0f382c42020-08-04 12:57:51 -0700117 // Last line starts with this with number of features for extra confidence check.
Keun young Park9a91efb2019-11-15 18:10:47 -0800118 private static final String CONFIG_FILE_LAST_LINE_MARKER = ",,";
119
120 // Set once in constructor and not updated. Access it without lock so that it can be accessed
121 // quickly.
122 private final HashSet<String> mEnabledFeatures;
123
124 private final Context mContext;
125
126 private final List<String> mDefaultEnabledFeaturesFromConfig;
127 private final List<String> mDisabledFeaturesFromVhal;
128
Keun young Parkb241d022020-04-20 20:31:34 -0700129 private final HandlerThread mHandlerThread = CarServiceUtils.getHandlerThread(
130 getClass().getSimpleName());
131 private final Handler mHandler = new Handler(mHandlerThread.getLooper());
Keun young Park9a91efb2019-11-15 18:10:47 -0800132 private final Object mLock = new Object();
133
134 @GuardedBy("mLock")
135 private final AtomicFile mFeatureConfigFile;
136
137 @GuardedBy("mLock")
138 private final List<String> mPendingEnabledFeatures = new ArrayList<>();
139
140 @GuardedBy("mLock")
141 private final List<String> mPendingDisabledFeatures = new ArrayList<>();
142
143 @GuardedBy("mLock")
144 private HashSet<String> mAvailableExperimentalFeatures = new HashSet<>();
145
Keun young Park9a91efb2019-11-15 18:10:47 -0800146 public CarFeatureController(@NonNull Context context,
147 @NonNull String[] defaultEnabledFeaturesFromConfig,
148 @NonNull String[] disabledFeaturesFromVhal, @NonNull File dataDir) {
Keun young Parkae34db62021-06-23 15:10:20 -0700149 if (!Build.IS_USER) {
150 OPTIONAL_FEATURES.addAll(NON_USER_ONLY_FEATURES);
151 }
Keun young Park9a91efb2019-11-15 18:10:47 -0800152 mContext = context;
153 mDefaultEnabledFeaturesFromConfig = Arrays.asList(defaultEnabledFeaturesFromConfig);
154 mDisabledFeaturesFromVhal = Arrays.asList(disabledFeaturesFromVhal);
Eric Jeongbd5fb562020-12-21 13:49:40 -0800155 Slog.i(TAG, "mDefaultEnabledFeaturesFromConfig:" + mDefaultEnabledFeaturesFromConfig
Keun young Park1fd33fe2019-12-19 18:25:14 -0800156 + ",mDisabledFeaturesFromVhal:" + mDisabledFeaturesFromVhal);
Keun young Park9a91efb2019-11-15 18:10:47 -0800157 mEnabledFeatures = new HashSet<>(MANDATORY_FEATURES);
158 mFeatureConfigFile = new AtomicFile(new File(dataDir, FEATURE_CONFIG_FILE_NAME), TAG);
159 boolean shouldLoadDefaultConfig = !mFeatureConfigFile.exists();
160 if (!shouldLoadDefaultConfig) {
161 if (!loadFromConfigFileLocked()) {
162 shouldLoadDefaultConfig = true;
163 }
164 }
Keun young Parkf7134692020-03-10 15:41:03 -0700165 if (!checkMandatoryFeaturesLocked()) { // mandatory feature missing, force default config
166 mEnabledFeatures.clear();
167 mEnabledFeatures.addAll(MANDATORY_FEATURES);
168 shouldLoadDefaultConfig = true;
169 }
Keun young Park9a91efb2019-11-15 18:10:47 -0800170 // Separate if to use this as backup for failure in loadFromConfigFileLocked()
171 if (shouldLoadDefaultConfig) {
172 parseDefaultConfig();
173 dispatchDefaultConfigUpdate();
174 }
Mark Tabry614e06e2020-03-15 03:30:01 -0700175 addSupportFeatures(mEnabledFeatures);
Keun young Park9a91efb2019-11-15 18:10:47 -0800176 }
177
Keun young Park1fd33fe2019-12-19 18:25:14 -0800178 @VisibleForTesting
179 List<String> getDisabledFeaturesFromVhal() {
180 return mDisabledFeaturesFromVhal;
181 }
182
Keun young Park9a91efb2019-11-15 18:10:47 -0800183 @Override
184 public void init() {
185 // nothing should be done here. This should work with only constructor.
186 }
187
188 @Override
189 public void release() {
190 // nothing should be done here.
191 }
192
193 @Override
Felipe Leme176a5fd2021-01-20 15:48:33 -0800194 public void dump(IndentingPrintWriter writer) {
Keun young Park9a91efb2019-11-15 18:10:47 -0800195 writer.println("*CarFeatureController*");
196 writer.println(" mEnabledFeatures:" + mEnabledFeatures);
197 writer.println(" mDefaultEnabledFeaturesFromConfig:" + mDefaultEnabledFeaturesFromConfig);
198 writer.println(" mDisabledFeaturesFromVhal:" + mDisabledFeaturesFromVhal);
199 synchronized (mLock) {
200 writer.println(" mAvailableExperimentalFeatures:" + mAvailableExperimentalFeatures);
201 writer.println(" mPendingEnabledFeatures:" + mPendingEnabledFeatures);
202 writer.println(" mPendingDisabledFeatures:" + mPendingDisabledFeatures);
203 }
204 }
205
206 /** Check {@link Car#isFeatureEnabled(String)} */
207 public boolean isFeatureEnabled(String featureName) {
208 return mEnabledFeatures.contains(featureName);
209 }
210
Keun young Parkf7134692020-03-10 15:41:03 -0700211 private boolean checkMandatoryFeaturesLocked() {
212 // Ensure that mandatory features are always there
213 for (String feature: MANDATORY_FEATURES) {
214 if (!mEnabledFeatures.contains(feature)) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800215 Slog.e(TAG, "Mandatory feature missing in mEnabledFeatures:" + feature);
Keun young Parkf7134692020-03-10 15:41:03 -0700216 return false;
217 }
218 }
219 return true;
220 }
221
Keun young Park9a91efb2019-11-15 18:10:47 -0800222 @FeaturerRequestEnum
223 private int checkFeatureExisting(String featureName) {
224 if (MANDATORY_FEATURES.contains(featureName)) {
225 return Car.FEATURE_REQUEST_MANDATORY;
226 }
227 if (!OPTIONAL_FEATURES.contains(featureName)) {
228 synchronized (mLock) {
229 if (!mAvailableExperimentalFeatures.contains(featureName)) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800230 Slog.e(TAG, "enableFeature requested for non-existing feature:"
Keun young Park9a91efb2019-11-15 18:10:47 -0800231 + featureName);
232 return Car.FEATURE_REQUEST_NOT_EXISTING;
233 }
234 }
235 }
236 return Car.FEATURE_REQUEST_SUCCESS;
237 }
238
239 /** Check {@link Car#enableFeature(String)} */
240 public int enableFeature(String featureName) {
241 assertPermission();
242 int checkResult = checkFeatureExisting(featureName);
243 if (checkResult != Car.FEATURE_REQUEST_SUCCESS) {
244 return checkResult;
245 }
246
247 boolean alreadyEnabled = mEnabledFeatures.contains(featureName);
248 boolean shouldUpdateConfigFile = false;
249 synchronized (mLock) {
250 if (mPendingDisabledFeatures.remove(featureName)) {
251 shouldUpdateConfigFile = true;
252 }
253 if (!mPendingEnabledFeatures.contains(featureName) && !alreadyEnabled) {
254 shouldUpdateConfigFile = true;
255 mPendingEnabledFeatures.add(featureName);
256 }
257 }
258 if (shouldUpdateConfigFile) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800259 Slog.w(TAG, "Enabling feature in config file:" + featureName);
Keun young Park9a91efb2019-11-15 18:10:47 -0800260 dispatchDefaultConfigUpdate();
261 }
262 if (alreadyEnabled) {
263 return Car.FEATURE_REQUEST_ALREADY_IN_THE_STATE;
264 } else {
265 return Car.FEATURE_REQUEST_SUCCESS;
266 }
267 }
268
269 /** Check {@link Car#disableFeature(String)} */
270 public int disableFeature(String featureName) {
271 assertPermission();
272 int checkResult = checkFeatureExisting(featureName);
273 if (checkResult != Car.FEATURE_REQUEST_SUCCESS) {
274 return checkResult;
275 }
276
277 boolean alreadyDisabled = !mEnabledFeatures.contains(featureName);
278 boolean shouldUpdateConfigFile = false;
279 synchronized (mLock) {
280 if (mPendingEnabledFeatures.remove(featureName)) {
281 shouldUpdateConfigFile = true;
282 }
283 if (!mPendingDisabledFeatures.contains(featureName) && !alreadyDisabled) {
284 shouldUpdateConfigFile = true;
285 mPendingDisabledFeatures.add(featureName);
286 }
287 }
288 if (shouldUpdateConfigFile) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800289 Slog.w(TAG, "Disabling feature in config file:" + featureName);
Keun young Park9a91efb2019-11-15 18:10:47 -0800290 dispatchDefaultConfigUpdate();
291 }
292 if (alreadyDisabled) {
293 return Car.FEATURE_REQUEST_ALREADY_IN_THE_STATE;
294 } else {
295 return Car.FEATURE_REQUEST_SUCCESS;
296 }
297 }
298
299 /**
300 * Set available experimental features. Only features set through this call will be allowed to
301 * be enabled for experimental features. Setting this is not allowed for USER build.
302 *
303 * @return True if set is allowed and set. False if experimental feature is not allowed.
304 */
305 public boolean setAvailableExperimentalFeatureList(List<String> experimentalFeatures) {
306 assertPermission();
307 if (Build.IS_USER) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800308 Slog.e(TAG, "Experimental feature list set for USER build",
Keun young Park9a91efb2019-11-15 18:10:47 -0800309 new RuntimeException());
310 return false;
311 }
312 synchronized (mLock) {
313 mAvailableExperimentalFeatures.clear();
314 mAvailableExperimentalFeatures.addAll(experimentalFeatures);
315 }
316 return true;
317 }
318
319 /** Check {@link Car#getAllEnabledFeatures()} */
320 public List<String> getAllEnabledFeatures() {
321 assertPermission();
322 return new ArrayList<>(mEnabledFeatures);
323 }
324
325 /** Check {@link Car#getAllPendingDisabledFeatures()} */
326 public List<String> getAllPendingDisabledFeatures() {
327 assertPermission();
328 synchronized (mLock) {
329 return new ArrayList<>(mPendingDisabledFeatures);
330 }
331 }
332
333 /** Check {@link Car#getAllPendingEnabledFeatures()} */
334 public List<String> getAllPendingEnabledFeatures() {
335 assertPermission();
336 synchronized (mLock) {
337 return new ArrayList<>(mPendingEnabledFeatures);
338 }
339 }
340
341 /** Returns currently enabled experimental features */
342 public @NonNull List<String> getEnabledExperimentalFeatures() {
343 if (Build.IS_USER) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800344 Slog.e(TAG, "getEnabledExperimentalFeatures called in USER build",
Keun young Park9a91efb2019-11-15 18:10:47 -0800345 new RuntimeException());
346 return Collections.emptyList();
347 }
348 ArrayList<String> experimentalFeature = new ArrayList<>();
349 for (String feature: mEnabledFeatures) {
350 if (MANDATORY_FEATURES.contains(feature)) {
351 continue;
352 }
353 if (OPTIONAL_FEATURES.contains(feature)) {
354 continue;
355 }
356 experimentalFeature.add(feature);
357 }
358 return experimentalFeature;
359 }
360
361 void handleCorruptConfigFileLocked(String msg, String line) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800362 Slog.e(TAG, msg + ", considered as corrupt, line:" + line);
Keun young Park9a91efb2019-11-15 18:10:47 -0800363 mEnabledFeatures.clear();
364 }
365
366 private boolean loadFromConfigFileLocked() {
367 // done without lock, should be only called from constructor.
368 FileInputStream fis;
369 try {
370 fis = mFeatureConfigFile.openRead();
371 } catch (FileNotFoundException e) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800372 Slog.i(TAG, "Feature config file not found, this could be 1st boot");
Keun young Park9a91efb2019-11-15 18:10:47 -0800373 return false;
374 }
375 try (BufferedReader reader = new BufferedReader(
376 new InputStreamReader(fis, StandardCharsets.UTF_8))) {
377 boolean lastLinePassed = false;
378 while (true) {
379 String line = reader.readLine();
380 if (line == null) {
381 if (!lastLinePassed) {
382 handleCorruptConfigFileLocked("No last line checksum", "");
383 return false;
384 }
385 break;
386 }
387 if (lastLinePassed && !line.isEmpty()) {
388 handleCorruptConfigFileLocked(
389 "Config file has additional line after last line marker", line);
390 return false;
391 } else {
392 if (line.startsWith(CONFIG_FILE_LAST_LINE_MARKER)) {
393 int numberOfFeatures;
394 try {
Mark Tabry614e06e2020-03-15 03:30:01 -0700395 numberOfFeatures = Integer.parseInt(line.substring(
Keun young Park9a91efb2019-11-15 18:10:47 -0800396 CONFIG_FILE_LAST_LINE_MARKER.length()));
397 } catch (NumberFormatException e) {
398 handleCorruptConfigFileLocked(
399 "Config file has corrupt last line, not a number",
400 line);
401 return false;
402 }
403 int actualNumberOfFeatures = mEnabledFeatures.size();
404 if (numberOfFeatures != actualNumberOfFeatures) {
405 handleCorruptConfigFileLocked(
406 "Config file has wrong number of features, expected:"
407 + numberOfFeatures
408 + " actual:" + actualNumberOfFeatures, line);
409 return false;
410 }
411 lastLinePassed = true;
412 } else {
413 mEnabledFeatures.add(line);
414 }
415 }
416 }
417 } catch (IOException e) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800418 Slog.w(TAG, "Cannot load config file", e);
Keun young Park9a91efb2019-11-15 18:10:47 -0800419 return false;
420 }
Eric Jeongbd5fb562020-12-21 13:49:40 -0800421 Slog.i(TAG, "Loaded features:" + mEnabledFeatures);
Keun young Park9a91efb2019-11-15 18:10:47 -0800422 return true;
423 }
424
Mark Tabry1b14be32020-03-25 11:53:58 -0700425 private void persistToFeatureConfigFile(HashSet<String> features) {
Mark Tabry614e06e2020-03-15 03:30:01 -0700426 removeSupportFeatures(features);
Keun young Park9a91efb2019-11-15 18:10:47 -0800427 synchronized (mLock) {
428 features.removeAll(mPendingDisabledFeatures);
429 features.addAll(mPendingEnabledFeatures);
430 FileOutputStream fos;
431 try {
432 fos = mFeatureConfigFile.startWrite();
433 } catch (IOException e) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800434 Slog.e(TAG, "Cannot create config file", e);
Keun young Park9a91efb2019-11-15 18:10:47 -0800435 return;
436 }
437 try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(fos,
438 StandardCharsets.UTF_8))) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800439 Slog.i(TAG, "Updating features:" + features);
Keun young Park9a91efb2019-11-15 18:10:47 -0800440 for (String feature : features) {
441 writer.write(feature);
442 writer.newLine();
443 }
444 writer.write(CONFIG_FILE_LAST_LINE_MARKER + features.size());
445 writer.flush();
446 mFeatureConfigFile.finishWrite(fos);
447 } catch (IOException e) {
448 mFeatureConfigFile.failWrite(fos);
Eric Jeongbd5fb562020-12-21 13:49:40 -0800449 Slog.e(TAG, "Cannot create config file", e);
Keun young Park9a91efb2019-11-15 18:10:47 -0800450 }
451 }
452 }
453
454 private void assertPermission() {
455 ICarImpl.assertPermission(mContext, Car.PERMISSION_CONTROL_CAR_FEATURES);
456 }
457
458 private void dispatchDefaultConfigUpdate() {
Mark Tabry1b14be32020-03-25 11:53:58 -0700459 mHandler.removeCallbacksAndMessages(null);
460 HashSet<String> featuresToPersist = new HashSet<>(mEnabledFeatures);
461 mHandler.post(() -> persistToFeatureConfigFile(featuresToPersist));
Keun young Park9a91efb2019-11-15 18:10:47 -0800462 }
463
464 private void parseDefaultConfig() {
465 for (String feature : mDefaultEnabledFeaturesFromConfig) {
Keun young Parkae34db62021-06-23 15:10:20 -0700466 if (mDisabledFeaturesFromVhal.contains(feature)) {
467 continue;
468 }
469 if (OPTIONAL_FEATURES.contains(feature)) {
470 mEnabledFeatures.add(feature);
471 } else if (NON_USER_ONLY_FEATURES.contains(feature)) {
472 Slog.e(TAG,
473 "config_default_enabled_optional_car_features including "
474 + "user build only feature, will be ignored:" + feature);
475 } else {
Keun young Park9a91efb2019-11-15 18:10:47 -0800476 throw new IllegalArgumentException(
477 "config_default_enabled_optional_car_features include non-optional "
478 + "features:" + feature);
479 }
Keun young Park9a91efb2019-11-15 18:10:47 -0800480 }
Eric Jeongbd5fb562020-12-21 13:49:40 -0800481 Slog.i(TAG, "Loaded default features:" + mEnabledFeatures);
Keun young Park9a91efb2019-11-15 18:10:47 -0800482 }
Mark Tabry614e06e2020-03-15 03:30:01 -0700483
484 private static void addSupportFeatures(Collection<String> features) {
485 SUPPORT_FEATURES.stream()
486 .filter(entry -> features.contains(entry.first))
487 .forEach(entry -> features.add(entry.second));
488 }
489
490 private static void removeSupportFeatures(Collection<String> features) {
491 SUPPORT_FEATURES.stream()
492 .filter(entry -> features.contains(entry.first))
493 .forEach(entry -> features.remove(entry.second));
494 }
Keun young Park9a91efb2019-11-15 18:10:47 -0800495}