• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package com.android.car;
18 
19 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
20 
21 import android.annotation.NonNull;
22 import android.car.Car;
23 import android.car.Car.FeaturerRequestEnum;
24 import android.car.CarFeatures;
25 import android.car.builtin.os.BuildHelper;
26 import android.car.builtin.util.AtomicFileHelper;
27 import android.car.builtin.util.Slogf;
28 import android.car.feature.Flags;
29 import android.content.Context;
30 import android.content.res.Resources;
31 import android.hardware.automotive.vehicle.VehicleProperty;
32 import android.os.Handler;
33 import android.os.HandlerThread;
34 import android.util.ArraySet;
35 import android.util.AtomicFile;
36 import android.util.Pair;
37 import android.util.proto.ProtoOutputStream;
38 
39 import com.android.car.hal.HalPropValue;
40 import com.android.car.hal.VehicleHal;
41 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
42 import com.android.car.internal.util.IndentingPrintWriter;
43 import com.android.internal.annotations.GuardedBy;
44 import com.android.internal.annotations.VisibleForTesting;
45 
46 import java.io.BufferedReader;
47 import java.io.BufferedWriter;
48 import java.io.File;
49 import java.io.FileInputStream;
50 import java.io.FileNotFoundException;
51 import java.io.FileOutputStream;
52 import java.io.IOException;
53 import java.io.InputStreamReader;
54 import java.io.OutputStreamWriter;
55 import java.nio.charset.StandardCharsets;
56 import java.util.ArrayList;
57 import java.util.Arrays;
58 import java.util.Collection;
59 import java.util.List;
60 
61 /**
62  * Component controlling the feature of car.
63  */
64 public final class CarFeatureController implements CarServiceBase {
65 
66     private static final String TAG = CarLog.tagFor(CarFeatureController.class);
67     private static final int INITIAL_VHAL_GET_RETRY = 2;
68 
69     // We define this here for compatibility with older feature lists only
70     private static final String BLUETOOTH_SERVICE = "car_bluetooth";
71 
72     private static final List<String> NON_FLAGGED_MANDATORY_FEATURES = List.of(
73             Car.APP_FOCUS_SERVICE,
74             Car.AUDIO_SERVICE,
75             Car.CAR_ACTIVITY_SERVICE,
76             Car.CAR_BUGREPORT_SERVICE,
77             Car.CAR_DEVICE_POLICY_SERVICE,
78             Car.CAR_DRIVING_STATE_SERVICE,
79             Car.CAR_INPUT_SERVICE,
80             Car.CAR_MEDIA_SERVICE,
81             Car.CAR_OCCUPANT_ZONE_SERVICE,
82             Car.CAR_PERFORMANCE_SERVICE,
83             Car.CAR_USER_SERVICE,
84             Car.CAR_UX_RESTRICTION_SERVICE,
85             Car.CAR_WATCHDOG_SERVICE,
86             Car.INFO_SERVICE,
87             Car.PACKAGE_SERVICE,
88             Car.POWER_SERVICE,
89             Car.PROJECTION_SERVICE,
90             Car.PROPERTY_SERVICE,
91             Car.TEST_SERVICE,
92             // All items below here are deprecated, but still should be supported
93             BLUETOOTH_SERVICE,
94             Car.CABIN_SERVICE,
95             Car.HVAC_SERVICE,
96             Car.SENSOR_SERVICE,
97             Car.VENDOR_EXTENSION_SERVICE
98     );
99 
100     private static final ArraySet<String> FLAGGED_MANDATORY_FEATURES = new ArraySet<>(1);
101 
102     static {
103         if (Flags.persistApSettings()) {
104             FLAGGED_MANDATORY_FEATURES.add(Car.CAR_WIFI_SERVICE);
105         }
106 
107         // Note: if a new entry is added here, the capacity of FLAGGED_MANDATORY_FEATURES
108         // should also be increased.
109     }
110 
111     // Use ArraySet for better search performance. Memory consumption is fixed and it not an issue.
112     // Should keep alphabetical order under each bucket.
113     // Update CarFeatureTest as well when this is updated.
114     private static final ArraySet<String> MANDATORY_FEATURES = combineFeatures(
115             NON_FLAGGED_MANDATORY_FEATURES,
116             FLAGGED_MANDATORY_FEATURES);
117 
118     private static final List<String> NON_FLAGGED_OPTIONAL_FEATURES = List.of(
119             CarFeatures.FEATURE_CAR_USER_NOTICE_SERVICE,
120             Car.CLUSTER_HOME_SERVICE,
121             Car.CAR_NAVIGATION_SERVICE,
122             Car.CAR_OCCUPANT_CONNECTION_SERVICE,
123             Car.CAR_REMOTE_DEVICE_SERVICE,
124             Car.DIAGNOSTIC_SERVICE,
125             Car.OCCUPANT_AWARENESS_SERVICE,
126             Car.STORAGE_MONITORING_SERVICE,
127             Car.VEHICLE_MAP_SERVICE,
128             Car.CAR_TELEMETRY_SERVICE,
129             Car.CAR_EVS_SERVICE,
130             Car.CAR_REMOTE_ACCESS_SERVICE,
131             Car.EXPERIMENTAL_CAR_KEYGUARD_SERVICE,
132             // All items below here are deprecated, but still could be supported
133             Car.CAR_INSTRUMENT_CLUSTER_SERVICE
134     );
135 
136     private static final ArraySet<String> FLAGGED_OPTIONAL_FEATURES = new ArraySet<>(1);
137 
138     static {
139         // TODO(b/327682912): Move to packages/services/Car/service/res/values/config.xml,
140         //  when removing the feature flag
141         if (Flags.displayCompatibility()) {
142             FLAGGED_OPTIONAL_FEATURES.add(Car.CAR_DISPLAY_COMPAT_SERVICE);
143         }
144         // Note: if a new entry is added here, the capacity of FLAGGED_OPTIONAL_FEATURES
145         // should also be increased.
146     }
147 
148     private static final ArraySet<String> OPTIONAL_FEATURES = combineFeatures(
149             NON_FLAGGED_OPTIONAL_FEATURES, FLAGGED_OPTIONAL_FEATURES);
150 
151     // This is a feature still under development and cannot be enabled in user build.
152     private static final ArraySet<String> NON_USER_ONLY_FEATURES = new ArraySet<>();
153 
154     static {
155         if (Flags.carPropertySimulation()) {
156             NON_USER_ONLY_FEATURES.add(Car.CAR_PROPERTY_SIMULATION_SERVICE);
157         }
158     }
159 
160     // Features that depend on another feature being enabled (i.e. legacy API support).
161     // For example, VMS_SUBSCRIBER_SERVICE will be enabled if VEHICLE_MAP_SERVICE is enabled
162     // and disabled if VEHICLE_MAP_SERVICE is disabled.
163     private static final List<Pair<String, String>> SUPPORT_FEATURES = List.of(
164             Pair.create(Car.VEHICLE_MAP_SERVICE, Car.VMS_SUBSCRIBER_SERVICE)
165     );
166 
167     private static final String FEATURE_CONFIG_FILE_NAME = "car_feature_config.txt";
168 
169     // Last line starts with this with number of features for extra confidence check.
170     private static final String CONFIG_FILE_LAST_LINE_MARKER = ",,";
171 
172     // This hash is generated using the featured enabled via config.xml file of resources. Whenever
173     // feature are updated in resource file, we should regenerate {@code FEATURE_CONFIG_FILE_NAME}.
174     private static final String CONFIG_FILE_HASH_MARKER = "Hash:";
175 
176     // Set once in constructor and not updated. Access it without lock so that it can be accessed
177     // quickly.
178     private final ArraySet<String> mEnabledFeatures;
179 
180     private final Context mContext;
181 
182     private final List<String> mDefaultEnabledFeaturesFromConfig;
183     private final List<String> mNonUserDefaultEnabledFeaturesFromConfig;
184     private final List<String> mDisabledFeaturesFromVhal;
185 
186     private final HandlerThread mHandlerThread = CarServiceUtils.getHandlerThread(
187             getClass().getSimpleName());
188     private final Handler mHandler = new Handler(mHandlerThread.getLooper());
189     private final Object mLock = new Object();
190 
191     @GuardedBy("mLock")
192     private final AtomicFile mFeatureConfigFile;
193 
194     @GuardedBy("mLock")
195     private final List<String> mPendingEnabledFeatures = new ArrayList<>();
196 
197     @GuardedBy("mLock")
198     private final List<String> mPendingDisabledFeatures = new ArrayList<>();
199 
200     @GuardedBy("mLock")
201     private ArraySet<String> mAvailableExperimentalFeatures = new ArraySet<>();
202 
CarFeatureController(@onNull Context context, @NonNull File dataDir, VehicleHal hal)203     public CarFeatureController(@NonNull Context context, @NonNull File dataDir, VehicleHal hal) {
204         if (!BuildHelper.isUserBuild()) {
205             OPTIONAL_FEATURES.addAll(NON_USER_ONLY_FEATURES);
206         }
207         mContext = context;
208         String[] disabledFeaturesFromVhal = null;
209         HalPropValue disabledOptionalFeatureValue = hal.getIfSupportedOrFailForEarlyStage(
210                 VehicleProperty.DISABLED_OPTIONAL_FEATURES, INITIAL_VHAL_GET_RETRY);
211         if (disabledOptionalFeatureValue != null) {
212             String disabledFeatures = disabledOptionalFeatureValue.getStringValue();
213             if (disabledFeatures != null && !disabledFeatures.isEmpty()) {
214                 disabledFeaturesFromVhal = disabledFeatures.split(",");
215             }
216         }
217         if (disabledFeaturesFromVhal == null) {
218             disabledFeaturesFromVhal = new String[0];
219         }
220         Resources res = mContext.getResources();
221         String[] defaultEnabledFeatures = res.getStringArray(
222                 R.array.config_allowed_optional_car_features);
223         String[] nonUserDefaultEnabledFeatures = res.getStringArray(
224                 R.array.config_allowed_optional_car_features_non_user_builds);
225         Arrays.sort(defaultEnabledFeatures);
226         Arrays.sort(nonUserDefaultEnabledFeatures);
227         mDefaultEnabledFeaturesFromConfig = Arrays.asList(defaultEnabledFeatures);
228         mNonUserDefaultEnabledFeaturesFromConfig = Arrays.asList(nonUserDefaultEnabledFeatures);
229         mDisabledFeaturesFromVhal = Arrays.asList(disabledFeaturesFromVhal);
230         Slogf.i(TAG, "mDefaultEnabledFeaturesFromConfig:" + mDefaultEnabledFeaturesFromConfig
231                 + ",mDisabledFeaturesFromVhal:" + mDisabledFeaturesFromVhal
232                 + ", mNonUserDefaultEnabledFeaturesFromConfig:"
233                 + mNonUserDefaultEnabledFeaturesFromConfig);
234         mEnabledFeatures = new ArraySet<>(MANDATORY_FEATURES);
235         mFeatureConfigFile = new AtomicFile(new File(dataDir, FEATURE_CONFIG_FILE_NAME));
236         boolean shouldLoadDefaultConfig = !AtomicFileHelper.exists(mFeatureConfigFile);
237         if (!shouldLoadDefaultConfig) {
238             if (!loadFromConfigFileLocked()) {
239                 shouldLoadDefaultConfig = true;
240             }
241         }
242         if (!checkMandatoryFeaturesLocked()) { // mandatory feature missing, force default config
243             mEnabledFeatures.clear();
244             mEnabledFeatures.addAll(MANDATORY_FEATURES);
245             shouldLoadDefaultConfig = true;
246         }
247         // Separate if to use this as backup for failure in loadFromConfigFileLocked()
248         if (shouldLoadDefaultConfig) {
249             parseDefaultConfig();
250             if (!BuildHelper.isUserBuild()) {
251                 parseNonUserAllowedConfigs();
252             }
253             dispatchDefaultConfigUpdate();
254         }
255         addSupportFeatures(mEnabledFeatures);
256     }
257 
258     @VisibleForTesting
getDisabledFeaturesFromVhal()259     List<String> getDisabledFeaturesFromVhal() {
260         return mDisabledFeaturesFromVhal;
261     }
262 
263     @Override
init()264     public void init() {
265         // nothing should be done here. This should work with only constructor.
266     }
267 
268     @Override
release()269     public void release() {
270         // nothing should be done here.
271     }
272 
273     @Override
274     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(IndentingPrintWriter writer)275     public void dump(IndentingPrintWriter writer) {
276         writer.println("*CarFeatureController*");
277         writer.println(" mEnabledFeatures:" + mEnabledFeatures);
278         writer.println(" mDefaultEnabledFeaturesFromConfig:" + mDefaultEnabledFeaturesFromConfig);
279         writer.println(" mNonUserDefaultEnabledFeaturesFromConfig:"
280                 + mNonUserDefaultEnabledFeaturesFromConfig);
281         writer.println(" mDisabledFeaturesFromVhal:" + mDisabledFeaturesFromVhal);
282         synchronized (mLock) {
283             writer.println(" mAvailableExperimentalFeatures:" + mAvailableExperimentalFeatures);
284             writer.println(" mPendingEnabledFeatures:" + mPendingEnabledFeatures);
285             writer.println(" mPendingDisabledFeatures:" + mPendingDisabledFeatures);
286             dumpConfigFile(writer);
287         }
288     }
289 
290     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dumpConfigFile(IndentingPrintWriter writer)291     private void dumpConfigFile(IndentingPrintWriter writer) {
292         writer.println(" mFeatureConfigFile:");
293         FileInputStream fis;
294         try {
295             synchronized (mLock) {
296                 fis = mFeatureConfigFile.openRead();
297             }
298         } catch (FileNotFoundException e) {
299             Slogf.i(TAG, "Feature config file not found");
300             return;
301         }
302         writer.increaseIndent();
303         try (BufferedReader reader =
304                      new BufferedReader(new InputStreamReader(fis, StandardCharsets.UTF_8))) {
305             while (true) {
306                 String line = reader.readLine();
307                 if (line == null) {
308                     break;
309                 }
310                 writer.println(line);
311             }
312         } catch (IOException e) {
313             Slogf.w(TAG, "Cannot read config file", e);
314         } finally {
315             try {
316                 fis.close();
317             } catch (IOException e) {
318                 Slogf.e(TAG, "Couldn't close FileInputStream");
319             }
320         }
321         writer.decreaseIndent();
322     }
323 
324     @Override
325     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dumpProto(ProtoOutputStream proto)326     public void dumpProto(ProtoOutputStream proto) {
327         for (int i = 0; i < mEnabledFeatures.size(); i++) {
328             String enabledFeature = mEnabledFeatures.valueAt(i);
329             proto.write(CarFeatureControlDumpProto.ENABLED_FEATURES, enabledFeature);
330         }
331         for (int i = 0; i < mDefaultEnabledFeaturesFromConfig.size(); i++) {
332             String defaultEnabledFeature = mDefaultEnabledFeaturesFromConfig.get(i);
333             proto.write(CarFeatureControlDumpProto.DEFAULT_ENABLED_FEATURES_FROM_CONFIG,
334                     defaultEnabledFeature);
335         }
336         for (int i = 0; i < mNonUserDefaultEnabledFeaturesFromConfig.size(); i++) {
337             String defaultEnabledFeature = mNonUserDefaultEnabledFeaturesFromConfig.get(i);
338             proto.write(CarFeatureControlDumpProto.DEFAULT_ENABLED_FEATURES_FOR_NONUSER_FROM_CONFIG,
339                     defaultEnabledFeature);
340         }
341         for (int i = 0; i < mDisabledFeaturesFromVhal.size(); i++) {
342             String disabledFeature = mDisabledFeaturesFromVhal.get(i);
343             proto.write(CarFeatureControlDumpProto.DISABLED_FEATURES_FROM_VHAL,
344                     disabledFeature);
345         }
346         synchronized (mLock) {
347             for (int i = 0; i < mAvailableExperimentalFeatures.size(); i++) {
348                 String experimentalFeature = mAvailableExperimentalFeatures.valueAt(i);
349                 proto.write(CarFeatureControlDumpProto.AVAILABLE_EXPERIMENTAL_FEATURES,
350                         experimentalFeature);
351             }
352             for (int i = 0; i < mPendingEnabledFeatures.size(); i++) {
353                 String pendingEnabledFeature = mPendingEnabledFeatures.get(i);
354                 proto.write(CarFeatureControlDumpProto.PENDING_ENABLED_FEATURES,
355                         pendingEnabledFeature);
356             }
357             for (int i = 0; i < mPendingDisabledFeatures.size(); i++) {
358                 String pendingDisabledFeature = mPendingDisabledFeatures.get(i);
359                 proto.write(CarFeatureControlDumpProto.PENDING_DISABLED_FEATURES,
360                         pendingDisabledFeature);
361             }
362         }
363     }
364 
365     /** Check {@link Car#isFeatureEnabled(String)} */
isFeatureEnabled(String featureName)366     public boolean isFeatureEnabled(String featureName) {
367         return mEnabledFeatures.contains(featureName);
368     }
369 
checkMandatoryFeaturesLocked()370     private boolean checkMandatoryFeaturesLocked() {
371         // Ensure that mandatory features are always there
372         for (int i = 0; i < MANDATORY_FEATURES.size(); i++) {
373             String mandatoryFeature = MANDATORY_FEATURES.valueAt(i);
374             if (!mEnabledFeatures.contains(mandatoryFeature)) {
375                 Slogf.e(TAG, "Mandatory feature missing in mEnabledFeatures:" + mandatoryFeature);
376                 return false;
377             }
378         }
379         return true;
380     }
381 
382     @FeaturerRequestEnum
checkFeatureExisting(String featureName)383     private int checkFeatureExisting(String featureName) {
384         if (MANDATORY_FEATURES.contains(featureName)) {
385             return Car.FEATURE_REQUEST_MANDATORY;
386         }
387         if (!OPTIONAL_FEATURES.contains(featureName)) {
388             synchronized (mLock) {
389                 if (!mAvailableExperimentalFeatures.contains(featureName)) {
390                     Slogf.e(TAG, "enableFeature requested for non-existing feature:"
391                             + featureName);
392                     return Car.FEATURE_REQUEST_NOT_EXISTING;
393                 }
394             }
395         }
396         return Car.FEATURE_REQUEST_SUCCESS;
397     }
398 
399     /** Check {@link Car#enableFeature(String)} */
enableFeature(String featureName)400     public int enableFeature(String featureName) {
401         assertPermission();
402         int checkResult = checkFeatureExisting(featureName);
403         if (checkResult != Car.FEATURE_REQUEST_SUCCESS) {
404             return checkResult;
405         }
406 
407         boolean alreadyEnabled = mEnabledFeatures.contains(featureName);
408         boolean shouldUpdateConfigFile = false;
409         synchronized (mLock) {
410             if (mPendingDisabledFeatures.remove(featureName)) {
411                 shouldUpdateConfigFile = true;
412             }
413             if (!mPendingEnabledFeatures.contains(featureName) && !alreadyEnabled) {
414                 shouldUpdateConfigFile = true;
415                 mPendingEnabledFeatures.add(featureName);
416             }
417         }
418         if (shouldUpdateConfigFile) {
419             Slogf.w(TAG, "Enabling feature in config file:" + featureName);
420             dispatchDefaultConfigUpdate();
421         }
422         if (alreadyEnabled) {
423             return Car.FEATURE_REQUEST_ALREADY_IN_THE_STATE;
424         } else {
425             return Car.FEATURE_REQUEST_SUCCESS;
426         }
427     }
428 
429     /** Check {@link Car#disableFeature(String)} */
disableFeature(String featureName)430     public int disableFeature(String featureName) {
431         assertPermission();
432         int checkResult = checkFeatureExisting(featureName);
433         if (checkResult != Car.FEATURE_REQUEST_SUCCESS) {
434             return checkResult;
435         }
436 
437         boolean alreadyDisabled = !mEnabledFeatures.contains(featureName);
438         boolean shouldUpdateConfigFile = false;
439         synchronized (mLock) {
440             if (mPendingEnabledFeatures.remove(featureName)) {
441                 shouldUpdateConfigFile = true;
442             }
443             if (!mPendingDisabledFeatures.contains(featureName) && !alreadyDisabled) {
444                 shouldUpdateConfigFile = true;
445                 mPendingDisabledFeatures.add(featureName);
446             }
447         }
448         if (shouldUpdateConfigFile) {
449             Slogf.w(TAG, "Disabling feature in config file:" + featureName);
450             dispatchDefaultConfigUpdate();
451         }
452         if (alreadyDisabled) {
453             return Car.FEATURE_REQUEST_ALREADY_IN_THE_STATE;
454         } else {
455             return Car.FEATURE_REQUEST_SUCCESS;
456         }
457     }
458 
459     /**
460      * Set available experimental features. Only features set through this call will be allowed to
461      * be enabled for experimental features. Setting this is not allowed for USER build.
462      *
463      * @return True if set is allowed and set. False if experimental feature is not allowed.
464      */
setAvailableExperimentalFeatureList(List<String> experimentalFeatures)465     public boolean setAvailableExperimentalFeatureList(List<String> experimentalFeatures) {
466         assertPermission();
467         if (BuildHelper.isUserBuild()) {
468             Slogf.e(TAG, "Experimental feature list set for USER build", new RuntimeException());
469             return false;
470         }
471         synchronized (mLock) {
472             mAvailableExperimentalFeatures.clear();
473             mAvailableExperimentalFeatures.addAll(experimentalFeatures);
474         }
475         return true;
476     }
477 
478     /** Check {@link Car#getAllEnabledFeatures()} */
getAllEnabledFeatures()479     public List<String> getAllEnabledFeatures() {
480         assertPermission();
481         return new ArrayList<>(mEnabledFeatures);
482     }
483 
484     /** Check {@link Car#getAllPendingDisabledFeatures()} */
getAllPendingDisabledFeatures()485     public List<String> getAllPendingDisabledFeatures() {
486         assertPermission();
487         synchronized (mLock) {
488             return new ArrayList<>(mPendingDisabledFeatures);
489         }
490     }
491 
492     /** Check {@link Car#getAllPendingEnabledFeatures()} */
getAllPendingEnabledFeatures()493     public List<String> getAllPendingEnabledFeatures() {
494         assertPermission();
495         synchronized (mLock) {
496             return new ArrayList<>(mPendingEnabledFeatures);
497         }
498     }
499 
500     /** Returns currently enabled experimental features */
getEnabledExperimentalFeatures()501     public @NonNull List<String> getEnabledExperimentalFeatures() {
502         ArrayList<String> experimentalFeature = new ArrayList<>();
503         if (BuildHelper.isUserBuild()) {
504             Slogf.e(TAG, "getEnabledExperimentalFeatures called in USER build",
505                     new RuntimeException());
506             return experimentalFeature;
507         }
508         for (int i = 0; i < mEnabledFeatures.size(); i++) {
509             String enabledFeature = mEnabledFeatures.valueAt(i);
510             if (MANDATORY_FEATURES.contains(enabledFeature)) {
511                 continue;
512             }
513             if (OPTIONAL_FEATURES.contains(enabledFeature)) {
514                 continue;
515             }
516             experimentalFeature.add(enabledFeature);
517         }
518         return experimentalFeature;
519     }
520 
handleCorruptConfigFileLocked(String msg, String line)521     void handleCorruptConfigFileLocked(String msg, String line) {
522         Slogf.e(TAG, msg + ", considered as corrupt, line:" + line);
523         mEnabledFeatures.clear();
524     }
525 
526     // TODO(b/227645920): add unit test
527     @GuardedBy("mLock")
loadFromConfigFileLocked()528     private boolean loadFromConfigFileLocked() {
529         // done without lock, should be only called from constructor.
530         FileInputStream fis;
531         try {
532             fis = mFeatureConfigFile.openRead();
533         } catch (FileNotFoundException e) {
534             Slogf.i(TAG, "Feature config file not found, this could be 1st boot");
535             return false;
536         }
537         try (BufferedReader reader =
538                      new BufferedReader(new InputStreamReader(fis, StandardCharsets.UTF_8))) {
539             boolean lastLinePassed = false;
540             boolean hashChecked = false;
541             while (true) {
542                 String line = reader.readLine();
543                 if (line == null) {
544                     if (!lastLinePassed) {
545                         handleCorruptConfigFileLocked("No last line checksum", "");
546                         return false;
547                     }
548                     break;
549                 }
550                 if (lastLinePassed && !line.isEmpty()) {
551                     handleCorruptConfigFileLocked(
552                             "Config file has additional line after last line marker", line);
553                     return false;
554                 } else {
555                     if (line.startsWith(CONFIG_FILE_HASH_MARKER)) {
556                         int expectedHashValue = mDefaultEnabledFeaturesFromConfig.hashCode();
557                         int fileHashValue = Integer
558                                 .parseInt(line.substring(CONFIG_FILE_HASH_MARKER.length()).strip());
559                         if (expectedHashValue != fileHashValue) {
560                             handleCorruptConfigFileLocked("Config hash doesn't match. Expected: "
561                                     + expectedHashValue, line);
562                             return false;
563                         }
564                         hashChecked = true;
565                         continue;
566                     }
567 
568                     if (!hashChecked) {
569                         handleCorruptConfigFileLocked("Config file doesn't have hash value.", "");
570                         return false;
571                     }
572 
573                     if (line.startsWith(CONFIG_FILE_LAST_LINE_MARKER)) {
574                         int numberOfFeatures;
575                         try {
576                             numberOfFeatures = Integer.parseInt(
577                                     line.substring(CONFIG_FILE_LAST_LINE_MARKER.length()));
578                         } catch (NumberFormatException e) {
579                             handleCorruptConfigFileLocked(
580                                     "Config file has corrupt last line, not a number", line);
581                             return false;
582                         }
583                         int actualNumberOfFeatures = mEnabledFeatures.size();
584                         if (numberOfFeatures != actualNumberOfFeatures) {
585                             handleCorruptConfigFileLocked(
586                                     "Config file has wrong number of features, expected:"
587                                             + numberOfFeatures + " actual:"
588                                             + actualNumberOfFeatures, line);
589                             return false;
590                         }
591                         lastLinePassed = true;
592                     } else {
593                         mEnabledFeatures.add(line);
594                     }
595                 }
596             }
597         } catch (IOException e) {
598             Slogf.w(TAG, "Cannot load config file", e);
599             return false;
600         }
601         Slogf.i(TAG, "Loaded features:" + mEnabledFeatures);
602         return true;
603     }
604 
persistToFeatureConfigFile(ArraySet<String> features)605     private void persistToFeatureConfigFile(ArraySet<String> features) {
606         removeSupportFeatures(features);
607         synchronized (mLock) {
608             features.removeAll(mPendingDisabledFeatures);
609             features.addAll(mPendingEnabledFeatures);
610             FileOutputStream fos;
611             try {
612                 fos = mFeatureConfigFile.startWrite();
613             } catch (IOException e) {
614                 Slogf.e(TAG, "Cannot create config file", e);
615                 return;
616             }
617             try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(fos,
618                     StandardCharsets.UTF_8))) {
619                 Slogf.i(TAG, "Updating features:" + features);
620                 writer.write(CONFIG_FILE_HASH_MARKER
621                         + mDefaultEnabledFeaturesFromConfig.hashCode());
622                 writer.newLine();
623                 for (int i = 0; i < features.size(); i++) {
624                     String feature = features.valueAt(i);
625                     writer.write(feature);
626                     writer.newLine();
627                 }
628                 writer.write(CONFIG_FILE_LAST_LINE_MARKER + features.size());
629                 writer.flush();
630                 mFeatureConfigFile.finishWrite(fos);
631             } catch (IOException e) {
632                 mFeatureConfigFile.failWrite(fos);
633                 Slogf.e(TAG, "Cannot create config file", e);
634             }
635         }
636     }
637 
assertPermission()638     private void assertPermission() {
639         CarServiceUtils.assertPermission(mContext, Car.PERMISSION_CONTROL_CAR_FEATURES);
640     }
641 
dispatchDefaultConfigUpdate()642     private void dispatchDefaultConfigUpdate() {
643         mHandler.removeCallbacksAndMessages(null);
644         ArraySet<String> featuresToPersist = new ArraySet<>(mEnabledFeatures);
645         mHandler.post(() -> persistToFeatureConfigFile(featuresToPersist));
646     }
647 
parseDefaultConfig()648     private void parseDefaultConfig() {
649         for (int i = 0; i < mDefaultEnabledFeaturesFromConfig.size(); i++) {
650             String defaultEnabledFeature = mDefaultEnabledFeaturesFromConfig.get(i);
651             if (mDisabledFeaturesFromVhal.contains(defaultEnabledFeature)) {
652                 continue;
653             }
654             if (OPTIONAL_FEATURES.contains(defaultEnabledFeature)) {
655                 mEnabledFeatures.add(defaultEnabledFeature);
656             } else if (NON_USER_ONLY_FEATURES.contains(defaultEnabledFeature)) {
657                 Slogf.e(TAG, "config_default_enabled_optional_car_features including "
658                         + "user build only feature, will be ignored:" + defaultEnabledFeature);
659             } else {
660                 Slogf.e(TAG, "config_default_enabled_optional_car_features include "
661                                 + "non-optional features:" + defaultEnabledFeature);
662             }
663         }
664         Slogf.i(TAG, "Loaded default features:" + mEnabledFeatures);
665     }
666 
parseNonUserAllowedConfigs()667     private void parseNonUserAllowedConfigs() {
668         for (int i = 0; i < mNonUserDefaultEnabledFeaturesFromConfig.size(); i++) {
669             String nonUserEnabledFeature = mNonUserDefaultEnabledFeaturesFromConfig.get(i);
670             if (mDisabledFeaturesFromVhal.contains(nonUserEnabledFeature)) {
671                 continue;
672             }
673             if (OPTIONAL_FEATURES.contains(nonUserEnabledFeature)) {
674                 mEnabledFeatures.add(nonUserEnabledFeature);
675             } else {
676                 Slogf.w(TAG, "config_default_enabled_optional_car_features include "
677                         + "non-optional features:" + nonUserEnabledFeature);
678             }
679         }
680         Slogf.i(TAG, "Loaded default and non user features:" + mEnabledFeatures);
681     }
682 
addSupportFeatures(Collection<String> features)683     private static void addSupportFeatures(Collection<String> features) {
684         for (int index = 0; index < SUPPORT_FEATURES.size(); index++) {
685             if (features.contains(SUPPORT_FEATURES.get(index).first)) {
686                 features.add(SUPPORT_FEATURES.get(index).second);
687             }
688         }
689     }
690 
removeSupportFeatures(Collection<String> features)691     private static void removeSupportFeatures(Collection<String> features) {
692         for (int index = 0; index < SUPPORT_FEATURES.size(); index++) {
693             if (features.contains(SUPPORT_FEATURES.get(index).first)) {
694                 features.remove(SUPPORT_FEATURES.get(index).second);
695             }
696         }
697     }
698 
combineFeatures(List<String> features, ArraySet<String> flaggedFeatures)699     private static ArraySet<String> combineFeatures(List<String> features,
700             ArraySet<String> flaggedFeatures) {
701         ArraySet<String> combinedFeatures = new ArraySet<>(features);
702         combinedFeatures.addAll(flaggedFeatures);
703         return combinedFeatures;
704     }
705 }
706