1 /* 2 * Copyright (C) 2023 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.server.deviceconfig; 18 19 import android.annotation.SuppressLint; 20 import android.provider.DeviceConfig; 21 import android.util.Slog; 22 23 import java.io.IOException; 24 import java.net.URI; 25 import java.nio.file.Files; 26 import java.nio.file.Path; 27 import java.util.stream.Stream; 28 29 /** 30 * @hide 31 */ 32 public class DeviceConfigBootstrapValues { 33 private static final String TAG = "DeviceConfig"; 34 private static final String SYSTEM_OVERRIDES_PATH = "file:///system/etc/device-config-defaults"; 35 private static final String META_NAMESPACE = "DeviceConfigBootstrapValues"; 36 private static final String META_KEY = "processed_values"; 37 38 private final String defaultValuesPath; 39 DeviceConfigBootstrapValues()40 public DeviceConfigBootstrapValues() { 41 this(SYSTEM_OVERRIDES_PATH); 42 } 43 DeviceConfigBootstrapValues(String defaultValuesPath)44 public DeviceConfigBootstrapValues(String defaultValuesPath) { 45 this.defaultValuesPath = defaultValuesPath; 46 } 47 48 /** 49 * Performs the logic to apply bootstrap values when needed. 50 * 51 * If a file with the bootstrap values exists and they haven't been parsed before, 52 * it will parse the file and apply the values. 53 * 54 * @throws IOException if there's a problem reading the bootstrap file 55 * @throws RuntimeException if setting the values in DeviceConfig throws an exception 56 */ applyValuesIfNeeded()57 public void applyValuesIfNeeded() throws IOException { 58 if (getPath().toFile().exists()) { 59 if (checkIfHasAlreadyParsedBootstrapValues()) { 60 Slog.i(TAG, "Bootstrap values already parsed, not processing again"); 61 } else { 62 parseAndApplyBootstrapValues(); 63 Slog.i(TAG, "Parsed bootstrap values"); 64 } 65 } else { 66 Slog.i(TAG, "Bootstrap values not found"); 67 } 68 } 69 70 @SuppressLint("MissingPermission") checkIfHasAlreadyParsedBootstrapValues()71 private boolean checkIfHasAlreadyParsedBootstrapValues() { 72 DeviceConfig.Properties properties = DeviceConfig.getProperties(META_NAMESPACE); 73 return properties.getKeyset().size() > 0; 74 } 75 76 @SuppressLint("MissingPermission") parseAndApplyBootstrapValues()77 private void parseAndApplyBootstrapValues() throws IOException { 78 Path path = getPath(); 79 try (Stream<String> lines = Files.lines(path)) { 80 lines.forEach(line -> processLine(line)); 81 } 82 // store a property in DeviceConfig so that we know we have successufully 83 // processed this 84 writeToDeviceConfig(META_NAMESPACE, META_KEY, "true"); 85 } 86 processLine(String line)87 private void processLine(String line) { 88 // contents for each line: 89 // <namespace>:<package>.<flag-name>=[enabled|disabled] 90 // we actually use <package>.<flag-name> combined in calls into DeviceConfig 91 int namespaceDelimiter = line.indexOf(':'); 92 String namespace = line.substring(0, namespaceDelimiter); 93 if (namespaceDelimiter < 1) { 94 throw new IllegalArgumentException("Unexpectedly found : at index " 95 + namespaceDelimiter); 96 } 97 int valueDelimiter = line.indexOf('='); 98 if (valueDelimiter < 5) { 99 throw new IllegalArgumentException("Unexpectedly found = at index " + valueDelimiter); 100 } 101 String key = line.substring(namespaceDelimiter + 1, valueDelimiter); 102 String value = line.substring(valueDelimiter + 1); 103 String val; 104 if ("enabled".equals(value)) { 105 val = "true"; 106 } else if ("disabled".equals(value)) { 107 val = "false"; 108 } else { 109 throw new IllegalArgumentException("Received unexpected value: " + value); 110 } 111 writeToDeviceConfig(namespace, key, val); 112 } 113 114 @SuppressLint("MissingPermission") writeToDeviceConfig(String namespace, String key, String value)115 private void writeToDeviceConfig(String namespace, String key, String value) { 116 boolean result = DeviceConfig.setProperty(namespace, key, value, /* makeDefault= */ true); 117 if (!result) { 118 throw new RuntimeException("Failed to set DeviceConfig property [" + namespace + "] " 119 + key + "=" + value); 120 } 121 } 122 getPath()123 private Path getPath() { 124 return Path.of(URI.create(defaultValuesPath)); 125 } 126 } 127