Cycle day editor and other confirmation dialogs.

Create dialog to edit "cycle day" when data usage resets.  Also added
dialogs to confirm enabling limit and restricting an application.

Change-Id: I1e08b17fabd1fcfc2f260807a61435d0ff1a8627
diff --git a/src/com/android/settings/DataUsageAppDetail.java b/src/com/android/settings/DataUsageAppDetail.java
index c7c89ee..4e929ad 100644
--- a/src/com/android/settings/DataUsageAppDetail.java
+++ b/src/com/android/settings/DataUsageAppDetail.java
@@ -21,8 +21,12 @@
 import static android.net.TrafficStats.TEMPLATE_MOBILE_ALL;
 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;
@@ -55,6 +59,8 @@
 
     private int mUid;
 
+    private static final String TAG_CONFIRM_RESTRICT = "confirmRestrict";
+
     private INetworkStatsService mStatsService;
     private INetworkPolicyManager mPolicyService;
 
@@ -167,6 +173,19 @@
         mText1.setText(Formatter.formatFileSize(context, totalCombined));
     }
 
+    private void setRestrictBackground(boolean restrictBackground) {
+        if (LOGD) Log.d(TAG, "setRestrictBackground()");
+        try {
+            mPolicyService.setUidPolicy(
+                    mUid, restrictBackground ? POLICY_REJECT_PAID_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.
      */
@@ -209,16 +228,48 @@
         /** {@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);
+            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_PAID_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 19caed8..beb3ed4f 100644
--- a/src/com/android/settings/DataUsageSummary.java
+++ b/src/com/android/settings/DataUsageSummary.java
@@ -25,8 +25,12 @@
 import static android.net.TrafficStats.TEMPLATE_WIFI;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 
+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.net.INetworkPolicyManager;
@@ -61,6 +65,7 @@
 import android.widget.BaseAdapter;
 import android.widget.LinearLayout;
 import android.widget.ListView;
+import android.widget.NumberPicker;
 import android.widget.Spinner;
 import android.widget.TabHost;
 import android.widget.TabHost.OnTabChangeListener;
@@ -90,6 +95,9 @@
     private static final String TAB_MOBILE = "mobile";
     private static final String TAB_WIFI = "wifi";
 
+    private static final String TAG_CONFIRM_LIMIT = "confirmLimit";
+    private static final String TAG_CYCLE_EDITOR = "cycleEditor";
+
     private static final long KB_IN_BYTES = 1024;
     private static final long MB_IN_BYTES = KB_IN_BYTES * 1024;
     private static final long GB_IN_BYTES = MB_IN_BYTES * 1024;
@@ -365,6 +373,24 @@
         refreshPreferenceViews();
     }
 
+    private void setPolicyCycleDay(int cycleDay) {
+        if (LOGD) Log.d(TAG, "setPolicyCycleDay()");
+        mPolicyModifier.setPolicyCycleDay(mTemplate, cycleDay);
+        updatePolicy(true);
+    }
+
+    private void setPolicyWarningBytes(long warningBytes) {
+        if (LOGD) Log.d(TAG, "setPolicyWarningBytes()");
+        mPolicyModifier.setPolicyWarningBytes(mTemplate, warningBytes);
+        updatePolicy(false);
+    }
+
+    private void setPolicyLimitBytes(long limitBytes) {
+        if (LOGD) Log.d(TAG, "setPolicyLimitBytes()");
+        mPolicyModifier.setPolicyLimitBytes(mTemplate, limitBytes);
+        updatePolicy(false);
+    }
+
     /**
      * Update chart sweeps and cycle list to reflect {@link NetworkPolicy} for
      * current {@link #mTemplate}.
@@ -474,14 +500,13 @@
         /** {@inheritDoc} */
         public void onClick(View v) {
             final boolean disableAtLimit = !mDisableAtLimit.isChecked();
-            mDisableAtLimit.setChecked(disableAtLimit);
-            refreshPreferenceViews();
-
-            // TODO: create policy if none exists
-            // TODO: show interstitial warning dialog to user
-            final long limitBytes = disableAtLimit ? 5 * GB_IN_BYTES : LIMIT_DISABLED;
-            mPolicyModifier.setPolicyLimitBytes(mTemplate, limitBytes);
-            updatePolicy(false);
+            if (disableAtLimit) {
+                // enabling limit; show confirmation dialog which eventually
+                // calls setPolicyLimitBytes() once user confirms.
+                ConfirmLimitFragment.show(DataUsageSummary.this);
+            } else {
+                setPolicyLimitBytes(LIMIT_DISABLED);
+            }
         }
     };
 
@@ -504,12 +529,15 @@
         public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
             final CycleItem cycle = (CycleItem) parent.getItemAtPosition(position);
             if (cycle instanceof CycleChangeItem) {
-                // TODO: show "define cycle" dialog
-                // also reset back to first cycle
-                Log.d(TAG, "CHANGE CYCLE DIALOG!!");
+                // show cycle editor; will eventually call setPolicyCycleDay()
+                // when user finishes editing.
+                CycleEditorFragment.show(DataUsageSummary.this);
+
+                // reset spinner to something other than "change cycle..."
+                mCycleSpinner.setSelection(0);
 
             } else {
-                if (LOGD) Log.d(TAG, "shoiwng cycle " + cycle);
+                if (LOGD) Log.d(TAG, "showing cycle " + cycle);
 
                 // update chart to show selected cycle, and update detail data
                 // to match updated sweep bounds.
@@ -558,19 +586,12 @@
 
         /** {@inheritDoc} */
         public void onWarningChanged() {
-            if (LOGD) Log.d(TAG, "onWarningChanged()");
-            final long warningBytes = mChart.getWarningBytes();
-            mPolicyModifier.setPolicyWarningBytes(mTemplate, warningBytes);
-            updatePolicy(false);
+            setPolicyWarningBytes(mChart.getWarningBytes());
         }
 
         /** {@inheritDoc} */
         public void onLimitChanged() {
-            if (LOGD) Log.d(TAG, "onLimitChanged()");
-            final long limitBytes = mDisableAtLimit.isChecked() ? mChart.getLimitBytes()
-                    : LIMIT_DISABLED;
-            mPolicyModifier.setPolicyLimitBytes(mTemplate, limitBytes);
-            updatePolicy(false);
+            setPolicyLimitBytes(mChart.getLimitBytes());
         }
     };
 
@@ -696,5 +717,116 @@
 
     }
 
+    /**
+     * Dialog to request user confirmation before setting
+     * {@link NetworkPolicy#limitBytes}.
+     */
+    public static class ConfirmLimitFragment extends DialogFragment {
+        public static final String EXTRA_MESSAGE_ID = "messageId";
+        public static final String EXTRA_LIMIT_BYTES = "limitBytes";
+
+        public static void show(DataUsageSummary parent) {
+            final Bundle args = new Bundle();
+
+            // TODO: customize default limits based on network template
+            switch (parent.mTemplate) {
+                case TEMPLATE_MOBILE_3G_LOWER: {
+                    args.putInt(EXTRA_MESSAGE_ID, R.string.data_usage_limit_dialog_3g);
+                    args.putLong(EXTRA_LIMIT_BYTES, 5 * GB_IN_BYTES);
+                    break;
+                }
+                case TEMPLATE_MOBILE_4G: {
+                    args.putInt(EXTRA_MESSAGE_ID, R.string.data_usage_limit_dialog_4g);
+                    args.putLong(EXTRA_LIMIT_BYTES, 5 * GB_IN_BYTES);
+                    break;
+                }
+                case TEMPLATE_MOBILE_ALL: {
+                    args.putInt(EXTRA_MESSAGE_ID, R.string.data_usage_limit_dialog_mobile);
+                    args.putLong(EXTRA_LIMIT_BYTES, 5 * GB_IN_BYTES);
+                    break;
+                }
+            }
+
+            final ConfirmLimitFragment dialog = new ConfirmLimitFragment();
+            dialog.setArguments(args);
+            dialog.setTargetFragment(parent, 0);
+            dialog.show(parent.getFragmentManager(), TAG_CONFIRM_LIMIT);
+        }
+
+        @Override
+        public Dialog onCreateDialog(Bundle savedInstanceState) {
+            final Context context = getActivity();
+
+            final int messageId = getArguments().getInt(EXTRA_MESSAGE_ID);
+            final long limitBytes = getArguments().getLong(EXTRA_LIMIT_BYTES);
+
+            final AlertDialog.Builder builder = new AlertDialog.Builder(context);
+            builder.setTitle(R.string.data_usage_limit_dialog_title);
+            builder.setMessage(messageId);
+
+            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.setPolicyLimitBytes(limitBytes);
+                    }
+                }
+            });
+
+            return builder.create();
+        }
+    }
+
+    /**
+     * Dialog to edit {@link NetworkPolicy#cycleDay}.
+     */
+    public static class CycleEditorFragment extends DialogFragment {
+        public static final String EXTRA_CYCLE_DAY = "cycleDay";
+
+        public static void show(DataUsageSummary parent) {
+            final NetworkPolicy policy = parent.mPolicyModifier.getPolicy(parent.mTemplate);
+            final Bundle args = new Bundle();
+            args.putInt(CycleEditorFragment.EXTRA_CYCLE_DAY, policy.cycleDay);
+
+            final CycleEditorFragment dialog = new CycleEditorFragment();
+            dialog.setArguments(args);
+            dialog.setTargetFragment(parent, 0);
+            dialog.show(parent.getFragmentManager(), TAG_CYCLE_EDITOR);
+        }
+
+        @Override
+        public Dialog onCreateDialog(Bundle savedInstanceState) {
+            final Context context = getActivity();
+
+            final AlertDialog.Builder builder = new AlertDialog.Builder(context);
+            final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
+
+            final View view = dialogInflater.inflate(R.layout.data_usage_cycle_editor, null, false);
+            final NumberPicker cycleDayPicker = (NumberPicker) view.findViewById(R.id.cycle_day);
+
+            final int oldCycleDay = getArguments().getInt(EXTRA_CYCLE_DAY, 1);
+
+            cycleDayPicker.setMinValue(1);
+            cycleDayPicker.setMaxValue(31);
+            cycleDayPicker.setValue(oldCycleDay);
+            cycleDayPicker.setWrapSelectorWheel(true);
+
+            builder.setTitle(R.string.data_usage_cycle_editor_title);
+            builder.setView(view);
+
+            builder.setPositiveButton(R.string.data_usage_cycle_editor_positive,
+                    new DialogInterface.OnClickListener() {
+                        public void onClick(DialogInterface dialog, int which) {
+                            final int cycleDay = cycleDayPicker.getValue();
+                            final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
+                            if (target != null) {
+                                target.setPolicyCycleDay(cycleDay);
+                            }
+                        }
+                    });
+
+            return builder.create();
+        }
+    }
 
 }
diff --git a/src/com/android/settings/net/NetworkPolicyModifier.java b/src/com/android/settings/net/NetworkPolicyModifier.java
index 1d8aca3..d90609e 100644
--- a/src/com/android/settings/net/NetworkPolicyModifier.java
+++ b/src/com/android/settings/net/NetworkPolicyModifier.java
@@ -36,6 +36,8 @@
  * about which policies can coexist.
  */
 public class NetworkPolicyModifier {
+    // TODO: refactor to "Editor"
+    // TODO: be more robust when missing policies from service
 
     private INetworkPolicyManager mPolicyService;
     private String mSubscriberId;
diff --git a/src/com/android/settings/widget/DataUsageChartView.java b/src/com/android/settings/widget/DataUsageChartView.java
index 9dbffcd..6c62fa8 100644
--- a/src/com/android/settings/widget/DataUsageChartView.java
+++ b/src/com/android/settings/widget/DataUsageChartView.java
@@ -127,6 +127,8 @@
         } else {
             mSweepDataWarn.setVisibility(View.INVISIBLE);
         }
+
+        requestLayout();
     }
 
     private OnSweepListener mSweepListener = new OnSweepListener() {
@@ -195,6 +197,7 @@
 
         requestLayout();
         mSeries.generatePath();
+        mSeries.invalidate();
     }
 
     public static class TimeAxis implements ChartAxis {