1 /* 2 * Copyright (C) 2024 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 package com.android.adservices.service; 17 18 import static com.android.adservices.flags.Flags.FLAG_ADSERVICES_OUTCOMERECEIVER_R_API_ENABLED; 19 import static com.android.adservices.flags.Flags.FLAG_FLEDGE_AD_SELECTION_FILTERING_ENABLED; 20 import static com.android.adservices.flags.Flags.FLAG_FLEDGE_AUCTION_SERVER_GET_AD_SELECTION_DATA_ID_ENABLED; 21 import static com.android.adservices.flags.Flags.FLAG_FLEDGE_CUSTOM_AUDIENCE_AUCTION_SERVER_REQUEST_FLAGS_ENABLED; 22 import static com.android.adservices.flags.Flags.FLAG_FLEDGE_SERVER_AUCTION_MULTI_CLOUD_ENABLED; 23 24 import android.util.Log; 25 import android.util.Pair; 26 27 import com.android.adservices.common.AdServicesUnitTestCase; 28 import com.android.internal.util.Preconditions; 29 30 import org.junit.Test; 31 32 import java.lang.reflect.Field; 33 import java.lang.reflect.Modifier; 34 import java.util.ArrayList; 35 import java.util.List; 36 import java.util.Map; 37 import java.util.Objects; 38 import java.util.stream.Collectors; 39 40 // NOTE: when making changes on com.android.adservices.flags.Flags, you need to install the new 41 // apex - just running atest wouldn't affect the test result 42 public final class FlagsConstantsTest extends AdServicesUnitTestCase { 43 44 private static final String ACONFIG_PREFIX = "com.android.adservices.flags."; 45 46 private static final String HOW_TO_FIX_IT_MESSAGE = 47 "If this is expected, you might need to change MISSING_FLAGS_ALLOWLIST or" 48 + " NON_CANONICAL_FLAGS (on this file)."; 49 50 /** 51 * List used by {@link #testAllAconfigFlagsAreMapped()}, it contains the name of flags that are 52 * present in the {@code aconfig} file but are missing on {@link FlagsConstants}. 53 * 54 * <p>Add more entries in the bottom, either explaining the reason or using a TODO(b/BUG) that 55 * will add the missing {@link com.android.adservices.service.PhFlags} / {@link 56 * com.android.adservices.service.FlagsConstants} counterpart. 57 */ 58 private static final List<String> MISSING_FLAGS_ALLOWLIST = 59 List.of( 60 // FLAG_ADSERVICES_OUTCOMERECEIVER_R_API_ENABLED guards APIs that are overloaded 61 // to take android.adservices.common.OutcomeReceiver (instead of 62 // android.os.OutcomeReceiver) and hence don't need to be checked at runtime. 63 FLAG_ADSERVICES_OUTCOMERECEIVER_R_API_ENABLED, 64 65 // TODO(b/323397060): Remove from this allowlist after implementing feature and 66 // adding matching DeviceConfig flag 67 FLAG_FLEDGE_AUCTION_SERVER_GET_AD_SELECTION_DATA_ID_ENABLED, 68 69 // The DeviceConfig flag guarding this feature is intentionally named 70 // differently so that it is not scoped to the Custom Audience API. 71 FLAG_FLEDGE_CUSTOM_AUDIENCE_AUCTION_SERVER_REQUEST_FLAGS_ENABLED, 72 73 // There used to be a matching DeviceConfig flag, but it guarded too many 74 // features. Because the feature APIs are unhidden and published already, they 75 // cannot be changed. The old DeviceConfig flag has instead been removed and 76 // split into individual feature flags to allow each feature to launch 77 // independently. 78 FLAG_FLEDGE_AD_SELECTION_FILTERING_ENABLED); 79 80 /** 81 * Map used by {@link #testAllAconfigFlagsAreMapped()} - key is the {@code aconfig} flag name, 82 * value is the {@link com.android.adservices.service.FlagsConstants} counterpart (i.e, the 83 * value defined by the constant, which is the key to a {@link android.provider.DeviceConfig} 84 * flag). 85 * 86 * <p>Flag names on {@link com.android.adservices.service.FlagsConstants} are expect to have the 87 * same name (minus prefix) as the {@code aconfig} counterpart, but there are a few exceptions 88 * like: 89 * 90 * <ul> 91 * <li>{@link android.provider.DeviceConfig} flag already pushed to production. 92 * <li>Same {@link android.provider.DeviceConfig} flag is guarding multiple APIs using 93 * different {@code aconfig} flags on their <code>@FlaggedApi</code> annotations. 94 * </ul> 95 * 96 * <p>Add more entries in the bottom, either explaining the reason or using a TODO(b/BUG) that 97 * will add the missing {@link com.android.adservices.service.PhFlags} / {@link 98 * com.android.adservices.service.FlagsConstants} counterpart. 99 */ 100 private static final Map<String, String> NON_CANONICAL_FLAGS = 101 Map.of( 102 // DeviceConfig flags for PA/FLEDGE are named "auction_server" instead of 103 // "server_auction." This API has already been released, and the aconfig flag 104 // cannot be renamed, so this mismatch is intentional. 105 FLAG_FLEDGE_SERVER_AUCTION_MULTI_CLOUD_ENABLED, 106 FlagsConstants.KEY_FLEDGE_AUCTION_SERVER_MULTI_CLOUD_ENABLED); 107 108 @Test testNoFlagHasTheAConfigPrefix()109 public void testNoFlagHasTheAConfigPrefix() throws Exception { 110 for (Pair<String, String> constant : getAllFlagNameConstants(FlagsConstants.class)) { 111 String name = constant.first; 112 String value = constant.second; 113 expect.withMessage( 114 "Value (%s) of constants %s starts with prefix %s", 115 value, name, ACONFIG_PREFIX) 116 .that(value.startsWith(ACONFIG_PREFIX)) 117 .isFalse(); 118 } 119 } 120 121 @Test testAllAconfigFlagsAreMapped()122 public void testAllAconfigFlagsAreMapped() throws Exception { 123 Map<String, String> reverseServiceFlags = 124 getAllFlagNameConstants(FlagsConstants.class).stream() 125 .collect(Collectors.toMap(p -> p.second, p -> p.first)); 126 List<String> missingFlags = new ArrayList<>(); 127 for (Pair<String, String> constant : 128 getAllFlagNameConstants(com.android.adservices.flags.Flags.class)) { 129 String constantName = constant.first; 130 String aconfigFlag = constant.second; 131 String expectedDeviceConfigFlag = getExpectedDeviceConfigFlag(aconfigFlag); 132 String serviceConstant = reverseServiceFlags.get(expectedDeviceConfigFlag); 133 if (serviceConstant == null) { 134 if (MISSING_FLAGS_ALLOWLIST.contains(aconfigFlag)) { 135 Log.i( 136 mTag, 137 "Missing mapping for allowlisted flag (" 138 + constantName 139 + "=" 140 + aconfigFlag 141 + ")"); 142 } else { 143 Log.e(mTag, "Missing mapping for " + constantName + "=" + aconfigFlag); 144 missingFlags.add(expectedDeviceConfigFlag); 145 } 146 } else { 147 Log.d(mTag, "Found mapping: " + constantName + "->" + serviceConstant); 148 } 149 } 150 expect.withMessage( 151 "aconfig flags missing counterpart on FlagsConstants. %s", 152 HOW_TO_FIX_IT_MESSAGE) 153 .that(missingFlags) 154 .isEmpty(); 155 } 156 aconfigToDeviceConfig(String flag)157 private static String aconfigToDeviceConfig(String flag) { 158 Preconditions.checkArgument( 159 Objects.requireNonNull(flag).startsWith(ACONFIG_PREFIX), 160 "Flag doesn't start with %s: %s", 161 ACONFIG_PREFIX, 162 flag); 163 return flag.substring(ACONFIG_PREFIX.length()); 164 } 165 getExpectedDeviceConfigFlag(String aconfigFlag)166 private String getExpectedDeviceConfigFlag(String aconfigFlag) { 167 String nonCanonical = NON_CANONICAL_FLAGS.get(aconfigFlag); 168 if (nonCanonical != null) { 169 Log.i(mTag, "Returning non-canonical flag for " + aconfigFlag + ": " + nonCanonical); 170 return nonCanonical; 171 } 172 return aconfigToDeviceConfig(aconfigFlag); 173 } 174 getAllFlagNameConstants(Class<?> clazz)175 private static List<Pair<String, String>> getAllFlagNameConstants(Class<?> clazz) 176 throws IllegalAccessException { 177 List<Pair<String, String>> constants = new ArrayList<>(); 178 for (Field field : clazz.getDeclaredFields()) { 179 int modifiers = field.getModifiers(); 180 if (Modifier.isStatic(modifiers) 181 && Modifier.isFinal(modifiers) 182 && (field.getType().equals(String.class))) { 183 String name = field.getName(); 184 if (name.startsWith("KEY_") || name.startsWith("FLAG_")) { 185 String value = (String) field.get(null); 186 constants.add(new Pair<>(name, value)); 187 } 188 } 189 } 190 return constants; 191 } 192 } 193