diff options
38 files changed, 481 insertions, 461 deletions
diff --git a/res/drawable-hdpi/data_sweep_left_activated.9.png b/res/drawable-hdpi/data_sweep_left_activated.9.png Binary files differindex e91ccf5..28efd35 100644 --- a/res/drawable-hdpi/data_sweep_left_activated.9.png +++ b/res/drawable-hdpi/data_sweep_left_activated.9.png diff --git a/res/drawable-hdpi/data_sweep_left_default.9.png b/res/drawable-hdpi/data_sweep_left_default.9.png Binary files differindex 76f33a5..4b6f2df 100644 --- a/res/drawable-hdpi/data_sweep_left_default.9.png +++ b/res/drawable-hdpi/data_sweep_left_default.9.png diff --git a/res/drawable-hdpi/data_sweep_limit_activated.9.png b/res/drawable-hdpi/data_sweep_limit_activated.9.png Binary files differindex 4744037..59de0d3 100644 --- a/res/drawable-hdpi/data_sweep_limit_activated.9.png +++ b/res/drawable-hdpi/data_sweep_limit_activated.9.png diff --git a/res/drawable-hdpi/data_sweep_limit_default.9.png b/res/drawable-hdpi/data_sweep_limit_default.9.png Binary files differindex dea3a0b..d7f3a88 100644 --- a/res/drawable-hdpi/data_sweep_limit_default.9.png +++ b/res/drawable-hdpi/data_sweep_limit_default.9.png diff --git a/res/drawable-hdpi/data_sweep_right_activated.9.png b/res/drawable-hdpi/data_sweep_right_activated.9.png Binary files differindex afb15d6..ccd7ff9 100644 --- a/res/drawable-hdpi/data_sweep_right_activated.9.png +++ b/res/drawable-hdpi/data_sweep_right_activated.9.png diff --git a/res/drawable-hdpi/data_sweep_right_default.9.png b/res/drawable-hdpi/data_sweep_right_default.9.png Binary files differindex 9d3808f..1179fde 100644 --- a/res/drawable-hdpi/data_sweep_right_default.9.png +++ b/res/drawable-hdpi/data_sweep_right_default.9.png diff --git a/res/drawable-hdpi/data_sweep_warning_activated.9.png b/res/drawable-hdpi/data_sweep_warning_activated.9.png Binary files differindex 81a7aa8..9ecd28b 100644 --- a/res/drawable-hdpi/data_sweep_warning_activated.9.png +++ b/res/drawable-hdpi/data_sweep_warning_activated.9.png diff --git a/res/drawable-hdpi/data_sweep_warning_default.9.png b/res/drawable-hdpi/data_sweep_warning_default.9.png Binary files differindex e2485fe..ec2b9df 100644 --- a/res/drawable-hdpi/data_sweep_warning_default.9.png +++ b/res/drawable-hdpi/data_sweep_warning_default.9.png diff --git a/res/drawable-mdpi/data_sweep_left_activated.9.png b/res/drawable-mdpi/data_sweep_left_activated.9.png Binary files differindex 39111d4..fc6764a 100644 --- a/res/drawable-mdpi/data_sweep_left_activated.9.png +++ b/res/drawable-mdpi/data_sweep_left_activated.9.png diff --git a/res/drawable-mdpi/data_sweep_left_default.9.png b/res/drawable-mdpi/data_sweep_left_default.9.png Binary files differindex 139ed1e..31343e7 100644 --- a/res/drawable-mdpi/data_sweep_left_default.9.png +++ b/res/drawable-mdpi/data_sweep_left_default.9.png diff --git a/res/drawable-mdpi/data_sweep_limit_activated.9.png b/res/drawable-mdpi/data_sweep_limit_activated.9.png Binary files differindex 64d539a..b01d5f0 100644 --- a/res/drawable-mdpi/data_sweep_limit_activated.9.png +++ b/res/drawable-mdpi/data_sweep_limit_activated.9.png diff --git a/res/drawable-mdpi/data_sweep_limit_default.9.png b/res/drawable-mdpi/data_sweep_limit_default.9.png Binary files differindex db41d66..a59649a 100644 --- a/res/drawable-mdpi/data_sweep_limit_default.9.png +++ b/res/drawable-mdpi/data_sweep_limit_default.9.png diff --git a/res/drawable-mdpi/data_sweep_right_activated.9.png b/res/drawable-mdpi/data_sweep_right_activated.9.png Binary files differindex 2fba366..5314538 100644 --- a/res/drawable-mdpi/data_sweep_right_activated.9.png +++ b/res/drawable-mdpi/data_sweep_right_activated.9.png diff --git a/res/drawable-mdpi/data_sweep_right_default.9.png b/res/drawable-mdpi/data_sweep_right_default.9.png Binary files differindex 533f165..cc3b586 100644 --- a/res/drawable-mdpi/data_sweep_right_default.9.png +++ b/res/drawable-mdpi/data_sweep_right_default.9.png diff --git a/res/drawable-mdpi/data_sweep_warning_activated.9.png b/res/drawable-mdpi/data_sweep_warning_activated.9.png Binary files differindex 6813526..ed44d4d 100644 --- a/res/drawable-mdpi/data_sweep_warning_activated.9.png +++ b/res/drawable-mdpi/data_sweep_warning_activated.9.png diff --git a/res/drawable-mdpi/data_sweep_warning_default.9.png b/res/drawable-mdpi/data_sweep_warning_default.9.png Binary files differindex 65ab32f..760ea18 100644 --- a/res/drawable-mdpi/data_sweep_warning_default.9.png +++ b/res/drawable-mdpi/data_sweep_warning_default.9.png diff --git a/res/drawable-xhdpi/data_sweep_left_activated.9.png b/res/drawable-xhdpi/data_sweep_left_activated.9.png Binary files differindex 83883be..ff9631e 100644 --- a/res/drawable-xhdpi/data_sweep_left_activated.9.png +++ b/res/drawable-xhdpi/data_sweep_left_activated.9.png diff --git a/res/drawable-xhdpi/data_sweep_left_default.9.png b/res/drawable-xhdpi/data_sweep_left_default.9.png Binary files differindex 968825b..2e0651c 100644 --- a/res/drawable-xhdpi/data_sweep_left_default.9.png +++ b/res/drawable-xhdpi/data_sweep_left_default.9.png diff --git a/res/drawable-xhdpi/data_sweep_limit_activated.9.png b/res/drawable-xhdpi/data_sweep_limit_activated.9.png Binary files differindex de988ae..ae02388 100644 --- a/res/drawable-xhdpi/data_sweep_limit_activated.9.png +++ b/res/drawable-xhdpi/data_sweep_limit_activated.9.png diff --git a/res/drawable-xhdpi/data_sweep_limit_default.9.png b/res/drawable-xhdpi/data_sweep_limit_default.9.png Binary files differindex 2c9b534..7391148 100644 --- a/res/drawable-xhdpi/data_sweep_limit_default.9.png +++ b/res/drawable-xhdpi/data_sweep_limit_default.9.png diff --git a/res/drawable-xhdpi/data_sweep_right_activated.9.png b/res/drawable-xhdpi/data_sweep_right_activated.9.png Binary files differindex 25508b5..81c1a3c 100644 --- a/res/drawable-xhdpi/data_sweep_right_activated.9.png +++ b/res/drawable-xhdpi/data_sweep_right_activated.9.png diff --git a/res/drawable-xhdpi/data_sweep_right_default.9.png b/res/drawable-xhdpi/data_sweep_right_default.9.png Binary files differindex 7363cd7..94a9f61 100644 --- a/res/drawable-xhdpi/data_sweep_right_default.9.png +++ b/res/drawable-xhdpi/data_sweep_right_default.9.png diff --git a/res/drawable-xhdpi/data_sweep_warning_activated.9.png b/res/drawable-xhdpi/data_sweep_warning_activated.9.png Binary files differindex 2da464b..0a2d2b1 100644 --- a/res/drawable-xhdpi/data_sweep_warning_activated.9.png +++ b/res/drawable-xhdpi/data_sweep_warning_activated.9.png diff --git a/res/drawable-xhdpi/data_sweep_warning_default.9.png b/res/drawable-xhdpi/data_sweep_warning_default.9.png Binary files differindex c7a7b29..d5f6044 100644 --- a/res/drawable-xhdpi/data_sweep_warning_default.9.png +++ b/res/drawable-xhdpi/data_sweep_warning_default.9.png diff --git a/res/drawable/data_sweep_left.xml b/res/drawable/data_sweep_left.xml index 739a74e..cb801a0 100644 --- a/res/drawable/data_sweep_left.xml +++ b/res/drawable/data_sweep_left.xml @@ -17,6 +17,6 @@ <selector xmlns:android="http://schemas.android.com/apk/res/android" android:exitFadeDuration="@android:integer/config_mediumAnimTime"> - <item android:state_activated="true" android:drawable="@drawable/data_sweep_left_activated" /> - <item android:state_activated="false" android:drawable="@drawable/data_sweep_left_default" /> + <item android:state_activated="true" android:state_enabled="true" android:drawable="@drawable/data_sweep_left_activated" /> + <item android:drawable="@drawable/data_sweep_left_default" /> </selector> diff --git a/res/drawable/data_sweep_limit.xml b/res/drawable/data_sweep_limit.xml index 29ecec8..378b0aa 100644 --- a/res/drawable/data_sweep_limit.xml +++ b/res/drawable/data_sweep_limit.xml @@ -17,6 +17,6 @@ <selector xmlns:android="http://schemas.android.com/apk/res/android" android:exitFadeDuration="@android:integer/config_mediumAnimTime"> - <item android:state_activated="true" android:drawable="@drawable/data_sweep_limit_activated" /> - <item android:state_activated="false" android:drawable="@drawable/data_sweep_limit_default" /> + <item android:state_activated="true" android:state_enabled="true" android:drawable="@drawable/data_sweep_limit_activated" /> + <item android:drawable="@drawable/data_sweep_limit_default" /> </selector> diff --git a/res/drawable/data_sweep_right.xml b/res/drawable/data_sweep_right.xml index 1a11469..a75a1b2 100644 --- a/res/drawable/data_sweep_right.xml +++ b/res/drawable/data_sweep_right.xml @@ -17,6 +17,6 @@ <selector xmlns:android="http://schemas.android.com/apk/res/android" android:exitFadeDuration="@android:integer/config_mediumAnimTime"> - <item android:state_activated="true" android:drawable="@drawable/data_sweep_right_activated" /> - <item android:state_activated="false" android:drawable="@drawable/data_sweep_right_default" /> + <item android:state_activated="true" android:state_enabled="true" android:drawable="@drawable/data_sweep_right_activated" /> + <item android:drawable="@drawable/data_sweep_right_default" /> </selector> diff --git a/res/drawable/data_sweep_warning.xml b/res/drawable/data_sweep_warning.xml index 5cafe06..001d0c5 100644 --- a/res/drawable/data_sweep_warning.xml +++ b/res/drawable/data_sweep_warning.xml @@ -17,6 +17,6 @@ <selector xmlns:android="http://schemas.android.com/apk/res/android" android:exitFadeDuration="@android:integer/config_mediumAnimTime"> - <item android:state_activated="true" android:drawable="@drawable/data_sweep_warning_activated" /> - <item android:state_activated="false" android:drawable="@drawable/data_sweep_warning_default" /> + <item android:state_activated="true" android:state_enabled="true" android:drawable="@drawable/data_sweep_warning_activated" /> + <item android:drawable="@drawable/data_sweep_warning_default" /> </selector> diff --git a/res/layout/data_usage_chart.xml b/res/layout/data_usage_chart.xml index 5fd640f..199a38e 100644 --- a/res/layout/data_usage_chart.xml +++ b/res/layout/data_usage_chart.xml @@ -17,6 +17,7 @@ <com.android.settings.widget.DataUsageChartView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:settings="http://schemas.android.com/apk/res/com.android.settings" + android:id="@+id/chart" android:layout_width="match_parent" android:layout_height="220dip" android:padding="16dip"> @@ -40,11 +41,19 @@ settings:fillColor="#c050ade5" settings:fillColorSecondary="#88566abc" /> + <com.android.settings.widget.ChartNetworkSeriesView + android:id="@+id/detail_series" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_gravity="left|bottom" + settings:strokeColor="#d88d3a" + settings:fillColor="#c0ba7f3e" + settings:fillColorSecondary="#0000" /> + <com.android.settings.widget.ChartSweepView android:id="@+id/sweep_left" android:layout_width="wrap_content" android:layout_height="match_parent" - android:layout_gravity="center_horizontal" settings:sweepDrawable="@drawable/data_sweep_left" settings:followAxis="horizontal" settings:showLabel="false" /> @@ -53,7 +62,6 @@ android:id="@+id/sweep_right" android:layout_width="wrap_content" android:layout_height="match_parent" - android:layout_gravity="center_horizontal" settings:sweepDrawable="@drawable/data_sweep_right" settings:followAxis="horizontal" settings:showLabel="false" /> @@ -62,7 +70,6 @@ android:id="@+id/sweep_limit" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_gravity="center_vertical" settings:sweepDrawable="@drawable/data_sweep_limit" settings:followAxis="vertical" settings:showLabel="true" /> @@ -71,7 +78,6 @@ android:id="@+id/sweep_warning" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_gravity="center_vertical" settings:sweepDrawable="@drawable/data_sweep_warning" settings:followAxis="vertical" settings:showLabel="true" /> diff --git a/res/layout/data_usage_detail.xml b/res/layout/data_usage_detail.xml index 9415b3f..2c8e490 100644 --- a/res/layout/data_usage_detail.xml +++ b/res/layout/data_usage_detail.xml @@ -14,43 +14,35 @@ limitations under the License. --> -<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/app_detail" android:layout_width="match_parent" - android:layout_height="match_parent"> + android:layout_height="match_parent" + android:orientation="vertical"> + + <TextView + android:id="@+id/app_title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="16dip" /> + + <TextView + android:id="@+id/app_subtitle" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="16dip" /> + + <Button + android:id="@+id/app_settings" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="16dip" + android:text="@string/data_usage_app_settings" /> + <LinearLayout + android:id="@+id/app_switches" android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical"> - - <FrameLayout - android:id="@+id/chart_container" - android:layout_width="match_parent" - android:layout_height="233dip" /> - - <TextView - android:id="@android:id/title" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginLeft="16dip" /> - - <TextView - android:id="@android:id/text1" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginLeft="16dip" /> - - <Button - android:id="@+id/data_usage_app_settings" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_margin="16dip" - android:text="@string/data_usage_app_settings" /> - - <LinearLayout - android:id="@+id/switches" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" /> - - </LinearLayout> -</ScrollView> + android:layout_height="wrap_content" + android:orientation="vertical" /> + +</LinearLayout> diff --git a/res/layout/data_usage_header.xml b/res/layout/data_usage_header.xml index 8e6f054..3f4ca5b 100644 --- a/res/layout/data_usage_header.xml +++ b/res/layout/data_usage_header.xml @@ -20,7 +20,7 @@ android:orientation="vertical"> <LinearLayout - android:id="@+id/switches" + android:id="@+id/network_switches" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" @@ -48,4 +48,7 @@ </LinearLayout> + <include layout="@layout/data_usage_chart" /> + <include layout="@layout/data_usage_detail" /> + </LinearLayout> diff --git a/src/com/android/settings/DataUsageAppDetail.java b/src/com/android/settings/DataUsageAppDetail.java deleted file mode 100644 index 6294ad3..0000000 --- a/src/com/android/settings/DataUsageAppDetail.java +++ /dev/null @@ -1,309 +0,0 @@ -/* - * Copyright (C) 2011 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.settings; - -import static android.net.NetworkPolicyManager.POLICY_NONE; -import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND; -import static com.android.settings.DataUsageSummary.getHistoryBounds; - -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.DialogFragment; -import android.app.Fragment; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.graphics.Color; -import android.net.INetworkPolicyManager; -import android.net.INetworkStatsService; -import android.net.NetworkPolicyManager; -import android.net.NetworkStats; -import android.net.NetworkStatsHistory; -import android.net.NetworkTemplate; -import android.os.Bundle; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.preference.CheckBoxPreference; -import android.preference.Preference; -import android.text.format.DateUtils; -import android.text.format.Formatter; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.FrameLayout; -import android.widget.LinearLayout; -import android.widget.TextView; - -import com.android.settings.widget.DataUsageChartView; -import com.android.settings.widget.DataUsageChartView.DataUsageChartListener; - -public class DataUsageAppDetail extends Fragment { - private static final String TAG = "DataUsage"; - private static final boolean LOGD = true; - - public static final String EXTRA_UID = "uid"; - public static final String EXTRA_NETWORK_TEMPLATE = "networkTemplate"; - - private int mUid; - private NetworkTemplate mTemplate; - - private Intent mAppSettingsIntent; - - private static final String TAG_CONFIRM_RESTRICT = "confirmRestrict"; - - private INetworkStatsService mStatsService; - private INetworkPolicyManager mPolicyService; - - private CheckBoxPreference mRestrictBackground; - private View mRestrictBackgroundView; - - private FrameLayout mChartContainer; - private TextView mTitle; - private TextView mText1; - private Button mAppSettings; - private LinearLayout mSwitches; - - private DataUsageChartView mChart; - private NetworkStatsHistory mHistory; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - mStatsService = INetworkStatsService.Stub.asInterface( - ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); - mPolicyService = INetworkPolicyManager.Stub.asInterface( - ServiceManager.getService(Context.NETWORK_POLICY_SERVICE)); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - - final Context context = inflater.getContext(); - final View view = inflater.inflate(R.layout.data_usage_detail, container, false); - - mChartContainer = (FrameLayout) view.findViewById(R.id.chart_container); - mTitle = (TextView) view.findViewById(android.R.id.title); - mText1 = (TextView) view.findViewById(android.R.id.text1); - mAppSettings = (Button) view.findViewById(R.id.data_usage_app_settings); - mSwitches = (LinearLayout) view.findViewById(R.id.switches); - - mRestrictBackground = new CheckBoxPreference(context); - mRestrictBackground.setTitle(R.string.data_usage_app_restrict_background); - mRestrictBackground.setSummary(R.string.data_usage_app_restrict_background_summary); - - // kick refresh once to force-create views - refreshPreferenceViews(); - - mSwitches.addView(mRestrictBackgroundView); - mRestrictBackgroundView.setOnClickListener(mRestrictBackgroundListener); - - mAppSettings.setOnClickListener(mAppSettingsListener); - - mChart = new DataUsageChartView(context); - mChartContainer.addView(mChart); - - mChart.setListener(mChartListener); - mChart.setChartColor(Color.parseColor("#d88d3a"), Color.parseColor("#c0ba7f3e"), - Color.parseColor("#88566abc")); - - return view; - } - - @Override - public void onResume() { - super.onResume(); - - updateBody(); - } - - private void updateBody() { - final PackageManager pm = getActivity().getPackageManager(); - - mUid = getArguments().getInt(EXTRA_UID); - mTemplate = getArguments().getParcelable(EXTRA_NETWORK_TEMPLATE); - - mTitle.setText(pm.getNameForUid(mUid)); - - // enable settings button when package provides it - // TODO: target torwards entire UID instead of just first package - final String[] packageNames = pm.getPackagesForUid(mUid); - if (packageNames != null && packageNames.length > 0) { - mAppSettingsIntent = new Intent(Intent.ACTION_MANAGE_NETWORK_USAGE); - mAppSettingsIntent.setPackage(packageNames[0]); - mAppSettingsIntent.addCategory(Intent.CATEGORY_DEFAULT); - - final boolean matchFound = pm.resolveActivity(mAppSettingsIntent, 0) != null; - mAppSettings.setEnabled(matchFound); - - } else { - mAppSettingsIntent = null; - mAppSettings.setEnabled(false); - } - - try { - // load stats for current uid and template - // TODO: read template from extras - mHistory = mStatsService.getHistoryForUid(mTemplate, mUid, NetworkStats.TAG_NONE); - } catch (RemoteException e) { - // since we can't do much without history, and we don't want to - // leave with half-baked UI, we bail hard. - throw new RuntimeException("problem reading network stats", e); - } - - // bind chart to historical stats - mChart.bindNetworkStats(mHistory); - - // show entire history known - final long[] bounds = getHistoryBounds(mHistory); - mChart.setVisibleRange(bounds[0], bounds[1] + DateUtils.WEEK_IN_MILLIS, bounds[1]); - updateDetailData(); - - final Context context = getActivity(); - if (NetworkPolicyManager.isUidValidForPolicy(context, mUid)) { - mRestrictBackgroundView.setVisibility(View.VISIBLE); - - final int uidPolicy; - try { - uidPolicy = mPolicyService.getUidPolicy(mUid); - } catch (RemoteException e) { - // since we can't do much without policy, we bail hard. - throw new RuntimeException("problem reading network policy", e); - } - - // update policy checkbox - final boolean restrictBackground = (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0; - mRestrictBackground.setChecked(restrictBackground); - - // kick preference views so they rebind from changes above - refreshPreferenceViews(); - - } else { - mRestrictBackgroundView.setVisibility(View.GONE); - } - } - - private void updateDetailData() { - if (LOGD) Log.d(TAG, "updateDetailData()"); - - final Context context = mChart.getContext(); - final long[] range = mChart.getInspectRange(); - final long[] total = mHistory.getTotalData(range[0], range[1], null); - final long totalCombined = total[0] + total[1]; - mText1.setText(Formatter.formatFileSize(context, totalCombined)); - } - - private void setRestrictBackground(boolean restrictBackground) { - if (LOGD) Log.d(TAG, "setRestrictBackground()"); - try { - mPolicyService.setUidPolicy( - mUid, restrictBackground ? POLICY_REJECT_METERED_BACKGROUND : POLICY_NONE); - } catch (RemoteException e) { - throw new RuntimeException("unable to save policy", e); - } - - mRestrictBackground.setChecked(restrictBackground); - refreshPreferenceViews(); - } - - /** - * Force rebind of hijacked {@link Preference} views. - */ - private void refreshPreferenceViews() { - mRestrictBackgroundView = mRestrictBackground.getView(mRestrictBackgroundView, mSwitches); - } - - private DataUsageChartListener mChartListener = new DataUsageChartListener() { - /** {@inheritDoc} */ - public void onInspectRangeChanged() { - if (LOGD) Log.d(TAG, "onInspectRangeChanged()"); - updateDetailData(); - } - - /** {@inheritDoc} */ - public void onWarningChanged() { - // ignored - } - - /** {@inheritDoc} */ - public void onLimitChanged() { - // ignored - } - }; - - private OnClickListener mAppSettingsListener = new OnClickListener() { - /** {@inheritDoc} */ - public void onClick(View v) { - // TODO: target torwards entire UID instead of just first package - startActivity(mAppSettingsIntent); - } - }; - - private OnClickListener mRestrictBackgroundListener = new OnClickListener() { - /** {@inheritDoc} */ - public void onClick(View v) { - final boolean restrictBackground = !mRestrictBackground.isChecked(); - - if (restrictBackground) { - // enabling restriction; show confirmation dialog which - // eventually calls setRestrictBackground() once user confirms. - ConfirmRestrictFragment.show(DataUsageAppDetail.this); - } else { - setRestrictBackground(false); - } - } - }; - - /** - * Dialog to request user confirmation before setting - * {@link #POLICY_REJECT_METERED_BACKGROUND}. - */ - public static class ConfirmRestrictFragment extends DialogFragment { - public static void show(DataUsageAppDetail parent) { - final ConfirmRestrictFragment dialog = new ConfirmRestrictFragment(); - dialog.setTargetFragment(parent, 0); - dialog.show(parent.getFragmentManager(), TAG_CONFIRM_RESTRICT); - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - final Context context = getActivity(); - - final AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setTitle(R.string.data_usage_app_restrict_dialog_title); - builder.setMessage(R.string.data_usage_app_restrict_dialog); - - builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - final DataUsageAppDetail target = (DataUsageAppDetail) getTargetFragment(); - if (target != null) { - target.setRestrictBackground(true); - } - } - }); - builder.setNegativeButton(android.R.string.cancel, null); - - return builder.create(); - } - } - -} diff --git a/src/com/android/settings/DataUsageSummary.java b/src/com/android/settings/DataUsageSummary.java index 692c753..098f57a 100644 --- a/src/com/android/settings/DataUsageSummary.java +++ b/src/com/android/settings/DataUsageSummary.java @@ -21,6 +21,8 @@ import static android.net.ConnectivityManager.TYPE_WIMAX; import static android.net.NetworkPolicy.LIMIT_DISABLED; import static android.net.NetworkPolicyManager.ACTION_DATA_USAGE_LIMIT; import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE; +import static android.net.NetworkPolicyManager.POLICY_NONE; +import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND; import static android.net.NetworkPolicyManager.computeLastCycleBoundary; import static android.net.NetworkPolicyManager.computeNextCycleBoundary; import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER; @@ -29,10 +31,12 @@ import static android.net.NetworkTemplate.MATCH_MOBILE_ALL; import static android.net.NetworkTemplate.MATCH_WIFI; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; +import android.animation.LayoutTransition; import android.app.AlertDialog; import android.app.Dialog; import android.app.DialogFragment; import android.app.Fragment; +import android.app.FragmentTransaction; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -54,7 +58,6 @@ import android.os.Bundle; import android.os.RemoteException; import android.os.ServiceManager; import android.preference.Preference; -import android.preference.PreferenceActivity; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.text.format.DateUtils; @@ -66,12 +69,14 @@ import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; +import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemSelectedListener; import android.widget.ArrayAdapter; import android.widget.BaseAdapter; +import android.widget.Button; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; @@ -106,8 +111,6 @@ public class DataUsageSummary extends Fragment { private static final String TAG = "DataUsage"; private static final boolean LOGD = true; - private static final int TEMPLATE_INVALID = -1; - private static final String TAB_3G = "3g"; private static final String TAB_4G = "4g"; private static final String TAB_MOBILE = "mobile"; @@ -116,6 +119,8 @@ public class DataUsageSummary extends Fragment { private static final String TAG_CONFIRM_LIMIT = "confirmLimit"; private static final String TAG_CYCLE_EDITOR = "cycleEditor"; private static final String TAG_POLICY_LIMIT = "policyLimit"; + private static final String TAG_CONFIRM_RESTRICT = "confirmRestrict"; + private static final String TAG_APP_DETAILS = "appDetails"; private static final long KB_IN_BYTES = 1024; private static final long MB_IN_BYTES = KB_IN_BYTES * 1024; @@ -135,25 +140,41 @@ public class DataUsageSummary extends Fragment { private ListView mListView; private DataUsageAdapter mAdapter; - private View mHeader; - private LinearLayout mSwitches; + private ViewGroup mHeader; + private LinearLayout mNetworkSwitches; private Switch mDataEnabled; - private CheckBox mDisableAtLimit; private View mDataEnabledView; + private CheckBox mDisableAtLimit; private View mDisableAtLimitView; - private DataUsageChartView mChart; - private Spinner mCycleSpinner; private CycleAdapter mCycleAdapter; + private DataUsageChartView mChart; + + private View mAppDetail; + private TextView mAppTitle; + private TextView mAppSubtitle; + private Button mAppSettings; + + private LinearLayout mAppSwitches; + private CheckBox mAppRestrict; + private View mAppRestrictView; + private boolean mShowWifi = false; private NetworkTemplate mTemplate = null; + private static final int UID_NONE = -1; + private int mUid = UID_NONE; + + private Intent mAppSettingsIntent; + private NetworkPolicyEditor mPolicyEditor; + private NetworkStatsHistory mHistory; + private NetworkStatsHistory mDetailHistory; private String mIntentTab = null; @@ -194,30 +215,57 @@ public class DataUsageSummary extends Fragment { mTabHost.setup(); mTabHost.setOnTabChangedListener(mTabListener); - mHeader = inflater.inflate(R.layout.data_usage_header, mListView, false); + mHeader = (ViewGroup) inflater.inflate(R.layout.data_usage_header, mListView, false); mListView.addHeaderView(mHeader, null, false); - mDataEnabled = new Switch(inflater.getContext()); - mDataEnabledView = inflatePreference(inflater, mSwitches, mDataEnabled); - mDataEnabled.setOnCheckedChangeListener(mDataEnabledListener); + { + // bind network switches + mNetworkSwitches = (LinearLayout) mHeader.findViewById(R.id.network_switches); - mDisableAtLimit = new CheckBox(inflater.getContext()); - mDisableAtLimit.setClickable(false); - mDisableAtLimitView = inflatePreference(inflater, mSwitches, mDisableAtLimit); - mDisableAtLimitView.setOnClickListener(mDisableAtLimitListener); + mDataEnabled = new Switch(inflater.getContext()); + mDataEnabledView = inflatePreference(inflater, mNetworkSwitches, mDataEnabled); + mDataEnabled.setOnCheckedChangeListener(mDataEnabledListener); + mNetworkSwitches.addView(mDataEnabledView); - mSwitches = (LinearLayout) mHeader.findViewById(R.id.switches); - mSwitches.addView(mDataEnabledView); - mSwitches.addView(mDisableAtLimitView); + mDisableAtLimit = new CheckBox(inflater.getContext()); + mDisableAtLimit.setClickable(false); + mDisableAtLimitView = inflatePreference(inflater, mNetworkSwitches, mDisableAtLimit); + mDisableAtLimitView.setOnClickListener(mDisableAtLimitListener); + mNetworkSwitches.addView(mDisableAtLimitView); + } + // bind cycle dropdown mCycleSpinner = (Spinner) mHeader.findViewById(R.id.cycles); mCycleAdapter = new CycleAdapter(context); mCycleSpinner.setAdapter(mCycleAdapter); mCycleSpinner.setOnItemSelectedListener(mCycleListener); - mChart = (DataUsageChartView) inflater.inflate(R.layout.data_usage_chart, mListView, false); + mChart = (DataUsageChartView) mHeader.findViewById(R.id.chart); mChart.setListener(mChartListener); - mListView.addHeaderView(mChart, null, false); + + { + // bind app detail controls + mAppDetail = view.findViewById(R.id.app_detail); + mAppTitle = (TextView) view.findViewById(R.id.app_title); + mAppSubtitle = (TextView) view.findViewById(R.id.app_subtitle); + mAppSwitches = (LinearLayout) view.findViewById(R.id.app_switches); + + mAppSettings = (Button) view.findViewById(R.id.app_settings); + mAppSettings.setOnClickListener(mAppSettingsListener); + + mAppRestrict = new CheckBox(inflater.getContext()); + mAppRestrict.setClickable(false); + mAppRestrictView = inflatePreference(inflater, mAppSwitches, mAppRestrict); + setPreferenceTitle(mAppRestrictView, R.string.data_usage_app_restrict_background); + setPreferenceSummary( + mAppRestrictView, R.string.data_usage_app_restrict_background_summary); + mAppRestrictView.setOnClickListener(mAppRestrictListener); + mAppSwitches.addView(mAppRestrictView); + } + + // TODO: tweak these transitions + final LayoutTransition transition = new LayoutTransition(); + mHeader.setLayoutTransition(transition); mAdapter = new DataUsageAdapter(); mListView.setOnItemClickListener(mListListener); @@ -433,6 +481,7 @@ public class DataUsageSummary extends Fragment { mChart.bindNetworkStats(mHistory); updatePolicy(true); + updateAppDetail(); // force scroll to top of body mListView.smoothScrollToPosition(0); @@ -440,6 +489,84 @@ public class DataUsageSummary extends Fragment { mBinding = false; } + private boolean isAppDetailMode() { + return mUid != UID_NONE; + } + + /** + * Update UID details panels to match {@link #mUid}, showing or hiding them + * depending on {@link #isAppDetailMode()}. + */ + private void updateAppDetail() { + if (isAppDetailMode()) { + mAppDetail.setVisibility(View.VISIBLE); + } else { + mAppDetail.setVisibility(View.GONE); + + // hide detail stats when not in detail mode + mChart.bindDetailNetworkStats(null); + return; + } + + // remove warning/limit sweeps while in detail mode + mChart.bindNetworkPolicy(null); + + final PackageManager pm = getActivity().getPackageManager(); + mAppTitle.setText(pm.getNameForUid(mUid)); + + // enable settings button when package provides it + // TODO: target torwards entire UID instead of just first package + final String[] packageNames = pm.getPackagesForUid(mUid); + if (packageNames != null && packageNames.length > 0) { + mAppSettingsIntent = new Intent(Intent.ACTION_MANAGE_NETWORK_USAGE); + mAppSettingsIntent.setPackage(packageNames[0]); + mAppSettingsIntent.addCategory(Intent.CATEGORY_DEFAULT); + + final boolean matchFound = pm.resolveActivity(mAppSettingsIntent, 0) != null; + mAppSettings.setEnabled(matchFound); + + } else { + mAppSettingsIntent = null; + mAppSettings.setEnabled(false); + } + + try { + // load stats for current uid and template + // TODO: read template from extras + mDetailHistory = mStatsService.getHistoryForUid(mTemplate, mUid, NetworkStats.TAG_NONE); + } catch (RemoteException e) { + // since we can't do much without history, and we don't want to + // leave with half-baked UI, we bail hard. + throw new RuntimeException("problem reading network stats", e); + } + + // bind chart to historical stats + mChart.bindDetailNetworkStats(mDetailHistory); + + updateDetailData(); + + final Context context = getActivity(); + if (NetworkPolicyManager.isUidValidForPolicy(context, mUid)) { + mAppRestrictView.setVisibility(View.VISIBLE); + + final int uidPolicy; + try { + uidPolicy = mPolicyService.getUidPolicy(mUid); + } catch (RemoteException e) { + // since we can't do much without policy, we bail hard. + throw new RuntimeException("problem reading network policy", e); + } + + // update policy checkbox + final boolean restrictBackground = (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0; + mAppRestrict.setChecked(restrictBackground); + + } else { + mAppRestrictView.setVisibility(View.GONE); + } + + } + private void setPolicyCycleDay(int cycleDay) { if (LOGD) Log.d(TAG, "setPolicyCycleDay()"); mPolicyEditor.setPolicyCycleDay(mTemplate, cycleDay); @@ -458,11 +585,30 @@ public class DataUsageSummary extends Fragment { updatePolicy(false); } + private void setAppRestrictBackground(boolean restrictBackground) { + if (LOGD) Log.d(TAG, "setRestrictBackground()"); + try { + mPolicyService.setUidPolicy( + mUid, restrictBackground ? POLICY_REJECT_METERED_BACKGROUND : POLICY_NONE); + } catch (RemoteException e) { + throw new RuntimeException("unable to save policy", e); + } + + mAppRestrict.setChecked(restrictBackground); + } + /** * Update chart sweeps and cycle list to reflect {@link NetworkPolicy} for * current {@link #mTemplate}. */ private void updatePolicy(boolean refreshCycle) { + if (isAppDetailMode()) { + mNetworkSwitches.setVisibility(View.GONE); + // we fall through to update cycle list for detail mode + } else { + mNetworkSwitches.setVisibility(View.VISIBLE); + } + final NetworkPolicy policy = mPolicyEditor.getPolicy(mTemplate); // reflect policy limit in checkbox @@ -572,18 +718,34 @@ public class DataUsageSummary extends Fragment { } }; + private View.OnClickListener mAppRestrictListener = new View.OnClickListener() { + /** {@inheritDoc} */ + public void onClick(View v) { + final boolean restrictBackground = !mAppRestrict.isChecked(); + + if (restrictBackground) { + // enabling restriction; show confirmation dialog which + // eventually calls setRestrictBackground() once user confirms. + ConfirmRestrictFragment.show(DataUsageSummary.this); + } else { + setAppRestrictBackground(false); + } + } + }; + + private OnClickListener mAppSettingsListener = new OnClickListener() { + /** {@inheritDoc} */ + public void onClick(View v) { + // TODO: target torwards entire UID instead of just first package + startActivity(mAppSettingsIntent); + } + }; + private OnItemClickListener mListListener = new OnItemClickListener() { /** {@inheritDoc} */ public void onItemClick(AdapterView<?> parent, View view, int position, long id) { final AppUsageItem app = (AppUsageItem) parent.getItemAtPosition(position); - - final Bundle args = new Bundle(); - args.putParcelable(DataUsageAppDetail.EXTRA_NETWORK_TEMPLATE, mTemplate); - args.putInt(DataUsageAppDetail.EXTRA_UID, app.uid); - - final PreferenceActivity activity = (PreferenceActivity) getActivity(); - activity.startPreferencePanel(DataUsageAppDetail.class.getName(), args, - R.string.data_usage_summary_title, null, null, 0); + AppDetailsFragment.show(DataUsageSummary.this, app.uid); } }; @@ -621,12 +783,30 @@ public class DataUsageSummary extends Fragment { }; /** - * Update {@link #mAdapter} with sorted list of applications data usage, - * based on current inspection from {@link #mChart}. + * Update details based on {@link #mChart} inspection range depending on + * current mode. In network mode, updates {@link #mAdapter} with sorted list + * of applications data usage, and when {@link #isAppDetailMode()} update + * app details. */ private void updateDetailData() { if (LOGD) Log.d(TAG, "updateDetailData()"); + if (isAppDetailMode()) { + if (mDetailHistory != null) { + final Context context = mChart.getContext(); + final long[] range = mChart.getInspectRange(); + final long[] total = mDetailHistory.getTotalData(range[0], range[1], null); + final long totalCombined = total[0] + total[1]; + mAppSubtitle.setText(Formatter.formatFileSize(context, totalCombined)); + } + + // clear any existing app list details + mAdapter.bindStats(null); + + return; + } + + // otherwise kick off task to update list new AsyncTask<Void, Void, NetworkStats>() { @Override protected NetworkStats doInBackground(Void... params) { @@ -753,15 +933,20 @@ public class DataUsageSummary extends Fragment { public static class DataUsageAdapter extends BaseAdapter { private ArrayList<AppUsageItem> mItems = Lists.newArrayList(); + /** + * Bind the given {@link NetworkStats}, or {@code null} to clear list. + */ public void bindStats(NetworkStats stats) { mItems.clear(); - for (int i = 0; i < stats.size; i++) { - final long total = stats.rx[i] + stats.tx[i]; - final AppUsageItem item = new AppUsageItem(); - item.uid = stats.uid[i]; - item.total = total; - mItems.add(item); + if (stats != null) { + for (int i = 0; i < stats.size; i++) { + final long total = stats.rx[i] + stats.tx[i]; + final AppUsageItem item = new AppUsageItem(); + item.uid = stats.uid[i]; + item.total = total; + mItems.add(item); + } } Collections.sort(mItems); @@ -806,6 +991,44 @@ public class DataUsageSummary extends Fragment { } /** + * Empty {@link Fragment} that controls display of UID details in + * {@link DataUsageSummary}. + */ + public static class AppDetailsFragment extends Fragment { + public static final String EXTRA_UID = "uid"; + + public static void show(DataUsageSummary parent, int uid) { + final Bundle args = new Bundle(); + args.putInt(EXTRA_UID, uid); + + final AppDetailsFragment fragment = new AppDetailsFragment(); + fragment.setArguments(args); + fragment.setTargetFragment(parent, 0); + + final FragmentTransaction ft = parent.getFragmentManager().beginTransaction(); + ft.add(fragment, TAG_APP_DETAILS); + ft.addToBackStack(TAG_APP_DETAILS); + ft.commit(); + } + + @Override + public void onStart() { + super.onStart(); + final DataUsageSummary target = (DataUsageSummary) getTargetFragment(); + target.mUid = getArguments().getInt(EXTRA_UID); + target.updateBody(); + } + + @Override + public void onStop() { + super.onStop(); + final DataUsageSummary target = (DataUsageSummary) getTargetFragment(); + target.mUid = UID_NONE; + target.updateBody(); + } + } + + /** * Dialog to request user confirmation before setting * {@link NetworkPolicy#limitBytes}. */ @@ -976,11 +1199,44 @@ public class DataUsageSummary extends Fragment { } /** + * Dialog to request user confirmation before setting + * {@link #POLICY_REJECT_METERED_BACKGROUND}. + */ + public static class ConfirmRestrictFragment extends DialogFragment { + public static void show(DataUsageSummary parent) { + final ConfirmRestrictFragment dialog = new ConfirmRestrictFragment(); + dialog.setTargetFragment(parent, 0); + dialog.show(parent.getFragmentManager(), TAG_CONFIRM_RESTRICT); + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final Context context = getActivity(); + + final AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(R.string.data_usage_app_restrict_dialog_title); + builder.setMessage(R.string.data_usage_app_restrict_dialog); + + builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + final DataUsageSummary target = (DataUsageSummary) getTargetFragment(); + if (target != null) { + target.setAppRestrictBackground(true); + } + } + }); + builder.setNegativeButton(android.R.string.cancel, null); + + return builder.create(); + } + } + + /** * Compute default tab that should be selected, based on * {@link NetworkPolicyManager#EXTRA_NETWORK_TEMPLATE} extra. */ private static String computeTabFromIntent(Intent intent) { - final int networkTemplate = intent.getIntExtra(EXTRA_NETWORK_TEMPLATE, TEMPLATE_INVALID); + final int networkTemplate = intent.getIntExtra(EXTRA_NETWORK_TEMPLATE, MATCH_MOBILE_ALL); switch (networkTemplate) { case MATCH_MOBILE_3G_LOWER: return TAB_3G; @@ -1083,4 +1339,14 @@ public class DataUsageSummary extends Fragment { title.setText(resId); } + /** + * Set {@link android.R.id#summary} for a preference view inflated with + * {@link #inflatePreference(LayoutInflater, ViewGroup, View)}. + */ + private static void setPreferenceSummary(View parent, int resId) { + final TextView summary = (TextView) parent.findViewById(android.R.id.summary); + summary.setVisibility(View.VISIBLE); + summary.setText(resId); + } + } diff --git a/src/com/android/settings/net/NetworkPolicyEditor.java b/src/com/android/settings/net/NetworkPolicyEditor.java index c50a490..61c2550 100644 --- a/src/com/android/settings/net/NetworkPolicyEditor.java +++ b/src/com/android/settings/net/NetworkPolicyEditor.java @@ -16,6 +16,8 @@ package com.android.settings.net; +import static android.net.NetworkPolicy.LIMIT_DISABLED; +import static android.net.NetworkPolicy.WARNING_DISABLED; import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER; import static android.net.NetworkTemplate.MATCH_MOBILE_4G; import static android.net.NetworkTemplate.MATCH_MOBILE_ALL; @@ -51,6 +53,14 @@ public class NetworkPolicyEditor { final NetworkPolicy[] policies = mPolicyService.getNetworkPolicies(); mPolicies.clear(); for (NetworkPolicy policy : policies) { + // TODO: find better place to clamp these + if (policy.limitBytes < -1) { + policy.limitBytes = LIMIT_DISABLED; + } + if (policy.warningBytes < -1) { + policy.warningBytes = WARNING_DISABLED; + } + mPolicies.add(policy); } } catch (RemoteException e) { diff --git a/src/com/android/settings/widget/ChartNetworkSeriesView.java b/src/com/android/settings/widget/ChartNetworkSeriesView.java index 0a34565..83c10cd 100644 --- a/src/com/android/settings/widget/ChartNetworkSeriesView.java +++ b/src/com/android/settings/widget/ChartNetworkSeriesView.java @@ -75,6 +75,7 @@ public class ChartNetworkSeriesView extends View { R.styleable.ChartNetworkSeriesView_fillColorSecondary, Color.RED); setChartColor(stroke, fill, fillSecondary); + setWillNotDraw(false); a.recycle(); @@ -110,8 +111,13 @@ public class ChartNetworkSeriesView extends View { mPathStroke.reset(); mPathFill.reset(); + invalidate(); } + /** + * Set the range to paint with {@link #mPaintFill}, leaving the remaining + * area to be painted with {@link #mPaintFillSecondary}. + */ public void setPrimaryRange(long left, long right) { mPrimaryLeft = left; mPrimaryRight = right; @@ -190,18 +196,21 @@ public class ChartNetworkSeriesView extends View { protected void onDraw(Canvas canvas) { int save; + final float primaryLeftPoint = mHoriz.convertToPoint(mPrimaryLeft); + final float primaryRightPoint = mHoriz.convertToPoint(mPrimaryRight); + save = canvas.save(); - canvas.clipRect(0, 0, mPrimaryLeft, getHeight()); + canvas.clipRect(0, 0, primaryLeftPoint, getHeight()); canvas.drawPath(mPathFill, mPaintFillSecondary); canvas.restoreToCount(save); save = canvas.save(); - canvas.clipRect(mPrimaryRight, 0, getWidth(), getHeight()); + canvas.clipRect(primaryRightPoint, 0, getWidth(), getHeight()); canvas.drawPath(mPathFill, mPaintFillSecondary); canvas.restoreToCount(save); save = canvas.save(); - canvas.clipRect(mPrimaryLeft, 0, mPrimaryRight, getHeight()); + canvas.clipRect(primaryLeftPoint, 0, primaryRightPoint, getHeight()); canvas.drawPath(mPathFill, mPaintFill); canvas.drawPath(mPathStroke, mPaintStroke); canvas.restoreToCount(save); diff --git a/src/com/android/settings/widget/ChartSweepView.java b/src/com/android/settings/widget/ChartSweepView.java index 881fde4..d8344d5 100644 --- a/src/com/android/settings/widget/ChartSweepView.java +++ b/src/com/android/settings/widget/ChartSweepView.java @@ -39,6 +39,8 @@ public class ChartSweepView extends FrameLayout { // TODO: paint label when requested private Drawable mSweep; + private Rect mSweepMargins = new Rect(); + private int mFollowAxis; private boolean mShowLabel; @@ -88,8 +90,28 @@ public class ChartSweepView extends FrameLayout { return mFollowAxis; } - public void getExtraMargins(Rect rect) { - mSweep.getPadding(rect); + /** + * Return margins of {@link #setSweepDrawable(Drawable)}, indicating how the + * sweep should be displayed around a content region. + */ + public Rect getSweepMargins() { + return mSweepMargins; + } + + /** + * Return the number of pixels that the "target" area is inset from the + * {@link View} edge, along the current {@link #setFollowAxis(int)}. + */ + public float getTargetInset() { + if (mFollowAxis == VERTICAL) { + final float targetHeight = mSweep.getIntrinsicHeight() - mSweepMargins.top + - mSweepMargins.bottom; + return mSweepMargins.top + (targetHeight / 2); + } else { + final float targetWidth = mSweep.getIntrinsicWidth() - mSweepMargins.left + - mSweepMargins.right; + return mSweepMargins.left + (targetWidth / 2); + } } public void addOnSweepListener(OnSweepListener listener) { @@ -115,6 +137,7 @@ public class ChartSweepView extends FrameLayout { } sweep.setVisible(getVisibility() == VISIBLE, false); mSweep = sweep; + sweep.getPadding(mSweepMargins); } else { mSweep = null; } @@ -175,33 +198,51 @@ public class ChartSweepView extends FrameLayout { final View parent = (View) getParent(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { - mTracking = event.copy(); - return true; + + // only start tracking when in sweet spot + final boolean accept; + if (mFollowAxis == VERTICAL) { + accept = event.getX() > getWidth() - (mSweepMargins.right * 2); + } else { + accept = event.getY() > getHeight() - (mSweepMargins.bottom * 2); + } + + if (accept) { + mTracking = event.copy(); + return true; + } else { + return false; + } } case MotionEvent.ACTION_MOVE: { getParent().requestDisallowInterceptTouchEvent(true); + final Rect sweepMargins = mSweepMargins; + + // content area of parent + final Rect parentContent = new Rect(parent.getPaddingLeft(), parent.getPaddingTop(), + parent.getWidth() - parent.getPaddingRight(), + parent.getHeight() - parent.getPaddingBottom()); + if (mFollowAxis == VERTICAL) { - final float chartHeight = parent.getHeight() - parent.getPaddingTop() - - parent.getPaddingBottom(); - final float translationY = MathUtils.constrain( - event.getRawY() - mTracking.getRawY(), -getTop(), - chartHeight - getTop()); - setTranslationY(translationY); - final float point = (getTop() + getTranslationY() + (getHeight() / 2)) - - parent.getPaddingTop(); - mValue = mAxis.convertToValue(point); + final float currentTargetY = getTop() + getTargetInset(); + final float requestedTargetY = currentTargetY + + (event.getRawY() - mTracking.getRawY()); + final float clampedTargetY = MathUtils.constrain( + requestedTargetY, parentContent.top, parentContent.bottom); + setTranslationY(clampedTargetY - currentTargetY); + + mValue = mAxis.convertToValue(clampedTargetY - parentContent.top); dispatchOnSweep(false); } else { - final float chartWidth = parent.getWidth() - parent.getPaddingLeft() - - parent.getPaddingRight(); - final float translationX = MathUtils.constrain( - event.getRawX() - mTracking.getRawX(), -getLeft(), - chartWidth - getLeft()); - setTranslationX(translationX); - final float point = (getLeft() + getTranslationX() + (getWidth() / 2)) - - parent.getPaddingLeft(); - mValue = mAxis.convertToValue(point); + final float currentTargetX = getLeft() + getTargetInset(); + final float requestedTargetX = currentTargetX + + (event.getRawX() - mTracking.getRawX()); + final float clampedTargetX = MathUtils.constrain( + requestedTargetX, parentContent.left, parentContent.right); + setTranslationX(clampedTargetX - currentTargetX); + + mValue = mAxis.convertToValue(clampedTargetX - parentContent.left); dispatchOnSweep(false); } return true; diff --git a/src/com/android/settings/widget/ChartView.java b/src/com/android/settings/widget/ChartView.java index d762631..bf5616d 100644 --- a/src/com/android/settings/widget/ChartView.java +++ b/src/com/android/settings/widget/ChartView.java @@ -36,6 +36,8 @@ public class ChartView extends FrameLayout { // TODO: extend something that supports two-dimensional scrolling + private static final int SWEEP_GRAVITY = Gravity.TOP | Gravity.LEFT; + ChartAxis mHoriz; ChartAxis mVert; @@ -74,7 +76,6 @@ public class ChartView extends FrameLayout { final Rect parentRect = new Rect(); final Rect childRect = new Rect(); - final Rect extraMargins = new Rect(); for (int i = 0; i < getChildCount(); i++) { final View child = getChildAt(i); @@ -91,21 +92,23 @@ public class ChartView extends FrameLayout { } else if (child instanceof ChartSweepView) { // sweep is always placed along specific dimension final ChartSweepView sweep = (ChartSweepView) child; + final Rect sweepMargins = sweep.getSweepMargins(); final float point = sweep.getPoint(); - sweep.getExtraMargins(extraMargins); if (sweep.getFollowAxis() == ChartSweepView.HORIZONTAL) { - parentRect.left = parentRect.right = (int) point + getPaddingLeft(); - parentRect.top -= extraMargins.top; - parentRect.bottom += extraMargins.bottom; - Gravity.apply(params.gravity, child.getMeasuredWidth(), parentRect.height(), + parentRect.left = parentRect.right = + (int) (sweep.getPoint() - sweep.getTargetInset()) + getPaddingLeft(); + parentRect.top -= sweepMargins.top; + parentRect.bottom += sweepMargins.bottom; + Gravity.apply(SWEEP_GRAVITY, child.getMeasuredWidth(), parentRect.height(), parentRect, childRect); } else { - parentRect.top = parentRect.bottom = (int) point + getPaddingTop(); - parentRect.left -= extraMargins.left; - parentRect.right += extraMargins.right; - Gravity.apply(params.gravity, parentRect.width(), child.getMeasuredHeight(), + parentRect.top = parentRect.bottom = + (int) (sweep.getPoint() - sweep.getTargetInset()) + getPaddingTop(); + parentRect.left -= sweepMargins.left; + parentRect.right += sweepMargins.right; + Gravity.apply(SWEEP_GRAVITY, parentRect.width(), child.getMeasuredHeight(), parentRect, childRect); } } diff --git a/src/com/android/settings/widget/DataUsageChartView.java b/src/com/android/settings/widget/DataUsageChartView.java index 1c76291..6fe4042 100644 --- a/src/com/android/settings/widget/DataUsageChartView.java +++ b/src/com/android/settings/widget/DataUsageChartView.java @@ -38,10 +38,10 @@ public class DataUsageChartView extends ChartView { private static final long GB_IN_BYTES = MB_IN_BYTES * 1024; // TODO: enforce that sweeps cant cross each other - // TODO: limit sweeps at graph boundaries private ChartGridView mGrid; private ChartNetworkSeriesView mSeries; + private ChartNetworkSeriesView mDetailSeries; private ChartSweepView mSweepLeft; private ChartSweepView mSweepRight; @@ -75,6 +75,8 @@ public class DataUsageChartView extends ChartView { mGrid = (ChartGridView) findViewById(R.id.grid); mSeries = (ChartNetworkSeriesView) findViewById(R.id.series); + mDetailSeries = (ChartNetworkSeriesView) findViewById(R.id.detail_series); + mDetailSeries.setVisibility(View.GONE); mSweepLeft = (ChartSweepView) findViewById(R.id.sweep_left); mSweepRight = (ChartSweepView) findViewById(R.id.sweep_right); @@ -89,6 +91,7 @@ public class DataUsageChartView extends ChartView { // tell everyone about our axis mGrid.init(mHoriz, mVert); mSeries.init(mHoriz, mVert); + mDetailSeries.init(mHoriz, mVert); mSweepLeft.init(mHoriz); mSweepRight.init(mHoriz); mSweepWarning.init(mVert); @@ -97,27 +100,21 @@ public class DataUsageChartView extends ChartView { setActivated(false); } - @Override - public void setActivated(boolean activated) { - super.setActivated(activated); - - mSweepLeft.setEnabled(activated); - mSweepRight.setEnabled(activated); - mSweepWarning.setEnabled(activated); - mSweepLimit.setEnabled(activated); - } - - @Deprecated - public void setChartColor(int stroke, int fill, int disabled) { - mSeries.setChartColor(stroke, fill, disabled); - } - public void setListener(DataUsageChartListener listener) { mListener = listener; } public void bindNetworkStats(NetworkStatsHistory stats) { mSeries.bindNetworkStats(stats); + updatePrimaryRange(); + requestLayout(); + } + + public void bindDetailNetworkStats(NetworkStatsHistory stats) { + mDetailSeries.bindNetworkStats(stats); + mDetailSeries.setVisibility(stats != null ? View.VISIBLE : View.GONE); + updatePrimaryRange(); + requestLayout(); } public void bindNetworkPolicy(NetworkPolicy policy) { @@ -146,22 +143,11 @@ public class DataUsageChartView extends ChartView { } requestLayout(); - - // TODO: eventually remove this; was to work around lack of sweep clamping - if (policy.limitBytes < -1 || policy.limitBytes > 5 * GB_IN_BYTES) { - policy.limitBytes = 5 * GB_IN_BYTES; - mLimitListener.onSweep(mSweepLimit, true); - } - if (policy.warningBytes < -1 || policy.warningBytes > 5 * GB_IN_BYTES) { - policy.warningBytes = 4 * GB_IN_BYTES; - mWarningListener.onSweep(mSweepWarning, true); - } - } private OnSweepListener mSweepListener = new OnSweepListener() { public void onSweep(ChartSweepView sweep, boolean sweepDone) { - mSeries.setPrimaryRange(mSweepLeft.getValue(), mSweepRight.getValue()); + updatePrimaryRange(); // update detail list only when done sweeping if (sweepDone && mListener != null) { @@ -236,13 +222,26 @@ public class DataUsageChartView extends ChartView { mSweepLeft.setValue(sweepMin); mSweepRight.setValue(sweepMax); - mSeries.setPrimaryRange(sweepMin, sweepMax); + updatePrimaryRange(); requestLayout(); mSeries.generatePath(); mSeries.invalidate(); } + private void updatePrimaryRange() { + final long left = mSweepLeft.getValue(); + final long right = mSweepRight.getValue(); + + // prefer showing primary range on detail series, when available + if (mDetailSeries.getVisibility() == View.VISIBLE) { + mDetailSeries.setPrimaryRange(left, right); + mSeries.setPrimaryRange(0, 0); + } else { + mSeries.setPrimaryRange(left, right); + } + } + public static class TimeAxis implements ChartAxis { private static final long TICK_INTERVAL = DateUtils.DAY_IN_MILLIS * 7; |