diff options
4 files changed, 394 insertions, 0 deletions
diff --git a/proto/src/typed_features.proto b/proto/src/typed_features.proto new file mode 100644 index 000000000000..c2b3b18ea7c8 --- /dev/null +++ b/proto/src/typed_features.proto @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; + +package com.android.service; +option java_multiple_files = true; + +// This message is to specify feature params that are a list of strings. +message StringListParamProto { + repeated string element = 1; +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/stats/pull/SettingsStatsUtil.java b/services/core/java/com/android/server/stats/pull/SettingsStatsUtil.java new file mode 100644 index 000000000000..7cdb84ba0404 --- /dev/null +++ b/services/core/java/com/android/server/stats/pull/SettingsStatsUtil.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.stats.pull; + +import static com.android.internal.util.FrameworkStatsLog.SETTING_SNAPSHOT__TYPE__ASSIGNED_BOOL_TYPE; +import static com.android.internal.util.FrameworkStatsLog.SETTING_SNAPSHOT__TYPE__ASSIGNED_FLOAT_TYPE; +import static com.android.internal.util.FrameworkStatsLog.SETTING_SNAPSHOT__TYPE__ASSIGNED_INT_TYPE; +import static com.android.internal.util.FrameworkStatsLog.SETTING_SNAPSHOT__TYPE__ASSIGNED_STRING_TYPE; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ContentResolver; +import android.content.Context; +import android.provider.DeviceConfig; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.Base64; +import android.util.Slog; +import android.util.StatsEvent; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.FrameworkStatsLog; +import com.android.service.nano.StringListParamProto; + +import java.util.ArrayList; +import java.util.List; + +/** + * Utility methods for creating {@link StatsEvent} data. + */ +final class SettingsStatsUtil { + private static final String TAG = "SettingsStatsUtil"; + private static final FlagsData[] GLOBAL_SETTINGS = new FlagsData[]{ + new FlagsData("GlobalFeature__boolean_whitelist", + SETTING_SNAPSHOT__TYPE__ASSIGNED_BOOL_TYPE), + new FlagsData("GlobalFeature__integer_whitelist", + SETTING_SNAPSHOT__TYPE__ASSIGNED_INT_TYPE), + new FlagsData("GlobalFeature__float_whitelist", + SETTING_SNAPSHOT__TYPE__ASSIGNED_FLOAT_TYPE), + new FlagsData("GlobalFeature__string_whitelist", + SETTING_SNAPSHOT__TYPE__ASSIGNED_STRING_TYPE) + }; + private static final FlagsData[] SECURE_SETTINGS = new FlagsData[]{ + new FlagsData("SecureFeature__boolean_whitelist", + SETTING_SNAPSHOT__TYPE__ASSIGNED_BOOL_TYPE), + new FlagsData("SecureFeature__integer_whitelist", + SETTING_SNAPSHOT__TYPE__ASSIGNED_INT_TYPE), + new FlagsData("SecureFeature__float_whitelist", + SETTING_SNAPSHOT__TYPE__ASSIGNED_FLOAT_TYPE), + new FlagsData("SecureFeature__string_whitelist", + SETTING_SNAPSHOT__TYPE__ASSIGNED_STRING_TYPE) + }; + private static final FlagsData[] SYSTEM_SETTINGS = new FlagsData[]{ + new FlagsData("SystemFeature__boolean_whitelist", + SETTING_SNAPSHOT__TYPE__ASSIGNED_BOOL_TYPE), + new FlagsData("SystemFeature__integer_whitelist", + SETTING_SNAPSHOT__TYPE__ASSIGNED_INT_TYPE), + new FlagsData("SystemFeature__float_whitelist", + SETTING_SNAPSHOT__TYPE__ASSIGNED_FLOAT_TYPE), + new FlagsData("SystemFeature__string_whitelist", + SETTING_SNAPSHOT__TYPE__ASSIGNED_STRING_TYPE) + }; + + @VisibleForTesting + @NonNull + static List<StatsEvent> logGlobalSettings(Context context, int atomTag, int userId) { + final List<StatsEvent> output = new ArrayList<>(); + final ContentResolver resolver = context.getContentResolver(); + + for (FlagsData flagsData : GLOBAL_SETTINGS) { + StringListParamProto proto = getList(flagsData.mFlagName); + if (proto == null) { + continue; + } + for (String key : proto.element) { + final String value = Settings.Global.getStringForUser(resolver, key, userId); + output.add(createStatsEvent(atomTag, key, value, userId, + flagsData.mDataType)); + } + } + return output; + } + + @NonNull + static List<StatsEvent> logSystemSettings(Context context, int atomTag, int userId) { + final List<StatsEvent> output = new ArrayList<>(); + final ContentResolver resolver = context.getContentResolver(); + + for (FlagsData flagsData : SYSTEM_SETTINGS) { + StringListParamProto proto = getList(flagsData.mFlagName); + if (proto == null) { + continue; + } + for (String key : proto.element) { + final String value = Settings.System.getStringForUser(resolver, key, userId); + output.add(createStatsEvent(atomTag, key, value, userId, + flagsData.mDataType)); + } + } + return output; + } + + @NonNull + static List<StatsEvent> logSecureSettings(Context context, int atomTag, int userId) { + final List<StatsEvent> output = new ArrayList<>(); + final ContentResolver resolver = context.getContentResolver(); + + for (FlagsData flagsData : SECURE_SETTINGS) { + StringListParamProto proto = getList(flagsData.mFlagName); + if (proto == null) { + continue; + } + for (String key : proto.element) { + final String value = Settings.Secure.getStringForUser(resolver, key, userId); + output.add(createStatsEvent(atomTag, key, value, userId, + flagsData.mDataType)); + } + } + return output; + } + + @VisibleForTesting + @Nullable + static StringListParamProto getList(String flag) { + final String base64 = DeviceConfig.getProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS, flag); + if (TextUtils.isEmpty(base64)) { + return null; + } + final byte[] decode = Base64.decode(base64, Base64.NO_PADDING | Base64.NO_WRAP); + StringListParamProto list = null; + try { + list = StringListParamProto.parseFrom(decode); + } catch (Exception e) { + Slog.e(TAG, "Error parsing string list proto", e); + } + return list; + } + + /** + * Create {@link StatsEvent} for SETTING_SNAPSHOT atom + */ + @NonNull + private static StatsEvent createStatsEvent(int atomTag, String key, String value, int userId, + int type) { + final StatsEvent.Builder builder = StatsEvent.newBuilder() + .setAtomId(atomTag) + .writeString(key); + boolean booleanValue = false; + int intValue = 0; + float floatValue = 0; + String stringValue = ""; + if (TextUtils.isEmpty(value)) { + builder.writeInt(FrameworkStatsLog.SETTING_SNAPSHOT__TYPE__NOTASSIGNED) + .writeBoolean(booleanValue) + .writeInt(intValue) + .writeFloat(floatValue) + .writeString(stringValue) + .writeInt(userId); + } else { + switch (type) { + case SETTING_SNAPSHOT__TYPE__ASSIGNED_BOOL_TYPE: + booleanValue = "1".equals(value); + break; + case FrameworkStatsLog.SETTING_SNAPSHOT__TYPE__ASSIGNED_INT_TYPE: + try { + intValue = Integer.parseInt(value); + } catch (NumberFormatException e) { + Slog.w(TAG, "Can not parse value to float: " + value); + } + break; + case SETTING_SNAPSHOT__TYPE__ASSIGNED_FLOAT_TYPE: + try { + floatValue = Float.parseFloat(value); + } catch (NumberFormatException e) { + Slog.w(TAG, "Can not parse value to float: " + value); + } + break; + case FrameworkStatsLog.SETTING_SNAPSHOT__TYPE__ASSIGNED_STRING_TYPE: + stringValue = value; + break; + default: + Slog.w(TAG, "Unexpected value type " + type); + } + builder.writeInt(type) + .writeBoolean(booleanValue) + .writeInt(intValue) + .writeFloat(floatValue) + .writeString(stringValue) + .writeInt(userId); + } + return builder.build(); + } + + /** Class for defining flag name and its data type. */ + static final class FlagsData { + /** {@link DeviceConfig} flag name, value of the flag is {@link StringListParamProto} */ + String mFlagName; + /** Data type of the value getting from {@link Settings} keys. */ + int mDataType; + + FlagsData(String flagName, int dataType) { + mFlagName = flagName; + mDataType = dataType; + } + } +} diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index e9da2c4c8472..288c22a94b45 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -416,6 +416,8 @@ public class StatsPullAtomService extends SystemService { return pullHealthHal(atomTag, data); case FrameworkStatsLog.ATTRIBUTED_APP_OPS: return pullAttributedAppOps(atomTag, data); + case FrameworkStatsLog.SETTING_SNAPSHOT: + return pullSettingsStats(atomTag, data); default: throw new UnsupportedOperationException("Unknown tagId=" + atomTag); } @@ -580,6 +582,7 @@ public class StatsPullAtomService extends SystemService { registerFullBatteryCapacity(); registerBatteryVoltage(); registerBatteryCycleCount(); + registerSettingsStats(); } /** @@ -3244,6 +3247,43 @@ public class StatsPullAtomService extends SystemService { return StatsManager.PULL_SUCCESS; } + private void registerSettingsStats() { + int tagId = FrameworkStatsLog.SETTING_SNAPSHOT; + mStatsManager.setPullAtomCallback( + tagId, + null, // use default PullAtomMetadata values + BackgroundThread.getExecutor(), + mStatsCallbackImpl + ); + } + + int pullSettingsStats(int atomTag, List<StatsEvent> pulledData) { + UserManager userManager = mContext.getSystemService(UserManager.class); + if (userManager == null) { + return StatsManager.PULL_SKIP; + } + + final long token = Binder.clearCallingIdentity(); + try { + for (UserInfo user : userManager.getUsers()) { + final int userId = user.getUserHandle().getIdentifier(); + + if (userId == UserHandle.USER_SYSTEM) { + pulledData.addAll(SettingsStatsUtil.logGlobalSettings(mContext, atomTag, + UserHandle.USER_SYSTEM)); + } + pulledData.addAll(SettingsStatsUtil.logSystemSettings(mContext, atomTag, userId)); + pulledData.addAll(SettingsStatsUtil.logSecureSettings(mContext, atomTag, userId)); + } + } catch (Exception e) { + Slog.e(TAG, "failed to pullSettingsStats", e); + return StatsManager.PULL_SKIP; + } finally { + Binder.restoreCallingIdentity(token); + } + return StatsManager.PULL_SUCCESS; + } + // Thermal event received from vendor thermal management subsystem private static final class ThermalEventListener extends IThermalEventListener.Stub { @Override diff --git a/services/tests/servicestests/src/com/android/server/stats/pull/SettingsStatsUtilTest.java b/services/tests/servicestests/src/com/android/server/stats/pull/SettingsStatsUtilTest.java new file mode 100644 index 000000000000..cfeadc6893db --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/stats/pull/SettingsStatsUtilTest.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.stats.pull; + +import static android.os.UserHandle.USER_SYSTEM; + +import static com.android.internal.util.FrameworkStatsLog.SETTING_SNAPSHOT; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import android.content.Context; +import android.provider.DeviceConfig; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Build/Install/Run: + * atest FrameworksServicesTests:SettingsStatsUtilTest + */ +@RunWith(AndroidJUnit4.class) +public class SettingsStatsUtilTest { + private static final String[] KEYS = new String[]{ + "screen_auto_brightness_adj", + "font_scale" + }; + private static final String ENCODED = "ChpzY3JlZW5fYXV0b19icmlnaHRuZXNzX2FkagoKZm9udF9zY2FsZQ"; + private static final String FLAG = "testflag"; + private Context mContext; + + @Before + public void setUp() { + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS, + FLAG, + "", + false /* makeDefault*/); + mContext = InstrumentationRegistry.getInstrumentation().getContext(); + } + + @Test + public void getList_emptyString_nullValue() { + assertNull(SettingsStatsUtil.getList(FLAG)); + } + + @Test + public void getList_notValidString_nullValue() { + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS, FLAG, "abcd", false); + + assertNull(SettingsStatsUtil.getList(FLAG)); + } + + @Test + public void getList_validString_correctValue() { + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS, FLAG, ENCODED, false); + + assertArrayEquals(KEYS, SettingsStatsUtil.getList(FLAG).element); + } + + @Test + public void logGlobalSettings_noWhitelist_correctSize() { + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS, + "GlobalFeature__boolean_whitelist", "", false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS, + "GlobalFeature__integer_whitelist", "", false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS, + "GlobalFeature__float_whitelist", "", false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS, + "GlobalFeature__string_whitelist", "", false); + + assertEquals(0, SettingsStatsUtil.logGlobalSettings(mContext, SETTING_SNAPSHOT, + USER_SYSTEM).size()); + } + + @Test + public void logGlobalSettings_correctSize() { + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS, + "GlobalFeature__boolean_whitelist", ENCODED, false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS, + "GlobalFeature__integer_whitelist", ENCODED, false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS, + "GlobalFeature__float_whitelist", ENCODED, false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS, + "GlobalFeature__string_whitelist", ENCODED, false); + + assertEquals(KEYS.length * 4, + SettingsStatsUtil.logGlobalSettings(mContext, SETTING_SNAPSHOT, + USER_SYSTEM).size()); + } +} |