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 aconfig_protos::{ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag};
24
25 use crate::codegen;
26 use crate::codegen::CodegenMode;
27 use crate::commands::OutputFile;
28
generate_java_code<I>( package: &str, parsed_flags_iter: I, codegen_mode: CodegenMode, ) -> Result<Vec<OutputFile>> where I: Iterator<Item = ProtoParsedFlag>,29 pub fn generate_java_code<I>(
30 package: &str,
31 parsed_flags_iter: I,
32 codegen_mode: CodegenMode,
33 ) -> Result<Vec<OutputFile>>
34 where
35 I: Iterator<Item = ProtoParsedFlag>,
36 {
37 let flag_elements: Vec<FlagElement> =
38 parsed_flags_iter.map(|pf| create_flag_element(package, &pf)).collect();
39 let namespace_flags = gen_flags_by_namespace(&flag_elements);
40 let properties_set: BTreeSet<String> =
41 flag_elements.iter().map(|fe| format_property_name(&fe.device_config_namespace)).collect();
42 let is_test_mode = codegen_mode == CodegenMode::Test;
43 let library_exported = codegen_mode == CodegenMode::Exported;
44 let runtime_lookup_required =
45 flag_elements.iter().any(|elem| elem.is_read_write) || library_exported;
46
47 let context = Context {
48 flag_elements,
49 namespace_flags,
50 is_test_mode,
51 runtime_lookup_required,
52 properties_set,
53 package_name: package.to_string(),
54 library_exported,
55 };
56 let mut template = TinyTemplate::new();
57 template.add_template("Flags.java", include_str!("../../templates/Flags.java.template"))?;
58 template.add_template(
59 "FeatureFlagsImpl.java",
60 include_str!("../../templates/FeatureFlagsImpl.java.template"),
61 )?;
62 template.add_template(
63 "FeatureFlags.java",
64 include_str!("../../templates/FeatureFlags.java.template"),
65 )?;
66 template.add_template(
67 "CustomFeatureFlags.java",
68 include_str!("../../templates/CustomFeatureFlags.java.template"),
69 )?;
70 template.add_template(
71 "FakeFeatureFlagsImpl.java",
72 include_str!("../../templates/FakeFeatureFlagsImpl.java.template"),
73 )?;
74
75 let path: PathBuf = package.split('.').collect();
76 [
77 "Flags.java",
78 "FeatureFlags.java",
79 "FeatureFlagsImpl.java",
80 "CustomFeatureFlags.java",
81 "FakeFeatureFlagsImpl.java",
82 ]
83 .iter()
84 .map(|file| {
85 Ok(OutputFile { contents: template.render(file, &context)?.into(), path: path.join(file) })
86 })
87 .collect::<Result<Vec<OutputFile>>>()
88 }
89
gen_flags_by_namespace(flags: &[FlagElement]) -> Vec<NamespaceFlags>90 fn gen_flags_by_namespace(flags: &[FlagElement]) -> Vec<NamespaceFlags> {
91 let mut namespace_to_flag: BTreeMap<String, Vec<FlagElement>> = BTreeMap::new();
92
93 for flag in flags {
94 match namespace_to_flag.get_mut(&flag.device_config_namespace) {
95 Some(flag_list) => flag_list.push(flag.clone()),
96 None => {
97 namespace_to_flag.insert(flag.device_config_namespace.clone(), vec![flag.clone()]);
98 }
99 }
100 }
101
102 namespace_to_flag
103 .iter()
104 .map(|(namespace, flags)| NamespaceFlags {
105 namespace: namespace.to_string(),
106 flags: flags.clone(),
107 })
108 .collect()
109 }
110
111 #[derive(Serialize)]
112 struct Context {
113 pub flag_elements: Vec<FlagElement>,
114 pub namespace_flags: Vec<NamespaceFlags>,
115 pub is_test_mode: bool,
116 pub runtime_lookup_required: bool,
117 pub properties_set: BTreeSet<String>,
118 pub package_name: String,
119 pub library_exported: bool,
120 }
121
122 #[derive(Serialize, Debug)]
123 struct NamespaceFlags {
124 pub namespace: String,
125 pub flags: Vec<FlagElement>,
126 }
127
128 #[derive(Serialize, Clone, Debug)]
129 struct FlagElement {
130 pub default_value: bool,
131 pub device_config_namespace: String,
132 pub device_config_flag: String,
133 pub flag_name_constant_suffix: String,
134 pub is_read_write: bool,
135 pub method_name: String,
136 pub properties: String,
137 }
138
create_flag_element(package: &str, pf: &ProtoParsedFlag) -> FlagElement139 fn create_flag_element(package: &str, pf: &ProtoParsedFlag) -> FlagElement {
140 let device_config_flag = codegen::create_device_config_ident(package, pf.name())
141 .expect("values checked at flag parse time");
142 FlagElement {
143 default_value: pf.state() == ProtoFlagState::ENABLED,
144 device_config_namespace: pf.namespace().to_string(),
145 device_config_flag,
146 flag_name_constant_suffix: pf.name().to_ascii_uppercase(),
147 is_read_write: pf.permission() == ProtoFlagPermission::READ_WRITE,
148 method_name: format_java_method_name(pf.name()),
149 properties: format_property_name(pf.namespace()),
150 }
151 }
152
format_java_method_name(flag_name: &str) -> String153 fn format_java_method_name(flag_name: &str) -> String {
154 let splits: Vec<&str> = flag_name.split('_').filter(|&word| !word.is_empty()).collect();
155 if splits.len() == 1 {
156 let name = splits[0];
157 name[0..1].to_ascii_lowercase() + &name[1..]
158 } else {
159 splits
160 .iter()
161 .enumerate()
162 .map(|(index, word)| {
163 if index == 0 {
164 word.to_ascii_lowercase()
165 } else {
166 word[0..1].to_ascii_uppercase() + &word[1..].to_ascii_lowercase()
167 }
168 })
169 .collect::<Vec<String>>()
170 .join("")
171 }
172 }
173
format_property_name(property_name: &str) -> String174 fn format_property_name(property_name: &str) -> String {
175 let name = format_java_method_name(property_name);
176 format!("mProperties{}{}", &name[0..1].to_ascii_uppercase(), &name[1..])
177 }
178
179 #[cfg(test)]
180 mod tests {
181 use super::*;
182 use std::collections::HashMap;
183
184 const EXPECTED_FEATUREFLAGS_COMMON_CONTENT: &str = r#"
185 package com.android.aconfig.test;
186 // TODO(b/303773055): Remove the annotation after access issue is resolved.
187 import android.compat.annotation.UnsupportedAppUsage;
188 /** @hide */
189 public interface FeatureFlags {
190 @com.android.aconfig.annotations.AssumeFalseForR8
191 @com.android.aconfig.annotations.AconfigFlagAccessor
192 @UnsupportedAppUsage
193 boolean disabledRo();
194 @com.android.aconfig.annotations.AconfigFlagAccessor
195 @UnsupportedAppUsage
196 boolean disabledRw();
197 @com.android.aconfig.annotations.AconfigFlagAccessor
198 @UnsupportedAppUsage
199 boolean disabledRwExported();
200 @com.android.aconfig.annotations.AconfigFlagAccessor
201 @UnsupportedAppUsage
202 boolean disabledRwInOtherNamespace();
203 @com.android.aconfig.annotations.AssumeTrueForR8
204 @com.android.aconfig.annotations.AconfigFlagAccessor
205 @UnsupportedAppUsage
206 boolean enabledFixedRo();
207 @com.android.aconfig.annotations.AssumeTrueForR8
208 @com.android.aconfig.annotations.AconfigFlagAccessor
209 @UnsupportedAppUsage
210 boolean enabledFixedRoExported();
211 @com.android.aconfig.annotations.AssumeTrueForR8
212 @com.android.aconfig.annotations.AconfigFlagAccessor
213 @UnsupportedAppUsage
214 boolean enabledRo();
215 @com.android.aconfig.annotations.AssumeTrueForR8
216 @com.android.aconfig.annotations.AconfigFlagAccessor
217 @UnsupportedAppUsage
218 boolean enabledRoExported();
219 @com.android.aconfig.annotations.AconfigFlagAccessor
220 @UnsupportedAppUsage
221 boolean enabledRw();
222 }
223 "#;
224
225 const EXPECTED_FLAG_COMMON_CONTENT: &str = r#"
226 package com.android.aconfig.test;
227 // TODO(b/303773055): Remove the annotation after access issue is resolved.
228 import android.compat.annotation.UnsupportedAppUsage;
229 /** @hide */
230 public final class Flags {
231 /** @hide */
232 public static final String FLAG_DISABLED_RO = "com.android.aconfig.test.disabled_ro";
233 /** @hide */
234 public static final String FLAG_DISABLED_RW = "com.android.aconfig.test.disabled_rw";
235 /** @hide */
236 public static final String FLAG_DISABLED_RW_EXPORTED = "com.android.aconfig.test.disabled_rw_exported";
237 /** @hide */
238 public static final String FLAG_DISABLED_RW_IN_OTHER_NAMESPACE = "com.android.aconfig.test.disabled_rw_in_other_namespace";
239 /** @hide */
240 public static final String FLAG_ENABLED_FIXED_RO = "com.android.aconfig.test.enabled_fixed_ro";
241 /** @hide */
242 public static final String FLAG_ENABLED_FIXED_RO_EXPORTED = "com.android.aconfig.test.enabled_fixed_ro_exported";
243 /** @hide */
244 public static final String FLAG_ENABLED_RO = "com.android.aconfig.test.enabled_ro";
245 /** @hide */
246 public static final String FLAG_ENABLED_RO_EXPORTED = "com.android.aconfig.test.enabled_ro_exported";
247 /** @hide */
248 public static final String FLAG_ENABLED_RW = "com.android.aconfig.test.enabled_rw";
249
250 @com.android.aconfig.annotations.AssumeFalseForR8
251 @com.android.aconfig.annotations.AconfigFlagAccessor
252 @UnsupportedAppUsage
253 public static boolean disabledRo() {
254 return FEATURE_FLAGS.disabledRo();
255 }
256 @com.android.aconfig.annotations.AconfigFlagAccessor
257 @UnsupportedAppUsage
258 public static boolean disabledRw() {
259 return FEATURE_FLAGS.disabledRw();
260 }
261 @com.android.aconfig.annotations.AconfigFlagAccessor
262 @UnsupportedAppUsage
263 public static boolean disabledRwExported() {
264 return FEATURE_FLAGS.disabledRwExported();
265 }
266 @com.android.aconfig.annotations.AconfigFlagAccessor
267 @UnsupportedAppUsage
268 public static boolean disabledRwInOtherNamespace() {
269 return FEATURE_FLAGS.disabledRwInOtherNamespace();
270 }
271 @com.android.aconfig.annotations.AssumeTrueForR8
272 @com.android.aconfig.annotations.AconfigFlagAccessor
273 @UnsupportedAppUsage
274 public static boolean enabledFixedRo() {
275 return FEATURE_FLAGS.enabledFixedRo();
276 }
277 @com.android.aconfig.annotations.AssumeTrueForR8
278 @com.android.aconfig.annotations.AconfigFlagAccessor
279 @UnsupportedAppUsage
280 public static boolean enabledFixedRoExported() {
281 return FEATURE_FLAGS.enabledFixedRoExported();
282 }
283 @com.android.aconfig.annotations.AssumeTrueForR8
284 @com.android.aconfig.annotations.AconfigFlagAccessor
285 @UnsupportedAppUsage
286 public static boolean enabledRo() {
287 return FEATURE_FLAGS.enabledRo();
288 }
289 @com.android.aconfig.annotations.AssumeTrueForR8
290 @com.android.aconfig.annotations.AconfigFlagAccessor
291 @UnsupportedAppUsage
292 public static boolean enabledRoExported() {
293 return FEATURE_FLAGS.enabledRoExported();
294 }
295 @com.android.aconfig.annotations.AconfigFlagAccessor
296 @UnsupportedAppUsage
297 public static boolean enabledRw() {
298 return FEATURE_FLAGS.enabledRw();
299 }
300 "#;
301
302 const EXPECTED_CUSTOMFEATUREFLAGS_CONTENT: &str = r#"
303 package com.android.aconfig.test;
304
305 // TODO(b/303773055): Remove the annotation after access issue is resolved.
306 import android.compat.annotation.UnsupportedAppUsage;
307 import java.util.Arrays;
308 import java.util.HashSet;
309 import java.util.List;
310 import java.util.Set;
311 import java.util.function.BiPredicate;
312 import java.util.function.Predicate;
313
314 /** @hide */
315 public class CustomFeatureFlags implements FeatureFlags {
316
317 private BiPredicate<String, Predicate<FeatureFlags>> mGetValueImpl;
318
319 public CustomFeatureFlags(BiPredicate<String, Predicate<FeatureFlags>> getValueImpl) {
320 mGetValueImpl = getValueImpl;
321 }
322
323 @Override
324 @UnsupportedAppUsage
325 public boolean disabledRo() {
326 return getValue(Flags.FLAG_DISABLED_RO,
327 FeatureFlags::disabledRo);
328 }
329 @Override
330 @UnsupportedAppUsage
331 public boolean disabledRw() {
332 return getValue(Flags.FLAG_DISABLED_RW,
333 FeatureFlags::disabledRw);
334 }
335 @Override
336 @UnsupportedAppUsage
337 public boolean disabledRwExported() {
338 return getValue(Flags.FLAG_DISABLED_RW_EXPORTED,
339 FeatureFlags::disabledRwExported);
340 }
341 @Override
342 @UnsupportedAppUsage
343 public boolean disabledRwInOtherNamespace() {
344 return getValue(Flags.FLAG_DISABLED_RW_IN_OTHER_NAMESPACE,
345 FeatureFlags::disabledRwInOtherNamespace);
346 }
347 @Override
348 @UnsupportedAppUsage
349 public boolean enabledFixedRo() {
350 return getValue(Flags.FLAG_ENABLED_FIXED_RO,
351 FeatureFlags::enabledFixedRo);
352 }
353 @Override
354 @UnsupportedAppUsage
355 public boolean enabledFixedRoExported() {
356 return getValue(Flags.FLAG_ENABLED_FIXED_RO_EXPORTED,
357 FeatureFlags::enabledFixedRoExported);
358 }
359 @Override
360 @UnsupportedAppUsage
361 public boolean enabledRo() {
362 return getValue(Flags.FLAG_ENABLED_RO,
363 FeatureFlags::enabledRo);
364 }
365 @Override
366 @UnsupportedAppUsage
367 public boolean enabledRoExported() {
368 return getValue(Flags.FLAG_ENABLED_RO_EXPORTED,
369 FeatureFlags::enabledRoExported);
370 }
371 @Override
372 @UnsupportedAppUsage
373 public boolean enabledRw() {
374 return getValue(Flags.FLAG_ENABLED_RW,
375 FeatureFlags::enabledRw);
376 }
377
378 public boolean isFlagReadOnlyOptimized(String flagName) {
379 if (mReadOnlyFlagsSet.contains(flagName) &&
380 isOptimizationEnabled()) {
381 return true;
382 }
383 return false;
384 }
385
386 @com.android.aconfig.annotations.AssumeTrueForR8
387 private boolean isOptimizationEnabled() {
388 return false;
389 }
390
391 protected boolean getValue(String flagName, Predicate<FeatureFlags> getter) {
392 return mGetValueImpl.test(flagName, getter);
393 }
394
395 public List<String> getFlagNames() {
396 return Arrays.asList(
397 Flags.FLAG_DISABLED_RO,
398 Flags.FLAG_DISABLED_RW,
399 Flags.FLAG_DISABLED_RW_EXPORTED,
400 Flags.FLAG_DISABLED_RW_IN_OTHER_NAMESPACE,
401 Flags.FLAG_ENABLED_FIXED_RO,
402 Flags.FLAG_ENABLED_FIXED_RO_EXPORTED,
403 Flags.FLAG_ENABLED_RO,
404 Flags.FLAG_ENABLED_RO_EXPORTED,
405 Flags.FLAG_ENABLED_RW
406 );
407 }
408
409 private Set<String> mReadOnlyFlagsSet = new HashSet<>(
410 Arrays.asList(
411 Flags.FLAG_DISABLED_RO,
412 Flags.FLAG_ENABLED_FIXED_RO,
413 Flags.FLAG_ENABLED_FIXED_RO_EXPORTED,
414 Flags.FLAG_ENABLED_RO,
415 Flags.FLAG_ENABLED_RO_EXPORTED,
416 ""
417 )
418 );
419 }
420 "#;
421
422 const EXPECTED_FAKEFEATUREFLAGSIMPL_CONTENT: &str = r#"
423 package com.android.aconfig.test;
424
425 import java.util.HashMap;
426 import java.util.Map;
427 import java.util.function.Predicate;
428
429 /** @hide */
430 public class FakeFeatureFlagsImpl extends CustomFeatureFlags {
431 private final Map<String, Boolean> mFlagMap = new HashMap<>();
432 private final FeatureFlags mDefaults;
433
434 public FakeFeatureFlagsImpl() {
435 this(null);
436 }
437
438 public FakeFeatureFlagsImpl(FeatureFlags defaults) {
439 super(null);
440 mDefaults = defaults;
441 // Initialize the map with null values
442 for (String flagName : getFlagNames()) {
443 mFlagMap.put(flagName, null);
444 }
445 }
446
447 @Override
448 protected boolean getValue(String flagName, Predicate<FeatureFlags> getter) {
449 Boolean value = this.mFlagMap.get(flagName);
450 if (value != null) {
451 return value;
452 }
453 if (mDefaults != null) {
454 return getter.test(mDefaults);
455 }
456 throw new IllegalArgumentException(flagName + " is not set");
457 }
458
459 public void setFlag(String flagName, boolean value) {
460 if (!this.mFlagMap.containsKey(flagName)) {
461 throw new IllegalArgumentException("no such flag " + flagName);
462 }
463 this.mFlagMap.put(flagName, value);
464 }
465
466 public void resetAll() {
467 for (Map.Entry entry : mFlagMap.entrySet()) {
468 entry.setValue(null);
469 }
470 }
471 }
472 "#;
473
474 #[test]
test_generate_java_code_production()475 fn test_generate_java_code_production() {
476 let parsed_flags = crate::test::parse_test_flags();
477 let mode = CodegenMode::Production;
478 let modified_parsed_flags =
479 crate::commands::modify_parsed_flags_based_on_mode(parsed_flags, mode).unwrap();
480 let generated_files =
481 generate_java_code(crate::test::TEST_PACKAGE, modified_parsed_flags.into_iter(), mode)
482 .unwrap();
483 let expect_flags_content = EXPECTED_FLAG_COMMON_CONTENT.to_string()
484 + r#"
485 private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl();
486 }"#;
487
488 let expect_featureflagsimpl_content = r#"
489 package com.android.aconfig.test;
490 // TODO(b/303773055): Remove the annotation after access issue is resolved.
491 import android.compat.annotation.UnsupportedAppUsage;
492 import android.provider.DeviceConfig;
493 import android.provider.DeviceConfig.Properties;
494 /** @hide */
495 public final class FeatureFlagsImpl implements FeatureFlags {
496 private static boolean aconfig_test_is_cached = false;
497 private static boolean other_namespace_is_cached = false;
498 private static boolean disabledRw = false;
499 private static boolean disabledRwExported = false;
500 private static boolean disabledRwInOtherNamespace = false;
501 private static boolean enabledRw = true;
502
503
504 private void load_overrides_aconfig_test() {
505 try {
506 Properties properties = DeviceConfig.getProperties("aconfig_test");
507 disabledRw =
508 properties.getBoolean("com.android.aconfig.test.disabled_rw", false);
509 disabledRwExported =
510 properties.getBoolean("com.android.aconfig.test.disabled_rw_exported", false);
511 enabledRw =
512 properties.getBoolean("com.android.aconfig.test.enabled_rw", true);
513 } catch (NullPointerException e) {
514 throw new RuntimeException(
515 "Cannot read value from namespace aconfig_test "
516 + "from DeviceConfig. It could be that the code using flag "
517 + "executed before SettingsProvider initialization. Please use "
518 + "fixed read-only flag by adding is_fixed_read_only: true in "
519 + "flag declaration.",
520 e
521 );
522 }
523 aconfig_test_is_cached = true;
524 }
525
526 private void load_overrides_other_namespace() {
527 try {
528 Properties properties = DeviceConfig.getProperties("other_namespace");
529 disabledRwInOtherNamespace =
530 properties.getBoolean("com.android.aconfig.test.disabled_rw_in_other_namespace", false);
531 } catch (NullPointerException e) {
532 throw new RuntimeException(
533 "Cannot read value from namespace other_namespace "
534 + "from DeviceConfig. It could be that the code using flag "
535 + "executed before SettingsProvider initialization. Please use "
536 + "fixed read-only flag by adding is_fixed_read_only: true in "
537 + "flag declaration.",
538 e
539 );
540 }
541 other_namespace_is_cached = true;
542 }
543
544 @Override
545 @com.android.aconfig.annotations.AconfigFlagAccessor
546 @UnsupportedAppUsage
547 public boolean disabledRo() {
548 return false;
549 }
550 @Override
551 @com.android.aconfig.annotations.AconfigFlagAccessor
552 @UnsupportedAppUsage
553 public boolean disabledRw() {
554 if (!aconfig_test_is_cached) {
555 load_overrides_aconfig_test();
556 }
557 return disabledRw;
558 }
559 @Override
560 @com.android.aconfig.annotations.AconfigFlagAccessor
561 @UnsupportedAppUsage
562 public boolean disabledRwExported() {
563 if (!aconfig_test_is_cached) {
564 load_overrides_aconfig_test();
565 }
566 return disabledRwExported;
567 }
568 @Override
569 @com.android.aconfig.annotations.AconfigFlagAccessor
570 @UnsupportedAppUsage
571 public boolean disabledRwInOtherNamespace() {
572 if (!other_namespace_is_cached) {
573 load_overrides_other_namespace();
574 }
575 return disabledRwInOtherNamespace;
576 }
577 @Override
578 @com.android.aconfig.annotations.AconfigFlagAccessor
579 @UnsupportedAppUsage
580 public boolean enabledFixedRo() {
581 return true;
582 }
583 @Override
584 @com.android.aconfig.annotations.AconfigFlagAccessor
585 @UnsupportedAppUsage
586 public boolean enabledFixedRoExported() {
587 return true;
588 }
589 @Override
590 @com.android.aconfig.annotations.AconfigFlagAccessor
591 @UnsupportedAppUsage
592 public boolean enabledRo() {
593 return true;
594 }
595 @Override
596 @com.android.aconfig.annotations.AconfigFlagAccessor
597 @UnsupportedAppUsage
598 public boolean enabledRoExported() {
599 return true;
600 }
601 @Override
602 @com.android.aconfig.annotations.AconfigFlagAccessor
603 @UnsupportedAppUsage
604 public boolean enabledRw() {
605 if (!aconfig_test_is_cached) {
606 load_overrides_aconfig_test();
607 }
608 return enabledRw;
609 }
610 }
611 "#;
612 let mut file_set = HashMap::from([
613 ("com/android/aconfig/test/Flags.java", expect_flags_content.as_str()),
614 ("com/android/aconfig/test/FeatureFlagsImpl.java", expect_featureflagsimpl_content),
615 ("com/android/aconfig/test/FeatureFlags.java", EXPECTED_FEATUREFLAGS_COMMON_CONTENT),
616 (
617 "com/android/aconfig/test/CustomFeatureFlags.java",
618 EXPECTED_CUSTOMFEATUREFLAGS_CONTENT,
619 ),
620 (
621 "com/android/aconfig/test/FakeFeatureFlagsImpl.java",
622 EXPECTED_FAKEFEATUREFLAGSIMPL_CONTENT,
623 ),
624 ]);
625
626 for file in generated_files {
627 let file_path = file.path.to_str().unwrap();
628 assert!(file_set.contains_key(file_path), "Cannot find {}", file_path);
629 assert_eq!(
630 None,
631 crate::test::first_significant_code_diff(
632 file_set.get(file_path).unwrap(),
633 &String::from_utf8(file.contents).unwrap()
634 ),
635 "File {} content is not correct",
636 file_path
637 );
638 file_set.remove(file_path);
639 }
640
641 assert!(file_set.is_empty());
642 }
643
644 #[test]
test_generate_java_code_exported()645 fn test_generate_java_code_exported() {
646 let parsed_flags = crate::test::parse_test_flags();
647 let mode = CodegenMode::Exported;
648 let modified_parsed_flags =
649 crate::commands::modify_parsed_flags_based_on_mode(parsed_flags, mode).unwrap();
650 let generated_files =
651 generate_java_code(crate::test::TEST_PACKAGE, modified_parsed_flags.into_iter(), mode)
652 .unwrap();
653
654 let expect_flags_content = r#"
655 package com.android.aconfig.test;
656 /** @hide */
657 public final class Flags {
658 /** @hide */
659 public static final String FLAG_DISABLED_RW_EXPORTED = "com.android.aconfig.test.disabled_rw_exported";
660 /** @hide */
661 public static final String FLAG_ENABLED_FIXED_RO_EXPORTED = "com.android.aconfig.test.enabled_fixed_ro_exported";
662 /** @hide */
663 public static final String FLAG_ENABLED_RO_EXPORTED = "com.android.aconfig.test.enabled_ro_exported";
664 public static boolean disabledRwExported() {
665 return FEATURE_FLAGS.disabledRwExported();
666 }
667 public static boolean enabledFixedRoExported() {
668 return FEATURE_FLAGS.enabledFixedRoExported();
669 }
670 public static boolean enabledRoExported() {
671 return FEATURE_FLAGS.enabledRoExported();
672 }
673 private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl();
674 }
675 "#;
676
677 let expect_feature_flags_content = r#"
678 package com.android.aconfig.test;
679 /** @hide */
680 public interface FeatureFlags {
681 boolean disabledRwExported();
682 boolean enabledFixedRoExported();
683 boolean enabledRoExported();
684 }
685 "#;
686
687 let expect_feature_flags_impl_content = r#"
688 package com.android.aconfig.test;
689 import android.provider.DeviceConfig;
690 import android.provider.DeviceConfig.Properties;
691 /** @hide */
692 public final class FeatureFlagsImpl implements FeatureFlags {
693 private static boolean aconfig_test_is_cached = false;
694 private static boolean disabledRwExported = false;
695 private static boolean enabledFixedRoExported = false;
696 private static boolean enabledRoExported = false;
697
698
699 private void load_overrides_aconfig_test() {
700 try {
701 Properties properties = DeviceConfig.getProperties("aconfig_test");
702 disabledRwExported =
703 properties.getBoolean("com.android.aconfig.test.disabled_rw_exported", false);
704 enabledFixedRoExported =
705 properties.getBoolean("com.android.aconfig.test.enabled_fixed_ro_exported", false);
706 enabledRoExported =
707 properties.getBoolean("com.android.aconfig.test.enabled_ro_exported", false);
708 } catch (NullPointerException e) {
709 throw new RuntimeException(
710 "Cannot read value from namespace aconfig_test "
711 + "from DeviceConfig. It could be that the code using flag "
712 + "executed before SettingsProvider initialization. Please use "
713 + "fixed read-only flag by adding is_fixed_read_only: true in "
714 + "flag declaration.",
715 e
716 );
717 }
718 aconfig_test_is_cached = true;
719 }
720 @Override
721 public boolean disabledRwExported() {
722 if (!aconfig_test_is_cached) {
723 load_overrides_aconfig_test();
724 }
725 return disabledRwExported;
726 }
727 @Override
728 public boolean enabledFixedRoExported() {
729 if (!aconfig_test_is_cached) {
730 load_overrides_aconfig_test();
731 }
732 return enabledFixedRoExported;
733 }
734 @Override
735 public boolean enabledRoExported() {
736 if (!aconfig_test_is_cached) {
737 load_overrides_aconfig_test();
738 }
739 return enabledRoExported;
740 }
741 }"#;
742
743 let expect_custom_feature_flags_content = r#"
744 package com.android.aconfig.test;
745
746 import java.util.Arrays;
747 import java.util.HashSet;
748 import java.util.List;
749 import java.util.Set;
750 import java.util.function.BiPredicate;
751 import java.util.function.Predicate;
752
753 /** @hide */
754 public class CustomFeatureFlags implements FeatureFlags {
755
756 private BiPredicate<String, Predicate<FeatureFlags>> mGetValueImpl;
757
758 public CustomFeatureFlags(BiPredicate<String, Predicate<FeatureFlags>> getValueImpl) {
759 mGetValueImpl = getValueImpl;
760 }
761
762 @Override
763 public boolean disabledRwExported() {
764 return getValue(Flags.FLAG_DISABLED_RW_EXPORTED,
765 FeatureFlags::disabledRwExported);
766 }
767 @Override
768 public boolean enabledFixedRoExported() {
769 return getValue(Flags.FLAG_ENABLED_FIXED_RO_EXPORTED,
770 FeatureFlags::enabledFixedRoExported);
771 }
772 @Override
773 public boolean enabledRoExported() {
774 return getValue(Flags.FLAG_ENABLED_RO_EXPORTED,
775 FeatureFlags::enabledRoExported);
776 }
777
778 protected boolean getValue(String flagName, Predicate<FeatureFlags> getter) {
779 return mGetValueImpl.test(flagName, getter);
780 }
781
782 public List<String> getFlagNames() {
783 return Arrays.asList(
784 Flags.FLAG_DISABLED_RW_EXPORTED,
785 Flags.FLAG_ENABLED_FIXED_RO_EXPORTED,
786 Flags.FLAG_ENABLED_RO_EXPORTED
787 );
788 }
789
790 private Set<String> mReadOnlyFlagsSet = new HashSet<>(
791 Arrays.asList(
792 ""
793 )
794 );
795 }
796 "#;
797
798 let mut file_set = HashMap::from([
799 ("com/android/aconfig/test/Flags.java", expect_flags_content),
800 ("com/android/aconfig/test/FeatureFlags.java", expect_feature_flags_content),
801 ("com/android/aconfig/test/FeatureFlagsImpl.java", expect_feature_flags_impl_content),
802 (
803 "com/android/aconfig/test/CustomFeatureFlags.java",
804 expect_custom_feature_flags_content,
805 ),
806 (
807 "com/android/aconfig/test/FakeFeatureFlagsImpl.java",
808 EXPECTED_FAKEFEATUREFLAGSIMPL_CONTENT,
809 ),
810 ]);
811
812 for file in generated_files {
813 let file_path = file.path.to_str().unwrap();
814 assert!(file_set.contains_key(file_path), "Cannot find {}", file_path);
815 assert_eq!(
816 None,
817 crate::test::first_significant_code_diff(
818 file_set.get(file_path).unwrap(),
819 &String::from_utf8(file.contents).unwrap()
820 ),
821 "File {} content is not correct",
822 file_path
823 );
824 file_set.remove(file_path);
825 }
826
827 assert!(file_set.is_empty());
828 }
829
830 #[test]
test_generate_java_code_test()831 fn test_generate_java_code_test() {
832 let parsed_flags = crate::test::parse_test_flags();
833 let mode = CodegenMode::Test;
834 let modified_parsed_flags =
835 crate::commands::modify_parsed_flags_based_on_mode(parsed_flags, mode).unwrap();
836 let generated_files =
837 generate_java_code(crate::test::TEST_PACKAGE, modified_parsed_flags.into_iter(), mode)
838 .unwrap();
839
840 let expect_flags_content = EXPECTED_FLAG_COMMON_CONTENT.to_string()
841 + r#"
842 public static void setFeatureFlags(FeatureFlags featureFlags) {
843 Flags.FEATURE_FLAGS = featureFlags;
844 }
845 public static void unsetFeatureFlags() {
846 Flags.FEATURE_FLAGS = null;
847 }
848 private static FeatureFlags FEATURE_FLAGS;
849 }
850 "#;
851 let expect_featureflagsimpl_content = r#"
852 package com.android.aconfig.test;
853 // TODO(b/303773055): Remove the annotation after access issue is resolved.
854 import android.compat.annotation.UnsupportedAppUsage;
855 /** @hide */
856 public final class FeatureFlagsImpl implements FeatureFlags {
857 @Override
858 @com.android.aconfig.annotations.AconfigFlagAccessor
859 @UnsupportedAppUsage
860 public boolean disabledRo() {
861 throw new UnsupportedOperationException(
862 "Method is not implemented.");
863 }
864 @Override
865 @com.android.aconfig.annotations.AconfigFlagAccessor
866 @UnsupportedAppUsage
867 public boolean disabledRw() {
868 throw new UnsupportedOperationException(
869 "Method is not implemented.");
870 }
871 @Override
872 @com.android.aconfig.annotations.AconfigFlagAccessor
873 @UnsupportedAppUsage
874 public boolean disabledRwExported() {
875 throw new UnsupportedOperationException(
876 "Method is not implemented.");
877 }
878 @Override
879 @com.android.aconfig.annotations.AconfigFlagAccessor
880 @UnsupportedAppUsage
881 public boolean disabledRwInOtherNamespace() {
882 throw new UnsupportedOperationException(
883 "Method is not implemented.");
884 }
885 @Override
886 @com.android.aconfig.annotations.AconfigFlagAccessor
887 @UnsupportedAppUsage
888 public boolean enabledFixedRo() {
889 throw new UnsupportedOperationException(
890 "Method is not implemented.");
891 }
892 @Override
893 @com.android.aconfig.annotations.AconfigFlagAccessor
894 @UnsupportedAppUsage
895 public boolean enabledFixedRoExported() {
896 throw new UnsupportedOperationException(
897 "Method is not implemented.");
898 }
899 @Override
900 @com.android.aconfig.annotations.AconfigFlagAccessor
901 @UnsupportedAppUsage
902 public boolean enabledRo() {
903 throw new UnsupportedOperationException(
904 "Method is not implemented.");
905 }
906 @Override
907 @com.android.aconfig.annotations.AconfigFlagAccessor
908 @UnsupportedAppUsage
909 public boolean enabledRoExported() {
910 throw new UnsupportedOperationException(
911 "Method is not implemented.");
912 }
913 @Override
914 @com.android.aconfig.annotations.AconfigFlagAccessor
915 @UnsupportedAppUsage
916 public boolean enabledRw() {
917 throw new UnsupportedOperationException(
918 "Method is not implemented.");
919 }
920 }
921 "#;
922
923 let mut file_set = HashMap::from([
924 ("com/android/aconfig/test/Flags.java", expect_flags_content.as_str()),
925 ("com/android/aconfig/test/FeatureFlags.java", EXPECTED_FEATUREFLAGS_COMMON_CONTENT),
926 ("com/android/aconfig/test/FeatureFlagsImpl.java", expect_featureflagsimpl_content),
927 (
928 "com/android/aconfig/test/CustomFeatureFlags.java",
929 EXPECTED_CUSTOMFEATUREFLAGS_CONTENT,
930 ),
931 (
932 "com/android/aconfig/test/FakeFeatureFlagsImpl.java",
933 EXPECTED_FAKEFEATUREFLAGSIMPL_CONTENT,
934 ),
935 ]);
936
937 for file in generated_files {
938 let file_path = file.path.to_str().unwrap();
939 assert!(file_set.contains_key(file_path), "Cannot find {}", file_path);
940 assert_eq!(
941 None,
942 crate::test::first_significant_code_diff(
943 file_set.get(file_path).unwrap(),
944 &String::from_utf8(file.contents).unwrap()
945 ),
946 "File {} content is not correct",
947 file_path
948 );
949 file_set.remove(file_path);
950 }
951
952 assert!(file_set.is_empty());
953 }
954
955 #[test]
test_generate_java_code_force_read_only()956 fn test_generate_java_code_force_read_only() {
957 let parsed_flags = crate::test::parse_test_flags();
958 let mode = CodegenMode::ForceReadOnly;
959 let modified_parsed_flags =
960 crate::commands::modify_parsed_flags_based_on_mode(parsed_flags, mode).unwrap();
961 let generated_files =
962 generate_java_code(crate::test::TEST_PACKAGE, modified_parsed_flags.into_iter(), mode)
963 .unwrap();
964 let expect_featureflags_content = r#"
965 package com.android.aconfig.test;
966 // TODO(b/303773055): Remove the annotation after access issue is resolved.
967 import android.compat.annotation.UnsupportedAppUsage;
968 /** @hide */
969 public interface FeatureFlags {
970 @com.android.aconfig.annotations.AssumeFalseForR8
971 @com.android.aconfig.annotations.AconfigFlagAccessor
972 @UnsupportedAppUsage
973 boolean disabledRo();
974 @com.android.aconfig.annotations.AssumeFalseForR8
975 @com.android.aconfig.annotations.AconfigFlagAccessor
976 @UnsupportedAppUsage
977 boolean disabledRw();
978 @com.android.aconfig.annotations.AssumeFalseForR8
979 @com.android.aconfig.annotations.AconfigFlagAccessor
980 @UnsupportedAppUsage
981 boolean disabledRwInOtherNamespace();
982 @com.android.aconfig.annotations.AssumeTrueForR8
983 @com.android.aconfig.annotations.AconfigFlagAccessor
984 @UnsupportedAppUsage
985 boolean enabledFixedRo();
986 @com.android.aconfig.annotations.AssumeTrueForR8
987 @com.android.aconfig.annotations.AconfigFlagAccessor
988 @UnsupportedAppUsage
989 boolean enabledRo();
990 @com.android.aconfig.annotations.AssumeTrueForR8
991 @com.android.aconfig.annotations.AconfigFlagAccessor
992 @UnsupportedAppUsage
993 boolean enabledRw();
994 }"#;
995
996 let expect_featureflagsimpl_content = r#"
997 package com.android.aconfig.test;
998 // TODO(b/303773055): Remove the annotation after access issue is resolved.
999 import android.compat.annotation.UnsupportedAppUsage;
1000 /** @hide */
1001 public final class FeatureFlagsImpl implements FeatureFlags {
1002 @Override
1003 @com.android.aconfig.annotations.AconfigFlagAccessor
1004 @UnsupportedAppUsage
1005 public boolean disabledRo() {
1006 return false;
1007 }
1008 @Override
1009 @com.android.aconfig.annotations.AconfigFlagAccessor
1010 @UnsupportedAppUsage
1011 public boolean disabledRw() {
1012 return false;
1013 }
1014 @Override
1015 @com.android.aconfig.annotations.AconfigFlagAccessor
1016 @UnsupportedAppUsage
1017 public boolean disabledRwInOtherNamespace() {
1018 return false;
1019 }
1020 @Override
1021 @com.android.aconfig.annotations.AconfigFlagAccessor
1022 @UnsupportedAppUsage
1023 public boolean enabledFixedRo() {
1024 return true;
1025 }
1026 @Override
1027 @com.android.aconfig.annotations.AconfigFlagAccessor
1028 @UnsupportedAppUsage
1029 public boolean enabledRo() {
1030 return true;
1031 }
1032 @Override
1033 @com.android.aconfig.annotations.AconfigFlagAccessor
1034 @UnsupportedAppUsage
1035 public boolean enabledRw() {
1036 return true;
1037 }
1038 }
1039 "#;
1040
1041 let expect_flags_content = r#"
1042 package com.android.aconfig.test;
1043 // TODO(b/303773055): Remove the annotation after access issue is resolved.
1044 import android.compat.annotation.UnsupportedAppUsage;
1045 /** @hide */
1046 public final class Flags {
1047 /** @hide */
1048 public static final String FLAG_DISABLED_RO = "com.android.aconfig.test.disabled_ro";
1049 /** @hide */
1050 public static final String FLAG_DISABLED_RW = "com.android.aconfig.test.disabled_rw";
1051 /** @hide */
1052 public static final String FLAG_DISABLED_RW_IN_OTHER_NAMESPACE = "com.android.aconfig.test.disabled_rw_in_other_namespace";
1053 /** @hide */
1054 public static final String FLAG_ENABLED_FIXED_RO = "com.android.aconfig.test.enabled_fixed_ro";
1055 /** @hide */
1056 public static final String FLAG_ENABLED_RO = "com.android.aconfig.test.enabled_ro";
1057 /** @hide */
1058 public static final String FLAG_ENABLED_RW = "com.android.aconfig.test.enabled_rw";
1059 @com.android.aconfig.annotations.AssumeFalseForR8
1060 @com.android.aconfig.annotations.AconfigFlagAccessor
1061 @UnsupportedAppUsage
1062 public static boolean disabledRo() {
1063 return FEATURE_FLAGS.disabledRo();
1064 }
1065 @com.android.aconfig.annotations.AssumeFalseForR8
1066 @com.android.aconfig.annotations.AconfigFlagAccessor
1067 @UnsupportedAppUsage
1068 public static boolean disabledRw() {
1069 return FEATURE_FLAGS.disabledRw();
1070 }
1071 @com.android.aconfig.annotations.AssumeFalseForR8
1072 @com.android.aconfig.annotations.AconfigFlagAccessor
1073 @UnsupportedAppUsage
1074 public static boolean disabledRwInOtherNamespace() {
1075 return FEATURE_FLAGS.disabledRwInOtherNamespace();
1076 }
1077 @com.android.aconfig.annotations.AssumeTrueForR8
1078 @com.android.aconfig.annotations.AconfigFlagAccessor
1079 @UnsupportedAppUsage
1080 public static boolean enabledFixedRo() {
1081 return FEATURE_FLAGS.enabledFixedRo();
1082 }
1083 @com.android.aconfig.annotations.AssumeTrueForR8
1084 @com.android.aconfig.annotations.AconfigFlagAccessor
1085 @UnsupportedAppUsage
1086 public static boolean enabledRo() {
1087 return FEATURE_FLAGS.enabledRo();
1088 }
1089 @com.android.aconfig.annotations.AssumeTrueForR8
1090 @com.android.aconfig.annotations.AconfigFlagAccessor
1091 @UnsupportedAppUsage
1092 public static boolean enabledRw() {
1093 return FEATURE_FLAGS.enabledRw();
1094 }
1095 private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl();
1096 }"#;
1097
1098 let expect_customfeatureflags_content = r#"
1099 package com.android.aconfig.test;
1100
1101 // TODO(b/303773055): Remove the annotation after access issue is resolved.
1102 import android.compat.annotation.UnsupportedAppUsage;
1103 import java.util.Arrays;
1104 import java.util.HashSet;
1105 import java.util.List;
1106 import java.util.Set;
1107 import java.util.function.BiPredicate;
1108 import java.util.function.Predicate;
1109
1110 /** @hide */
1111 public class CustomFeatureFlags implements FeatureFlags {
1112
1113 private BiPredicate<String, Predicate<FeatureFlags>> mGetValueImpl;
1114
1115 public CustomFeatureFlags(BiPredicate<String, Predicate<FeatureFlags>> getValueImpl) {
1116 mGetValueImpl = getValueImpl;
1117 }
1118
1119 @Override
1120 @UnsupportedAppUsage
1121 public boolean disabledRo() {
1122 return getValue(Flags.FLAG_DISABLED_RO,
1123 FeatureFlags::disabledRo);
1124 }
1125 @Override
1126 @UnsupportedAppUsage
1127 public boolean disabledRw() {
1128 return getValue(Flags.FLAG_DISABLED_RW,
1129 FeatureFlags::disabledRw);
1130 }
1131 @Override
1132 @UnsupportedAppUsage
1133 public boolean disabledRwInOtherNamespace() {
1134 return getValue(Flags.FLAG_DISABLED_RW_IN_OTHER_NAMESPACE,
1135 FeatureFlags::disabledRwInOtherNamespace);
1136 }
1137 @Override
1138 @UnsupportedAppUsage
1139 public boolean enabledFixedRo() {
1140 return getValue(Flags.FLAG_ENABLED_FIXED_RO,
1141 FeatureFlags::enabledFixedRo);
1142 }
1143 @Override
1144 @UnsupportedAppUsage
1145 public boolean enabledRo() {
1146 return getValue(Flags.FLAG_ENABLED_RO,
1147 FeatureFlags::enabledRo);
1148 }
1149 @Override
1150 @UnsupportedAppUsage
1151 public boolean enabledRw() {
1152 return getValue(Flags.FLAG_ENABLED_RW,
1153 FeatureFlags::enabledRw);
1154 }
1155
1156 public boolean isFlagReadOnlyOptimized(String flagName) {
1157 if (mReadOnlyFlagsSet.contains(flagName) &&
1158 isOptimizationEnabled()) {
1159 return true;
1160 }
1161 return false;
1162 }
1163
1164 @com.android.aconfig.annotations.AssumeTrueForR8
1165 private boolean isOptimizationEnabled() {
1166 return false;
1167 }
1168
1169 protected boolean getValue(String flagName, Predicate<FeatureFlags> getter) {
1170 return mGetValueImpl.test(flagName, getter);
1171 }
1172
1173 public List<String> getFlagNames() {
1174 return Arrays.asList(
1175 Flags.FLAG_DISABLED_RO,
1176 Flags.FLAG_DISABLED_RW,
1177 Flags.FLAG_DISABLED_RW_IN_OTHER_NAMESPACE,
1178 Flags.FLAG_ENABLED_FIXED_RO,
1179 Flags.FLAG_ENABLED_RO,
1180 Flags.FLAG_ENABLED_RW
1181 );
1182 }
1183
1184 private Set<String> mReadOnlyFlagsSet = new HashSet<>(
1185 Arrays.asList(
1186 Flags.FLAG_DISABLED_RO,
1187 Flags.FLAG_DISABLED_RW,
1188 Flags.FLAG_DISABLED_RW_IN_OTHER_NAMESPACE,
1189 Flags.FLAG_ENABLED_FIXED_RO,
1190 Flags.FLAG_ENABLED_RO,
1191 Flags.FLAG_ENABLED_RW,
1192 ""
1193 )
1194 );
1195 }
1196 "#;
1197
1198 let mut file_set = HashMap::from([
1199 ("com/android/aconfig/test/Flags.java", expect_flags_content),
1200 ("com/android/aconfig/test/FeatureFlagsImpl.java", expect_featureflagsimpl_content),
1201 ("com/android/aconfig/test/FeatureFlags.java", expect_featureflags_content),
1202 ("com/android/aconfig/test/CustomFeatureFlags.java", expect_customfeatureflags_content),
1203 (
1204 "com/android/aconfig/test/FakeFeatureFlagsImpl.java",
1205 EXPECTED_FAKEFEATUREFLAGSIMPL_CONTENT,
1206 ),
1207 ]);
1208
1209 for file in generated_files {
1210 let file_path = file.path.to_str().unwrap();
1211 assert!(file_set.contains_key(file_path), "Cannot find {}", file_path);
1212 assert_eq!(
1213 None,
1214 crate::test::first_significant_code_diff(
1215 file_set.get(file_path).unwrap(),
1216 &String::from_utf8(file.contents).unwrap()
1217 ),
1218 "File {} content is not correct",
1219 file_path
1220 );
1221 file_set.remove(file_path);
1222 }
1223
1224 assert!(file_set.is_empty());
1225 }
1226
1227 #[test]
test_format_java_method_name()1228 fn test_format_java_method_name() {
1229 let expected = "someSnakeName";
1230 let input = "____some_snake___name____";
1231 let formatted_name = format_java_method_name(input);
1232 assert_eq!(expected, formatted_name);
1233
1234 let input = "someSnakeName";
1235 let formatted_name = format_java_method_name(input);
1236 assert_eq!(expected, formatted_name);
1237
1238 let input = "SomeSnakeName";
1239 let formatted_name = format_java_method_name(input);
1240 assert_eq!(expected, formatted_name);
1241
1242 let input = "SomeSnakeName_";
1243 let formatted_name = format_java_method_name(input);
1244 assert_eq!(expected, formatted_name);
1245
1246 let input = "_SomeSnakeName";
1247 let formatted_name = format_java_method_name(input);
1248 assert_eq!(expected, formatted_name);
1249 }
1250
1251 #[test]
test_format_property_name()1252 fn test_format_property_name() {
1253 let expected = "mPropertiesSomeSnakeName";
1254 let input = "____some_snake___name____";
1255 let formatted_name = format_property_name(input);
1256 assert_eq!(expected, formatted_name);
1257
1258 let input = "someSnakeName";
1259 let formatted_name = format_property_name(input);
1260 assert_eq!(expected, formatted_name);
1261
1262 let input = "SomeSnakeName";
1263 let formatted_name = format_property_name(input);
1264 assert_eq!(expected, formatted_name);
1265
1266 let input = "SomeSnakeName_";
1267 let formatted_name = format_property_name(input);
1268 assert_eq!(expected, formatted_name);
1269 }
1270 }
1271