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