1 /* 2 * Copyright (C) 2025 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 android.os.flagging; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.internal.configinfra.aconfigd.x.Aconfigd.StorageRequestMessage; 22 import android.internal.configinfra.aconfigd.x.Aconfigd.StorageRequestMessages; 23 import android.internal.configinfra.aconfigd.x.Aconfigd.StorageReturnMessage; 24 import android.internal.configinfra.aconfigd.x.Aconfigd.StorageReturnMessages; 25 import android.net.LocalSocket; 26 import android.net.LocalSocketAddress; 27 import android.util.Slog; 28 import android.util.configinfrastructure.proto.ProtoInputStream; 29 import android.util.proto.ProtoOutputStream; 30 31 import java.io.DataInputStream; 32 import java.io.DataOutputStream; 33 import java.io.IOException; 34 import java.io.InputStream; 35 import java.util.HashSet; 36 import java.util.Map; 37 import java.util.Set; 38 39 /** 40 * Writes messages to aconfigd, and parses responses. 41 * 42 * <p>Uses ProtoInputStream, rather than a proto lite lib. 43 * 44 * @hide 45 */ 46 public final class AconfigdProtoStreamer { 47 private static final String SYSTEM_SOCKET_ADDRESS = "aconfigd_system"; 48 private static final String MAINLINE_SOCKET_ADDRESS = "aconfigd_mainline"; 49 50 private static final String TAG = "FlagManager"; 51 52 /** 53 * Create a new AconfigdProtoStreamer. 54 * 55 * @hide 56 */ AconfigdProtoStreamer()57 public AconfigdProtoStreamer() {} 58 59 /** 60 * Send override removal requests to aconfigd. 61 * 62 * @param flags the set of flag names to remove local overrides for 63 * @param removeType the type of removal: immediately, or on reboot 64 * @hide 65 */ sendClearFlagOverrideRequests(@onNull Set<String> flags, long removeType)66 public void sendClearFlagOverrideRequests(@NonNull Set<String> flags, long removeType) 67 throws IOException { 68 ProtoOutputStream requestOutputStream = new ProtoOutputStream(); 69 for (Flag flag : Flag.buildFlagsWithoutValues(flags)) { 70 long msgsToken = requestOutputStream.start(StorageRequestMessages.MSGS); 71 long msgToken = 72 requestOutputStream.start(StorageRequestMessage.REMOVE_LOCAL_OVERRIDE_MESSAGE); 73 requestOutputStream.write( 74 StorageRequestMessage.RemoveLocalOverrideMessage.PACKAGE_NAME, 75 flag.packageName); 76 requestOutputStream.write( 77 StorageRequestMessage.RemoveLocalOverrideMessage.FLAG_NAME, flag.flagName); 78 requestOutputStream.write( 79 StorageRequestMessage.RemoveLocalOverrideMessage.REMOVE_ALL, false); 80 requestOutputStream.write( 81 StorageRequestMessage.RemoveLocalOverrideMessage.REMOVE_OVERRIDE_TYPE, 82 removeType); 83 requestOutputStream.end(msgToken); 84 requestOutputStream.end(msgsToken); 85 } 86 87 sendBytesAndParseResponse( 88 requestOutputStream.getBytes(), StorageReturnMessage.REMOVE_LOCAL_OVERRIDE_MESSAGE); 89 } 90 91 /** 92 * Send OTA flag-staging requests to aconfigd. 93 * 94 * @param flags a map from flag names to the values that will be staged 95 * @param buildFingerprint the build fingerprint on which the values will be un-staged 96 * @hide 97 */ sendOtaFlagOverrideRequests( @onNull Map<String, Boolean> flags, @NonNull String buildFingerprint)98 public void sendOtaFlagOverrideRequests( 99 @NonNull Map<String, Boolean> flags, @NonNull String buildFingerprint) 100 throws IOException { 101 ProtoOutputStream requestOutputStream = new ProtoOutputStream(); 102 long msgsToken = requestOutputStream.start(StorageRequestMessages.MSGS); 103 long msgToken = requestOutputStream.start(StorageRequestMessage.OTA_STAGING_MESSAGE); 104 requestOutputStream.write( 105 StorageRequestMessage.OTAFlagStagingMessage.BUILD_ID, buildFingerprint); 106 for (Flag flag : Flag.buildFlags(flags)) { 107 long flagOverrideMsgToken = 108 requestOutputStream.start(StorageRequestMessage.FLAG_OVERRIDE_MESSAGE); 109 requestOutputStream.write( 110 StorageRequestMessage.FlagOverrideMessage.PACKAGE_NAME, flag.packageName); 111 requestOutputStream.write( 112 StorageRequestMessage.FlagOverrideMessage.FLAG_NAME, flag.flagName); 113 requestOutputStream.write( 114 StorageRequestMessage.FlagOverrideMessage.FLAG_VALUE, flag.value); 115 requestOutputStream.end(flagOverrideMsgToken); 116 } 117 requestOutputStream.end(msgToken); 118 requestOutputStream.end(msgsToken); 119 120 sendBytesAndParseResponse( 121 requestOutputStream.getBytes(), StorageReturnMessage.OTA_STAGING_MESSAGE); 122 } 123 124 /** 125 * Send flag override requests to aconfigd, and parse the response. 126 * 127 * @hide 128 */ sendFlagOverrideRequests(@onNull Map<String, Boolean> flags, long overrideType)129 public void sendFlagOverrideRequests(@NonNull Map<String, Boolean> flags, long overrideType) 130 throws IOException { 131 ProtoOutputStream requestOutputStream = new ProtoOutputStream(); 132 for (Flag flag : Flag.buildFlags(flags)) { 133 long msgsToken = requestOutputStream.start(StorageRequestMessages.MSGS); 134 long msgToken = requestOutputStream.start(StorageRequestMessage.FLAG_OVERRIDE_MESSAGE); 135 requestOutputStream.write( 136 StorageRequestMessage.FlagOverrideMessage.PACKAGE_NAME, flag.packageName); 137 requestOutputStream.write( 138 StorageRequestMessage.FlagOverrideMessage.FLAG_NAME, flag.flagName); 139 requestOutputStream.write( 140 StorageRequestMessage.FlagOverrideMessage.FLAG_VALUE, flag.value); 141 requestOutputStream.write( 142 StorageRequestMessage.FlagOverrideMessage.OVERRIDE_TYPE, overrideType); 143 requestOutputStream.end(msgToken); 144 requestOutputStream.end(msgsToken); 145 } 146 147 sendBytesAndParseResponse( 148 requestOutputStream.getBytes(), StorageReturnMessage.FLAG_OVERRIDE_MESSAGE); 149 } 150 sendBytesAndParseResponse(byte[] requestBytes, long responseMessageToken)151 private void sendBytesAndParseResponse(byte[] requestBytes, long responseMessageToken) 152 throws IOException { 153 try { 154 LocalSocket systemSocket = new LocalSocket(); 155 LocalSocketAddress systemAddress = 156 new LocalSocketAddress( 157 SYSTEM_SOCKET_ADDRESS, LocalSocketAddress.Namespace.RESERVED); 158 if (!systemSocket.isConnected()) { 159 systemSocket.connect(systemAddress); 160 } 161 162 InputStream inputStream = sendBytesOverSocket(requestBytes, systemSocket); 163 parseAconfigdResponse(inputStream, responseMessageToken); 164 165 systemSocket.shutdownInput(); 166 systemSocket.shutdownOutput(); 167 systemSocket.close(); 168 169 } catch (IOException systemException) { 170 Slog.i( 171 TAG, 172 "failed to send request to system socket; trying mainline socket", 173 systemException); 174 175 LocalSocket mainlineSocket = new LocalSocket(); 176 LocalSocketAddress mainlineAddress = 177 new LocalSocketAddress( 178 MAINLINE_SOCKET_ADDRESS, LocalSocketAddress.Namespace.RESERVED); 179 if (!mainlineSocket.isConnected()) { 180 mainlineSocket.connect(mainlineAddress); 181 } 182 183 InputStream inputStream = sendBytesOverSocket(requestBytes, mainlineSocket); 184 parseAconfigdResponse(inputStream, responseMessageToken); 185 186 mainlineSocket.shutdownInput(); 187 mainlineSocket.shutdownOutput(); 188 mainlineSocket.close(); 189 } 190 } 191 sendBytesOverSocket(byte[] requestBytes, LocalSocket socket)192 private InputStream sendBytesOverSocket(byte[] requestBytes, LocalSocket socket) 193 throws IOException { 194 InputStream responseInputStream = null; 195 196 DataInputStream inputStream = new DataInputStream(socket.getInputStream()); 197 DataOutputStream outputStream = new DataOutputStream(socket.getOutputStream()); 198 outputStream.writeInt(requestBytes.length); 199 outputStream.write(requestBytes); 200 responseInputStream = socket.getInputStream(); 201 202 return responseInputStream; 203 } 204 parseAconfigdResponse(InputStream inputStream, long responseMessageToken)205 private void parseAconfigdResponse(InputStream inputStream, long responseMessageToken) 206 throws IOException { 207 ProtoInputStream proto = new ProtoInputStream(inputStream); 208 while (true) { 209 long currentToken = proto.nextField(); 210 211 if (currentToken == ProtoInputStream.NO_MORE_FIELDS) { 212 return; 213 } 214 215 if (currentToken != ((int) StorageReturnMessages.MSGS)) { 216 continue; 217 } 218 219 long msgsToken = proto.start(StorageReturnMessages.MSGS); 220 long nextToken = proto.nextField(); 221 222 if (nextToken == ((int) responseMessageToken)) { 223 long msgToken = proto.start(responseMessageToken); 224 proto.end(msgToken); 225 } else if (nextToken == ((int) StorageReturnMessage.ERROR_MESSAGE)) { 226 String errmsg = proto.readString(StorageReturnMessage.ERROR_MESSAGE); 227 throw new IOException("override request failed: " + errmsg); 228 } else if (nextToken == ProtoInputStream.NO_MORE_FIELDS) { 229 // Do nothing. 230 } else { 231 throw new IOException( 232 "invalid message type, expecting only return message" 233 + " or error message"); 234 } 235 236 proto.end(msgsToken); 237 } 238 } 239 240 private static class Flag { 241 public final String packageName; 242 public final String flagName; 243 public final String value; 244 Flag( @onNull String packageName, @NonNull String flagName, @Nullable Boolean value)245 public Flag( 246 @NonNull String packageName, @NonNull String flagName, @Nullable Boolean value) { 247 this.packageName = packageName; 248 this.flagName = flagName; 249 250 if (value != null) { 251 this.value = Boolean.toString(value); 252 } else { 253 this.value = null; 254 } 255 } 256 buildFlags(@onNull Map<String, Boolean> flags)257 public static Set<Flag> buildFlags(@NonNull Map<String, Boolean> flags) { 258 HashSet<Flag> flagSet = new HashSet(); 259 for (Map.Entry<String, Boolean> flagAndValue : flags.entrySet()) { 260 String packageName = ""; 261 String flagName = flagAndValue.getKey(); 262 263 int periodIndex = flagName.lastIndexOf("."); 264 if (periodIndex != -1) { 265 packageName = flagName.substring(0, flagName.lastIndexOf(".")); 266 flagName = flagName.substring(flagName.lastIndexOf(".") + 1); 267 } 268 269 flagSet.add(new Flag(packageName, flagName, flagAndValue.getValue())); 270 } 271 return flagSet; 272 } 273 buildFlagsWithoutValues(@onNull Set<String> flags)274 public static Set<Flag> buildFlagsWithoutValues(@NonNull Set<String> flags) { 275 HashSet<Flag> flagSet = new HashSet(); 276 for (String qualifiedFlagName : flags) { 277 String packageName = ""; 278 String flagName = qualifiedFlagName; 279 int periodIndex = qualifiedFlagName.lastIndexOf("."); 280 if (periodIndex != -1) { 281 packageName = 282 qualifiedFlagName.substring(0, qualifiedFlagName.lastIndexOf(".")); 283 flagName = qualifiedFlagName.substring(qualifiedFlagName.lastIndexOf(".") + 1); 284 } 285 286 flagSet.add(new Flag(packageName, flagName, null)); 287 } 288 return flagSet; 289 } 290 } 291 } 292