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 package android.platform.test.flag.util;
18 
19 import com.google.auto.value.AutoValue;
20 
21 import javax.annotation.Nullable;
22 
23 /**
24  * Contains the information of a flag.
25  *
26  * <p>There are two types of flags:
27  * <li>Legacy flags: the format is {namespace}/{flagName}. For these flags, packageName and
28  *     flagsClassName will be null, and both fullFlagName and simpleFlagName will be the flagName.
29  * <li>AConfig flags: the format is {packageName}.{simpleFlagName}. For these flags, namespace will
30  *     be null, fullFlagName will be {packageName}.{simpleFlagName}, and flagsClassName will be
31  *     {packageName}.Flags.
32  */
33 @AutoValue
34 public abstract class Flag {
35     public static final String NAMESPACE_FLAG_SEPARATOR = "/";
36 
37     /** The format of flag with namespace is {namespace}/{flagName}. */
38     public static final String FLAG_WITH_NAMESPACE_FORMAT = "%s/%s";
39 
40     /** The format of aconfig full flag name is {packageName}.{simpleFlagName}. */
41     public static final String ACONFIG_FULL_FLAG_FORMAT = "%s.%s";
42 
43     private static final String PACKAGE_NAME_SIMPLE_NAME_SEPARATOR = ".";
44     private static final String FLAGS_CLASS_FORMAT = "%s.Flags";
45 
46     /**
47      * The possible prefix when flag repackaging is happened on the class. TODO(b/324009565): Remove
48      * this prefix when the long term solution is ready.
49      */
50     private static final String REPACKAGE_PREFIX = "com.android.internal.hidden_from_bootclasspath";
51 
createFlag(String flag)52     public static Flag createFlag(String flag) {
53         String namespace = null;
54         String fullFlagName = null;
55         String packageName = null;
56         String simpleFlagName = null;
57         if (flag.contains(NAMESPACE_FLAG_SEPARATOR)) {
58             String[] flagSplits = flag.split(NAMESPACE_FLAG_SEPARATOR, /* limit= */ 2);
59             namespace = flagSplits[0];
60             fullFlagName = flagSplits[1];
61             simpleFlagName = fullFlagName;
62         } else {
63             fullFlagName = flag;
64             if (!fullFlagName.contains(PACKAGE_NAME_SIMPLE_NAME_SEPARATOR)) {
65                 throw new IllegalArgumentException(
66                         String.format(
67                                 "Flag %s is invalid. The format should be {packageName}"
68                                         + ".{simpleFlagName}",
69                                 flag));
70             }
71             int index = fullFlagName.lastIndexOf(PACKAGE_NAME_SIMPLE_NAME_SEPARATOR);
72             packageName = fullFlagName.substring(0, index);
73             simpleFlagName = fullFlagName.substring(index + 1);
74         }
75 
76         return new AutoValue_Flag(namespace, fullFlagName, packageName, simpleFlagName);
77     }
78 
79     @Nullable
namespace()80     public abstract String namespace();
81 
fullFlagName()82     public abstract String fullFlagName();
83 
84     @Nullable
packageName()85     public abstract String packageName();
86 
simpleFlagName()87     public abstract String simpleFlagName();
88 
89     @Nullable
flagsClassName()90     public String flagsClassName() {
91         return flagsClassPackageName() == null
92                 ? null
93                 : String.format(FLAGS_CLASS_FORMAT, flagsClassPackageName());
94     }
95 
96     /**
97      * The real package name of the Flags class. May be different to the packageName when
98      * repackaging is applied.
99      */
100     @Nullable
flagsClassPackageName()101     public String flagsClassPackageName() {
102         String packageName = packageName();
103         if (packageName == null) {
104             return null;
105         }
106 
107         try {
108             Class.forName(
109                     String.format(FLAGS_CLASS_FORMAT, packageName),
110                     false,
111                     this.getClass().getClassLoader());
112             return packageName;
113         } catch (ClassNotFoundException e) {
114             return String.format("%s.%s", REPACKAGE_PREFIX, packageName);
115         }
116     }
117 }
118