am a53188fe: Data usage: precise editing, restrict help, D-pad.

* commit 'a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2':
  Data usage: precise editing, restrict help, D-pad.
This commit is contained in:
Jeff Sharkey
2011-09-13 23:57:27 -07:00
committed by Android Git Automerger
8 changed files with 498 additions and 84 deletions

View File

@@ -20,6 +20,7 @@ import static android.net.ConnectivityManager.TYPE_ETHERNET;
import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.ConnectivityManager.TYPE_WIMAX;
import static android.net.NetworkPolicy.LIMIT_DISABLED;
import static android.net.NetworkPolicy.WARNING_DISABLED;
import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE;
import static android.net.NetworkPolicyManager.POLICY_NONE;
import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
@@ -129,6 +130,7 @@ import com.google.android.collect.Lists;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import libcore.util.Objects;
@@ -156,7 +158,10 @@ public class DataUsageSummary extends Fragment {
private static final String TAG_CONFIRM_DATA_ROAMING = "confirmDataRoaming";
private static final String TAG_CONFIRM_LIMIT = "confirmLimit";
private static final String TAG_CYCLE_EDITOR = "cycleEditor";
private static final String TAG_WARNING_EDITOR = "warningEditor";
private static final String TAG_LIMIT_EDITOR = "limitEditor";
private static final String TAG_CONFIRM_RESTRICT = "confirmRestrict";
private static final String TAG_DENIED_RESTRICT = "deniedRestrict";
private static final String TAG_CONFIRM_APP_RESTRICT = "confirmAppRestrict";
private static final String TAG_APP_DETAILS = "appDetails";
@@ -295,7 +300,10 @@ public class DataUsageSummary extends Fragment {
mTabHost.setOnTabChangedListener(mTabListener);
mHeader = (ViewGroup) inflater.inflate(R.layout.data_usage_header, mListView, false);
mListView.addHeaderView(mHeader, null, false);
mHeader.setClickable(true);
mListView.addHeaderView(mHeader, null, true);
mListView.setItemsCanFocus(true);
if (mInsetSide > 0) {
// inset selector and divider drawables
@@ -316,7 +324,10 @@ public class DataUsageSummary extends Fragment {
mDisableAtLimit = new CheckBox(inflater.getContext());
mDisableAtLimit.setClickable(false);
mDisableAtLimit.setFocusable(false);
mDisableAtLimitView = inflatePreference(inflater, mNetworkSwitches, mDisableAtLimit);
mDisableAtLimitView.setClickable(true);
mDisableAtLimitView.setFocusable(true);
mDisableAtLimitView.setOnClickListener(mDisableAtLimitListener);
mNetworkSwitches.addView(mDisableAtLimitView);
}
@@ -346,7 +357,10 @@ public class DataUsageSummary extends Fragment {
mAppRestrict = new CheckBox(inflater.getContext());
mAppRestrict.setClickable(false);
mAppRestrict.setFocusable(false);
mAppRestrictView = inflatePreference(inflater, mAppSwitches, mAppRestrict);
mAppRestrictView.setClickable(true);
mAppRestrictView.setFocusable(true);
mAppRestrictView.setOnClickListener(mAppRestrictListener);
mAppSwitches.addView(mAppRestrictView);
}
@@ -456,7 +470,11 @@ public class DataUsageSummary extends Fragment {
case R.id.data_usage_menu_restrict_background: {
final boolean restrictBackground = !item.isChecked();
if (restrictBackground) {
ConfirmRestrictFragment.show(this);
if (hasLimitedNetworks()) {
ConfirmRestrictFragment.show(this);
} else {
DeniedRestrictFragment.show(this);
}
} else {
// no confirmation to drop restriction
setRestrictBackground(false);
@@ -619,7 +637,6 @@ public class DataUsageSummary extends Fragment {
if (LOGD) Log.d(TAG, "updateBody() with currentTab=" + currentTab);
mDataEnabledView.setVisibility(View.VISIBLE);
mDisableAtLimitView.setVisibility(View.VISIBLE);
if (TAB_MOBILE.equals(currentTab)) {
setPreferenceTitle(mDataEnabledView, R.string.data_usage_enable_mobile);
@@ -735,7 +752,7 @@ public class DataUsageSummary extends Fragment {
setPreferenceTitle(mAppRestrictView, R.string.data_usage_app_restrict_background);
setPreferenceSummary(mAppRestrictView,
getString(R.string.data_usage_app_restrict_background_summary,
buildLimitedNetworksList()));
buildLimitedNetworksString()));
mAppRestrictView.setVisibility(View.VISIBLE);
mAppRestrict.setChecked(getAppRestrictBackground());
@@ -745,12 +762,6 @@ public class DataUsageSummary extends Fragment {
}
}
private void setPolicyCycleDay(int cycleDay) {
if (LOGD) Log.d(TAG, "setPolicyCycleDay()");
mPolicyEditor.setPolicyCycleDay(mTemplate, cycleDay);
updatePolicy(true);
}
private void setPolicyWarningBytes(long warningBytes) {
if (LOGD) Log.d(TAG, "setPolicyWarningBytes()");
mPolicyEditor.setPolicyWarningBytes(mTemplate, warningBytes);
@@ -863,15 +874,8 @@ public class DataUsageSummary extends Fragment {
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);
// when heading back to summary without cycle refresh, kick details
// update to repopulate list.
if (!refreshCycle) {
updateDetailData();
}
}
// TODO: move enabled state directly into policy
@@ -907,6 +911,8 @@ public class DataUsageSummary extends Fragment {
* item, updating the inspection range on {@link #mChart}.
*/
private void updateCycleList(NetworkPolicy policy) {
// stash away currently selected cycle to try restoring below
final CycleItem previousItem = (CycleItem) mCycleSpinner.getSelectedItem();
mCycleAdapter.clear();
final Context context = mCycleSpinner.getContext();
@@ -954,8 +960,18 @@ public class DataUsageSummary extends Fragment {
// force pick the current cycle (first item)
if (mCycleAdapter.getCount() > 0) {
mCycleSpinner.setSelection(0);
mCycleListener.onItemSelected(mCycleSpinner, null, 0, 0);
final int position = mCycleAdapter.findNearestPosition(previousItem);
mCycleSpinner.setSelection(position);
// only force-update cycle when changed; skipping preserves any
// user-defined inspection region.
final CycleItem selectedItem = mCycleAdapter.getItem(position);
if (!Objects.equal(selectedItem, previousItem)) {
mCycleListener.onItemSelected(mCycleSpinner, null, position, 0);
} else {
// but still kick off loader for detailed list
updateDetailData();
}
} else {
updateDetailData();
}
@@ -1002,9 +1018,16 @@ public class DataUsageSummary extends Fragment {
final boolean restrictBackground = !mAppRestrict.isChecked();
if (restrictBackground) {
// enabling restriction; show confirmation dialog which
// eventually calls setRestrictBackground() once user confirms.
ConfirmAppRestrictFragment.show(DataUsageSummary.this);
if (hasLimitedNetworks()) {
// enabling restriction; show confirmation dialog which
// eventually calls setRestrictBackground() once user
// confirms.
ConfirmAppRestrictFragment.show(DataUsageSummary.this);
} else {
// no limited networks; show dialog to guide user towards
// setting a network limit. doesn't mutate restrict state.
DeniedRestrictFragment.show(DataUsageSummary.this);
}
} else {
setAppRestrictBackground(false);
}
@@ -1211,13 +1234,22 @@ public class DataUsageSummary extends Fragment {
public void onLimitChanged() {
setPolicyLimitBytes(mChart.getLimitBytes());
}
};
/** {@inheritDoc} */
public void requestWarningEdit() {
WarningEditorFragment.show(DataUsageSummary.this);
}
/** {@inheritDoc} */
public void requestLimitEdit() {
LimitEditorFragment.show(DataUsageSummary.this);
}
};
/**
* List item that reflects a specific data usage cycle.
*/
public static class CycleItem {
public static class CycleItem implements Comparable<CycleItem> {
public CharSequence label;
public long start;
public long end;
@@ -1236,6 +1268,20 @@ public class DataUsageSummary extends Fragment {
public String toString() {
return label.toString();
}
@Override
public boolean equals(Object o) {
if (o instanceof CycleItem) {
final CycleItem another = (CycleItem) o;
return start == another.start && end == another.end;
}
return false;
}
/** {@inheritDoc} */
public int compareTo(CycleItem another) {
return Long.compare(start, another.start);
}
}
private static final StringBuilder sBuilder = new StringBuilder(50);
@@ -1291,6 +1337,25 @@ public class DataUsageSummary extends Fragment {
add(mChangeItem);
}
}
/**
* Find position of {@link CycleItem} in this adapter which is nearest
* the given {@link CycleItem}.
*/
public int findNearestPosition(CycleItem target) {
if (target != null) {
final int count = getCount();
for (int i = count - 1; i >= 0; i--) {
final CycleItem item = getItem(i);
if (item instanceof CycleChangeItem) {
continue;
} else if (item.compareTo(target) >= 0) {
return i;
}
}
}
return 0;
}
}
private static class AppUsageItem implements Comparable<AppUsageItem> {
@@ -1528,12 +1593,11 @@ public class DataUsageSummary extends Fragment {
* Dialog to edit {@link NetworkPolicy#cycleDay}.
*/
public static class CycleEditorFragment extends DialogFragment {
private static final String EXTRA_CYCLE_DAY = "cycleDay";
private static final String EXTRA_TEMPLATE = "template";
public static void show(DataUsageSummary parent) {
final NetworkPolicy policy = parent.mPolicyEditor.getPolicy(parent.mTemplate);
final Bundle args = new Bundle();
args.putInt(CycleEditorFragment.EXTRA_CYCLE_DAY, policy.cycleDay);
args.putParcelable(EXTRA_TEMPLATE, parent.mTemplate);
final CycleEditorFragment dialog = new CycleEditorFragment();
dialog.setArguments(args);
@@ -1544,6 +1608,8 @@ public class DataUsageSummary extends Fragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Context context = getActivity();
final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
final NetworkPolicyEditor editor = target.mPolicyEditor;
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
@@ -1551,11 +1617,12 @@ public class DataUsageSummary extends Fragment {
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);
final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE);
final int cycleDay = editor.getPolicyCycleDay(template);
cycleDayPicker.setMinValue(1);
cycleDayPicker.setMaxValue(31);
cycleDayPicker.setValue(oldCycleDay);
cycleDayPicker.setValue(cycleDay);
cycleDayPicker.setWrapSelectorWheel(true);
builder.setTitle(R.string.data_usage_cycle_editor_title);
@@ -1565,10 +1632,8 @@ public class DataUsageSummary extends Fragment {
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);
}
editor.setPolicyCycleDay(template, cycleDay);
target.updatePolicy(true);
}
});
@@ -1576,6 +1641,125 @@ public class DataUsageSummary extends Fragment {
}
}
/**
* Dialog to edit {@link NetworkPolicy#warningBytes}.
*/
public static class WarningEditorFragment extends DialogFragment {
private static final String EXTRA_TEMPLATE = "template";
public static void show(DataUsageSummary parent) {
final Bundle args = new Bundle();
args.putParcelable(EXTRA_TEMPLATE, parent.mTemplate);
final WarningEditorFragment dialog = new WarningEditorFragment();
dialog.setArguments(args);
dialog.setTargetFragment(parent, 0);
dialog.show(parent.getFragmentManager(), TAG_WARNING_EDITOR);
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Context context = getActivity();
final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
final NetworkPolicyEditor editor = target.mPolicyEditor;
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
final View view = dialogInflater.inflate(R.layout.data_usage_bytes_editor, null, false);
final NumberPicker bytesPicker = (NumberPicker) view.findViewById(R.id.bytes);
final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE);
final long warningBytes = editor.getPolicyWarningBytes(template);
final long limitBytes = editor.getPolicyLimitBytes(template);
bytesPicker.setMinValue(0);
if (limitBytes != LIMIT_DISABLED) {
bytesPicker.setMaxValue((int) (limitBytes / MB_IN_BYTES) - 1);
} else {
bytesPicker.setMaxValue(Integer.MAX_VALUE);
}
bytesPicker.setValue((int) (warningBytes / MB_IN_BYTES));
bytesPicker.setWrapSelectorWheel(false);
builder.setTitle(R.string.data_usage_warning_editor_title);
builder.setView(view);
builder.setPositiveButton(R.string.data_usage_cycle_editor_positive,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
// clear focus to finish pending text edits
bytesPicker.clearFocus();
final long bytes = bytesPicker.getValue() * MB_IN_BYTES;
editor.setPolicyWarningBytes(template, bytes);
target.updatePolicy(false);
}
});
return builder.create();
}
}
/**
* Dialog to edit {@link NetworkPolicy#limitBytes}.
*/
public static class LimitEditorFragment extends DialogFragment {
private static final String EXTRA_TEMPLATE = "template";
public static void show(DataUsageSummary parent) {
final Bundle args = new Bundle();
args.putParcelable(EXTRA_TEMPLATE, parent.mTemplate);
final LimitEditorFragment dialog = new LimitEditorFragment();
dialog.setArguments(args);
dialog.setTargetFragment(parent, 0);
dialog.show(parent.getFragmentManager(), TAG_LIMIT_EDITOR);
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Context context = getActivity();
final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
final NetworkPolicyEditor editor = target.mPolicyEditor;
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
final View view = dialogInflater.inflate(R.layout.data_usage_bytes_editor, null, false);
final NumberPicker bytesPicker = (NumberPicker) view.findViewById(R.id.bytes);
final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE);
final long warningBytes = editor.getPolicyWarningBytes(template);
final long limitBytes = editor.getPolicyLimitBytes(template);
bytesPicker.setMaxValue(Integer.MAX_VALUE);
if (warningBytes != WARNING_DISABLED) {
bytesPicker.setMinValue((int) (warningBytes / MB_IN_BYTES) + 1);
} else {
bytesPicker.setMinValue(0);
}
bytesPicker.setValue((int) (limitBytes / MB_IN_BYTES));
bytesPicker.setWrapSelectorWheel(false);
builder.setTitle(R.string.data_usage_limit_editor_title);
builder.setView(view);
builder.setPositiveButton(R.string.data_usage_cycle_editor_positive,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
// clear focus to finish pending text edits
bytesPicker.clearFocus();
final long bytes = bytesPicker.getValue() * MB_IN_BYTES;
editor.setPolicyLimitBytes(template, bytes);
target.updatePolicy(false);
}
});
return builder.create();
}
}
/**
* Dialog to request user confirmation before disabling data.
*/
@@ -1661,7 +1845,7 @@ public class DataUsageSummary extends Fragment {
final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
if (target != null) {
final CharSequence limitedNetworks = target.buildLimitedNetworksList();
final CharSequence limitedNetworks = target.buildLimitedNetworksString();
builder.setMessage(
getString(R.string.data_usage_restrict_background, limitedNetworks));
}
@@ -1680,6 +1864,31 @@ public class DataUsageSummary extends Fragment {
}
}
/**
* Dialog to inform user that {@link #POLICY_REJECT_METERED_BACKGROUND}
* change has been denied, usually based on
* {@link DataUsageSummary#hasLimitedNetworks()}.
*/
public static class DeniedRestrictFragment extends DialogFragment {
public static void show(DataUsageSummary parent) {
final DeniedRestrictFragment dialog = new DeniedRestrictFragment();
dialog.setTargetFragment(parent, 0);
dialog.show(parent.getFragmentManager(), TAG_DENIED_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_background);
builder.setMessage(R.string.data_usage_restrict_denied_dialog);
builder.setPositiveButton(android.R.string.ok, null);
return builder.create();
}
}
/**
* Dialog to request user confirmation before setting
* {@link #POLICY_REJECT_METERED_BACKGROUND}.
@@ -1878,11 +2087,33 @@ public class DataUsageSummary extends Fragment {
return view;
}
/**
* Test if any networks are currently limited.
*/
private boolean hasLimitedNetworks() {
return !buildLimitedNetworksList().isEmpty();
}
/**
* Build string describing currently limited networks, which defines when
* background data is restricted.
*/
private CharSequence buildLimitedNetworksList() {
private CharSequence buildLimitedNetworksString() {
final List<CharSequence> limited = buildLimitedNetworksList();
// handle case where no networks limited
if (limited.isEmpty()) {
limited.add(getText(R.string.data_usage_list_none));
}
return TextUtils.join(limited);
}
/**
* Build list of currently limited networks, which defines when background
* data is restricted.
*/
private List<CharSequence> buildLimitedNetworksList() {
final Context context = getActivity();
final String subscriberId = getActiveSubscriberId(context);
@@ -1904,12 +2135,7 @@ public class DataUsageSummary extends Fragment {
limited.add(getText(R.string.data_usage_tab_ethernet));
}
// handle case where no networks limited
if (limited.isEmpty()) {
limited.add(getText(R.string.data_usage_list_none));
}
return TextUtils.join(limited);
return limited;
}
/**

View File

@@ -149,6 +149,10 @@ public class NetworkPolicyEditor {
template, cycleDay, WARNING_DISABLED, LIMIT_DISABLED, SNOOZE_NEVER);
}
public int getPolicyCycleDay(NetworkTemplate template) {
return getPolicy(template).cycleDay;
}
public void setPolicyCycleDay(NetworkTemplate template, int cycleDay) {
final NetworkPolicy policy = getOrCreatePolicy(template);
policy.cycleDay = cycleDay;
@@ -156,6 +160,10 @@ public class NetworkPolicyEditor {
writeAsync();
}
public long getPolicyWarningBytes(NetworkTemplate template) {
return getPolicy(template).warningBytes;
}
public void setPolicyWarningBytes(NetworkTemplate template, long warningBytes) {
final NetworkPolicy policy = getOrCreatePolicy(template);
policy.warningBytes = warningBytes;
@@ -163,6 +171,10 @@ public class NetworkPolicyEditor {
writeAsync();
}
public long getPolicyLimitBytes(NetworkTemplate template) {
return getPolicy(template).limitBytes;
}
public void setPolicyLimitBytes(NetworkTemplate template, long limitBytes) {
final NetworkPolicy policy = getOrCreatePolicy(template);
policy.limitBytes = limitBytes;

View File

@@ -69,6 +69,8 @@ public class ChartDataUsageView extends ChartView {
public void onInspectRangeChanged();
public void onWarningChanged();
public void onLimitChanged();
public void requestWarningEdit();
public void requestLimitEdit();
}
private DataUsageChartListener mListener;
@@ -123,6 +125,15 @@ public class ChartDataUsageView extends ChartView {
mSweepWarning.addOnSweepListener(mVertListener);
mSweepLimit.addOnSweepListener(mVertListener);
mSweepWarning.setDragInterval(5 * MB_IN_BYTES);
mSweepLimit.setDragInterval(5 * MB_IN_BYTES);
// TODO: make time sweeps adjustable through dpad
mSweepLeft.setClickable(false);
mSweepLeft.setFocusable(false);
mSweepRight.setClickable(false);
mSweepRight.setFocusable(false);
// tell everyone about our axis
mGrid.init(mHoriz, mVert);
mSeries.init(mHoriz, mVert);
@@ -276,6 +287,7 @@ public class ChartDataUsageView extends ChartView {
}
private OnSweepListener mHorizListener = new OnSweepListener() {
/** {@inheritDoc} */
public void onSweep(ChartSweepView sweep, boolean sweepDone) {
updatePrimaryRange();
@@ -284,6 +296,11 @@ public class ChartDataUsageView extends ChartView {
mListener.onInspectRangeChanged();
}
}
/** {@inheritDoc} */
public void requestEdit(ChartSweepView sweep) {
// ignored
}
};
private void sendUpdateAxisDelayed(ChartSweepView sweep, boolean force) {
@@ -298,6 +315,7 @@ public class ChartDataUsageView extends ChartView {
}
private OnSweepListener mVertListener = new OnSweepListener() {
/** {@inheritDoc} */
public void onSweep(ChartSweepView sweep, boolean sweepDone) {
if (sweepDone) {
clearUpdateAxisDelayed(sweep);
@@ -313,6 +331,15 @@ public class ChartDataUsageView extends ChartView {
sendUpdateAxisDelayed(sweep, false);
}
}
/** {@inheritDoc} */
public void requestEdit(ChartSweepView sweep) {
if (sweep == mSweepWarning && mListener != null) {
mListener.requestWarningEdit();
} else if (sweep == mSweepLimit && mListener != null) {
mListener.requestLimitEdit();
}
}
};
@Override
@@ -540,7 +567,7 @@ public class ChartDataUsageView extends ChartView {
final CharSequence unit;
final long unitFactor;
if (value <= 100 * MB_IN_BYTES) {
if (value < 1000 * MB_IN_BYTES) {
unit = res.getText(com.android.internal.R.string.megabyteShort);
unitFactor = MB_IN_BYTES;
} else {
@@ -551,6 +578,7 @@ public class ChartDataUsageView extends ChartView {
final double result = (double) value / unitFactor;
final double resultRounded;
final CharSequence size;
if (result < 10) {
size = String.format("%.1f", result);
resultRounded = (unitFactor * Math.round(result * 10)) / 10;

View File

@@ -83,11 +83,22 @@ public class ChartSweepView extends View {
public static final int HORIZONTAL = 0;
public static final int VERTICAL = 1;
private int mTouchMode = MODE_NONE;
private static final int MODE_NONE = 0;
private static final int MODE_DRAG = 1;
private static final int MODE_LABEL = 2;
private long mDragInterval = 1;
public interface OnSweepListener {
public void onSweep(ChartSweepView sweep, boolean sweepDone);
public void requestEdit(ChartSweepView sweep);
}
private OnSweepListener mListener;
private float mTrackingStart;
private MotionEvent mTracking;
public ChartSweepView(Context context) {
@@ -112,15 +123,28 @@ public class ChartSweepView extends View {
setLabelTemplate(a.getResourceId(R.styleable.ChartSweepView_labelTemplate, 0));
setLabelColor(a.getColor(R.styleable.ChartSweepView_labelColor, Color.BLUE));
// TODO: moved focused state directly into assets
setBackgroundResource(R.drawable.data_usage_sweep_background);
mOutlinePaint.setColor(Color.RED);
mOutlinePaint.setStrokeWidth(1f);
mOutlinePaint.setStyle(Style.STROKE);
a.recycle();
setClickable(true);
setFocusable(true);
setOnClickListener(mClickListener);
setWillNotDraw(false);
}
private OnClickListener mClickListener = new OnClickListener() {
public void onClick(View v) {
dispatchRequestEdit();
}
};
void init(ChartAxis axis) {
mAxis = Preconditions.checkNotNull(axis, "missing axis");
}
@@ -133,6 +157,10 @@ public class ChartSweepView extends View {
return mMargins;
}
public void setDragInterval(long dragInterval) {
mDragInterval = dragInterval;
}
/**
* Return the number of pixels that the "target" area is inset from the
* {@link View} edge, along the current {@link #setFollowAxis(int)}.
@@ -159,9 +187,16 @@ public class ChartSweepView extends View {
}
}
private void dispatchRequestEdit() {
if (mListener != null) {
mListener.requestEdit(this);
}
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
setFocusable(enabled);
requestLayout();
}
@@ -232,6 +267,7 @@ public class ChartSweepView extends View {
private void invalidateLabel() {
if (mLabelTemplate != null && mAxis != null) {
mLabelValue = mAxis.buildLabel(getResources(), mLabelTemplate, mValue);
setContentDescription(mLabelTemplate);
invalidateLabelOffset();
invalidate();
} else {
@@ -369,11 +405,16 @@ public class ChartSweepView extends View {
case MotionEvent.ACTION_DOWN: {
// only start tracking when in sweet spot
final boolean accept;
final boolean acceptDrag;
final boolean acceptLabel;
if (mFollowAxis == VERTICAL) {
accept = event.getX() > getWidth() - (mSweepPadding.right * 8);
acceptDrag = event.getX() > getWidth() - (mSweepPadding.right * 8);
acceptLabel = mLabelLayout != null ? event.getX() < mLabelLayout.getWidth()
: false;
} else {
accept = event.getY() > getHeight() - (mSweepPadding.bottom * 8);
acceptDrag = event.getY() > getHeight() - (mSweepPadding.bottom * 8);
acceptLabel = mLabelLayout != null ? event.getY() < mLabelLayout.getHeight()
: false;
}
final MotionEvent eventInParent = event.copy();
@@ -385,56 +426,83 @@ public class ChartSweepView extends View {
return false;
}
if (accept) {
if (acceptDrag) {
if (mFollowAxis == VERTICAL) {
mTrackingStart = getTop() - mMargins.top;
} else {
mTrackingStart = getLeft() - mMargins.left;
}
mTracking = event.copy();
mTouchMode = MODE_DRAG;
// starting drag should activate entire chart
if (!parent.isActivated()) {
parent.setActivated(true);
}
return true;
} else if (acceptLabel) {
mTouchMode = MODE_LABEL;
return true;
} else {
mTouchMode = MODE_NONE;
return false;
}
}
case MotionEvent.ACTION_MOVE: {
if (mTouchMode == MODE_LABEL) {
return true;
}
getParent().requestDisallowInterceptTouchEvent(true);
// content area of parent
final Rect parentContent = getParentContentRect();
final Rect clampRect = computeClampRect(parentContent);
if (clampRect.isEmpty()) return true;
long value;
if (mFollowAxis == VERTICAL) {
final float currentTargetY = getTop() - mMargins.top;
final float requestedTargetY = currentTargetY
final float requestedTargetY = mTrackingStart
+ (event.getRawY() - mTracking.getRawY());
final float clampedTargetY = MathUtils.constrain(
requestedTargetY, clampRect.top, clampRect.bottom);
setTranslationY(clampedTargetY - currentTargetY);
setValue(mAxis.convertToValue(clampedTargetY - parentContent.top));
value = mAxis.convertToValue(clampedTargetY - parentContent.top);
} else {
final float currentTargetX = getLeft() - mMargins.left;
final float requestedTargetX = currentTargetX
final float requestedTargetX = mTrackingStart
+ (event.getRawX() - mTracking.getRawX());
final float clampedTargetX = MathUtils.constrain(
requestedTargetX, clampRect.left, clampRect.right);
setTranslationX(clampedTargetX - currentTargetX);
setValue(mAxis.convertToValue(clampedTargetX - parentContent.left));
value = mAxis.convertToValue(clampedTargetX - parentContent.left);
}
// round value from drag to nearest increment
value -= value % mDragInterval;
setValue(value);
dispatchOnSweep(false);
return true;
}
case MotionEvent.ACTION_UP: {
mTracking = null;
mValue = mLabelValue;
dispatchOnSweep(true);
setTranslationX(0);
setTranslationY(0);
requestLayout();
if (mTouchMode == MODE_LABEL) {
performClick();
} else if (mTouchMode == MODE_DRAG) {
mTrackingStart = 0;
mTracking = null;
mValue = mLabelValue;
dispatchOnSweep(true);
setTranslationX(0);
setTranslationY(0);
requestLayout();
}
mTouchMode = MODE_NONE;
return true;
}
default: {
@@ -501,7 +569,9 @@ public class ChartSweepView extends View {
final Rect dynamicRect = buildClampRect(
parentContent, getValidAfterDynamic(), getValidBeforeDynamic(), mNeighborMargin);
rect.intersect(dynamicRect);
if (!rect.intersect(dynamicRect)) {
rect.setEmpty();
}
return rect;
}
@@ -587,7 +657,7 @@ public class ChartSweepView extends View {
mContentOffset.bottom -= offset;
mMargins.bottom += offset;
} else {
final int heightAfter = heightBefore * 3;
final int heightAfter = heightBefore * 2;
setMeasuredDimension(widthBefore, heightAfter);
mContentOffset.offset(0, (heightAfter - heightBefore) / 2);
@@ -608,13 +678,11 @@ public class ChartSweepView extends View {
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
final int width = getWidth();
final int height = getHeight();
if (DRAW_OUTLINE) {
canvas.drawRect(0, 0, width, height, mOutlinePaint);
}
final int labelSize;
if (isEnabled() && mLabelLayout != null) {
final int count = canvas.save();
@@ -637,6 +705,11 @@ public class ChartSweepView extends View {
}
mSweep.draw(canvas);
if (DRAW_OUTLINE) {
mOutlinePaint.setColor(Color.RED);
canvas.drawRect(0, 0, width, height, mOutlinePaint);
}
}
public static float getLabelTop(ChartSweepView view) {