hood-core/src/main/java/at/favre/lib/hood/util/defaults/DefaultProperties.java
/*
* Copyright 2016 Patrick Favre-Bulle
*
* 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 at.favre.lib.hood.util.defaults;
import android.Manifest;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ConfigurationInfo;
import android.content.pm.PackageManager;
import android.net.TrafficStats;
import android.os.BatteryManager;
import android.os.Build;
import android.os.Debug;
import android.os.PowerManager;
import android.os.StrictMode;
import android.os.SystemClock;
import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.util.DisplayMetrics;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TimeZone;
import at.favre.lib.hood.Hood;
import at.favre.lib.hood.interfaces.PageEntry;
import at.favre.lib.hood.interfaces.Section;
import at.favre.lib.hood.interfaces.values.DynamicValue;
import at.favre.lib.hood.util.DeviceStatusUtil;
import at.favre.lib.hood.util.HoodUtil;
import at.favre.lib.hood.util.PermissionTranslator;
import at.favre.lib.hood.util.TypeTranslators;
import timber.log.Timber;
/**
* A set of methods that returns default {@link at.favre.lib.hood.interfaces.HoodAPI#createPropertyEntry(CharSequence, DynamicValue)} type page entries.
*/
public final class DefaultProperties {
private static final String TAG = DefaultProperties.class.getName();
private DefaultProperties() {
}
/**
* Returns entries of some basic device data like model number and sdk version.
*
* @return list of entries
*/
public static Section.HeaderSection createSectionBasicDeviceInfo() {
Section.ModifiableHeaderSection section = Hood.ext().createSection("Device");
section.add(Hood.get().createPropertyEntry("model", Build.MODEL));
section.add(Hood.get().createPropertyEntry("name", Build.DEVICE));
section.add(Hood.get().createPropertyEntry("brand", Build.MANUFACTURER));
section.add(Hood.get().createPropertyEntry("version", Build.VERSION.RELEASE));
section.add(Hood.get().createPropertyEntry("version_minor", Build.VERSION.INCREMENTAL));
section.add(Hood.get().createPropertyEntry("build-id", Build.ID));
section.add(Hood.get().createPropertyEntry("sdk-int", String.valueOf(Build.VERSION.SDK_INT)));
section.add(Hood.get().createPropertyEntry("serial", Build.SERIAL));
return section;
}
/**
* Additional device info
*
* @param activity can be null, but will just return an empty list
* @return list of page-entries
*/
public static List<PageEntry<?>> createDetailedDeviceInfo(@Nullable final Activity activity) {
List<PageEntry<?>> entries = new ArrayList<>();
if (activity != null) {
DisplayMetrics metrics = new DisplayMetrics();
activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
entries.add(Hood.get().createPropertyEntry("dpi", "x" + metrics.density + "/" + metrics.densityDpi + "dpi"));
entries.add(Hood.get().createPropertyEntry("resolution", metrics.heightPixels + "x" + metrics.widthPixels));
entries.add(Hood.get().createPropertyEntry("locale/timezone", new DynamicValue<String>() {
@Override
public String getValue() {
return HoodUtil.getCurrentLocale(activity).toString() + "/" + TimeZone.getDefault().getDisplayName();
}
}));
entries.add(Hood.get().createPropertyEntry("uptime", new DynamicValue<String>() {
@Override
public String getValue() {
return HoodUtil.millisToDaysHoursMinString(SystemClock.elapsedRealtime());
}
}));
entries.add(Hood.get().createPropertyEntry("android-id", Settings.Secure.getString(activity.getContentResolver(), Settings.Secure.ANDROID_ID)));
}
return entries;
}
/**
* Very technical memory & classloader process states - this data is usually very volatile
*
* @param context
* @return section for info
*/
public static Section.HeaderSection createInternalProcessDebugInfo(@Nullable final Context context) {
Section.ModifiableHeaderSection section = Hood.ext().createSection("Process Debug Info");
if (context != null) {
section.add(Hood.get().createPropertyEntry("heap-native", new DynamicValue<String>() {
@Override
public String getValue() {
return "used " + HoodUtil.humanReadableByteCount(Debug.getNativeHeapAllocatedSize(), false) + " of " + HoodUtil.humanReadableByteCount(Debug.getNativeHeapSize(), false);
}
}));
section.add(Hood.get().createPropertyEntry("memory", new DynamicValue<String>() {
@Override
public String getValue() {
return "used " + HoodUtil.humanReadableByteCount(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(), false) + " (max:" + HoodUtil.humanReadableByteCount(Runtime.getRuntime().maxMemory(), false) + ")";
}
}));
section.add(Hood.get().createPropertyEntry("pss", new DynamicValue<String>() {
@Override
public String getValue() {
return HoodUtil.humanReadableByteCount(Debug.getPss(), false);
}
}));
section.add(Hood.get().createPropertyEntry("loaded-classes", new DynamicValue<String>() {
@Override
public String getValue() {
return String.valueOf(Debug.getLoadedClassCount());
}
}));
section.add(Hood.get().createPropertyEntry("local/death/proxy objs", new DynamicValue<String>() {
@Override
public String getValue() {
return Debug.getBinderLocalObjectCount() + "/" + Debug.getBinderDeathObjectCount() + "/" + Debug.getBinderProxyObjectCount();
}
}));
section.add(Hood.get().createPropertyEntry("debugger-connected", new DynamicValue<String>() {
@Override
public String getValue() {
return String.valueOf(Debug.isDebuggerConnected());
}
}));
}
return section;
}
/**
* Traverses the static fields of given arbitrary class. Will create an entry for each non-null
* static public field. Ignores "serialVersionUID".
* <p>
* NOTE: this uses reflection to traverse to class, so if you want to keep it after proguard, use
* a keep rule like:
* <p>
* <pre>
* -keep public class your.package.name.BuildConfig { public *;}
* </pre>
*
* @param clazz the BuildConfig.java class you want the info
* @return list of page-entries
*/
public static List<PageEntry<?>> createStaticFieldsInfo(Class<?> clazz) {
List<PageEntry<?>> entries = new ArrayList<>();
Field[] declaredFields = clazz.getDeclaredFields();
for (Field field : declaredFields) {
if (Modifier.isStatic(field.getModifiers()))
try {
String key = field.getName();
String value = String.valueOf(field.get(null));
if (key != null && !key.equals("serialVersionUID") &&
value != null && !value.equals("null") && !value.trim().isEmpty()) {
entries.add(Hood.get().createPropertyEntry(key, String.valueOf(field.get(null))));
}
} catch (Exception e) {
Timber.w("could not get field from class (" + field + ")");
}
}
return entries;
}
/**
* Traverses the static fields of given class (which must be of Type BuildConfig) and parses the main
* fields that is create by the android gradle plugin (ie. version, app_id, etc.)
* NOTE: this uses reflection to traverse to class, so if you want to keep it after proguard, use
* a keep rule like:
* <p>
* <pre>
* -keep public class your.package.name.BuildConfig { public *;}
* </pre>
*
* @param buildConfig the BuildConfig.java class you want the info
* @return the section containing header, entries etc.
*/
public static Section.HeaderSection createSectionAppVersionInfoFromBuildConfig(Class<?> buildConfig) {
Section.ModifiableHeaderSection section = Hood.ext().createSection("App Version");
Field[] declaredFields = buildConfig.getDeclaredFields();
for (Field field : declaredFields) {
if (Modifier.isStatic(field.getModifiers())) {
try {
String key = null;
String value = String.valueOf(field.get(null));
switch (field.getName().toLowerCase()) {
case "version_name":
key = "version";
break;
case "version_code":
key = "version-code";
break;
case "application_id":
key = "app-id";
break;
case "build_type":
key = "build-type";
break;
case "flavor":
key = "flavor";
break;
case "debug":
key = "debug";
break;
}
if (key != null && value != null && !value.trim().isEmpty()) {
section.add(Hood.get().createPropertyEntry(key, String.valueOf(field.get(null))));
}
} catch (Exception e) {
Timber.w("could not get field from BuildConfig (" + field + ")");
}
}
}
return section;
}
/**
* Returns for each provided permission a page-entry containing the current dynamic state (granted, denied, etc.) including click
* actions to request the permission.
*
* @param activity can be null, but will just return an empty list
* @param androidPermissions see {@link android.Manifest.permission}
* @return the section containing header, entries etc.
*/
public static Section.HeaderSection createSectionRuntimePermissions(@Nullable final Activity activity, List<String> androidPermissions) {
Section.ModifiableHeaderSection section = Hood.ext().createSection("Permissions");
if (activity != null && !androidPermissions.isEmpty()) {
for (final String perm : androidPermissions) {
section.add(Hood.get().createPropertyEntry(perm.replace("android.permission.", ""), new DynamicValue<String>() {
@Override
public String getValue() {
return TypeTranslators.translatePermissionState(PermissionTranslator.getPermissionStatus(activity, perm));
}
}, Hood.ext().createOnClickActionAskPermission(perm, activity), false));
}
}
return section;
}
/**
* Get current state of various connectivity adapters (network, wifi, bt, nfc,...). Requires to have the specific
* permission to be able to show the actual state (see eg. {@link DeviceStatusUtil#getNetworkConnectivityState(Context)}.
* <p>
* Click action is opening the system settings for each adapter.
*
* @param context can be null, but will just return an empty list
* @return the section containing header, entries etc.
*/
public static Section.HeaderSection createSectionConnectivityStatusInfo(@Nullable final Context context) {
return createSectionConnectivityStatusInfo(context, true, true, true, true);
}
/**
* See {@link #createSectionConnectivityStatusInfo(Context)}
*
* @param context can be null, but will just return an empty list
* @param includeNetworkState if network state should be included
* @param includeWifiState if wifi state should be included
* @param includeBtState if bluetooth state should be included
* @param includeNfcState if nfc state should be included
* @return the section containing header, entries etc.
*/
public static Section.HeaderSection createSectionConnectivityStatusInfo(@Nullable final Context context, boolean includeNetworkState, boolean includeWifiState, boolean includeBtState, boolean includeNfcState) {
Section.ModifiableHeaderSection section = Hood.ext().createSection("Connectivity Status");
if (context != null) {
if (includeNetworkState) {
section.add(Hood.get().createPropertyEntry("internet", new DynamicValue<String>() {
@Override
public String getValue() {
return String.valueOf(DeviceStatusUtil.getNetworkConnectivityState(context));
}
}, Hood.ext().createOnClickActionStartIntent(new Intent(Settings.ACTION_SETTINGS)), false));
}
if (includeWifiState) {
final DeviceStatusUtil.Status wifiState = DeviceStatusUtil.getWifiStatus(context);
section.add(Hood.get().createPropertyEntry("wifi", new DynamicValue<String>() {
@Override
public String getValue() {
return String.valueOf(wifiState);
}
}, wifiState == DeviceStatusUtil.Status.UNSUPPORTED ?
Hood.ext().createOnClickActionToast() :
Hood.ext().createOnClickActionStartIntent(new Intent(Settings.ACTION_WIFI_SETTINGS)), false));
}
if (includeBtState) {
final DeviceStatusUtil.Status btState = DeviceStatusUtil.getBluetoothStatus(context);
section.add(Hood.get().createPropertyEntry("bluetooth", new DynamicValue<String>() {
@Override
public String getValue() {
return String.valueOf(btState);
}
}, btState == DeviceStatusUtil.Status.UNSUPPORTED ?
Hood.ext().createOnClickActionToast() :
Hood.ext().createOnClickActionStartIntent(new Intent(Settings.ACTION_BLUETOOTH_SETTINGS)), false));
}
if (includeNfcState) {
final DeviceStatusUtil.Status nfcState = DeviceStatusUtil.getNfcState(context);
section.add(Hood.get().createPropertyEntry("nfc", new DynamicValue<String>() {
@Override
public String getValue() {
return String.valueOf(nfcState);
}
}, nfcState == DeviceStatusUtil.Status.UNSUPPORTED || Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN ?
Hood.ext().createOnClickActionToast() :
Hood.ext().createOnClickActionStartIntent(new Intent(Settings.ACTION_NFC_SETTINGS)), false));
}
}
return section;
}
/**
* Convince feature to add state of multiple system features.
* Uses {@link PackageManager#hasSystemFeature(String)} call.
* <p>
* See https://developer.android.com/guide/topics/manifest/uses-feature-element.html#features-reference for
* available system features.
*
* @param context can be null, but will just return an empty list
* @param labelSystemFeatureMap a map which has ui labels as key and android system feature string
* (as returned as name by {@link PackageManager#getSystemAvailableFeatures()}) as value
* @return list of page-entries (one for each map entry)
*/
public static List<PageEntry<?>> createSystemFeatureInfo(@Nullable Context context, Map<CharSequence, String> labelSystemFeatureMap) {
List<PageEntry<?>> entries = new ArrayList<>();
if (context != null) {
for (Map.Entry<CharSequence, String> entry : labelSystemFeatureMap.entrySet()) {
boolean supported;
if (entry.getValue().matches("^-?\\d+$")) {
final ConfigurationInfo configurationInfo = ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getDeviceConfigurationInfo();
supported = configurationInfo.reqGlEsVersion >= Integer.valueOf(entry.getValue());
} else {
supported = context.getPackageManager().hasSystemFeature(entry.getValue());
}
entries.add(Hood.get().createPropertyEntry(entry.getKey(), String.valueOf(supported)));
}
}
return entries;
}
/**
* Returns a list of the most important {@link TelephonyManager} states and ids. Will only
* work if the app has the {@link Manifest.permission#READ_PHONE_STATE} permission or just
* returns an empty list.
*
* @param context can be null, but will just return an empty list
* @return the list of entries
*/
//@RequiresPermission(Manifest.permission.READ_PHONE_STATE)
public static Section.HeaderSection createSectionTelephonyManger(@Nullable Context context) {
Section.ModifiableHeaderSection section = Hood.ext().createSection("Telephony Status");
if (context != null) {
if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) {
section.setErrorMessage("Cannot display data - requires READ_PHONE_STATE permission.");
} else {
final TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
section.add(Hood.get().createPropertyEntry("sim-serial", new DynamicValue<String>() {
@Override
public String getValue() {
return telephonyManager.getSimSerialNumber();
}
}));
section.add(Hood.get().createPropertyEntry("sim-state", new DynamicValue<String>() {
@Override
public String getValue() {
return TypeTranslators.translateSimState(telephonyManager.getSimState());
}
}));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
section.add(Hood.get().createPropertyEntry("sim-slots", new DynamicValue<String>() {
@TargetApi(Build.VERSION_CODES.M)
@Override
public String getValue() {
return String.valueOf(telephonyManager.getPhoneCount());
}
}));
}
section.add(Hood.get().createPropertyEntry("subscriber-id", new DynamicValue<String>() {
@Override
public String getValue() {
return telephonyManager.getSubscriberId();
}
}));
section.add(Hood.get().createPropertyEntry("operator", new DynamicValue<String>() {
@Override
public String getValue() {
return telephonyManager.getNetworkOperatorName();
}
}));
section.add(Hood.get().createPropertyEntry("connection-type", new DynamicValue<String>() {
@Override
public String getValue() {
return TypeTranslators.translateTelephonyNetworkType(telephonyManager.getNetworkType());
}
}));
section.add(Hood.get().createPropertyEntry("MSISDN", new DynamicValue<String>() {
@Override
public String getValue() {
return telephonyManager.getLine1Number();
}
}));
section.add(Hood.get().createPropertyEntry("IMEI", new DynamicValue<String>() {
@Override
public String getValue() {
return telephonyManager.getDeviceId();
}
}));
}
}
return section;
}
/**
* Adds info for current {@link StrictMode} config. Currently only shows the bitmask used to
* store the policies states. Unfortunately there is not much public API to examine StrictMode
*
* @return section
*/
public static Section.HeaderSection createSectionStrictMode() {
Section.ModifiableHeaderSection section = Hood.ext().createSection("StrictMode");
section.add(Hood.get().createPropertyEntry("thread-policy", new DynamicValue<String>() {
@Override
public String getValue() {
return String.valueOf(StrictMode.getThreadPolicy());
}
}, true));
section.add(Hood.get().createPropertyEntry("vm-policy", new DynamicValue<String>() {
@Override
public String getValue() {
return StrictMode.getVmPolicy().toString();
}
}, true));
return section;
}
public static Section.HeaderSection createSectionAndroidDebugSettings(final Context context) {
Section.ModifiableHeaderSection section = Hood.ext().createSection("Android Debug Settings");
if (context != null) {
section.add(Hood.get().createPropertyEntry("developer-mode", new DynamicValue<String>() {
@Override
public String getValue() {
int settingsInt;
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR1) {
settingsInt = Settings.Global.getInt(context.getContentResolver(), Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0);
} else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN) {
settingsInt = Settings.Secure.getInt(context.getContentResolver(), Settings.Secure.DEVELOPMENT_SETTINGS_ENABLED, 0);
} else {
return "unknown (SDK 16+ only)";
}
return String.valueOf(settingsInt == 1);
}
}, DefaultButtonDefinitions.getDevSettingsAction().onClickAction, false));
section.add(Hood.get().createPropertyEntry("usb-debugging", new DynamicValue<String>() {
@Override
public String getValue() {
int settingsInt;
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR1) {
settingsInt = Settings.Global.getInt(context.getContentResolver(), Settings.Global.ADB_ENABLED, 0);
} else {
settingsInt = Settings.System.getInt(context.getContentResolver(), Settings.System.ADB_ENABLED, 0);
}
return String.valueOf(settingsInt == 1);
}
}, DefaultButtonDefinitions.getDevSettingsAction().onClickAction, false));
section.add(Hood.get().createPropertyEntry("don't keep activities", new DynamicValue<String>() {
@Override
public String getValue() {
int settingsInt;
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR1) {
settingsInt = Settings.Global.getInt(context.getContentResolver(), Settings.Global.ALWAYS_FINISH_ACTIVITIES, 0);
} else {
settingsInt = Settings.System.getInt(context.getContentResolver(), Settings.System.ALWAYS_FINISH_ACTIVITIES, 0);
}
return String.valueOf(settingsInt == 1);
}
}, DefaultButtonDefinitions.getDevSettingsAction().onClickAction, false));
}
return section;
}
/**
* Convenience to create a CI and source control section. Pass everything you want and keep the rest null.
*
* @param scmRev git hash e.g. "git log -1 --format=%H"
* @param scmBranch git branch e.g. "git rev-parse --abbrev-ref HEAD"
* @param ciBuildId build number
* @param ciBuildJob job name
* @param ciBuildTime time on ci server
* @return the section containing all the data
*/
public static Section.HeaderSection createSectionSourceControlAndCI(@Nullable String scmRev, @Nullable String scmBranch, @Nullable String scmCommitDate,
@Nullable String ciBuildId, @Nullable String ciBuildJob, @Nullable String ciBuildTime) {
Section.ModifiableHeaderSection section = Hood.ext().createSection("Source Control & CI");
if (scmBranch != null) {
section.add(Hood.get().createPropertyEntry("scm-branch", scmBranch));
}
if (scmRev != null) {
section.add(Hood.get().createPropertyEntry("scm-rev", scmRev));
}
if (scmCommitDate != null) {
section.add(Hood.get().createPropertyEntry("scm-date", scmCommitDate));
}
if (ciBuildJob != null) {
section.add(Hood.get().createPropertyEntry("ci-job", ciBuildJob));
}
if (ciBuildId != null) {
section.add(Hood.get().createPropertyEntry("ci-build-id", ciBuildId));
}
if (ciBuildTime != null) {
section.add(Hood.get().createPropertyEntry("ci-build-time", ciBuildTime));
}
return section;
}
/**
* Creates a section with current battery status and health
*
* @param context can be null, but will just return an empty section
* @return section
*/
public static Section.HeaderSection createSectionBatteryInfo(@Nullable final Context context) {
Section.ModifiableHeaderSection section = Hood.ext().createSection("Battery Info");
if (context != null) {
final IntentFilter battIntentFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
final Intent batteryStatus = context.registerReceiver(null, battIntentFilter);
if (batteryStatus != null) {
section.add(Hood.get().createPropertyEntry("level", new DynamicValue<String>() {
@Override
public String getValue() {
final Intent batteryStatus = context.registerReceiver(null, battIntentFilter);
float level = (float) batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
float maxScale = (float) batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
float battPercent = level / maxScale * 100;
return battPercent + "%";
}
}, DefaultButtonDefinitions.getBatterySummarySettingsAction().onClickAction, false));
section.add(Hood.get().createPropertyEntry("status", new DynamicValue<String>() {
@Override
public String getValue() {
final Intent batteryStatus = context.registerReceiver(null, battIntentFilter);
return TypeTranslators.translateBatteryStatus(batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1))
+ " (" + TypeTranslators.translateBatteryPlugged(batteryStatus.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1)) + ")";
}
}));
section.add(Hood.get().createPropertyEntry("health", new DynamicValue<String>() {
@Override
public String getValue() {
final Intent batteryStatus = context.registerReceiver(null, battIntentFilter);
return TypeTranslators.translateBatteryHealth(batteryStatus.getIntExtra(BatteryManager.EXTRA_HEALTH, -1));
}
}));
section.add(Hood.get().createPropertyEntry("temp/volt",
new DynamicValue<String>() {
@Override
public String getValue() {
final Intent batteryStatus = context.registerReceiver(null, battIntentFilter);
return (float) batteryStatus.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, -1) / 10f + "°C/" +
batteryStatus.getIntExtra(BatteryManager.EXTRA_VOLTAGE, -1) + "mV";
}
}
));
section.add(Hood.get().createPropertyEntry("batt-saving", new DynamicValue<String>() {
@Override
public String getValue() {
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
return String.valueOf(powerManager.isPowerSaveMode());
}
return "unsupported";
}
}, DefaultButtonDefinitions.getBatterySaverSettingsAction().onClickAction, false));
}
}
return section;
}
/**
* Converts a {@link Properties} object to page-entries
*
* @param properties
* @return list of page-entries
*/
public static List<PageEntry<?>> createPropertiesEntries(@NonNull Properties properties) {
return createFromMap(properties);
}
/**
* Converts an arbitrary map to page-entries. Uses {@link #toString()} on the contained objects.
*
* @param hashMap
* @return list of page-entries
*/
public static List<PageEntry<?>> createFromMap(Map<?, ?> hashMap) {
return createFromEntrySet(hashMap.entrySet());
}
private static List<PageEntry<?>> createFromEntrySet(Set<? extends Map.Entry<?, ?>> entrySet) {
List<PageEntry<?>> entries = new ArrayList<>();
for (Map.Entry propEntry : entrySet) {
if (propEntry != null && propEntry.getKey() != null && propEntry.getValue() != null) {
entries.add(Hood.get().createPropertyEntry(propEntry.getKey().toString(), propEntry.getValue().toString()));
}
}
return entries;
}
/**
* Creates a section showing some stats from {@link TrafficStats} showing tx/rx bytes and packages
* of tagged sockets from boot until now.
*
* @param uids optional uid to get rx/tx bytes and packages
* @return section
*/
public static Section.HeaderSection createTransferStatSection(int... uids) {
Section.ModifiableHeaderSection section = Hood.ext().createSection("Traffic Stats");
if (uids != null) {
for (int uid : uids) {
section.add(createTxRxdSection("Socket " + uid, TrafficStats.getUidTxBytes(uid), TrafficStats.getUidTxPackets(uid),
TrafficStats.getUidRxBytes(uid), TrafficStats.getUidRxPackets(uid)));
}
}
section.add(createTxRxdSection("Mobile", TrafficStats.getMobileTxBytes(), TrafficStats.getMobileTxPackets(),
TrafficStats.getMobileRxBytes(), TrafficStats.getMobileRxPackets()));
section.add(createTxRxdSection("Total", TrafficStats.getTotalTxBytes(), TrafficStats.getTotalTxPackets(),
TrafficStats.getTotalRxBytes(), TrafficStats.getTotalRxPackets()));
return section;
}
private static List<PageEntry<?>> createTxRxdSection(String name, final long txBytes,
final long txPackets, final long rxBytes, final long rxPackets) {
List<PageEntry<?>> list = new LinkedList<>();
list.add(Hood.get().createPropertyEntry(name + " TX", new DynamicValue<String>() {
@Override
public String getValue() {
return txBytes == TrafficStats.UNSUPPORTED ? "UNSUPPORTED" : txPackets + " pkt / " + HoodUtil.humanReadableByteCount(txBytes, false);
}
}));
list.add(Hood.get().createPropertyEntry(name + " RCVD", new DynamicValue<String>() {
@Override
public String getValue() {
return rxPackets == TrafficStats.UNSUPPORTED ? "UNSUPPORTED" : rxPackets + " pkt / " + HoodUtil.humanReadableByteCount(rxBytes, false);
}
}));
return list;
}
}