• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.healthfitness.flags;
18 
19 import static com.android.healthfitness.flags.DatabaseVersions.DB_VERSION_ACTIVITY_INTENSITY;
20 import static com.android.healthfitness.flags.DatabaseVersions.DB_VERSION_CLOUD_BACKUP_AND_RESTORE;
21 import static com.android.healthfitness.flags.DatabaseVersions.DB_VERSION_ECOSYSTEM_METRICS;
22 import static com.android.healthfitness.flags.DatabaseVersions.DB_VERSION_PERSONAL_HEALTH_RECORD;
23 import static com.android.healthfitness.flags.DatabaseVersions.LAST_ROLLED_OUT_DB_VERSION;
24 import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
25 
26 import com.android.internal.annotations.VisibleForTesting;
27 
28 import java.util.Map;
29 import java.util.TreeMap;
30 import java.util.function.BooleanSupplier;
31 
32 /**
33  * A helper class to act as the source of truth for whether a feature is enabled or not by taking
34  * into account both feature flag and DB flag. See go/hc-aconfig-and-db.
35  *
36  * @hide
37  */
38 public final class AconfigFlagHelper {
39     private static final String TAG = "HC" + AconfigFlagHelper.class.getSimpleName();
40 
41     // For testing purposes, this field needs to be made public instead of package-private so the
42     // unit tests can access it. This is due to tests don't run in the same classloader as the
43     // framework. See
44     // https://groups.google.com/a/google.com/g/android-chatty-eng/c/TymmRzs3UcY/m/_JeFcynRBwAJ.
45     @VisibleForTesting(visibility = PRIVATE)
46     // Using BooleanSupplier instead of Boolean due to b/370447278#comment2
47     public static final TreeMap<Integer, BooleanSupplier> DB_VERSION_TO_DB_FLAG_MAP =
48             new TreeMap<>();
49 
50     /**
51      * Returns the DB version based on DB flag values, this DB version is used to initialize {@link
52      * android.database.sqlite.SQLiteOpenHelper} to dictate which DB upgrades will be executed.
53      */
getDbVersion()54     public static synchronized int getDbVersion() {
55         if (!Flags.infraToGuardDbChanges()) {
56             return LAST_ROLLED_OUT_DB_VERSION;
57         }
58 
59         int dbVersion = LAST_ROLLED_OUT_DB_VERSION;
60         for (Map.Entry<Integer, BooleanSupplier> entry : getDbVersionToDbFlagMap().entrySet()) {
61             if (!entry.getValue().getAsBoolean()) {
62                 break;
63             }
64             dbVersion = entry.getKey();
65         }
66 
67         return dbVersion;
68     }
69 
70     /**
71      * Returns whether the DB flag of a feature is enabled.
72      *
73      * <p>A DB flag is deemed to be enabled if and only if the DB flag as well as all other features
74      * with smaller version numbers have their DB flags enabled.
75      *
76      * <p>For example, if DB_VERSION_TO_DB_FLAG_MAP contains these:
77      *
78      * <pre>{@code
79      * DB_F1 = true
80      * DB_F2 = true
81      * DB_F3 = true
82      * DB_F4 = false
83      * }</pre>
84      *
85      * Then isDbFlagEnabled(3) will return true and isDbFlagEnabled(4) will return false.
86      *
87      * <p>In case the map contains a disconnected line of "true"s before the last "false" like this:
88      *
89      * <pre>{@code
90      * DB_F1 = true
91      * DB_F2 = false
92      * DB_F3 = true
93      * DB_F4 = false
94      * }</pre>
95      *
96      * Then isDbFlagEnabled(3) will return false even though DB_F3 is mapped to true.
97      *
98      * @see #getDbVersion()
99      * @see ag/28760234 for example of how to use this method
100      */
isDbFlagEnabled(int dbVersion)101     private static synchronized boolean isDbFlagEnabled(int dbVersion) {
102         return getDbVersion() >= dbVersion;
103     }
104 
AconfigFlagHelper()105     private AconfigFlagHelper() {}
106 
107     // =============================================================================================
108     // Only things in below this comment should be updated when we move DB schema changes of a
109     // feature from "under development" to "finalized". "finalized" here means the DB schema changes
110     // won't be changed again, they will be assigned a DB version and a DB flag, if further changes
111     // are required to the DB schema, then new DB version and DB flag are required.
112     // =============================================================================================
113 
114     /**
115      * Returns a map of DB version => DB flag with the DB versions being keys and ordered.
116      *
117      * <p>Note: Because the map is initialized with aconfig flag values, hence it needs to be
118      * initialized at run time via a method call rather than static block or static field, otherwise
119      * the <code>@EnableFlags</code> annotations won't work in unit tests due to its evaluation
120      * being done after the map has already been initialized.
121      */
getDbVersionToDbFlagMap()122     private static Map<Integer, BooleanSupplier> getDbVersionToDbFlagMap() {
123         if (!DB_VERSION_TO_DB_FLAG_MAP.isEmpty()) {
124             return DB_VERSION_TO_DB_FLAG_MAP;
125         }
126 
127         DB_VERSION_TO_DB_FLAG_MAP.put(
128                 DB_VERSION_PERSONAL_HEALTH_RECORD, Flags::personalHealthRecordDatabase);
129         DB_VERSION_TO_DB_FLAG_MAP.put(DB_VERSION_ACTIVITY_INTENSITY, Flags::activityIntensityDb);
130         DB_VERSION_TO_DB_FLAG_MAP.put(
131                 DB_VERSION_ECOSYSTEM_METRICS, Flags::ecosystemMetricsDbChanges);
132         DB_VERSION_TO_DB_FLAG_MAP.put(
133                 DB_VERSION_CLOUD_BACKUP_AND_RESTORE, Flags::cloudBackupAndRestoreDb);
134 
135         return DB_VERSION_TO_DB_FLAG_MAP;
136     }
137 
138     /** Returns a boolean indicating whether PHR feature is enabled. */
isPersonalHealthRecordEnabled()139     public static synchronized boolean isPersonalHealthRecordEnabled() {
140         return Flags.personalHealthRecord() && isDbFlagEnabled(DB_VERSION_PERSONAL_HEALTH_RECORD);
141     }
142 
143     /** Returns a boolean indicating whether Activity Intensity data type is enabled. */
isActivityIntensityEnabled()144     public static boolean isActivityIntensityEnabled() {
145         return Flags.activityIntensity()
146                 && isDbFlagEnabled(DB_VERSION_ACTIVITY_INTENSITY)
147                 && Flags.healthConnectMappings();
148     }
149 
150     /** Returns a boolean indicating whether Ecosystem Metrics is enabled. */
isEcosystemMetricsEnabled()151     public static boolean isEcosystemMetricsEnabled() {
152         return Flags.ecosystemMetrics() && isDbFlagEnabled(DB_VERSION_ECOSYSTEM_METRICS);
153     }
154 
155     /** Returns a boolean indicating whether cloud backup & restore is enabled. */
isCloudBackupRestoreEnabled()156     public static boolean isCloudBackupRestoreEnabled() {
157         return Flags.cloudBackupAndRestore()
158                 && isDbFlagEnabled(DB_VERSION_CLOUD_BACKUP_AND_RESTORE);
159     }
160 }
161