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.EXPERIMENTAL_CAR_USER_SERVICE,
126             Car.OCCUPANT_AWARENESS_SERVICE,
127             Car.STORAGE_MONITORING_SERVICE,
128             Car.VEHICLE_MAP_SERVICE,
129             Car.CAR_TELEMETRY_SERVICE,
130             Car.CAR_EVS_SERVICE,
131             Car.CAR_REMOTE_ACCESS_SERVICE,
132             Car.EXPERIMENTAL_CAR_KEYGUARD_SERVICE,
133             // All items below here are deprecated, but still could be supported
134             Car.CAR_INSTRUMENT_CLUSTER_SERVICE
135     );
136 
137     private static final ArraySet<String> FLAGGED_OPTIONAL_FEATURES = new ArraySet<>(1);
138 
139     static {
140         // TODO(b/327682912): Move to packages/services/Car/service/res/values/config.xml,
141         //  when removing the feature flag
142         if (Flags.displayCompatibility()) {
143             FLAGGED_OPTIONAL_FEATURES.add(Car.CAR_DISPLAY_COMPAT_SERVICE);
144         }
145 
146         // Note: if a new entry is added here, the capacity of FLAGGED_OPTIONAL_FEATURES
147         // should also be increased.
148     }
149 
150     private static final ArraySet<String> OPTIONAL_FEATURES = combineFeatures(
151             NON_FLAGGED_OPTIONAL_FEATURES, FLAGGED_OPTIONAL_FEATURES);
152 
153     // This is a feature still under development and cannot be enabled in user build.
154     private static final ArraySet<String> NON_USER_ONLY_FEATURES = new ArraySet<>();
155 
156     // Features that depend on another feature being enabled (i.e. legacy API support).
157     // For example, VMS_SUBSCRIBER_SERVICE will be enabled if VEHICLE_MAP_SERVICE is enabled
158     // and disabled if VEHICLE_MAP_SERVICE is disabled.
159     private static final List<Pair<String, String>> SUPPORT_FEATURES = List.of(
160             Pair.create(Car.VEHICLE_MAP_SERVICE, Car.VMS_SUBSCRIBER_SERVICE)
161     );
162 
163     private static final String FEATURE_CONFIG_FILE_NAME = "car_feature_config.txt";
164 
165     // Last line starts with this with number of features for extra confidence check.
166     private static final String CONFIG_FILE_LAST_LINE_MARKER = ",,";
167 
168     // This hash is generated using the featured enabled via config.xml file of resources. Whenever
169     // feature are updated in resource file, we should regenerate {@code FEATURE_CONFIG_FILE_NAME}.
170     private static final String CONFIG_FILE_HASH_MARKER = "Hash:";
171 
172     // Set once in constructor and not updated. Access it without lock so that it can be accessed
173     // quickly.
174     private final ArraySet<String> mEnabledFeatures;
175 
176     private final Context mContext;
177 
178     private final List<String> mDefaultEnabledFeaturesFromConfig;
179     private final List<String> mDisabledFeaturesFromVhal;
180 
181     private final HandlerThread mHandlerThread = CarServiceUtils.getHandlerThread(
182             getClass().getSimpleName());
183     private final Handler mHandler = new Handler(mHandlerThread.getLooper());
184     private final Object mLock = new Object();
185 
186     @GuardedBy("mLock")
187     private final AtomicFile mFeatureConfigFile;
188 
189     @GuardedBy("mLock")
190     private final List<String> mPendingEnabledFeatures = new ArrayList<>();
191 
192     @GuardedBy("mLock")
193     private final List<String> mPendingDisabledFeatures = new ArrayList<>();
194 
195     @GuardedBy("mLock")
196     private ArraySet<String> mAvailableExperimentalFeatures = new ArraySet<>();
197 
CarFeatureController(@onNull Context context, @NonNull File dataDir, VehicleHal hal)198     public CarFeatureController(@NonNull Context context, @NonNull File dataDir, VehicleHal hal) {
199         if (!BuildHelper.isUserBuild()) {
200             OPTIONAL_FEATURES.addAll(NON_USER_ONLY_FEATURES);
201         }
202         mContext = context;
203         String[] disabledFeaturesFromVhal = null;
204         HalPropValue disabledOptionalFeatureValue = hal.getIfSupportedOrFailForEarlyStage(
205                 VehicleProperty.DISABLED_OPTIONAL_FEATURES, INITIAL_VHAL_GET_RETRY);
206         if (disabledOptionalFeatureValue != null) {
207             String disabledFeatures = disabledOptionalFeatureValue.getStringValue();
208             if (disabledFeatures != null && !disabledFeatures.isEmpty()) {
209                 disabledFeaturesFromVhal = disabledFeatures.split(",");
210             }
211         }
212         if (disabledFeaturesFromVhal == null) {
213             disabledFeaturesFromVhal = new String[0];
214         }
215         Resources res = mContext.getResources();
216         String[] defaultEnabledFeatures = res.getStringArray(
217                 R.array.config_allowed_optional_car_features);
218         Arrays.sort(defaultEnabledFeatures);
219         mDefaultEnabledFeaturesFromConfig = Arrays.asList(defaultEnabledFeatures);
220         mDisabledFeaturesFromVhal = Arrays.asList(disabledFeaturesFromVhal);
221         Slogf.i(TAG, "mDefaultEnabledFeaturesFromConfig:" + mDefaultEnabledFeaturesFromConfig
222                 + ",mDisabledFeaturesFromVhal:" + mDisabledFeaturesFromVhal);
223         mEnabledFeatures = new ArraySet<>(MANDATORY_FEATURES);
224         mFeatureConfigFile = new AtomicFile(new File(dataDir, FEATURE_CONFIG_FILE_NAME));
225         boolean shouldLoadDefaultConfig = !AtomicFileHelper.exists(mFeatureConfigFile);
226         if (!shouldLoadDefaultConfig) {
227             if (!loadFromConfigFileLocked()) {
228                 shouldLoadDefaultConfig = true;
229             }
230         }
231         if (!checkMandatoryFeaturesLocked()) { // mandatory feature missing, force default config
232             mEnabledFeatures.clear();
233             mEnabledFeatures.addAll(MANDATORY_FEATURES);
234             shouldLoadDefaultConfig = true;
235         }
236         // Separate if to use this as backup for failure in loadFromConfigFileLocked()
237         if (shouldLoadDefaultConfig) {
238             parseDefaultConfig();
239             dispatchDefaultConfigUpdate();
240         }
241         addSupportFeatures(mEnabledFeatures);
242     }
243 
244     @VisibleForTesting
getDisabledFeaturesFromVhal()245     List<String> getDisabledFeaturesFromVhal() {
246         return mDisabledFeaturesFromVhal;
247     }
248 
249     @Override
init()250     public void init() {
251         // nothing should be done here. This should work with only constructor.
252     }
253 
254     @Override
release()255     public void release() {
256         // nothing should be done here.
257     }
258 
259     @Override
260     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(IndentingPrintWriter writer)261     public void dump(IndentingPrintWriter writer) {
262         writer.println("*CarFeatureController*");
263         writer.println(" mEnabledFeatures:" + mEnabledFeatures);
264         writer.println(" mDefaultEnabledFeaturesFromConfig:" + mDefaultEnabledFeaturesFromConfig);
265         writer.println(" mDisabledFeaturesFromVhal:" + mDisabledFeaturesFromVhal);
266         synchronized (mLock) {
267             writer.println(" mAvailableExperimentalFeatures:" + mAvailableExperimentalFeatures);
268             writer.println(" mPendingEnabledFeatures:" + mPendingEnabledFeatures);
269             writer.println(" mPendingDisabledFeatures:" + mPendingDisabledFeatures);
270             dumpConfigFile(writer);
271         }
272     }
273 
274     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dumpConfigFile(IndentingPrintWriter writer)275     private void dumpConfigFile(IndentingPrintWriter writer) {
276         writer.println(" mFeatureConfigFile:");
277         FileInputStream fis;
278         try {
279             synchronized (mLock) {
280                 fis = mFeatureConfigFile.openRead();
281             }
282         } catch (FileNotFoundException e) {
283             Slogf.i(TAG, "Feature config file not found");
284             return;
285         }
286         writer.increaseIndent();
287         try (BufferedReader reader =
288                      new BufferedReader(new InputStreamReader(fis, StandardCharsets.UTF_8))) {
289             while (true) {
290                 String line = reader.readLine();
291                 if (line == null) {
292                     break;
293                 }
294                 writer.println(line);
295             }
296         } catch (IOException e) {
297             Slogf.w(TAG, "Cannot read config file", e);
298         } finally {
299             try {
300                 fis.close();
301             } catch (IOException e) {
302                 Slogf.e(TAG, "Couldn't close FileInputStream");
303             }
304         }
305         writer.decreaseIndent();
306     }
307 
308     @Override
309     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dumpProto(ProtoOutputStream proto)310     public void dumpProto(ProtoOutputStream proto) {
311         for (int i = 0; i < mEnabledFeatures.size(); i++) {
312             String enabledFeature = mEnabledFeatures.valueAt(i);
313             proto.write(CarFeatureControlDumpProto.ENABLED_FEATURES, enabledFeature);
314         }
315         for (int i = 0; i < mDefaultEnabledFeaturesFromConfig.size(); i++) {
316             String defaultEnabledFeature = mDefaultEnabledFeaturesFromConfig.get(i);
317             proto.write(CarFeatureControlDumpProto.DEFAULT_ENABLED_FEATURES_FROM_CONFIG,
318                     defaultEnabledFeature);
319         }
320         for (int i = 0; i < mDisabledFeaturesFromVhal.size(); i++) {
321             String disabledFeature = mDisabledFeaturesFromVhal.get(i);
322             proto.write(CarFeatureControlDumpProto.DISABLED_FEATURES_FROM_VHAL,
323                     disabledFeature);
324         }
325         synchronized (mLock) {
326             for (int i = 0; i < mAvailableExperimentalFeatures.size(); i++) {
327                 String experimentalFeature = mAvailableExperimentalFeatures.valueAt(i);
328                 proto.write(CarFeatureControlDumpProto.AVAILABLE_EXPERIMENTAL_FEATURES,
329                         experimentalFeature);
330             }
331             for (int i = 0; i < mPendingEnabledFeatures.size(); i++) {
332                 String pendingEnabledFeature = mPendingEnabledFeatures.get(i);
333                 proto.write(CarFeatureControlDumpProto.PENDING_ENABLED_FEATURES,
334                         pendingEnabledFeature);
335             }
336             for (int i = 0; i < mPendingDisabledFeatures.size(); i++) {
337                 String pendingDisabledFeature = mPendingDisabledFeatures.get(i);
338                 proto.write(CarFeatureControlDumpProto.PENDING_DISABLED_FEATURES,
339                         pendingDisabledFeature);
340             }
341         }
342     }
343 
344     /** Check {@link Car#isFeatureEnabled(String)} */
isFeatureEnabled(String featureName)345     public boolean isFeatureEnabled(String featureName) {
346         return mEnabledFeatures.contains(featureName);
347     }
348 
checkMandatoryFeaturesLocked()349     private boolean checkMandatoryFeaturesLocked() {
350         // Ensure that mandatory features are always there
351         for (int i = 0; i < MANDATORY_FEATURES.size(); i++) {
352             String mandatoryFeature = MANDATORY_FEATURES.valueAt(i);
353             if (!mEnabledFeatures.contains(mandatoryFeature)) {
354                 Slogf.e(TAG, "Mandatory feature missing in mEnabledFeatures:" + mandatoryFeature);
355                 return false;
356             }
357         }
358         return true;
359     }
360 
361     @FeaturerRequestEnum
checkFeatureExisting(String featureName)362     private int checkFeatureExisting(String featureName) {
363         if (MANDATORY_FEATURES.contains(featureName)) {
364             return Car.FEATURE_REQUEST_MANDATORY;
365         }
366         if (!OPTIONAL_FEATURES.contains(featureName)) {
367             synchronized (mLock) {
368                 if (!mAvailableExperimentalFeatures.contains(featureName)) {
369                     Slogf.e(TAG, "enableFeature requested for non-existing feature:"
370                             + featureName);
371                     return Car.FEATURE_REQUEST_NOT_EXISTING;
372                 }
373             }
374         }
375         return Car.FEATURE_REQUEST_SUCCESS;
376     }
377 
378     /** Check {@link Car#enableFeature(String)} */
enableFeature(String featureName)379     public int enableFeature(String featureName) {
380         assertPermission();
381         int checkResult = checkFeatureExisting(featureName);
382         if (checkResult != Car.FEATURE_REQUEST_SUCCESS) {
383             return checkResult;
384         }
385 
386         boolean alreadyEnabled = mEnabledFeatures.contains(featureName);
387         boolean shouldUpdateConfigFile = false;
388         synchronized (mLock) {
389             if (mPendingDisabledFeatures.remove(featureName)) {
390                 shouldUpdateConfigFile = true;
391             }
392             if (!mPendingEnabledFeatures.contains(featureName) && !alreadyEnabled) {
393                 shouldUpdateConfigFile = true;
394                 mPendingEnabledFeatures.add(featureName);
395             }
396         }
397         if (shouldUpdateConfigFile) {
398             Slogf.w(TAG, "Enabling feature in config file:" + featureName);
399             dispatchDefaultConfigUpdate();
400         }
401         if (alreadyEnabled) {
402             return Car.FEATURE_REQUEST_ALREADY_IN_THE_STATE;
403         } else {
404             return Car.FEATURE_REQUEST_SUCCESS;
405         }
406     }
407 
408     /** Check {@link Car#disableFeature(String)} */
disableFeature(String featureName)409     public int disableFeature(String featureName) {
410         assertPermission();
411         int checkResult = checkFeatureExisting(featureName);
412         if (checkResult != Car.FEATURE_REQUEST_SUCCESS) {
413             return checkResult;
414         }
415 
416         boolean alreadyDisabled = !mEnabledFeatures.contains(featureName);
417         boolean shouldUpdateConfigFile = false;
418         synchronized (mLock) {
419             if (mPendingEnabledFeatures.remove(featureName)) {
420                 shouldUpdateConfigFile = true;
421             }
422             if (!mPendingDisabledFeatures.contains(featureName) && !alreadyDisabled) {
423                 shouldUpdateConfigFile = true;
424                 mPendingDisabledFeatures.add(featureName);
425             }
426         }
427         if (shouldUpdateConfigFile) {
428             Slogf.w(TAG, "Disabling feature in config file:" + featureName);
429             dispatchDefaultConfigUpdate();
430         }
431         if (alreadyDisabled) {
432             return Car.FEATURE_REQUEST_ALREADY_IN_THE_STATE;
433         } else {
434             return Car.FEATURE_REQUEST_SUCCESS;
435         }
436     }
437 
438     /**
439      * Set available experimental features. Only features set through this call will be allowed to
440      * be enabled for experimental features. Setting this is not allowed for USER build.
441      *
442      * @return True if set is allowed and set. False if experimental feature is not allowed.
443      */
setAvailableExperimentalFeatureList(List<String> experimentalFeatures)444     public boolean setAvailableExperimentalFeatureList(List<String> experimentalFeatures) {
445         assertPermission();
446         if (BuildHelper.isUserBuild()) {
447             Slogf.e(TAG, "Experimental feature list set for USER build", new RuntimeException());
448             return false;
449         }
450         synchronized (mLock) {
451             mAvailableExperimentalFeatures.clear();
452             mAvailableExperimentalFeatures.addAll(experimentalFeatures);
453         }
454         return true;
455     }
456 
457     /** Check {@link Car#getAllEnabledFeatures()} */
getAllEnabledFeatures()458     public List<String> getAllEnabledFeatures() {
459         assertPermission();
460         return new ArrayList<>(mEnabledFeatures);
461     }
462 
463     /** Check {@link Car#getAllPendingDisabledFeatures()} */
getAllPendingDisabledFeatures()464     public List<String> getAllPendingDisabledFeatures() {
465         assertPermission();
466         synchronized (mLock) {
467             return new ArrayList<>(mPendingDisabledFeatures);
468         }
469     }
470 
471     /** Check {@link Car#getAllPendingEnabledFeatures()} */
getAllPendingEnabledFeatures()472     public List<String> getAllPendingEnabledFeatures() {
473         assertPermission();
474         synchronized (mLock) {
475             return new ArrayList<>(mPendingEnabledFeatures);
476         }
477     }
478 
479     /** Returns currently enabled experimental features */
getEnabledExperimentalFeatures()480     public @NonNull List<String> getEnabledExperimentalFeatures() {
481         ArrayList<String> experimentalFeature = new ArrayList<>();
482         if (BuildHelper.isUserBuild()) {
483             Slogf.e(TAG, "getEnabledExperimentalFeatures called in USER build",
484                     new RuntimeException());
485             return experimentalFeature;
486         }
487         for (int i = 0; i < mEnabledFeatures.size(); i++) {
488             String enabledFeature = mEnabledFeatures.valueAt(i);
489             if (MANDATORY_FEATURES.contains(enabledFeature)) {
490                 continue;
491             }
492             if (OPTIONAL_FEATURES.contains(enabledFeature)) {
493                 continue;
494             }
495             experimentalFeature.add(enabledFeature);
496         }
497         return experimentalFeature;
498     }
499 
handleCorruptConfigFileLocked(String msg, String line)500     void handleCorruptConfigFileLocked(String msg, String line) {
501         Slogf.e(TAG, msg + ", considered as corrupt, line:" + line);
502         mEnabledFeatures.clear();
503     }
504 
505     // TODO(b/227645920): add unit test
506     @GuardedBy("mLock")
loadFromConfigFileLocked()507     private boolean loadFromConfigFileLocked() {
508         // done without lock, should be only called from constructor.
509         FileInputStream fis;
510         try {
511             fis = mFeatureConfigFile.openRead();
512         } catch (FileNotFoundException e) {
513             Slogf.i(TAG, "Feature config file not found, this could be 1st boot");
514             return false;
515         }
516         try (BufferedReader reader =
517                      new BufferedReader(new InputStreamReader(fis, StandardCharsets.UTF_8))) {
518             boolean lastLinePassed = false;
519             boolean hashChecked = false;
520             while (true) {
521                 String line = reader.readLine();
522                 if (line == null) {
523                     if (!lastLinePassed) {
524                         handleCorruptConfigFileLocked("No last line checksum", "");
525                         return false;
526                     }
527                     break;
528                 }
529                 if (lastLinePassed && !line.isEmpty()) {
530                     handleCorruptConfigFileLocked(
531                             "Config file has additional line after last line marker", line);
532                     return false;
533                 } else {
534                     if (line.startsWith(CONFIG_FILE_HASH_MARKER)) {
535                         int expectedHashValue = mDefaultEnabledFeaturesFromConfig.hashCode();
536                         int fileHashValue = Integer
537                                 .parseInt(line.substring(CONFIG_FILE_HASH_MARKER.length()).strip());
538                         if (expectedHashValue != fileHashValue) {
539                             handleCorruptConfigFileLocked("Config hash doesn't match. Expected: "
540                                     + expectedHashValue, line);
541                             return false;
542                         }
543                         hashChecked = true;
544                         continue;
545                     }
546 
547                     if (!hashChecked) {
548                         handleCorruptConfigFileLocked("Config file doesn't have hash value.", "");
549                         return false;
550                     }
551 
552                     if (line.startsWith(CONFIG_FILE_LAST_LINE_MARKER)) {
553                         int numberOfFeatures;
554                         try {
555                             numberOfFeatures = Integer.parseInt(
556                                     line.substring(CONFIG_FILE_LAST_LINE_MARKER.length()));
557                         } catch (NumberFormatException e) {
558                             handleCorruptConfigFileLocked(
559                                     "Config file has corrupt last line, not a number", line);
560                             return false;
561                         }
562                         int actualNumberOfFeatures = mEnabledFeatures.size();
563                         if (numberOfFeatures != actualNumberOfFeatures) {
564                             handleCorruptConfigFileLocked(
565                                     "Config file has wrong number of features, expected:"
566                                             + numberOfFeatures + " actual:"
567                                             + actualNumberOfFeatures, line);
568                             return false;
569                         }
570                         lastLinePassed = true;
571                     } else {
572                         mEnabledFeatures.add(line);
573                     }
574                 }
575             }
576         } catch (IOException e) {
577             Slogf.w(TAG, "Cannot load config file", e);
578             return false;
579         }
580         Slogf.i(TAG, "Loaded features:" + mEnabledFeatures);
581         return true;
582     }
583 
persistToFeatureConfigFile(ArraySet<String> features)584     private void persistToFeatureConfigFile(ArraySet<String> features) {
585         removeSupportFeatures(features);
586         synchronized (mLock) {
587             features.removeAll(mPendingDisabledFeatures);
588             features.addAll(mPendingEnabledFeatures);
589             FileOutputStream fos;
590             try {
591                 fos = mFeatureConfigFile.startWrite();
592             } catch (IOException e) {
593                 Slogf.e(TAG, "Cannot create config file", e);
594                 return;
595             }
596             try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(fos,
597                     StandardCharsets.UTF_8))) {
598                 Slogf.i(TAG, "Updating features:" + features);
599                 writer.write(CONFIG_FILE_HASH_MARKER
600                         + mDefaultEnabledFeaturesFromConfig.hashCode());
601                 writer.newLine();
602                 for (int i = 0; i < features.size(); i++) {
603                     String feature = features.valueAt(i);
604                     writer.write(feature);
605                     writer.newLine();
606                 }
607                 writer.write(CONFIG_FILE_LAST_LINE_MARKER + features.size());
608                 writer.flush();
609                 mFeatureConfigFile.finishWrite(fos);
610             } catch (IOException e) {
611                 mFeatureConfigFile.failWrite(fos);
612                 Slogf.e(TAG, "Cannot create config file", e);
613             }
614         }
615     }
616 
assertPermission()617     private void assertPermission() {
618         CarServiceUtils.assertPermission(mContext, Car.PERMISSION_CONTROL_CAR_FEATURES);
619     }
620 
dispatchDefaultConfigUpdate()621     private void dispatchDefaultConfigUpdate() {
622         mHandler.removeCallbacksAndMessages(null);
623         ArraySet<String> featuresToPersist = new ArraySet<>(mEnabledFeatures);
624         mHandler.post(() -> persistToFeatureConfigFile(featuresToPersist));
625     }
626 
parseDefaultConfig()627     private void parseDefaultConfig() {
628         for (int i = 0; i < mDefaultEnabledFeaturesFromConfig.size(); i++) {
629             String defaultEnabledFeature = mDefaultEnabledFeaturesFromConfig.get(i);
630             if (mDisabledFeaturesFromVhal.contains(defaultEnabledFeature)) {
631                 continue;
632             }
633             if (OPTIONAL_FEATURES.contains(defaultEnabledFeature)) {
634                 mEnabledFeatures.add(defaultEnabledFeature);
635             } else if (NON_USER_ONLY_FEATURES.contains(defaultEnabledFeature)) {
636                 Slogf.e(TAG, "config_default_enabled_optional_car_features including "
637                         + "user build only feature, will be ignored:" + defaultEnabledFeature);
638             } else {
639                 Slogf.e(TAG, "config_default_enabled_optional_car_features include "
640                                 + "non-optional features:" + defaultEnabledFeature);
641             }
642         }
643         Slogf.i(TAG, "Loaded default features:" + mEnabledFeatures);
644     }
645 
addSupportFeatures(Collection<String> features)646     private static void addSupportFeatures(Collection<String> features) {
647         for (int index = 0; index < SUPPORT_FEATURES.size(); index++) {
648             if (features.contains(SUPPORT_FEATURES.get(index).first)) {
649                 features.add(SUPPORT_FEATURES.get(index).second);
650             }
651         }
652     }
653 
removeSupportFeatures(Collection<String> features)654     private static void removeSupportFeatures(Collection<String> features) {
655         for (int index = 0; index < SUPPORT_FEATURES.size(); index++) {
656             if (features.contains(SUPPORT_FEATURES.get(index).first)) {
657                 features.remove(SUPPORT_FEATURES.get(index).second);
658             }
659         }
660     }
661 
combineFeatures(List<String> features, ArraySet<String> flaggedFeatures)662     private static ArraySet<String> combineFeatures(List<String> features,
663             ArraySet<String> flaggedFeatures) {
664         ArraySet<String> combinedFeatures = new ArraySet<>(features);
665         combinedFeatures.addAll(flaggedFeatures);
666         return combinedFeatures;
667     }
668 }
669