1 /*
2  * Copyright (C) 2020 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.server.stats.pull;
18 
19 import static com.android.internal.util.FrameworkStatsLog.SETTING_SNAPSHOT__TYPE__ASSIGNED_BOOL_TYPE;
20 import static com.android.internal.util.FrameworkStatsLog.SETTING_SNAPSHOT__TYPE__ASSIGNED_FLOAT_TYPE;
21 import static com.android.internal.util.FrameworkStatsLog.SETTING_SNAPSHOT__TYPE__ASSIGNED_INT_TYPE;
22 import static com.android.internal.util.FrameworkStatsLog.SETTING_SNAPSHOT__TYPE__ASSIGNED_STRING_TYPE;
23 
24 import android.annotation.NonNull;
25 import android.annotation.Nullable;
26 import android.content.ContentResolver;
27 import android.content.Context;
28 import android.provider.DeviceConfig;
29 import android.provider.Settings;
30 import android.text.TextUtils;
31 import android.util.Base64;
32 import android.util.Slog;
33 import android.util.StatsEvent;
34 
35 import com.android.internal.annotations.VisibleForTesting;
36 import com.android.internal.util.FrameworkStatsLog;
37 import com.android.service.nano.StringListParamProto;
38 
39 import java.util.ArrayList;
40 import java.util.List;
41 
42 /**
43  * Utility methods for creating {@link StatsEvent} data.
44  */
45 final class SettingsStatsUtil {
46     private static final String TAG = "SettingsStatsUtil";
47     private static final FlagsData[] GLOBAL_SETTINGS = new FlagsData[]{
48             new FlagsData("GlobalFeature__boolean_whitelist",
49                     SETTING_SNAPSHOT__TYPE__ASSIGNED_BOOL_TYPE),
50             new FlagsData("GlobalFeature__integer_whitelist",
51                     SETTING_SNAPSHOT__TYPE__ASSIGNED_INT_TYPE),
52             new FlagsData("GlobalFeature__float_whitelist",
53                     SETTING_SNAPSHOT__TYPE__ASSIGNED_FLOAT_TYPE),
54             new FlagsData("GlobalFeature__string_whitelist",
55                     SETTING_SNAPSHOT__TYPE__ASSIGNED_STRING_TYPE)
56     };
57     private static final FlagsData[] SECURE_SETTINGS = new FlagsData[]{
58             new FlagsData("SecureFeature__boolean_whitelist",
59                     SETTING_SNAPSHOT__TYPE__ASSIGNED_BOOL_TYPE),
60             new FlagsData("SecureFeature__integer_whitelist",
61                     SETTING_SNAPSHOT__TYPE__ASSIGNED_INT_TYPE),
62             new FlagsData("SecureFeature__float_whitelist",
63                     SETTING_SNAPSHOT__TYPE__ASSIGNED_FLOAT_TYPE),
64             new FlagsData("SecureFeature__string_whitelist",
65                     SETTING_SNAPSHOT__TYPE__ASSIGNED_STRING_TYPE)
66     };
67     private static final FlagsData[] SYSTEM_SETTINGS = new FlagsData[]{
68             new FlagsData("SystemFeature__boolean_whitelist",
69                     SETTING_SNAPSHOT__TYPE__ASSIGNED_BOOL_TYPE),
70             new FlagsData("SystemFeature__integer_whitelist",
71                     SETTING_SNAPSHOT__TYPE__ASSIGNED_INT_TYPE),
72             new FlagsData("SystemFeature__float_whitelist",
73                     SETTING_SNAPSHOT__TYPE__ASSIGNED_FLOAT_TYPE),
74             new FlagsData("SystemFeature__string_whitelist",
75                     SETTING_SNAPSHOT__TYPE__ASSIGNED_STRING_TYPE)
76     };
77 
78     @VisibleForTesting
79     @NonNull
logGlobalSettings(Context context, int atomTag, int userId)80     static List<StatsEvent> logGlobalSettings(Context context, int atomTag, int userId) {
81         final List<StatsEvent> output = new ArrayList<>();
82         final ContentResolver resolver = context.getContentResolver();
83 
84         for (FlagsData flagsData : GLOBAL_SETTINGS) {
85             StringListParamProto proto = getList(flagsData.mFlagName);
86             if (proto == null) {
87                 continue;
88             }
89             for (String key : proto.element) {
90                 final String value = Settings.Global.getStringForUser(resolver, key, userId);
91                 output.add(createStatsEvent(atomTag, key, value, userId,
92                         flagsData.mDataType));
93             }
94         }
95         return output;
96     }
97 
98     @NonNull
logSystemSettings(Context context, int atomTag, int userId)99     static List<StatsEvent> logSystemSettings(Context context, int atomTag, int userId) {
100         final List<StatsEvent> output = new ArrayList<>();
101         final ContentResolver resolver = context.getContentResolver();
102 
103         for (FlagsData flagsData : SYSTEM_SETTINGS) {
104             StringListParamProto proto = getList(flagsData.mFlagName);
105             if (proto == null) {
106                 continue;
107             }
108             for (String key : proto.element) {
109                 final String value = Settings.System.getStringForUser(resolver, key, userId);
110                 output.add(createStatsEvent(atomTag, key, value, userId,
111                         flagsData.mDataType));
112             }
113         }
114         return output;
115     }
116 
117     @NonNull
logSecureSettings(Context context, int atomTag, int userId)118     static List<StatsEvent> logSecureSettings(Context context, int atomTag, int userId) {
119         final List<StatsEvent> output = new ArrayList<>();
120         final ContentResolver resolver = context.getContentResolver();
121 
122         for (FlagsData flagsData : SECURE_SETTINGS) {
123             StringListParamProto proto = getList(flagsData.mFlagName);
124             if (proto == null) {
125                 continue;
126             }
127             for (String key : proto.element) {
128                 final String value = Settings.Secure.getStringForUser(resolver, key, userId);
129                 output.add(createStatsEvent(atomTag, key, value, userId,
130                         flagsData.mDataType));
131             }
132         }
133         return output;
134     }
135 
136     @VisibleForTesting
137     @Nullable
getList(String flag)138     static StringListParamProto getList(String flag) {
139         final String base64 = DeviceConfig.getProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS, flag);
140         if (TextUtils.isEmpty(base64)) {
141             return null;
142         }
143         final byte[] decode = Base64.decode(base64, Base64.NO_PADDING | Base64.NO_WRAP);
144         StringListParamProto list = null;
145         try {
146             list = StringListParamProto.parseFrom(decode);
147         } catch (Exception e) {
148             Slog.e(TAG, "Error parsing string list proto", e);
149         }
150         return list;
151     }
152 
153     /**
154      * Create {@link StatsEvent} for SETTING_SNAPSHOT atom
155      */
156     @NonNull
createStatsEvent(int atomTag, String key, String value, int userId, int type)157     private static StatsEvent createStatsEvent(int atomTag, String key, String value, int userId,
158             int type) {
159         final StatsEvent.Builder builder = StatsEvent.newBuilder()
160                 .setAtomId(atomTag)
161                 .writeString(key);
162         boolean booleanValue = false;
163         int intValue = 0;
164         float floatValue = 0;
165         String stringValue = "";
166         if (TextUtils.isEmpty(value)) {
167             builder.writeInt(FrameworkStatsLog.SETTING_SNAPSHOT__TYPE__NOTASSIGNED)
168                     .writeBoolean(booleanValue)
169                     .writeInt(intValue)
170                     .writeFloat(floatValue)
171                     .writeString(stringValue)
172                     .writeInt(userId);
173         } else {
174             switch (type) {
175                 case SETTING_SNAPSHOT__TYPE__ASSIGNED_BOOL_TYPE:
176                     booleanValue = "1".equals(value);
177                     break;
178                 case FrameworkStatsLog.SETTING_SNAPSHOT__TYPE__ASSIGNED_INT_TYPE:
179                     try {
180                         intValue = Integer.parseInt(value);
181                     } catch (NumberFormatException e) {
182                         Slog.w(TAG, "Can not parse value to float: " + value);
183                     }
184                     break;
185                 case SETTING_SNAPSHOT__TYPE__ASSIGNED_FLOAT_TYPE:
186                     try {
187                         floatValue = Float.parseFloat(value);
188                     } catch (NumberFormatException e) {
189                         Slog.w(TAG, "Can not parse value to float: " + value);
190                     }
191                     break;
192                 case FrameworkStatsLog.SETTING_SNAPSHOT__TYPE__ASSIGNED_STRING_TYPE:
193                     stringValue = value;
194                     break;
195                 default:
196                     Slog.w(TAG, "Unexpected value type " + type);
197             }
198             builder.writeInt(type)
199                     .writeBoolean(booleanValue)
200                     .writeInt(intValue)
201                     .writeFloat(floatValue)
202                     .writeString(stringValue)
203                     .writeInt(userId);
204         }
205         return builder.build();
206     }
207 
208     /** Class for defining flag name and its data type. */
209     static final class FlagsData {
210         /** {@link DeviceConfig} flag name, value of the flag is {@link StringListParamProto} */
211         String mFlagName;
212         /** Data type of the value getting from {@link Settings} keys. */
213         int mDataType;
214 
FlagsData(String flagName, int dataType)215         FlagsData(String flagName, int dataType) {
216             mFlagName = flagName;
217             mDataType = dataType;
218         }
219     }
220 }
221