• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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