/*
* Copyright (C) 2015 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.afwsamples.testdpc.policy.networking;
import android.annotation.TargetApi;
import android.app.AlertDialog;
import android.app.DatePickerDialog;
import android.app.ListFragment;
import android.app.usage.NetworkStats;
import android.app.usage.NetworkStatsManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.net.ConnectivityManager;
import android.os.Build;
import android.os.Bundle;
import android.os.RemoteException;
import android.text.Html;
import android.text.Spanned;
import android.text.format.Formatter;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.DatePicker;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.Spinner;
import android.widget.TextView;
import com.afwsamples.testdpc.R;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
/**
* Fragment for checking network usage of apps using {@link android.app.usage.NetworkStatsManager}
*/
@TargetApi(Build.VERSION_CODES.M)
public class NetworkUsageStatsFragment extends ListFragment implements View.OnClickListener,
AdapterView.OnItemSelectedListener {
private static final String TAG = "TestDPC.NetworkUsageStatsFragment";
// See @array/network_stats_queries_array
private static final int QUERY_DEVICE_POS = 1;
private static final int QUERY_PROFILE_POS = 2;
private static final int QUERY_APPSUMMARY_POS = 3;
private static final int QUERY_APPHISTORY_POS = 4;
private NetworkStatsManager mNetstatsManager;
private PackageManager mPackageManager;
private Spinner mQuerySpinner;
private TextView mExplanation;
private Date mStartDate;
private Date mEndDate;
private Button mStartDateButton;
private Button mEndDateButton;
private TextView mDataUsageSummary;
private ListView mDataUsageList;
private List<List<NetworkStats.Bucket>> mListData;
private ArrayAdapter<List<NetworkStats.Bucket>> mListAdapter;
private ListView mAppHistoryList;
private Button mBackToAppsListButton;
private DateFormat mDateStringFormat;
private DateFormat mHourMinuteDateFormat;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDateStringFormat = new SimpleDateFormat("*dd/MM/yyyy*");
mHourMinuteDateFormat = new SimpleDateFormat("kk:mm");
}
@Override
public View onCreateView(final LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
View view = inflater.inflate(R.layout.network_usage_stats, container, false);
getActivity().getActionBar().setTitle(R.string.data_usage);
mPackageManager = getActivity().getPackageManager();
mNetstatsManager = (NetworkStatsManager)getActivity().getSystemService(
Context.NETWORK_STATS_SERVICE);
mListData = new ArrayList<>();
mListAdapter = new ArrayAdapter<List<NetworkStats.Bucket>>(getActivity(),
R.layout.data_usage_item, android.R.id.title, mListData) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View appView = convertView;
if (convertView == null) {
appView = inflater.inflate(R.layout.data_usage_item, parent, false);
final TextView title = (TextView) appView.findViewById(android.R.id.title);
final TextView summary = (TextView) appView.findViewById(android.R.id.summary);
final TextView state = (TextView) appView.findViewById(R.id.state);
ImageView imageView = (ImageView) appView.findViewById(android.R.id.icon);
appView.setTag(new ViewHolder(title, summary, state, imageView));
}
List<NetworkStats.Bucket> item = getItem(position);
bindView(appView, item);
return appView;
}
};
mQuerySpinner = (Spinner) view.findViewById(R.id.query_type_spinner);
mQuerySpinner.setOnItemSelectedListener(this);
mExplanation = (TextView) view.findViewById(R.id.explanation);
mStartDate = getTodayPlus(0, 0);
mEndDate = getTodayPlus(Calendar.DAY_OF_MONTH, 1);
mStartDateButton = (Button) view.findViewById(R.id.start_date_button);
if (mStartDateButton != null) {
mStartDateButton.setOnClickListener(this);
}
mEndDateButton = (Button) view.findViewById(R.id.end_date_button);
if (mEndDateButton != null) {
mEndDateButton.setOnClickListener(this);
}
updateButtonsText();
mDataUsageSummary = (TextView) view.findViewById(R.id.data_usage_summary);
mDataUsageList = (ListView) view.findViewById(android.R.id.list);
mDataUsageList.setAdapter(mListAdapter);
mAppHistoryList = (ListView) view.findViewById(R.id.app_history);
mBackToAppsListButton = (Button) view.findViewById(R.id.back_to_apps_button);
if (mBackToAppsListButton != null) {
mBackToAppsListButton.setOnClickListener(this);
}
return view;
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.start_date_button: {
pickDate(mStartDate);
} break;
case R.id.end_date_button: {
pickDate(mEndDate);
} break;
case R.id.back_to_apps_button: {
transitionAppHistoryView(View.GONE);
}
}
}
@Override
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
NetworkStats result = null;
NetworkStats.Bucket bucket = null;
mDataUsageSummary.setVisibility(View.GONE);
mDataUsageList.setVisibility(View.GONE);
mAppHistoryList.setVisibility(View.GONE);
mBackToAppsListButton.setVisibility(View.GONE);
mExplanation.setText("");
try {
switch (pos) {
case QUERY_DEVICE_POS: {
bucket = mNetstatsManager.querySummaryForDevice(
ConnectivityManager.TYPE_WIFI, "", mStartDate.getTime(),
mEndDate.getTime());
mExplanation.setText(R.string.network_stats_device_summary_explanation);
mDataUsageSummary.setText(getString(R.string.network_stats_device_summary,
formatSize(bucket.getRxBytes()), bucket.getRxPackets(),
formatSize(bucket.getTxBytes()), bucket.getTxPackets()));
mDataUsageSummary.setVisibility(View.VISIBLE);
} break;
case QUERY_PROFILE_POS: {
bucket = mNetstatsManager.querySummaryForUser(
ConnectivityManager.TYPE_WIFI, "", mStartDate.getTime(),
mEndDate.getTime());
mExplanation.setText(R.string.network_stats_profile_summary_explanation);
mDataUsageSummary.setText(getString(R.string.network_stats_profile_summary,
formatSize(bucket.getRxBytes()), bucket.getRxPackets(),
formatSize(bucket.getTxBytes()), bucket.getTxPackets()));
mDataUsageSummary.setVisibility(View.VISIBLE);
} break;
case QUERY_APPSUMMARY_POS: {
result = mNetstatsManager.querySummary(
ConnectivityManager.TYPE_WIFI, "", mStartDate.getTime(),
mEndDate.getTime());
mListData.clear();
if (result != null) {
while (result.hasNextBucket()) {
bucket = new NetworkStats.Bucket();
result.getNextBucket(bucket);
mListData.add(Arrays.asList(new NetworkStats.Bucket[] { bucket }));
}
}
mListAdapter.notifyDataSetChanged();
mDataUsageList.setVisibility(View.VISIBLE);
} break;
case QUERY_APPHISTORY_POS: {
result = mNetstatsManager.queryDetails(
ConnectivityManager.TYPE_WIFI, "", mStartDate.getTime(),
mEndDate.getTime());
mListData.clear();
SparseArray<List<NetworkStats.Bucket>> uidMap = new SparseArray<>();
if (result != null) {
while (result.hasNextBucket()) {
bucket = new NetworkStats.Bucket();
result.getNextBucket(bucket);
final int uid = bucket.getUid();
List<NetworkStats.Bucket> list = uidMap.get(uid);
if (list == null) {
list = new ArrayList<NetworkStats.Bucket>();
mListData.add(list);
uidMap.put(uid, list);
}
list.add(bucket);
}
}
mListAdapter.notifyDataSetChanged();
mDataUsageList.setVisibility(View.VISIBLE);
} break;
}
} catch (SecurityException e) {
showErrorDialog(getString(R.string.network_stats_security_error_msg));
} catch (RemoteException | NumberFormatException e) {
showErrorDialog(e.toString());
} finally {
if (result != null) {
result.close();
}
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
// NOP
}
@Override
public void onListItemClick (ListView l, View v, int position, long id) {
List<NetworkStats.Bucket> item = mListAdapter.getItem(position);
transitionAppHistoryView(View.GONE);
if (item != null && item.size() > 1) {
transitionAppHistoryView(View.VISIBLE);
ArrayAdapter<NetworkStats.Bucket> adapter = new ArrayAdapter<NetworkStats.Bucket>(
getActivity(), android.R.layout.two_line_list_item, android.R.id.title, item) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
if (convertView == null) {
view = getActivity().getLayoutInflater().inflate(
R.layout.network_usage_app_history_item, parent, false);
}
NetworkStats.Bucket item = getItem(position);
Date startDate = new Date(item.getStartTimeStamp());
Date endDate = new Date(item.getEndTimeStamp());
TextView text1 = (TextView) view.findViewById(R.id.text1);
TextView text2 = (TextView) view.findViewById(R.id.text2);
text1.setText(getDateString(startDate, endDate));
text2.setText(getString(R.string.network_stats_bucket_usage,
formatSize(item.getRxBytes()), item.getRxPackets(),
formatSize(item.getTxBytes()), item.getTxPackets()));
return view;
}
private Spanned getDateString(Date startDate, Date endDate) {
String startDateString = mDateStringFormat.format(startDate);
String startHourMinuteString = mHourMinuteDateFormat.format(startDate);
String endHourMinuteString = mHourMinuteDateFormat.format(endDate);
String resultString = "<b>" + startDateString + "</b> " +
startHourMinuteString + " - " + endHourMinuteString;
return Html.fromHtml(resultString);
}
};
mAppHistoryList.setAdapter(adapter);
}
}
private void showErrorDialog(CharSequence message) {
new AlertDialog.Builder(getActivity())
.setTitle(R.string.network_stats_error_title)
.setIconAttribute(android.R.attr.alertDialogIcon)
.setMessage(message)
.setPositiveButton(android.R.string.ok, null)
.show();
}
private void pickDate(final Date target) {
final Calendar calendar = Calendar.getInstance();
calendar.setTime(target);
DatePickerDialog dialog = new DatePickerDialog(getActivity(),
new DatePickerDialog.OnDateSetListener() {
@Override
public void onDateSet(DatePicker view, int year, int monthOfYear,
int dayOfMonth) {
calendar.set(Calendar.YEAR, year);
calendar.set(Calendar.MONTH, monthOfYear);
calendar.set(Calendar.DAY_OF_MONTH, dayOfMonth);
target.setTime(calendar.getTimeInMillis());
updateButtonsText();
}
}, calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH),
calendar.get(Calendar.DAY_OF_MONTH));
dialog.show();
}
private void updateButtonsText() {
DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.MEDIUM);
if (mStartDateButton != null) {
mStartDateButton.setText(dateFormat.format(mStartDate));
}
if (mEndDateButton != null) {
mEndDateButton.setText(dateFormat.format(mEndDate));
}
}
private Date getTodayPlus(int calendarField, int valueToAdd) {
final Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
if (valueToAdd > 0) {
calendar.add(calendarField, valueToAdd);
}
return calendar.getTime();
}
private void bindView(View appView, List<NetworkStats.Bucket> item) {
final ViewHolder viewHolder = (ViewHolder) appView.getTag();
final TextView title = viewHolder.title;
final TextView summary = viewHolder.summary;
final TextView state = viewHolder.state;
final ImageView imageView = viewHolder.image;
final Resources res = getResources();
Drawable icon = res.getDrawable(android.R.drawable.ic_dialog_info, null);
NetworkStats.Bucket bucket = item.get(0);
final int uid = bucket.getUid();
switch (uid) {
case NetworkStats.Bucket.UID_REMOVED: {
title.setText(R.string.network_stats_uid_removed);
} break;
case NetworkStats.Bucket.UID_TETHERING: {
title.setText(R.string.network_stats_uid_tethering);
} break;
case android.os.Process.SYSTEM_UID: {
title.setText(R.string.network_stats_uid_system);
} break;
default: {
title.setText(getString(R.string.network_stats_uid, uid));
icon = mPackageManager.getDefaultActivityIcon();
final String[] packageNames = mPackageManager.getPackagesForUid(uid);
final int length = packageNames != null ? packageNames.length : 0;
try {
if (length == 1) {
final String pkgName = packageNames[0];
final ApplicationInfo info = mPackageManager.getApplicationInfo(pkgName,
0 /* no flags */);
if (info != null) {
title.setText(info.loadLabel(mPackageManager));
icon = info.loadIcon(mPackageManager);
}
} else {
for (int i = 0; i < length; i++) {
final String packageName = packageNames[i];
final PackageInfo packageInfo = mPackageManager.getPackageInfo(
packageName, 0 /* no flags */);
final ApplicationInfo appInfo = mPackageManager.getApplicationInfo(
packageName, 0 /* no flags */);
if (appInfo != null && packageInfo != null) {
if (packageInfo.sharedUserLabel != 0) {
title.setText(mPackageManager.getText(packageName,
packageInfo.sharedUserLabel,
packageInfo.applicationInfo));
icon = appInfo.loadIcon(mPackageManager);
}
}
}
}
} catch (PackageManager.NameNotFoundException e) {
// keep the default activity icon
}
}
}
imageView.setImageDrawable(icon);
final int bucketsCount = item.size();
if (bucketsCount == 1) {
summary.setText(formatSize(bucket.getRxBytes() + bucket.getTxBytes()));
switch (bucket.getState()) {
case NetworkStats.Bucket.STATE_FOREGROUND: {
state.setText(R.string.network_stats_foreground_state);
} break;
case NetworkStats.Bucket.STATE_DEFAULT: {
state.setText(R.string.network_stats_default_state);
} break;
case NetworkStats.Bucket.STATE_ALL: {
state.setText(R.string.network_stats_combined_state);
} break;
}
} else {
summary.setText(getString(R.string.network_stats_items, bucketsCount));
state.setText(R.string.network_stats_combined_state);
}
}
private void transitionAppHistoryView(int appHistoryVisibility) {
mAppHistoryList.setVisibility(appHistoryVisibility);
mBackToAppsListButton.setVisibility(appHistoryVisibility);
mDataUsageList.setVisibility(
appHistoryVisibility == View.VISIBLE ? View.GONE : View.VISIBLE);
}
private String formatSize(long sizeBytes) {
return Formatter.formatFileSize(getActivity(), sizeBytes);
}
private static class ViewHolder {
public final TextView title;
public final TextView summary;
public final TextView state;
public final ImageView image;
public ViewHolder(final TextView title, final TextView summary, final TextView state,
final ImageView image) {
this.title = title;
this.summary = summary;
this.state = state;
this.image = image;
}
}
}