• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 use anyhow::Result;
18 use serde::Serialize;
19 use std::collections::{BTreeMap, BTreeSet};
20 use std::path::PathBuf;
21 use tinytemplate::TinyTemplate;
22 
23 use crate::codegen;
24 use crate::codegen::CodegenMode;
25 use crate::commands::{should_include_flag, OutputFile};
26 use aconfig_protos::{ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag};
27 use convert_finalized_flags::{FinalizedFlag, FinalizedFlagMap};
28 use std::collections::HashMap;
29 
30 // Arguments to configure codegen for generate_java_code.
31 pub struct JavaCodegenConfig {
32     pub codegen_mode: CodegenMode,
33     pub flag_ids: HashMap<String, u16>,
34     pub allow_instrumentation: bool,
35     pub package_fingerprint: u64,
36     pub new_exported: bool,
37     pub single_exported_file: bool,
38     pub finalized_flags: FinalizedFlagMap,
39 }
40 
generate_java_code<I>( package: &str, parsed_flags_iter: I, config: JavaCodegenConfig, ) -> Result<Vec<OutputFile>> where I: Iterator<Item = ProtoParsedFlag>,41 pub fn generate_java_code<I>(
42     package: &str,
43     parsed_flags_iter: I,
44     config: JavaCodegenConfig,
45 ) -> Result<Vec<OutputFile>>
46 where
47     I: Iterator<Item = ProtoParsedFlag>,
48 {
49     let flag_elements: Vec<FlagElement> = parsed_flags_iter
50         .map(|pf| {
51             create_flag_element(package, &pf, config.flag_ids.clone(), &config.finalized_flags)
52         })
53         .collect();
54     let namespace_flags = gen_flags_by_namespace(&flag_elements);
55     let properties_set: BTreeSet<String> =
56         flag_elements.iter().map(|fe| format_property_name(&fe.device_config_namespace)).collect();
57     let is_test_mode = config.codegen_mode == CodegenMode::Test;
58     let library_exported = config.codegen_mode == CodegenMode::Exported;
59     let runtime_lookup_required =
60         flag_elements.iter().any(|elem| elem.is_read_write) || library_exported;
61     let container = (flag_elements.first().expect("zero template flags").container).to_string();
62     let is_platform_container =
63         matches!(container.as_str(), "system" | "system_ext" | "product" | "vendor");
64     let context = Context {
65         flag_elements,
66         namespace_flags,
67         is_test_mode,
68         runtime_lookup_required,
69         properties_set,
70         package_name: package.to_string(),
71         library_exported,
72         allow_instrumentation: config.allow_instrumentation,
73         container,
74         is_platform_container,
75         package_fingerprint: format!("0x{:X}L", config.package_fingerprint),
76         new_exported: config.new_exported,
77         single_exported_file: config.single_exported_file,
78     };
79     let mut template = TinyTemplate::new();
80     if library_exported && config.single_exported_file {
81         template.add_template(
82             "ExportedFlags.java",
83             include_str!("../../templates/ExportedFlags.java.template"),
84         )?;
85     }
86     template.add_template("Flags.java", include_str!("../../templates/Flags.java.template"))?;
87     add_feature_flags_impl_template(&context, &mut template)?;
88     template.add_template(
89         "FeatureFlags.java",
90         include_str!("../../templates/FeatureFlags.java.template"),
91     )?;
92     template.add_template(
93         "CustomFeatureFlags.java",
94         include_str!("../../templates/CustomFeatureFlags.java.template"),
95     )?;
96     template.add_template(
97         "FakeFeatureFlagsImpl.java",
98         include_str!("../../templates/FakeFeatureFlagsImpl.java.template"),
99     )?;
100 
101     let path: PathBuf = package.split('.').collect();
102     let mut files = vec![
103         "Flags.java",
104         "FeatureFlags.java",
105         "FeatureFlagsImpl.java",
106         "CustomFeatureFlags.java",
107         "FakeFeatureFlagsImpl.java",
108     ];
109     if library_exported && config.single_exported_file {
110         files.push("ExportedFlags.java");
111     }
112     files
113         .iter()
114         .map(|file| {
115             Ok(OutputFile {
116                 contents: template.render(file, &context)?.into(),
117                 path: path.join(file),
118             })
119         })
120         .collect::<Result<Vec<OutputFile>>>()
121 }
122 
gen_flags_by_namespace(flags: &[FlagElement]) -> Vec<NamespaceFlags>123 fn gen_flags_by_namespace(flags: &[FlagElement]) -> Vec<NamespaceFlags> {
124     let mut namespace_to_flag: BTreeMap<String, Vec<FlagElement>> = BTreeMap::new();
125 
126     for flag in flags {
127         match namespace_to_flag.get_mut(&flag.device_config_namespace) {
128             Some(flag_list) => flag_list.push(flag.clone()),
129             None => {
130                 namespace_to_flag.insert(flag.device_config_namespace.clone(), vec![flag.clone()]);
131             }
132         }
133     }
134 
135     namespace_to_flag
136         .iter()
137         .map(|(namespace, flags)| NamespaceFlags {
138             namespace: namespace.to_string(),
139             flags: flags.clone(),
140         })
141         .collect()
142 }
143 
144 #[derive(Serialize)]
145 struct Context {
146     pub flag_elements: Vec<FlagElement>,
147     pub namespace_flags: Vec<NamespaceFlags>,
148     pub is_test_mode: bool,
149     pub runtime_lookup_required: bool,
150     pub properties_set: BTreeSet<String>,
151     pub package_name: String,
152     pub library_exported: bool,
153     pub allow_instrumentation: bool,
154     pub container: String,
155     pub is_platform_container: bool,
156     pub package_fingerprint: String,
157     pub new_exported: bool,
158     pub single_exported_file: bool,
159 }
160 
161 #[derive(Serialize, Debug)]
162 struct NamespaceFlags {
163     pub namespace: String,
164     pub flags: Vec<FlagElement>,
165 }
166 
167 #[derive(Serialize, Clone, Debug)]
168 struct FlagElement {
169     pub container: String,
170     pub default_value: bool,
171     pub device_config_namespace: String,
172     pub device_config_flag: String,
173     pub flag_name: String,
174     pub flag_name_constant_suffix: String,
175     pub flag_offset: u16,
176     pub is_read_write: bool,
177     pub method_name: String,
178     pub properties: String,
179     pub finalized_sdk_present: bool,
180     pub finalized_sdk_value: i32,
181 }
182 
create_flag_element( package: &str, pf: &ProtoParsedFlag, flag_offsets: HashMap<String, u16>, finalized_flags: &FinalizedFlagMap, ) -> FlagElement183 fn create_flag_element(
184     package: &str,
185     pf: &ProtoParsedFlag,
186     flag_offsets: HashMap<String, u16>,
187     finalized_flags: &FinalizedFlagMap,
188 ) -> FlagElement {
189     let device_config_flag = codegen::create_device_config_ident(package, pf.name())
190         .expect("values checked at flag parse time");
191 
192     let no_assigned_offset = !should_include_flag(pf);
193 
194     let flag_offset = match flag_offsets.get(pf.name()) {
195         Some(offset) => offset,
196         None => {
197             // System/vendor/product RO+disabled flags have no offset in storage files.
198             // Assign placeholder value.
199             if no_assigned_offset {
200                 &0
201             }
202             // All other flags _must_ have an offset.
203             else {
204                 panic!("{}", format!("missing flag offset for {}", pf.name()));
205             }
206         }
207     };
208 
209     // An empty map is provided if check_api_level is disabled.
210     let mut finalized_sdk_present: bool = false;
211     let mut finalized_sdk_value: i32 = 0;
212     if !finalized_flags.is_empty() {
213         let finalized_sdk = finalized_flags.get_finalized_level(&FinalizedFlag {
214             flag_name: pf.name().to_string(),
215             package_name: package.to_string(),
216         });
217         finalized_sdk_present = finalized_sdk.is_some();
218         finalized_sdk_value = finalized_sdk.map(|f| f.0).unwrap_or_default();
219     }
220 
221     FlagElement {
222         container: pf.container().to_string(),
223         default_value: pf.state() == ProtoFlagState::ENABLED,
224         device_config_namespace: pf.namespace().to_string(),
225         device_config_flag,
226         flag_name: pf.name().to_string(),
227         flag_name_constant_suffix: pf.name().to_ascii_uppercase(),
228         flag_offset: *flag_offset,
229         is_read_write: pf.permission() == ProtoFlagPermission::READ_WRITE,
230         method_name: format_java_method_name(pf.name()),
231         properties: format_property_name(pf.namespace()),
232         finalized_sdk_present,
233         finalized_sdk_value,
234     }
235 }
236 
format_java_method_name(flag_name: &str) -> String237 fn format_java_method_name(flag_name: &str) -> String {
238     let splits: Vec<&str> = flag_name.split('_').filter(|&word| !word.is_empty()).collect();
239     if splits.len() == 1 {
240         let name = splits[0];
241         name[0..1].to_ascii_lowercase() + &name[1..]
242     } else {
243         splits
244             .iter()
245             .enumerate()
246             .map(|(index, word)| {
247                 if index == 0 {
248                     word.to_ascii_lowercase()
249                 } else {
250                     word[0..1].to_ascii_uppercase() + &word[1..].to_ascii_lowercase()
251                 }
252             })
253             .collect::<Vec<String>>()
254             .join("")
255     }
256 }
257 
format_property_name(property_name: &str) -> String258 fn format_property_name(property_name: &str) -> String {
259     let name = format_java_method_name(property_name);
260     format!("mProperties{}{}", &name[0..1].to_ascii_uppercase(), &name[1..])
261 }
262 
add_feature_flags_impl_template( context: &Context, template: &mut TinyTemplate, ) -> Result<(), tinytemplate::error::Error>263 fn add_feature_flags_impl_template(
264     context: &Context,
265     template: &mut TinyTemplate,
266 ) -> Result<(), tinytemplate::error::Error> {
267     if context.is_test_mode {
268         // Test mode has its own template, so use regardless of any other settings.
269         template.add_template(
270             "FeatureFlagsImpl.java",
271             include_str!("../../templates/FeatureFlagsImpl.test_mode.java.template"),
272         )?;
273         return Ok(());
274     }
275 
276     match (context.library_exported, context.new_exported, context.allow_instrumentation) {
277         // Exported library with new_exported enabled, use new storage exported template.
278         (true, true, _) => {
279             template.add_template(
280                 "FeatureFlagsImpl.java",
281                 include_str!("../../templates/FeatureFlagsImpl.exported.java.template"),
282             )?;
283         }
284 
285         // Exported library with new_exported NOT enabled, use legacy (device
286         // config) template, because regardless of allow_instrumentation, we use
287         // device config for exported libs if new_exported isn't enabled.
288         // Remove once new_exported is fully rolled out.
289         (true, false, _) => {
290             template.add_template(
291                 "FeatureFlagsImpl.java",
292                 include_str!("../../templates/FeatureFlagsImpl.deviceConfig.java.template"),
293             )?;
294         }
295 
296         // New storage internal mode.
297         (false, _, true) => {
298             template.add_template(
299                 "FeatureFlagsImpl.java",
300                 include_str!("../../templates/FeatureFlagsImpl.new_storage.java.template"),
301             )?;
302         }
303 
304         // Device config internal mode. Use legacy (device config) template.
305         (false, _, false) => {
306             template.add_template(
307                 "FeatureFlagsImpl.java",
308                 include_str!("../../templates/FeatureFlagsImpl.deviceConfig.java.template"),
309             )?;
310         }
311     };
312     Ok(())
313 }
314 
315 #[cfg(test)]
316 mod tests {
317     use convert_finalized_flags::ApiLevel;
318 
319     use super::*;
320     use crate::commands::assign_flag_ids;
321     use std::collections::HashMap;
322 
323     const EXPECTED_FEATUREFLAGS_COMMON_CONTENT: &str = r#"
324     package com.android.aconfig.test;
325     // TODO(b/303773055): Remove the annotation after access issue is resolved.
326     import android.compat.annotation.UnsupportedAppUsage;
327     /** @hide */
328     public interface FeatureFlags {
329         @com.android.aconfig.annotations.AssumeFalseForR8
330         @com.android.aconfig.annotations.AconfigFlagAccessor
331         @UnsupportedAppUsage
332         boolean disabledRo();
333         @com.android.aconfig.annotations.AconfigFlagAccessor
334         @UnsupportedAppUsage
335         boolean disabledRw();
336         @com.android.aconfig.annotations.AconfigFlagAccessor
337         @UnsupportedAppUsage
338         boolean disabledRwExported();
339         @com.android.aconfig.annotations.AconfigFlagAccessor
340         @UnsupportedAppUsage
341         boolean disabledRwInOtherNamespace();
342         @com.android.aconfig.annotations.AssumeTrueForR8
343         @com.android.aconfig.annotations.AconfigFlagAccessor
344         @UnsupportedAppUsage
345         boolean enabledFixedRo();
346         @com.android.aconfig.annotations.AssumeTrueForR8
347         @com.android.aconfig.annotations.AconfigFlagAccessor
348         @UnsupportedAppUsage
349         boolean enabledFixedRoExported();
350         @com.android.aconfig.annotations.AssumeTrueForR8
351         @com.android.aconfig.annotations.AconfigFlagAccessor
352         @UnsupportedAppUsage
353         boolean enabledRo();
354         @com.android.aconfig.annotations.AssumeTrueForR8
355         @com.android.aconfig.annotations.AconfigFlagAccessor
356         @UnsupportedAppUsage
357         boolean enabledRoExported();
358         @com.android.aconfig.annotations.AconfigFlagAccessor
359         @UnsupportedAppUsage
360         boolean enabledRw();
361     }
362     "#;
363 
364     const EXPECTED_FLAG_COMMON_CONTENT: &str = r#"
365     package com.android.aconfig.test;
366     // TODO(b/303773055): Remove the annotation after access issue is resolved.
367     import android.compat.annotation.UnsupportedAppUsage;
368     /** @hide */
369     public final class Flags {
370         /** @hide */
371         public static final String FLAG_DISABLED_RO = "com.android.aconfig.test.disabled_ro";
372         /** @hide */
373         public static final String FLAG_DISABLED_RW = "com.android.aconfig.test.disabled_rw";
374         /** @hide */
375         public static final String FLAG_DISABLED_RW_EXPORTED = "com.android.aconfig.test.disabled_rw_exported";
376         /** @hide */
377         public static final String FLAG_DISABLED_RW_IN_OTHER_NAMESPACE = "com.android.aconfig.test.disabled_rw_in_other_namespace";
378         /** @hide */
379         public static final String FLAG_ENABLED_FIXED_RO = "com.android.aconfig.test.enabled_fixed_ro";
380         /** @hide */
381         public static final String FLAG_ENABLED_FIXED_RO_EXPORTED = "com.android.aconfig.test.enabled_fixed_ro_exported";
382         /** @hide */
383         public static final String FLAG_ENABLED_RO = "com.android.aconfig.test.enabled_ro";
384         /** @hide */
385         public static final String FLAG_ENABLED_RO_EXPORTED = "com.android.aconfig.test.enabled_ro_exported";
386         /** @hide */
387         public static final String FLAG_ENABLED_RW = "com.android.aconfig.test.enabled_rw";
388 
389         @com.android.aconfig.annotations.AssumeFalseForR8
390         @com.android.aconfig.annotations.AconfigFlagAccessor
391         @UnsupportedAppUsage
392         public static boolean disabledRo() {
393             return FEATURE_FLAGS.disabledRo();
394         }
395         @com.android.aconfig.annotations.AconfigFlagAccessor
396         @UnsupportedAppUsage
397         public static boolean disabledRw() {
398             return FEATURE_FLAGS.disabledRw();
399         }
400         @com.android.aconfig.annotations.AconfigFlagAccessor
401         @UnsupportedAppUsage
402         public static boolean disabledRwExported() {
403             return FEATURE_FLAGS.disabledRwExported();
404         }
405         @com.android.aconfig.annotations.AconfigFlagAccessor
406         @UnsupportedAppUsage
407         public static boolean disabledRwInOtherNamespace() {
408             return FEATURE_FLAGS.disabledRwInOtherNamespace();
409         }
410         @com.android.aconfig.annotations.AssumeTrueForR8
411         @com.android.aconfig.annotations.AconfigFlagAccessor
412         @UnsupportedAppUsage
413         public static boolean enabledFixedRo() {
414             return FEATURE_FLAGS.enabledFixedRo();
415         }
416         @com.android.aconfig.annotations.AssumeTrueForR8
417         @com.android.aconfig.annotations.AconfigFlagAccessor
418         @UnsupportedAppUsage
419         public static boolean enabledFixedRoExported() {
420             return FEATURE_FLAGS.enabledFixedRoExported();
421         }
422         @com.android.aconfig.annotations.AssumeTrueForR8
423         @com.android.aconfig.annotations.AconfigFlagAccessor
424         @UnsupportedAppUsage
425         public static boolean enabledRo() {
426             return FEATURE_FLAGS.enabledRo();
427         }
428         @com.android.aconfig.annotations.AssumeTrueForR8
429         @com.android.aconfig.annotations.AconfigFlagAccessor
430         @UnsupportedAppUsage
431         public static boolean enabledRoExported() {
432             return FEATURE_FLAGS.enabledRoExported();
433         }
434         @com.android.aconfig.annotations.AconfigFlagAccessor
435         @UnsupportedAppUsage
436         public static boolean enabledRw() {
437             return FEATURE_FLAGS.enabledRw();
438         }
439     "#;
440 
441     const EXPECTED_CUSTOMFEATUREFLAGS_CONTENT: &str = r#"
442     package com.android.aconfig.test;
443 
444     // TODO(b/303773055): Remove the annotation after access issue is resolved.
445     import android.compat.annotation.UnsupportedAppUsage;
446     import java.util.Arrays;
447     import java.util.HashSet;
448     import java.util.List;
449     import java.util.Set;
450     import java.util.function.BiPredicate;
451     import java.util.function.Predicate;
452 
453     /** @hide */
454     public class CustomFeatureFlags implements FeatureFlags {
455 
456         private BiPredicate<String, Predicate<FeatureFlags>> mGetValueImpl;
457 
458         public CustomFeatureFlags(BiPredicate<String, Predicate<FeatureFlags>> getValueImpl) {
459             mGetValueImpl = getValueImpl;
460         }
461 
462         @Override
463         @UnsupportedAppUsage
464         public boolean disabledRo() {
465             return getValue(Flags.FLAG_DISABLED_RO,
466                     FeatureFlags::disabledRo);
467         }
468         @Override
469         @UnsupportedAppUsage
470         public boolean disabledRw() {
471             return getValue(Flags.FLAG_DISABLED_RW,
472                 FeatureFlags::disabledRw);
473         }
474         @Override
475         @UnsupportedAppUsage
476         public boolean disabledRwExported() {
477             return getValue(Flags.FLAG_DISABLED_RW_EXPORTED,
478                 FeatureFlags::disabledRwExported);
479         }
480         @Override
481         @UnsupportedAppUsage
482         public boolean disabledRwInOtherNamespace() {
483             return getValue(Flags.FLAG_DISABLED_RW_IN_OTHER_NAMESPACE,
484                 FeatureFlags::disabledRwInOtherNamespace);
485         }
486         @Override
487         @UnsupportedAppUsage
488         public boolean enabledFixedRo() {
489             return getValue(Flags.FLAG_ENABLED_FIXED_RO,
490                 FeatureFlags::enabledFixedRo);
491         }
492         @Override
493         @UnsupportedAppUsage
494         public boolean enabledFixedRoExported() {
495             return getValue(Flags.FLAG_ENABLED_FIXED_RO_EXPORTED,
496                 FeatureFlags::enabledFixedRoExported);
497         }
498         @Override
499         @UnsupportedAppUsage
500         public boolean enabledRo() {
501             return getValue(Flags.FLAG_ENABLED_RO,
502                 FeatureFlags::enabledRo);
503         }
504         @Override
505         @UnsupportedAppUsage
506         public boolean enabledRoExported() {
507             return getValue(Flags.FLAG_ENABLED_RO_EXPORTED,
508                 FeatureFlags::enabledRoExported);
509         }
510         @Override
511         @UnsupportedAppUsage
512         public boolean enabledRw() {
513             return getValue(Flags.FLAG_ENABLED_RW,
514                 FeatureFlags::enabledRw);
515         }
516 
517         public boolean isFlagReadOnlyOptimized(String flagName) {
518             if (mReadOnlyFlagsSet.contains(flagName) &&
519                 isOptimizationEnabled()) {
520                     return true;
521             }
522             return false;
523         }
524 
525         @com.android.aconfig.annotations.AssumeTrueForR8
526         private boolean isOptimizationEnabled() {
527             return false;
528         }
529 
530         protected boolean getValue(String flagName, Predicate<FeatureFlags> getter) {
531             return mGetValueImpl.test(flagName, getter);
532         }
533 
534         public List<String> getFlagNames() {
535             return Arrays.asList(
536                 Flags.FLAG_DISABLED_RO,
537                 Flags.FLAG_DISABLED_RW,
538                 Flags.FLAG_DISABLED_RW_EXPORTED,
539                 Flags.FLAG_DISABLED_RW_IN_OTHER_NAMESPACE,
540                 Flags.FLAG_ENABLED_FIXED_RO,
541                 Flags.FLAG_ENABLED_FIXED_RO_EXPORTED,
542                 Flags.FLAG_ENABLED_RO,
543                 Flags.FLAG_ENABLED_RO_EXPORTED,
544                 Flags.FLAG_ENABLED_RW
545             );
546         }
547 
548         private Set<String> mReadOnlyFlagsSet = new HashSet<>(
549             Arrays.asList(
550                 Flags.FLAG_DISABLED_RO,
551                 Flags.FLAG_ENABLED_FIXED_RO,
552                 Flags.FLAG_ENABLED_FIXED_RO_EXPORTED,
553                 Flags.FLAG_ENABLED_RO,
554                 Flags.FLAG_ENABLED_RO_EXPORTED,
555                 ""
556             )
557         );
558     }
559     "#;
560 
561     const EXPECTED_FAKEFEATUREFLAGSIMPL_CONTENT: &str = r#"
562     package com.android.aconfig.test;
563 
564     import java.util.HashMap;
565     import java.util.Map;
566     import java.util.function.Predicate;
567 
568     /** @hide */
569     public class FakeFeatureFlagsImpl extends CustomFeatureFlags {
570         private final Map<String, Boolean> mFlagMap = new HashMap<>();
571         private final FeatureFlags mDefaults;
572 
573         public FakeFeatureFlagsImpl() {
574             this(null);
575         }
576 
577         public FakeFeatureFlagsImpl(FeatureFlags defaults) {
578             super(null);
579             mDefaults = defaults;
580             // Initialize the map with null values
581             for (String flagName : getFlagNames()) {
582                 mFlagMap.put(flagName, null);
583             }
584         }
585 
586         @Override
587         protected boolean getValue(String flagName, Predicate<FeatureFlags> getter) {
588             Boolean value = this.mFlagMap.get(flagName);
589             if (value != null) {
590                 return value;
591             }
592             if (mDefaults != null) {
593                 return getter.test(mDefaults);
594             }
595             throw new IllegalArgumentException(flagName + " is not set");
596         }
597 
598         public void setFlag(String flagName, boolean value) {
599             if (!this.mFlagMap.containsKey(flagName)) {
600                 throw new IllegalArgumentException("no such flag " + flagName);
601             }
602             this.mFlagMap.put(flagName, value);
603         }
604 
605         public void resetAll() {
606             for (Map.Entry entry : mFlagMap.entrySet()) {
607                 entry.setValue(null);
608             }
609         }
610     }
611     "#;
612 
613     #[test]
test_generate_java_code_production()614     fn test_generate_java_code_production() {
615         let parsed_flags = crate::test::parse_test_flags();
616         let mode = CodegenMode::Production;
617         let modified_parsed_flags =
618             crate::commands::modify_parsed_flags_based_on_mode(parsed_flags, mode).unwrap();
619         let flag_ids =
620             assign_flag_ids(crate::test::TEST_PACKAGE, modified_parsed_flags.iter()).unwrap();
621         let config = JavaCodegenConfig {
622             codegen_mode: mode,
623             flag_ids,
624             allow_instrumentation: true,
625             package_fingerprint: 5801144784618221668,
626             new_exported: false,
627             single_exported_file: false,
628             finalized_flags: FinalizedFlagMap::new(),
629         };
630         let generated_files = generate_java_code(
631             crate::test::TEST_PACKAGE,
632             modified_parsed_flags.into_iter(),
633             config,
634         )
635         .unwrap();
636         let expect_flags_content = EXPECTED_FLAG_COMMON_CONTENT.to_string()
637             + r#"
638             private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl();
639         }"#;
640 
641         let expected_featureflagsmpl_content = r#"
642         package com.android.aconfig.test;
643         // TODO(b/303773055): Remove the annotation after access issue is resolved.
644         import android.compat.annotation.UnsupportedAppUsage;
645         import android.os.flagging.PlatformAconfigPackageInternal;
646         import android.util.Log;
647         /** @hide */
648         public final class FeatureFlagsImpl implements FeatureFlags {
649             private static final String TAG = "FeatureFlagsImpl";
650             private static volatile boolean isCached = false;
651             private static boolean disabledRw = false;
652             private static boolean disabledRwExported = false;
653             private static boolean disabledRwInOtherNamespace = false;
654             private static boolean enabledRw = true;
655             private void init() {
656                 try {
657                     PlatformAconfigPackageInternal reader = PlatformAconfigPackageInternal.load("com.android.aconfig.test", 0x5081CE7221C77064L);
658                     disabledRw = reader.getBooleanFlagValue(0);
659                     disabledRwExported = reader.getBooleanFlagValue(1);
660                     enabledRw = reader.getBooleanFlagValue(7);
661                     disabledRwInOtherNamespace = reader.getBooleanFlagValue(2);
662                 } catch (Exception e) {
663                     Log.e(TAG, e.toString());
664                 } catch (LinkageError e) {
665                     // for mainline module running on older devices.
666                     // This should be replaces to version check, after the version bump.
667                     Log.e(TAG, e.toString());
668                 }
669                 isCached = true;
670             }
671 
672             @Override
673             @com.android.aconfig.annotations.AconfigFlagAccessor
674             @UnsupportedAppUsage
675             public boolean disabledRo() {
676                 return false;
677             }
678             @Override
679             @com.android.aconfig.annotations.AconfigFlagAccessor
680             @UnsupportedAppUsage
681             public boolean disabledRw() {
682                 if (!isCached) {
683                     init();
684                 }
685                 return disabledRw;
686             }
687             @Override
688             @com.android.aconfig.annotations.AconfigFlagAccessor
689             @UnsupportedAppUsage
690             public boolean disabledRwExported() {
691                 if (!isCached) {
692                     init();
693                 }
694                 return disabledRwExported;
695             }
696             @Override
697             @com.android.aconfig.annotations.AconfigFlagAccessor
698             @UnsupportedAppUsage
699             public boolean disabledRwInOtherNamespace() {
700                 if (!isCached) {
701                     init();
702                 }
703                 return disabledRwInOtherNamespace;
704             }
705             @Override
706             @com.android.aconfig.annotations.AconfigFlagAccessor
707             @UnsupportedAppUsage
708             public boolean enabledFixedRo() {
709                 return true;
710             }
711             @Override
712             @com.android.aconfig.annotations.AconfigFlagAccessor
713             @UnsupportedAppUsage
714             public boolean enabledFixedRoExported() {
715                 return true;
716             }
717             @Override
718             @com.android.aconfig.annotations.AconfigFlagAccessor
719             @UnsupportedAppUsage
720             public boolean enabledRo() {
721                 return true;
722             }
723             @Override
724             @com.android.aconfig.annotations.AconfigFlagAccessor
725             @UnsupportedAppUsage
726             public boolean enabledRoExported() {
727                 return true;
728             }
729             @Override
730             @com.android.aconfig.annotations.AconfigFlagAccessor
731             @UnsupportedAppUsage
732             public boolean enabledRw() {
733                 if (!isCached) {
734                     init();
735                 }
736                 return enabledRw;
737             }
738         }
739         "#;
740 
741         let mut file_set = HashMap::from([
742             ("com/android/aconfig/test/Flags.java", expect_flags_content.as_str()),
743             ("com/android/aconfig/test/FeatureFlagsImpl.java", expected_featureflagsmpl_content),
744             ("com/android/aconfig/test/FeatureFlags.java", EXPECTED_FEATUREFLAGS_COMMON_CONTENT),
745             (
746                 "com/android/aconfig/test/CustomFeatureFlags.java",
747                 EXPECTED_CUSTOMFEATUREFLAGS_CONTENT,
748             ),
749             (
750                 "com/android/aconfig/test/FakeFeatureFlagsImpl.java",
751                 EXPECTED_FAKEFEATUREFLAGSIMPL_CONTENT,
752             ),
753         ]);
754 
755         for file in generated_files {
756             let file_path = file.path.to_str().unwrap();
757             assert!(file_set.contains_key(file_path), "Cannot find {}", file_path);
758             assert_eq!(
759                 None,
760                 crate::test::first_significant_code_diff(
761                     file_set.get(file_path).unwrap(),
762                     &String::from_utf8(file.contents).unwrap()
763                 ),
764                 "File {} content is not correct",
765                 file_path
766             );
767             file_set.remove(file_path);
768         }
769 
770         assert!(file_set.is_empty());
771     }
772 
773     #[test]
test_generate_java_code_exported()774     fn test_generate_java_code_exported() {
775         let parsed_flags = crate::test::parse_test_flags();
776         let mode = CodegenMode::Exported;
777         let modified_parsed_flags =
778             crate::commands::modify_parsed_flags_based_on_mode(parsed_flags, mode).unwrap();
779         let flag_ids =
780             assign_flag_ids(crate::test::TEST_PACKAGE, modified_parsed_flags.iter()).unwrap();
781         let config = JavaCodegenConfig {
782             codegen_mode: mode,
783             flag_ids,
784             allow_instrumentation: true,
785             package_fingerprint: 5801144784618221668,
786             new_exported: false,
787             single_exported_file: false,
788             finalized_flags: FinalizedFlagMap::new(),
789         };
790         let generated_files = generate_java_code(
791             crate::test::TEST_PACKAGE,
792             modified_parsed_flags.into_iter(),
793             config,
794         )
795         .unwrap();
796 
797         let expect_flags_content = r#"
798         package com.android.aconfig.test;
799         import android.os.Build;
800         /** @hide */
801         public final class Flags {
802             /** @hide */
803             public static final String FLAG_DISABLED_RW_EXPORTED = "com.android.aconfig.test.disabled_rw_exported";
804             /** @hide */
805             public static final String FLAG_ENABLED_FIXED_RO_EXPORTED = "com.android.aconfig.test.enabled_fixed_ro_exported";
806             /** @hide */
807             public static final String FLAG_ENABLED_RO_EXPORTED = "com.android.aconfig.test.enabled_ro_exported";
808             public static boolean disabledRwExported() {
809                 return FEATURE_FLAGS.disabledRwExported();
810             }
811             public static boolean enabledFixedRoExported() {
812                 return FEATURE_FLAGS.enabledFixedRoExported();
813             }
814             public static boolean enabledRoExported() {
815                 return FEATURE_FLAGS.enabledRoExported();
816             }
817             private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl();
818         }
819         "#;
820 
821         let expect_feature_flags_content = r#"
822         package com.android.aconfig.test;
823         /** @hide */
824         public interface FeatureFlags {
825             boolean disabledRwExported();
826             boolean enabledFixedRoExported();
827             boolean enabledRoExported();
828         }
829         "#;
830 
831         let expect_feature_flags_impl_content = r#"
832         package com.android.aconfig.test;
833         import android.os.Binder;
834         import android.provider.DeviceConfig;
835         import android.provider.DeviceConfig.Properties;
836         /** @hide */
837         public final class FeatureFlagsImpl implements FeatureFlags {
838             private static volatile boolean aconfig_test_is_cached = false;
839             private static boolean disabledRwExported = false;
840             private static boolean enabledFixedRoExported = false;
841             private static boolean enabledRoExported = false;
842 
843             private void load_overrides_aconfig_test() {
844                 final long ident = Binder.clearCallingIdentity();
845                 try {
846                     Properties properties = DeviceConfig.getProperties("aconfig_test");
847                     disabledRwExported =
848                         properties.getBoolean(Flags.FLAG_DISABLED_RW_EXPORTED, false);
849                     enabledFixedRoExported =
850                         properties.getBoolean(Flags.FLAG_ENABLED_FIXED_RO_EXPORTED, false);
851                     enabledRoExported =
852                         properties.getBoolean(Flags.FLAG_ENABLED_RO_EXPORTED, false);
853                 } catch (NullPointerException e) {
854                     throw new RuntimeException(
855                         "Cannot read value from namespace aconfig_test "
856                         + "from DeviceConfig. It could be that the code using flag "
857                         + "executed before SettingsProvider initialization. Please use "
858                         + "fixed read-only flag by adding is_fixed_read_only: true in "
859                         + "flag declaration.",
860                         e
861                     );
862                 } catch (SecurityException e) {
863                     // for isolated process case, skip loading flag value from the storage, use the default
864                 } finally {
865                     Binder.restoreCallingIdentity(ident);
866                 }
867                 aconfig_test_is_cached = true;
868             }
869             @Override
870             public boolean disabledRwExported() {
871                 if (!aconfig_test_is_cached) {
872                         load_overrides_aconfig_test();
873                 }
874                 return disabledRwExported;
875             }
876             @Override
877             public boolean enabledFixedRoExported() {
878                 if (!aconfig_test_is_cached) {
879                         load_overrides_aconfig_test();
880                 }
881                 return enabledFixedRoExported;
882             }
883             @Override
884             public boolean enabledRoExported() {
885                 if (!aconfig_test_is_cached) {
886                         load_overrides_aconfig_test();
887                 }
888                 return enabledRoExported;
889             }
890         }"#;
891 
892         let expect_custom_feature_flags_content = r#"
893         package com.android.aconfig.test;
894 
895         import java.util.Arrays;
896         import java.util.HashMap;
897         import java.util.Map;
898         import java.util.HashSet;
899         import java.util.List;
900         import java.util.Set;
901         import java.util.function.BiPredicate;
902         import java.util.function.Predicate;
903 
904         import android.os.Build;
905 
906         /** @hide */
907         public class CustomFeatureFlags implements FeatureFlags {
908 
909             private BiPredicate<String, Predicate<FeatureFlags>> mGetValueImpl;
910 
911             public CustomFeatureFlags(BiPredicate<String, Predicate<FeatureFlags>> getValueImpl) {
912                 mGetValueImpl = getValueImpl;
913             }
914 
915             @Override
916             public boolean disabledRwExported() {
917                 return getValue(Flags.FLAG_DISABLED_RW_EXPORTED,
918                     FeatureFlags::disabledRwExported);
919             }
920             @Override
921             public boolean enabledFixedRoExported() {
922                 return getValue(Flags.FLAG_ENABLED_FIXED_RO_EXPORTED,
923                     FeatureFlags::enabledFixedRoExported);
924             }
925             @Override
926             public boolean enabledRoExported() {
927                 return getValue(Flags.FLAG_ENABLED_RO_EXPORTED,
928                     FeatureFlags::enabledRoExported);
929             }
930 
931             protected boolean getValue(String flagName, Predicate<FeatureFlags> getter) {
932                 return mGetValueImpl.test(flagName, getter);
933             }
934 
935             public List<String> getFlagNames() {
936                 return Arrays.asList(
937                     Flags.FLAG_DISABLED_RW_EXPORTED,
938                     Flags.FLAG_ENABLED_FIXED_RO_EXPORTED,
939                     Flags.FLAG_ENABLED_RO_EXPORTED
940                 );
941             }
942 
943             private Set<String> mReadOnlyFlagsSet = new HashSet<>(
944                 Arrays.asList(
945                     ""
946                 )
947             );
948 
949             private Map<String, Integer> mFinalizedFlags = new HashMap<>(
950                 Map.ofEntries(
951                     Map.entry("", Integer.MAX_VALUE)
952                 )
953             );
954 
955             public boolean isFlagFinalized(String flagName) {
956                 if (!mFinalizedFlags.containsKey(flagName)) {
957                     return false;
958                 }
959                 return Build.VERSION.SDK_INT >= mFinalizedFlags.get(flagName);
960             }
961         }
962     "#;
963 
964         let mut file_set = HashMap::from([
965             ("com/android/aconfig/test/Flags.java", expect_flags_content),
966             ("com/android/aconfig/test/FeatureFlags.java", expect_feature_flags_content),
967             ("com/android/aconfig/test/FeatureFlagsImpl.java", expect_feature_flags_impl_content),
968             (
969                 "com/android/aconfig/test/CustomFeatureFlags.java",
970                 expect_custom_feature_flags_content,
971             ),
972             (
973                 "com/android/aconfig/test/FakeFeatureFlagsImpl.java",
974                 EXPECTED_FAKEFEATUREFLAGSIMPL_CONTENT,
975             ),
976         ]);
977 
978         for file in generated_files {
979             let file_path = file.path.to_str().unwrap();
980             assert!(file_set.contains_key(file_path), "Cannot find {}", file_path);
981             assert_eq!(
982                 None,
983                 crate::test::first_significant_code_diff(
984                     file_set.get(file_path).unwrap(),
985                     &String::from_utf8(file.contents).unwrap()
986                 ),
987                 "File {} content is not correct",
988                 file_path
989             );
990             file_set.remove(file_path);
991         }
992 
993         assert!(file_set.is_empty());
994     }
995 
996     #[test]
test_generate_java_code_new_exported()997     fn test_generate_java_code_new_exported() {
998         let parsed_flags = crate::test::parse_test_flags();
999         let mode = CodegenMode::Exported;
1000         let modified_parsed_flags =
1001             crate::commands::modify_parsed_flags_based_on_mode(parsed_flags, mode).unwrap();
1002         let flag_ids =
1003             assign_flag_ids(crate::test::TEST_PACKAGE, modified_parsed_flags.iter()).unwrap();
1004         let config = JavaCodegenConfig {
1005             codegen_mode: mode,
1006             flag_ids,
1007             allow_instrumentation: true,
1008             package_fingerprint: 5801144784618221668,
1009             new_exported: true,
1010             single_exported_file: false,
1011             finalized_flags: FinalizedFlagMap::new(),
1012         };
1013         let generated_files = generate_java_code(
1014             crate::test::TEST_PACKAGE,
1015             modified_parsed_flags.into_iter(),
1016             config,
1017         )
1018         .unwrap();
1019 
1020         let expect_flags_content = r#"
1021         package com.android.aconfig.test;
1022         import android.os.Build;
1023         /** @hide */
1024         public final class Flags {
1025             /** @hide */
1026             public static final String FLAG_DISABLED_RW_EXPORTED = "com.android.aconfig.test.disabled_rw_exported";
1027             /** @hide */
1028             public static final String FLAG_ENABLED_FIXED_RO_EXPORTED = "com.android.aconfig.test.enabled_fixed_ro_exported";
1029             /** @hide */
1030             public static final String FLAG_ENABLED_RO_EXPORTED = "com.android.aconfig.test.enabled_ro_exported";
1031             public static boolean disabledRwExported() {
1032                 return FEATURE_FLAGS.disabledRwExported();
1033             }
1034             public static boolean enabledFixedRoExported() {
1035                 return FEATURE_FLAGS.enabledFixedRoExported();
1036             }
1037             public static boolean enabledRoExported() {
1038                 return FEATURE_FLAGS.enabledRoExported();
1039             }
1040             private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl();
1041         }
1042         "#;
1043 
1044         let expect_feature_flags_content = r#"
1045         package com.android.aconfig.test;
1046         /** @hide */
1047         public interface FeatureFlags {
1048             boolean disabledRwExported();
1049             boolean enabledFixedRoExported();
1050             boolean enabledRoExported();
1051         }
1052         "#;
1053 
1054         let expect_feature_flags_impl_content = r#"
1055         package com.android.aconfig.test;
1056         import android.os.Build;
1057         import android.os.flagging.AconfigPackage;
1058         import android.util.Log;
1059         /** @hide */
1060         public final class FeatureFlagsImpl implements FeatureFlags {
1061             private static final String TAG = "FeatureFlagsImplExport";
1062             private static volatile boolean isCached = false;
1063             private static boolean disabledRwExported = false;
1064             private static boolean enabledFixedRoExported = false;
1065             private static boolean enabledRoExported = false;
1066             private void init() {
1067                 try {
1068                     AconfigPackage reader = AconfigPackage.load("com.android.aconfig.test");
1069                     disabledRwExported = reader.getBooleanFlagValue("disabled_rw_exported", false);
1070                     enabledFixedRoExported = reader.getBooleanFlagValue("enabled_fixed_ro_exported", false);
1071                     enabledRoExported = reader.getBooleanFlagValue("enabled_ro_exported", false);
1072                 } catch (Exception e) {
1073                     // pass
1074                     Log.e(TAG, e.toString());
1075                 } catch (LinkageError e) {
1076                     // for mainline module running on older devices.
1077                     // This should be replaces to version check, after the version bump.
1078                     Log.w(TAG, e.toString());
1079                 }
1080                 isCached = true;
1081             }
1082             @Override
1083             public boolean disabledRwExported() {
1084                 if (!isCached) {
1085                     init();
1086                 }
1087                 return disabledRwExported;
1088             }
1089             @Override
1090             public boolean enabledFixedRoExported() {
1091                 if (!isCached) {
1092                     init();
1093                 }
1094                 return enabledFixedRoExported;
1095             }
1096             @Override
1097             public boolean enabledRoExported() {
1098                 if (!isCached) {
1099                     init();
1100                 }
1101                 return enabledRoExported;
1102             }
1103         }"#;
1104 
1105         let expect_custom_feature_flags_content = r#"
1106         package com.android.aconfig.test;
1107 
1108         import java.util.Arrays;
1109         import java.util.HashMap;
1110         import java.util.Map;
1111         import java.util.HashSet;
1112         import java.util.List;
1113         import java.util.Set;
1114         import java.util.function.BiPredicate;
1115         import java.util.function.Predicate;
1116         import android.os.Build;
1117 
1118         /** @hide */
1119         public class CustomFeatureFlags implements FeatureFlags {
1120 
1121             private BiPredicate<String, Predicate<FeatureFlags>> mGetValueImpl;
1122 
1123             public CustomFeatureFlags(BiPredicate<String, Predicate<FeatureFlags>> getValueImpl) {
1124                 mGetValueImpl = getValueImpl;
1125             }
1126 
1127             @Override
1128             public boolean disabledRwExported() {
1129                 return getValue(Flags.FLAG_DISABLED_RW_EXPORTED,
1130                     FeatureFlags::disabledRwExported);
1131             }
1132             @Override
1133             public boolean enabledFixedRoExported() {
1134                 return getValue(Flags.FLAG_ENABLED_FIXED_RO_EXPORTED,
1135                     FeatureFlags::enabledFixedRoExported);
1136             }
1137             @Override
1138             public boolean enabledRoExported() {
1139                 return getValue(Flags.FLAG_ENABLED_RO_EXPORTED,
1140                     FeatureFlags::enabledRoExported);
1141             }
1142 
1143             protected boolean getValue(String flagName, Predicate<FeatureFlags> getter) {
1144                 return mGetValueImpl.test(flagName, getter);
1145             }
1146 
1147             public List<String> getFlagNames() {
1148                 return Arrays.asList(
1149                     Flags.FLAG_DISABLED_RW_EXPORTED,
1150                     Flags.FLAG_ENABLED_FIXED_RO_EXPORTED,
1151                     Flags.FLAG_ENABLED_RO_EXPORTED
1152                 );
1153             }
1154 
1155             private Set<String> mReadOnlyFlagsSet = new HashSet<>(
1156                 Arrays.asList(
1157                     ""
1158                 )
1159             );
1160 
1161             private Map<String, Integer> mFinalizedFlags = new HashMap<>(
1162                 Map.ofEntries(
1163                     Map.entry("", Integer.MAX_VALUE)
1164                 )
1165             );
1166 
1167             public boolean isFlagFinalized(String flagName) {
1168                 if (!mFinalizedFlags.containsKey(flagName)) {
1169                     return false;
1170                 }
1171                 return Build.VERSION.SDK_INT >= mFinalizedFlags.get(flagName);
1172             }
1173         }
1174     "#;
1175 
1176         let mut file_set = HashMap::from([
1177             ("com/android/aconfig/test/Flags.java", expect_flags_content),
1178             ("com/android/aconfig/test/FeatureFlags.java", expect_feature_flags_content),
1179             ("com/android/aconfig/test/FeatureFlagsImpl.java", expect_feature_flags_impl_content),
1180             (
1181                 "com/android/aconfig/test/CustomFeatureFlags.java",
1182                 expect_custom_feature_flags_content,
1183             ),
1184             (
1185                 "com/android/aconfig/test/FakeFeatureFlagsImpl.java",
1186                 EXPECTED_FAKEFEATUREFLAGSIMPL_CONTENT,
1187             ),
1188         ]);
1189 
1190         for file in generated_files {
1191             let file_path = file.path.to_str().unwrap();
1192             assert!(file_set.contains_key(file_path), "Cannot find {}", file_path);
1193             assert_eq!(
1194                 None,
1195                 crate::test::first_significant_code_diff(
1196                     file_set.get(file_path).unwrap(),
1197                     &String::from_utf8(file.contents).unwrap()
1198                 ),
1199                 "File {} content is not correct",
1200                 file_path
1201             );
1202             file_set.remove(file_path);
1203         }
1204 
1205         assert!(file_set.is_empty());
1206     }
1207 
1208     #[test]
test_generate_java_code_new_exported_with_sdk_check()1209     fn test_generate_java_code_new_exported_with_sdk_check() {
1210         let parsed_flags = crate::test::parse_test_flags();
1211         let mode = CodegenMode::Exported;
1212         let modified_parsed_flags =
1213             crate::commands::modify_parsed_flags_based_on_mode(parsed_flags, mode).unwrap();
1214         let flag_ids =
1215             assign_flag_ids(crate::test::TEST_PACKAGE, modified_parsed_flags.iter()).unwrap();
1216         let mut finalized_flags = FinalizedFlagMap::new();
1217         finalized_flags.insert_if_new(
1218             ApiLevel(36),
1219             FinalizedFlag {
1220                 flag_name: "disabled_rw_exported".to_string(),
1221                 package_name: "com.android.aconfig.test".to_string(),
1222             },
1223         );
1224         let config = JavaCodegenConfig {
1225             codegen_mode: mode,
1226             flag_ids,
1227             allow_instrumentation: true,
1228             package_fingerprint: 5801144784618221668,
1229             new_exported: true,
1230             single_exported_file: false,
1231             finalized_flags,
1232         };
1233         let generated_files = generate_java_code(
1234             crate::test::TEST_PACKAGE,
1235             modified_parsed_flags.into_iter(),
1236             config,
1237         )
1238         .unwrap();
1239 
1240         let expect_flags_content = r#"
1241         package com.android.aconfig.test;
1242         import android.os.Build;
1243         /** @hide */
1244         public final class Flags {
1245             /** @hide */
1246             public static final String FLAG_DISABLED_RW_EXPORTED = "com.android.aconfig.test.disabled_rw_exported";
1247             /** @hide */
1248             public static final String FLAG_ENABLED_FIXED_RO_EXPORTED = "com.android.aconfig.test.enabled_fixed_ro_exported";
1249             /** @hide */
1250             public static final String FLAG_ENABLED_RO_EXPORTED = "com.android.aconfig.test.enabled_ro_exported";
1251             public static boolean disabledRwExported() {
1252                 if (Build.VERSION.SDK_INT >= 36) {
1253                   return true;
1254                 }
1255                 return FEATURE_FLAGS.disabledRwExported();
1256             }
1257             public static boolean enabledFixedRoExported() {
1258                 return FEATURE_FLAGS.enabledFixedRoExported();
1259             }
1260             public static boolean enabledRoExported() {
1261                 return FEATURE_FLAGS.enabledRoExported();
1262             }
1263             private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl();
1264         }
1265         "#;
1266 
1267         let expect_feature_flags_content = r#"
1268         package com.android.aconfig.test;
1269         /** @hide */
1270         public interface FeatureFlags {
1271             boolean disabledRwExported();
1272             boolean enabledFixedRoExported();
1273             boolean enabledRoExported();
1274         }
1275         "#;
1276 
1277         let expect_feature_flags_impl_content = r#"
1278         package com.android.aconfig.test;
1279         import android.os.Build;
1280         import android.os.flagging.AconfigPackage;
1281         import android.util.Log;
1282         /** @hide */
1283         public final class FeatureFlagsImpl implements FeatureFlags {
1284             private static final String TAG = "FeatureFlagsImplExport";
1285             private static volatile boolean isCached = false;
1286             private static boolean disabledRwExported = false;
1287             private static boolean enabledFixedRoExported = false;
1288             private static boolean enabledRoExported = false;
1289             private void init() {
1290                 try {
1291                     AconfigPackage reader = AconfigPackage.load("com.android.aconfig.test");
1292                     disabledRwExported = Build.VERSION.SDK_INT >= 36 ? true : reader.getBooleanFlagValue("disabled_rw_exported", false);
1293                     enabledFixedRoExported = reader.getBooleanFlagValue("enabled_fixed_ro_exported", false);
1294                     enabledRoExported = reader.getBooleanFlagValue("enabled_ro_exported", false);
1295                 } catch (Exception e) {
1296                     // pass
1297                     Log.e(TAG, e.toString());
1298                 } catch (LinkageError e) {
1299                     // for mainline module running on older devices.
1300                     // This should be replaces to version check, after the version bump.
1301                     Log.w(TAG, e.toString());
1302                 }
1303                 isCached = true;
1304             }
1305             @Override
1306             public boolean disabledRwExported() {
1307                 if (!isCached) {
1308                     init();
1309                 }
1310                 return disabledRwExported;
1311             }
1312             @Override
1313             public boolean enabledFixedRoExported() {
1314                 if (!isCached) {
1315                     init();
1316                 }
1317                 return enabledFixedRoExported;
1318             }
1319             @Override
1320             public boolean enabledRoExported() {
1321                 if (!isCached) {
1322                     init();
1323                 }
1324                 return enabledRoExported;
1325             }
1326         }"#;
1327 
1328         let expect_custom_feature_flags_content = r#"
1329         package com.android.aconfig.test;
1330 
1331         import java.util.Arrays;
1332         import java.util.HashMap;
1333         import java.util.Map;
1334         import java.util.HashSet;
1335         import java.util.List;
1336         import java.util.Set;
1337         import java.util.function.BiPredicate;
1338         import java.util.function.Predicate;
1339         import android.os.Build;
1340 
1341         /** @hide */
1342         public class CustomFeatureFlags implements FeatureFlags {
1343 
1344             private BiPredicate<String, Predicate<FeatureFlags>> mGetValueImpl;
1345 
1346             public CustomFeatureFlags(BiPredicate<String, Predicate<FeatureFlags>> getValueImpl) {
1347                 mGetValueImpl = getValueImpl;
1348             }
1349 
1350             @Override
1351             public boolean disabledRwExported() {
1352                 return getValue(Flags.FLAG_DISABLED_RW_EXPORTED,
1353                     FeatureFlags::disabledRwExported);
1354             }
1355             @Override
1356             public boolean enabledFixedRoExported() {
1357                 return getValue(Flags.FLAG_ENABLED_FIXED_RO_EXPORTED,
1358                     FeatureFlags::enabledFixedRoExported);
1359             }
1360             @Override
1361             public boolean enabledRoExported() {
1362                 return getValue(Flags.FLAG_ENABLED_RO_EXPORTED,
1363                     FeatureFlags::enabledRoExported);
1364             }
1365 
1366             protected boolean getValue(String flagName, Predicate<FeatureFlags> getter) {
1367                 return mGetValueImpl.test(flagName, getter);
1368             }
1369 
1370             public List<String> getFlagNames() {
1371                 return Arrays.asList(
1372                     Flags.FLAG_DISABLED_RW_EXPORTED,
1373                     Flags.FLAG_ENABLED_FIXED_RO_EXPORTED,
1374                     Flags.FLAG_ENABLED_RO_EXPORTED
1375                 );
1376             }
1377 
1378             private Set<String> mReadOnlyFlagsSet = new HashSet<>(
1379                 Arrays.asList(
1380                     ""
1381                 )
1382             );
1383 
1384             private Map<String, Integer> mFinalizedFlags = new HashMap<>(
1385                 Map.ofEntries(
1386                     Map.entry(Flags.FLAG_DISABLED_RW_EXPORTED, 36),
1387                     Map.entry("", Integer.MAX_VALUE)
1388                 )
1389             );
1390 
1391             public boolean isFlagFinalized(String flagName) {
1392                 if (!mFinalizedFlags.containsKey(flagName)) {
1393                     return false;
1394                 }
1395                 return Build.VERSION.SDK_INT >= mFinalizedFlags.get(flagName);
1396             }
1397         }
1398     "#;
1399 
1400         let mut file_set = HashMap::from([
1401             ("com/android/aconfig/test/Flags.java", expect_flags_content),
1402             ("com/android/aconfig/test/FeatureFlags.java", expect_feature_flags_content),
1403             ("com/android/aconfig/test/FeatureFlagsImpl.java", expect_feature_flags_impl_content),
1404             (
1405                 "com/android/aconfig/test/CustomFeatureFlags.java",
1406                 expect_custom_feature_flags_content,
1407             ),
1408             (
1409                 "com/android/aconfig/test/FakeFeatureFlagsImpl.java",
1410                 EXPECTED_FAKEFEATUREFLAGSIMPL_CONTENT,
1411             ),
1412         ]);
1413 
1414         for file in generated_files {
1415             let file_path = file.path.to_str().unwrap();
1416             assert!(file_set.contains_key(file_path), "Cannot find {}", file_path);
1417             assert_eq!(
1418                 None,
1419                 crate::test::first_significant_code_diff(
1420                     file_set.get(file_path).unwrap(),
1421                     &String::from_utf8(file.contents).unwrap()
1422                 ),
1423                 "File {} content is not correct",
1424                 file_path
1425             );
1426             file_set.remove(file_path);
1427         }
1428 
1429         assert!(file_set.is_empty());
1430     }
1431 
1432     // Test that the SDK check isn't added unless the library is exported (even
1433     // if the flag is present in finalized_flags).
1434     #[test]
test_generate_java_code_flags_with_sdk_check()1435     fn test_generate_java_code_flags_with_sdk_check() {
1436         let parsed_flags = crate::test::parse_test_flags();
1437         let mode = CodegenMode::Production;
1438         let modified_parsed_flags =
1439             crate::commands::modify_parsed_flags_based_on_mode(parsed_flags, mode).unwrap();
1440         let flag_ids =
1441             assign_flag_ids(crate::test::TEST_PACKAGE, modified_parsed_flags.iter()).unwrap();
1442         let mut finalized_flags = FinalizedFlagMap::new();
1443         finalized_flags.insert_if_new(
1444             ApiLevel(36),
1445             FinalizedFlag {
1446                 flag_name: "disabled_rw".to_string(),
1447                 package_name: "com.android.aconfig.test".to_string(),
1448             },
1449         );
1450         let config = JavaCodegenConfig {
1451             codegen_mode: mode,
1452             flag_ids,
1453             allow_instrumentation: true,
1454             package_fingerprint: 5801144784618221668,
1455             new_exported: true,
1456             single_exported_file: false,
1457             finalized_flags,
1458         };
1459         let generated_files = generate_java_code(
1460             crate::test::TEST_PACKAGE,
1461             modified_parsed_flags.into_iter(),
1462             config,
1463         )
1464         .unwrap();
1465 
1466         let expect_flags_content = EXPECTED_FLAG_COMMON_CONTENT.to_string()
1467             + r#"
1468         private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl();
1469         }"#;
1470 
1471         let file = generated_files.iter().find(|f| f.path.ends_with("Flags.java")).unwrap();
1472         assert_eq!(
1473             None,
1474             crate::test::first_significant_code_diff(
1475                 &expect_flags_content,
1476                 &String::from_utf8(file.contents.clone()).unwrap()
1477             ),
1478             "Flags content is not correct"
1479         );
1480     }
1481 
1482     #[test]
test_generate_java_code_test()1483     fn test_generate_java_code_test() {
1484         let parsed_flags = crate::test::parse_test_flags();
1485         let mode = CodegenMode::Test;
1486         let modified_parsed_flags =
1487             crate::commands::modify_parsed_flags_based_on_mode(parsed_flags, mode).unwrap();
1488         let flag_ids =
1489             assign_flag_ids(crate::test::TEST_PACKAGE, modified_parsed_flags.iter()).unwrap();
1490         let config = JavaCodegenConfig {
1491             codegen_mode: mode,
1492             flag_ids,
1493             allow_instrumentation: true,
1494             package_fingerprint: 5801144784618221668,
1495             new_exported: false,
1496             single_exported_file: false,
1497             finalized_flags: FinalizedFlagMap::new(),
1498         };
1499         let generated_files = generate_java_code(
1500             crate::test::TEST_PACKAGE,
1501             modified_parsed_flags.into_iter(),
1502             config,
1503         )
1504         .unwrap();
1505 
1506         let expect_flags_content = EXPECTED_FLAG_COMMON_CONTENT.to_string()
1507             + r#"
1508             public static void setFeatureFlags(FeatureFlags featureFlags) {
1509                 Flags.FEATURE_FLAGS = featureFlags;
1510             }
1511             public static void unsetFeatureFlags() {
1512                 Flags.FEATURE_FLAGS = null;
1513             }
1514             private static FeatureFlags FEATURE_FLAGS;
1515         }
1516         "#;
1517         let expect_featureflagsimpl_content = r#"
1518         package com.android.aconfig.test;
1519         /** @hide */
1520         public final class FeatureFlagsImpl implements FeatureFlags {
1521             @Override
1522             @com.android.aconfig.annotations.AconfigFlagAccessor
1523             public boolean disabledRo() {
1524                 throw new UnsupportedOperationException(
1525                     "Method is not implemented.");
1526             }
1527             @Override
1528             @com.android.aconfig.annotations.AconfigFlagAccessor
1529             public boolean disabledRw() {
1530                 throw new UnsupportedOperationException(
1531                     "Method is not implemented.");
1532             }
1533             @Override
1534             @com.android.aconfig.annotations.AconfigFlagAccessor
1535             public boolean disabledRwExported() {
1536                 throw new UnsupportedOperationException(
1537                     "Method is not implemented.");
1538             }
1539             @Override
1540             @com.android.aconfig.annotations.AconfigFlagAccessor
1541             public boolean disabledRwInOtherNamespace() {
1542                 throw new UnsupportedOperationException(
1543                     "Method is not implemented.");
1544             }
1545             @Override
1546             @com.android.aconfig.annotations.AconfigFlagAccessor
1547             public boolean enabledFixedRo() {
1548                 throw new UnsupportedOperationException(
1549                     "Method is not implemented.");
1550             }
1551             @Override
1552             @com.android.aconfig.annotations.AconfigFlagAccessor
1553             public boolean enabledFixedRoExported() {
1554                 throw new UnsupportedOperationException(
1555                     "Method is not implemented.");
1556             }
1557             @Override
1558             @com.android.aconfig.annotations.AconfigFlagAccessor
1559             public boolean enabledRo() {
1560                 throw new UnsupportedOperationException(
1561                     "Method is not implemented.");
1562             }
1563             @Override
1564             @com.android.aconfig.annotations.AconfigFlagAccessor
1565             public boolean enabledRoExported() {
1566                 throw new UnsupportedOperationException(
1567                     "Method is not implemented.");
1568             }
1569             @Override
1570             @com.android.aconfig.annotations.AconfigFlagAccessor
1571             public boolean enabledRw() {
1572                 throw new UnsupportedOperationException(
1573                     "Method is not implemented.");
1574             }
1575         }
1576         "#;
1577 
1578         let mut file_set = HashMap::from([
1579             ("com/android/aconfig/test/Flags.java", expect_flags_content.as_str()),
1580             ("com/android/aconfig/test/FeatureFlags.java", EXPECTED_FEATUREFLAGS_COMMON_CONTENT),
1581             ("com/android/aconfig/test/FeatureFlagsImpl.java", expect_featureflagsimpl_content),
1582             (
1583                 "com/android/aconfig/test/CustomFeatureFlags.java",
1584                 EXPECTED_CUSTOMFEATUREFLAGS_CONTENT,
1585             ),
1586             (
1587                 "com/android/aconfig/test/FakeFeatureFlagsImpl.java",
1588                 EXPECTED_FAKEFEATUREFLAGSIMPL_CONTENT,
1589             ),
1590         ]);
1591 
1592         for file in generated_files {
1593             let file_path = file.path.to_str().unwrap();
1594             assert!(file_set.contains_key(file_path), "Cannot find {}", file_path);
1595             assert_eq!(
1596                 None,
1597                 crate::test::first_significant_code_diff(
1598                     file_set.get(file_path).unwrap(),
1599                     &String::from_utf8(file.contents).unwrap()
1600                 ),
1601                 "File {} content is not correct",
1602                 file_path
1603             );
1604             file_set.remove(file_path);
1605         }
1606 
1607         assert!(file_set.is_empty());
1608     }
1609 
1610     #[test]
test_generate_java_code_force_read_only()1611     fn test_generate_java_code_force_read_only() {
1612         let parsed_flags = crate::test::parse_test_flags();
1613         let mode = CodegenMode::ForceReadOnly;
1614         let modified_parsed_flags =
1615             crate::commands::modify_parsed_flags_based_on_mode(parsed_flags, mode).unwrap();
1616         let flag_ids =
1617             assign_flag_ids(crate::test::TEST_PACKAGE, modified_parsed_flags.iter()).unwrap();
1618         let config = JavaCodegenConfig {
1619             codegen_mode: mode,
1620             flag_ids,
1621             allow_instrumentation: true,
1622             package_fingerprint: 5801144784618221668,
1623             new_exported: false,
1624             single_exported_file: false,
1625             finalized_flags: FinalizedFlagMap::new(),
1626         };
1627         let generated_files = generate_java_code(
1628             crate::test::TEST_PACKAGE,
1629             modified_parsed_flags.into_iter(),
1630             config,
1631         )
1632         .unwrap();
1633         let expect_featureflags_content = r#"
1634         package com.android.aconfig.test;
1635         // TODO(b/303773055): Remove the annotation after access issue is resolved.
1636         import android.compat.annotation.UnsupportedAppUsage;
1637         /** @hide */
1638         public interface FeatureFlags {
1639             @com.android.aconfig.annotations.AssumeFalseForR8
1640             @com.android.aconfig.annotations.AconfigFlagAccessor
1641             @UnsupportedAppUsage
1642             boolean disabledRo();
1643             @com.android.aconfig.annotations.AssumeFalseForR8
1644             @com.android.aconfig.annotations.AconfigFlagAccessor
1645             @UnsupportedAppUsage
1646             boolean disabledRw();
1647             @com.android.aconfig.annotations.AssumeFalseForR8
1648             @com.android.aconfig.annotations.AconfigFlagAccessor
1649             @UnsupportedAppUsage
1650             boolean disabledRwInOtherNamespace();
1651             @com.android.aconfig.annotations.AssumeTrueForR8
1652             @com.android.aconfig.annotations.AconfigFlagAccessor
1653             @UnsupportedAppUsage
1654             boolean enabledFixedRo();
1655             @com.android.aconfig.annotations.AssumeTrueForR8
1656             @com.android.aconfig.annotations.AconfigFlagAccessor
1657             @UnsupportedAppUsage
1658             boolean enabledRo();
1659             @com.android.aconfig.annotations.AssumeTrueForR8
1660             @com.android.aconfig.annotations.AconfigFlagAccessor
1661             @UnsupportedAppUsage
1662             boolean enabledRw();
1663         }"#;
1664 
1665         let expect_featureflagsimpl_content = r#"
1666         package com.android.aconfig.test;
1667         // TODO(b/303773055): Remove the annotation after access issue is resolved.
1668         import android.compat.annotation.UnsupportedAppUsage;
1669         /** @hide */
1670         public final class FeatureFlagsImpl implements FeatureFlags {
1671             @Override
1672             @com.android.aconfig.annotations.AconfigFlagAccessor
1673             @UnsupportedAppUsage
1674             public boolean disabledRo() {
1675                 return false;
1676             }
1677             @Override
1678             @com.android.aconfig.annotations.AconfigFlagAccessor
1679             @UnsupportedAppUsage
1680             public boolean disabledRw() {
1681                 return false;
1682             }
1683             @Override
1684             @com.android.aconfig.annotations.AconfigFlagAccessor
1685             @UnsupportedAppUsage
1686             public boolean disabledRwInOtherNamespace() {
1687                 return false;
1688             }
1689             @Override
1690             @com.android.aconfig.annotations.AconfigFlagAccessor
1691             @UnsupportedAppUsage
1692             public boolean enabledFixedRo() {
1693                 return true;
1694             }
1695             @Override
1696             @com.android.aconfig.annotations.AconfigFlagAccessor
1697             @UnsupportedAppUsage
1698             public boolean enabledRo() {
1699                 return true;
1700             }
1701             @Override
1702             @com.android.aconfig.annotations.AconfigFlagAccessor
1703             @UnsupportedAppUsage
1704             public boolean enabledRw() {
1705                 return true;
1706             }
1707         }
1708         "#;
1709 
1710         let expect_flags_content = r#"
1711         package com.android.aconfig.test;
1712         // TODO(b/303773055): Remove the annotation after access issue is resolved.
1713         import android.compat.annotation.UnsupportedAppUsage;
1714         /** @hide */
1715         public final class Flags {
1716             /** @hide */
1717             public static final String FLAG_DISABLED_RO = "com.android.aconfig.test.disabled_ro";
1718             /** @hide */
1719             public static final String FLAG_DISABLED_RW = "com.android.aconfig.test.disabled_rw";
1720             /** @hide */
1721             public static final String FLAG_DISABLED_RW_IN_OTHER_NAMESPACE = "com.android.aconfig.test.disabled_rw_in_other_namespace";
1722             /** @hide */
1723             public static final String FLAG_ENABLED_FIXED_RO = "com.android.aconfig.test.enabled_fixed_ro";
1724             /** @hide */
1725             public static final String FLAG_ENABLED_RO = "com.android.aconfig.test.enabled_ro";
1726             /** @hide */
1727             public static final String FLAG_ENABLED_RW = "com.android.aconfig.test.enabled_rw";
1728             @com.android.aconfig.annotations.AssumeFalseForR8
1729             @com.android.aconfig.annotations.AconfigFlagAccessor
1730             @UnsupportedAppUsage
1731             public static boolean disabledRo() {
1732                 return FEATURE_FLAGS.disabledRo();
1733             }
1734             @com.android.aconfig.annotations.AssumeFalseForR8
1735             @com.android.aconfig.annotations.AconfigFlagAccessor
1736             @UnsupportedAppUsage
1737             public static boolean disabledRw() {
1738                 return FEATURE_FLAGS.disabledRw();
1739             }
1740             @com.android.aconfig.annotations.AssumeFalseForR8
1741             @com.android.aconfig.annotations.AconfigFlagAccessor
1742             @UnsupportedAppUsage
1743             public static boolean disabledRwInOtherNamespace() {
1744                 return FEATURE_FLAGS.disabledRwInOtherNamespace();
1745             }
1746             @com.android.aconfig.annotations.AssumeTrueForR8
1747             @com.android.aconfig.annotations.AconfigFlagAccessor
1748             @UnsupportedAppUsage
1749             public static boolean enabledFixedRo() {
1750                 return FEATURE_FLAGS.enabledFixedRo();
1751             }
1752             @com.android.aconfig.annotations.AssumeTrueForR8
1753             @com.android.aconfig.annotations.AconfigFlagAccessor
1754             @UnsupportedAppUsage
1755             public static boolean enabledRo() {
1756                 return FEATURE_FLAGS.enabledRo();
1757             }
1758             @com.android.aconfig.annotations.AssumeTrueForR8
1759             @com.android.aconfig.annotations.AconfigFlagAccessor
1760             @UnsupportedAppUsage
1761             public static boolean enabledRw() {
1762                 return FEATURE_FLAGS.enabledRw();
1763             }
1764             private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl();
1765         }"#;
1766 
1767         let expect_customfeatureflags_content = r#"
1768         package com.android.aconfig.test;
1769 
1770         // TODO(b/303773055): Remove the annotation after access issue is resolved.
1771         import android.compat.annotation.UnsupportedAppUsage;
1772         import java.util.Arrays;
1773         import java.util.HashSet;
1774         import java.util.List;
1775         import java.util.Set;
1776         import java.util.function.BiPredicate;
1777         import java.util.function.Predicate;
1778 
1779         /** @hide */
1780         public class CustomFeatureFlags implements FeatureFlags {
1781 
1782             private BiPredicate<String, Predicate<FeatureFlags>> mGetValueImpl;
1783 
1784             public CustomFeatureFlags(BiPredicate<String, Predicate<FeatureFlags>> getValueImpl) {
1785                 mGetValueImpl = getValueImpl;
1786             }
1787 
1788             @Override
1789             @UnsupportedAppUsage
1790             public boolean disabledRo() {
1791                 return getValue(Flags.FLAG_DISABLED_RO,
1792                         FeatureFlags::disabledRo);
1793             }
1794             @Override
1795             @UnsupportedAppUsage
1796             public boolean disabledRw() {
1797                 return getValue(Flags.FLAG_DISABLED_RW,
1798                     FeatureFlags::disabledRw);
1799             }
1800             @Override
1801             @UnsupportedAppUsage
1802             public boolean disabledRwInOtherNamespace() {
1803                 return getValue(Flags.FLAG_DISABLED_RW_IN_OTHER_NAMESPACE,
1804                     FeatureFlags::disabledRwInOtherNamespace);
1805             }
1806             @Override
1807             @UnsupportedAppUsage
1808             public boolean enabledFixedRo() {
1809                 return getValue(Flags.FLAG_ENABLED_FIXED_RO,
1810                     FeatureFlags::enabledFixedRo);
1811             }
1812             @Override
1813             @UnsupportedAppUsage
1814             public boolean enabledRo() {
1815                 return getValue(Flags.FLAG_ENABLED_RO,
1816                     FeatureFlags::enabledRo);
1817             }
1818             @Override
1819             @UnsupportedAppUsage
1820             public boolean enabledRw() {
1821                 return getValue(Flags.FLAG_ENABLED_RW,
1822                     FeatureFlags::enabledRw);
1823             }
1824 
1825             public boolean isFlagReadOnlyOptimized(String flagName) {
1826                 if (mReadOnlyFlagsSet.contains(flagName) &&
1827                     isOptimizationEnabled()) {
1828                         return true;
1829                 }
1830                 return false;
1831             }
1832 
1833             @com.android.aconfig.annotations.AssumeTrueForR8
1834             private boolean isOptimizationEnabled() {
1835                 return false;
1836             }
1837 
1838             protected boolean getValue(String flagName, Predicate<FeatureFlags> getter) {
1839                 return mGetValueImpl.test(flagName, getter);
1840             }
1841 
1842             public List<String> getFlagNames() {
1843                 return Arrays.asList(
1844                     Flags.FLAG_DISABLED_RO,
1845                     Flags.FLAG_DISABLED_RW,
1846                     Flags.FLAG_DISABLED_RW_IN_OTHER_NAMESPACE,
1847                     Flags.FLAG_ENABLED_FIXED_RO,
1848                     Flags.FLAG_ENABLED_RO,
1849                     Flags.FLAG_ENABLED_RW
1850                 );
1851             }
1852 
1853             private Set<String> mReadOnlyFlagsSet = new HashSet<>(
1854                 Arrays.asList(
1855                     Flags.FLAG_DISABLED_RO,
1856                     Flags.FLAG_DISABLED_RW,
1857                     Flags.FLAG_DISABLED_RW_IN_OTHER_NAMESPACE,
1858                     Flags.FLAG_ENABLED_FIXED_RO,
1859                     Flags.FLAG_ENABLED_RO,
1860                     Flags.FLAG_ENABLED_RW,
1861                     ""
1862                 )
1863             );
1864         }
1865         "#;
1866 
1867         let mut file_set = HashMap::from([
1868             ("com/android/aconfig/test/Flags.java", expect_flags_content),
1869             ("com/android/aconfig/test/FeatureFlagsImpl.java", expect_featureflagsimpl_content),
1870             ("com/android/aconfig/test/FeatureFlags.java", expect_featureflags_content),
1871             ("com/android/aconfig/test/CustomFeatureFlags.java", expect_customfeatureflags_content),
1872             (
1873                 "com/android/aconfig/test/FakeFeatureFlagsImpl.java",
1874                 EXPECTED_FAKEFEATUREFLAGSIMPL_CONTENT,
1875             ),
1876         ]);
1877 
1878         for file in generated_files {
1879             let file_path = file.path.to_str().unwrap();
1880             assert!(file_set.contains_key(file_path), "Cannot find {}", file_path);
1881             assert_eq!(
1882                 None,
1883                 crate::test::first_significant_code_diff(
1884                     file_set.get(file_path).unwrap(),
1885                     &String::from_utf8(file.contents).unwrap()
1886                 ),
1887                 "File {} content is not correct",
1888                 file_path
1889             );
1890             file_set.remove(file_path);
1891         }
1892 
1893         assert!(file_set.is_empty());
1894     }
1895 
1896     #[test]
test_generate_java_code_exported_flags()1897     fn test_generate_java_code_exported_flags() {
1898         let parsed_flags = crate::test::parse_test_flags();
1899         let mode = CodegenMode::Exported;
1900         let modified_parsed_flags =
1901             crate::commands::modify_parsed_flags_based_on_mode(parsed_flags, mode).unwrap();
1902         let flag_ids =
1903             assign_flag_ids(crate::test::TEST_PACKAGE, modified_parsed_flags.iter()).unwrap();
1904         let mut finalized_flags = FinalizedFlagMap::new();
1905         finalized_flags.insert_if_new(
1906             ApiLevel(36),
1907             FinalizedFlag {
1908                 flag_name: "disabled_rw_exported".to_string(),
1909                 package_name: "com.android.aconfig.test".to_string(),
1910             },
1911         );
1912         let config = JavaCodegenConfig {
1913             codegen_mode: mode,
1914             flag_ids,
1915             allow_instrumentation: true,
1916             package_fingerprint: 5801144784618221668,
1917             new_exported: true,
1918             single_exported_file: true,
1919             finalized_flags,
1920         };
1921         let generated_files = generate_java_code(
1922             crate::test::TEST_PACKAGE,
1923             modified_parsed_flags.into_iter(),
1924             config,
1925         )
1926         .unwrap();
1927 
1928         let expect_exported_flags_content = r#"
1929         package com.android.aconfig.test;
1930 
1931         import android.os.Build;
1932         import android.os.flagging.AconfigPackage;
1933         import android.util.Log;
1934         public final class ExportedFlags {
1935 
1936             public static final String FLAG_DISABLED_RW_EXPORTED = "com.android.aconfig.test.disabled_rw_exported";
1937             public static final String FLAG_ENABLED_FIXED_RO_EXPORTED = "com.android.aconfig.test.enabled_fixed_ro_exported";
1938             public static final String FLAG_ENABLED_RO_EXPORTED = "com.android.aconfig.test.enabled_ro_exported";
1939             private static final String TAG = "ExportedFlags";
1940             private static volatile boolean isCached = false;
1941 
1942             private static boolean disabledRwExported = false;
1943             private static boolean enabledFixedRoExported = false;
1944             private static boolean enabledRoExported = false;
1945             private ExportedFlags() {}
1946 
1947             private void init() {
1948                 try {
1949                     AconfigPackage reader = AconfigPackage.load("com.android.aconfig.test");
1950                     disabledRwExported = reader.getBooleanFlagValue("disabled_rw_exported", false);
1951                     enabledFixedRoExported = reader.getBooleanFlagValue("enabled_fixed_ro_exported", false);
1952                     enabledRoExported = reader.getBooleanFlagValue("enabled_ro_exported", false);
1953                 } catch (Exception e) {
1954                     // pass
1955                     Log.e(TAG, e.toString());
1956                 } catch (LinkageError e) {
1957                     // for mainline module running on older devices.
1958                     // This should be replaces to version check, after the version bump.
1959                     Log.w(TAG, e.toString());
1960                 }
1961                 isCached = true;
1962             }
1963             public static boolean disabledRwExported() {
1964                 if (Build.VERSION.SDK_INT >= 36) {
1965                   return true;
1966                 }
1967 
1968                 if (!featureFlags.isCached) {
1969                     featureFlags.init();
1970                 }
1971                 return featureFlags.disabledRwExported;
1972             }
1973             public static boolean enabledFixedRoExported() {
1974                 if (!featureFlags.isCached) {
1975                     featureFlags.init();
1976                 }
1977                 return featureFlags.enabledFixedRoExported;
1978             }
1979             public static boolean enabledRoExported() {
1980                 if (!featureFlags.isCached) {
1981                     featureFlags.init();
1982                 }
1983                 return featureFlags.enabledRoExported;
1984             }
1985             private static ExportedFlags featureFlags = new ExportedFlags();
1986         }"#;
1987 
1988         let file = generated_files.iter().find(|f| f.path.ends_with("ExportedFlags.java")).unwrap();
1989         assert_eq!(
1990             None,
1991             crate::test::first_significant_code_diff(
1992                 expect_exported_flags_content,
1993                 &String::from_utf8(file.contents.clone()).unwrap()
1994             ),
1995             "ExportedFlags content is not correct"
1996         );
1997     }
1998 
1999     #[test]
test_format_java_method_name()2000     fn test_format_java_method_name() {
2001         let expected = "someSnakeName";
2002         let input = "____some_snake___name____";
2003         let formatted_name = format_java_method_name(input);
2004         assert_eq!(expected, formatted_name);
2005 
2006         let input = "someSnakeName";
2007         let formatted_name = format_java_method_name(input);
2008         assert_eq!(expected, formatted_name);
2009 
2010         let input = "SomeSnakeName";
2011         let formatted_name = format_java_method_name(input);
2012         assert_eq!(expected, formatted_name);
2013 
2014         let input = "SomeSnakeName_";
2015         let formatted_name = format_java_method_name(input);
2016         assert_eq!(expected, formatted_name);
2017 
2018         let input = "_SomeSnakeName";
2019         let formatted_name = format_java_method_name(input);
2020         assert_eq!(expected, formatted_name);
2021     }
2022 
2023     #[test]
test_format_property_name()2024     fn test_format_property_name() {
2025         let expected = "mPropertiesSomeSnakeName";
2026         let input = "____some_snake___name____";
2027         let formatted_name = format_property_name(input);
2028         assert_eq!(expected, formatted_name);
2029 
2030         let input = "someSnakeName";
2031         let formatted_name = format_property_name(input);
2032         assert_eq!(expected, formatted_name);
2033 
2034         let input = "SomeSnakeName";
2035         let formatted_name = format_property_name(input);
2036         assert_eq!(expected, formatted_name);
2037 
2038         let input = "SomeSnakeName_";
2039         let formatted_name = format_property_name(input);
2040         assert_eq!(expected, formatted_name);
2041     }
2042 }
2043