First pass at detailed app data usage, policy.

Fragment to show application data usage details, including chart with
inspection ranges.  Button that invokes ACTION_MANAGE_NETWORK_USAGE
towards application, and UID-specific policy controls.  Fragment is
launched when clicking list items from data usage summary page.

Change-Id: Ie1564aa8af98e1a7083817a997059a5a7b1caa50
diff --git a/src/com/android/settings/DataUsageAppDetail.java b/src/com/android/settings/DataUsageAppDetail.java
new file mode 100644
index 0000000..c7c89ee
--- /dev/null
+++ b/src/com/android/settings/DataUsageAppDetail.java
@@ -0,0 +1,224 @@
+/*
+ * 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_PAID_BACKGROUND;
+import static android.net.TrafficStats.TEMPLATE_MOBILE_ALL;
+import static com.android.settings.DataUsageSummary.getHistoryBounds;
+
+import android.app.Fragment;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.graphics.Color;
+import android.net.INetworkPolicyManager;
+import android.net.INetworkStatsService;
+import android.net.NetworkStatsHistory;
+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;
+
+    private int mUid;
+
+    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 int mUidPolicy;
+    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();
+
+        final Context context = getActivity();
+
+        mUid = getArguments().getInt(Intent.EXTRA_UID);
+        mTitle.setText(context.getPackageManager().getNameForUid(mUid));
+
+        updateBody();
+    }
+
+    private void updateBody() {
+        try {
+            // load stats for current uid and template
+            // TODO: read template from extras
+            mUidPolicy = mPolicyService.getUidPolicy(mUid);
+            mHistory = mStatsService.getHistoryForUid(mUid, TEMPLATE_MOBILE_ALL);
+        } catch (RemoteException e) {
+            // since we can't do much without policy or 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();
+
+        // update policy checkbox
+        final boolean restrictBackground = (mUidPolicy & POLICY_REJECT_PAID_BACKGROUND) != 0;
+        mRestrictBackground.setChecked(restrictBackground);
+
+        // kick preference views so they rebind from changes above
+        refreshPreferenceViews();
+    }
+
+    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));
+    }
+
+    /**
+     * 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
+            final PackageManager pm = getActivity().getPackageManager();
+            final String packageName = pm.getPackagesForUid(mUid)[0];
+
+            final Intent intent = new Intent(Intent.ACTION_MANAGE_NETWORK_USAGE);
+            intent.setPackage(packageName);
+            startActivity(intent);
+        }
+    };
+
+    private OnClickListener mRestrictBackgroundListener = new OnClickListener() {
+        /** {@inheritDoc} */
+        public void onClick(View v) {
+            final boolean restrictBackground = !mRestrictBackground.isChecked();
+            mRestrictBackground.setChecked(restrictBackground);
+            refreshPreferenceViews();
+
+            try {
+                mPolicyService.setUidPolicy(
+                        mUid, restrictBackground ? POLICY_REJECT_PAID_BACKGROUND : POLICY_NONE);
+            } catch (RemoteException e) {
+                throw new RuntimeException("unable to save policy", e);
+            }
+        }
+    };
+
+}
diff --git a/src/com/android/settings/DataUsageSummary.java b/src/com/android/settings/DataUsageSummary.java
index 44a86df..2350d54 100644
--- a/src/com/android/settings/DataUsageSummary.java
+++ b/src/com/android/settings/DataUsageSummary.java
@@ -27,6 +27,7 @@
 
 import android.app.Fragment;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.net.INetworkPolicyManager;
 import android.net.INetworkStatsService;
@@ -38,6 +39,7 @@
 import android.os.ServiceManager;
 import android.preference.CheckBoxPreference;
 import android.preference.Preference;
+import android.preference.PreferenceActivity;
 import android.preference.SwitchPreference;
 import android.telephony.TelephonyManager;
 import android.text.format.DateUtils;
@@ -348,7 +350,7 @@
         // bind chart to historical stats
         mChart.bindNetworkStats(mHistory);
 
-        updatePolicy();
+        updatePolicy(true);
 
         // force scroll to top of body
         mListView.smoothScrollToPosition(0);
@@ -361,15 +363,17 @@
      * Update chart sweeps and cycle list to reflect {@link NetworkPolicy} for
      * current {@link #mTemplate}.
      */
-    private void updatePolicy() {
+    private void updatePolicy(boolean refreshCycle) {
         final NetworkPolicy policy = mPolicyModifier.getPolicy(mTemplate);
 
         // reflect policy limit in checkbox
         mDisableAtLimit.setChecked(policy != null && policy.limitBytes != LIMIT_DISABLED);
         mChart.bindNetworkPolicy(policy);
 
-        // generate cycle list based on policy and available history
-        updateCycleList(policy);
+        if (refreshCycle) {
+            // generate cycle list based on policy and available history
+            updateCycleList(policy);
+        }
 
         // kick preference views so they rebind from changes above
         refreshPreferenceViews();
@@ -379,7 +383,7 @@
      * Return full time bounds (earliest and latest time recorded) of the given
      * {@link NetworkStatsHistory}.
      */
-    private static long[] getHistoryBounds(NetworkStatsHistory history) {
+    public static long[] getHistoryBounds(NetworkStatsHistory history) {
         final long currentTime = System.currentTimeMillis();
 
         long start = currentTime;
@@ -471,17 +475,21 @@
             // TODO: show interstitial warning dialog to user
             final long limitBytes = disableAtLimit ? 5 * GB_IN_BYTES : LIMIT_DISABLED;
             mPolicyModifier.setPolicyLimitBytes(mTemplate, limitBytes);
-            updatePolicy();
+            updatePolicy(false);
         }
     };
 
     private OnItemClickListener mListListener = new OnItemClickListener() {
         /** {@inheritDoc} */
         public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-            final Object object = parent.getItemAtPosition(position);
+            final AppUsageItem app = (AppUsageItem) parent.getItemAtPosition(position);
 
-            // TODO: show app details
-            Log.d(TAG, "showing app details for " + object);
+            final Bundle args = new Bundle();
+            args.putInt(Intent.EXTRA_UID, app.uid);
+
+            final PreferenceActivity activity = (PreferenceActivity) getActivity();
+            activity.startPreferencePanel(DataUsageAppDetail.class.getName(), args,
+                    R.string.data_usage_summary_title, null, null, 0);
         }
     };
 
@@ -547,7 +555,7 @@
             if (LOGD) Log.d(TAG, "onWarningChanged()");
             final long warningBytes = mChart.getWarningBytes();
             mPolicyModifier.setPolicyWarningBytes(mTemplate, warningBytes);
-            updatePolicy();
+            updatePolicy(false);
         }
 
         /** {@inheritDoc} */
@@ -556,7 +564,7 @@
             final long limitBytes = mDisableAtLimit.isChecked() ? mChart.getLimitBytes()
                     : LIMIT_DISABLED;
             mPolicyModifier.setPolicyLimitBytes(mTemplate, limitBytes);
-            updatePolicy();
+            updatePolicy(false);
         }
     };
 
@@ -615,22 +623,22 @@
         }
     }
 
+    private static class AppUsageItem implements Comparable<AppUsageItem> {
+        public int uid;
+        public long total;
+
+        /** {@inheritDoc} */
+        public int compareTo(AppUsageItem another) {
+            return Long.compare(another.total, total);
+        }
+    }
+
     /**
      * Adapter of applications, sorted by total usage descending.
      */
     public static class DataUsageAdapter extends BaseAdapter {
         private ArrayList<AppUsageItem> mItems = Lists.newArrayList();
 
-        private static class AppUsageItem implements Comparable<AppUsageItem> {
-            public int uid;
-            public long total;
-
-            /** {@inheritDoc} */
-            public int compareTo(AppUsageItem another) {
-                return Long.compare(another.total, total);
-            }
-        }
-
         public void bindStats(NetworkStats stats) {
             mItems.clear();
 
diff --git a/src/com/android/settings/widget/ChartNetworkSeriesView.java b/src/com/android/settings/widget/ChartNetworkSeriesView.java
index d0a2742..5fc79dd 100644
--- a/src/com/android/settings/widget/ChartNetworkSeriesView.java
+++ b/src/com/android/settings/widget/ChartNetworkSeriesView.java
@@ -40,9 +40,9 @@
     private final ChartAxis mHoriz;
     private final ChartAxis mVert;
 
-    private final Paint mPaintStroke;
-    private final Paint mPaintFill;
-    private final Paint mPaintFillDisabled;
+    private Paint mPaintStroke;
+    private Paint mPaintFill;
+    private Paint mPaintFillDisabled;
 
     private NetworkStatsHistory mStats;
 
@@ -58,24 +58,29 @@
         mHoriz = Preconditions.checkNotNull(horiz, "missing horiz");
         mVert = Preconditions.checkNotNull(vert, "missing vert");
 
+        setChartColor(Color.parseColor("#24aae1"), Color.parseColor("#c050ade5"),
+                Color.parseColor("#88566abc"));
+
+        mPathStroke = new Path();
+        mPathFill = new Path();
+    }
+
+    public void setChartColor(int stroke, int fill, int disabled) {
         mPaintStroke = new Paint();
         mPaintStroke.setStrokeWidth(6.0f);
-        mPaintStroke.setColor(Color.parseColor("#24aae1"));
+        mPaintStroke.setColor(stroke);
         mPaintStroke.setStyle(Style.STROKE);
         mPaintStroke.setAntiAlias(true);
 
         mPaintFill = new Paint();
-        mPaintFill.setColor(Color.parseColor("#c050ade5"));
+        mPaintFill.setColor(fill);
         mPaintFill.setStyle(Style.FILL);
         mPaintFill.setAntiAlias(true);
 
         mPaintFillDisabled = new Paint();
-        mPaintFillDisabled.setColor(Color.parseColor("#88566abc"));
+        mPaintFillDisabled.setColor(disabled);
         mPaintFillDisabled.setStyle(Style.FILL);
         mPaintFillDisabled.setAntiAlias(true);
-
-        mPathStroke = new Path();
-        mPathFill = new Path();
     }
 
     public void bindNetworkStats(NetworkStatsHistory stats) {
diff --git a/src/com/android/settings/widget/DataUsageChartView.java b/src/com/android/settings/widget/DataUsageChartView.java
index 6a702d0..9dbffcd 100644
--- a/src/com/android/settings/widget/DataUsageChartView.java
+++ b/src/com/android/settings/widget/DataUsageChartView.java
@@ -86,6 +86,13 @@
         mSweepTime1.addOnSweepListener(mSweepListener);
         mSweepTime2.addOnSweepListener(mSweepListener);
 
+        mSweepDataWarn.setVisibility(View.INVISIBLE);
+        mSweepDataLimit.setVisibility(View.INVISIBLE);
+
+    }
+
+    public void setChartColor(int stroke, int fill, int disabled) {
+        mSeries.setChartColor(stroke, fill, disabled);
     }
 
     public void setListener(DataUsageChartListener listener) {