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