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