1adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project/*
2320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson * Copyright (C) 2011 The Android Open Source Project
3320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson *
4320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson * Licensed under the Apache License, Version 2.0 (the "License");
5320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson * you may not use this file except in compliance with the License.
6320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson * You may obtain a copy of the License at
7320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson *
8adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project *      http://www.apache.org/licenses/LICENSE-2.0
9320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson *
10adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project * Unless required by applicable law or agreed to in writing, software
11adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project * distributed under the License is distributed on an "AS IS" BASIS,
12adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project * See the License for the specific language governing permissions and
14adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project * limitations under the License.
15adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project */
16adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
17adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Projectpackage com.android.settings.widget;
18320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
19320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilsonimport static android.net.TrafficStats.MB_IN_BYTES;
20adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
21adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Projectimport android.content.Context;
22adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Projectimport android.content.res.Resources;
23adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Projectimport android.net.NetworkPolicy;
24320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilsonimport android.net.NetworkStatsHistory;
25320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilsonimport android.net.TrafficStats;
26adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Projectimport android.os.Handler;
27320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilsonimport android.os.Message;
28320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilsonimport android.text.Spannable;
29320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilsonimport android.text.SpannableStringBuilder;
30320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilsonimport android.text.TextUtils;
31320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilsonimport android.text.format.DateUtils;
32adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Projectimport android.text.format.Formatter;
33adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Projectimport android.text.format.Formatter.BytesResult;
34320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilsonimport android.text.format.Time;
35adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Projectimport android.util.AttributeSet;
36320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilsonimport android.util.Log;
37320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilsonimport android.util.MathUtils;
38320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilsonimport android.view.MotionEvent;
39320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilsonimport android.view.View;
40320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
41320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilsonimport com.android.settings.R;
42f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughesimport com.android.settings.widget.ChartSweepView.OnSweepListener;
43320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
44320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilsonimport java.util.Arrays;
45adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Projectimport java.util.Calendar;
46adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Projectimport java.util.Objects;
47320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
48320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson/**
49320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson * Specific {@link ChartView} that displays {@link ChartNetworkSeriesView} along
50320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson * with {@link ChartSweepView} for inspection ranges and warning/limits.
51320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson */
52adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Projectpublic class ChartDataUsageView extends ChartView {
53320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
54320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson    private static final int MSG_UPDATE_AXIS = 100;
55320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson    private static final long DELAY_MILLIS = 250;
56320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
57320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson    private ChartGridView mGrid;
58adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    private ChartNetworkSeriesView mSeries;
59adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    private ChartNetworkSeriesView mDetailSeries;
60320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
61320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson    private NetworkStatsHistory mHistory;
62320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
63320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson    private ChartSweepView mSweepWarning;
64320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson    private ChartSweepView mSweepLimit;
65320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
66320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson    private long mInspectStart;
67320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson    private long mInspectEnd;
68320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
69320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson    private Handler mHandler;
70320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
71320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson    /** Current maximum value of {@link #mVert}. */
72320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson    private long mVertMax;
73320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
74320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson    public interface DataUsageChartListener {
75320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        public void onWarningChanged();
76320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        public void onLimitChanged();
77320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        public void requestWarningEdit();
78320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        public void requestLimitEdit();
79320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson    }
80320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
81320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson    private DataUsageChartListener mListener;
82320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
83320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson    public ChartDataUsageView(Context context) {
84320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        this(context, null, 0);
85320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson    }
86adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
87320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson    public ChartDataUsageView(Context context, AttributeSet attrs) {
88320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        this(context, attrs, 0);
89320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson    }
90320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
91320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson    public ChartDataUsageView(Context context, AttributeSet attrs, int defStyle) {
92320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        super(context, attrs, defStyle);
93adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        init(new TimeAxis(), new InvertedChartAxis(new DataAxis()));
94f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes
95320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        mHandler = new Handler() {
96320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            @Override
97f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes            public void handleMessage(Message msg) {
98f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes                final ChartSweepView sweep = (ChartSweepView) msg.obj;
99adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project                updateVertAxisBounds(sweep);
100320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson                updateEstimateVisible();
101320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
102320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson                // we keep dispatching repeating updates until sweep is dropped
103adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project                sendUpdateAxisDelayed(sweep, true);
104320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            }
105320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        };
106adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
107f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes
108320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson    @Override
109320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson    protected void onFinishInflate() {
110320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        super.onFinishInflate();
111adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
112320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        mGrid = (ChartGridView) findViewById(R.id.grid);
113320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        mSeries = (ChartNetworkSeriesView) findViewById(R.id.series);
114320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        mDetailSeries = (ChartNetworkSeriesView) findViewById(R.id.detail_series);
115320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        mDetailSeries.setVisibility(View.GONE);
116f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes
117320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        mSweepLimit = (ChartSweepView) findViewById(R.id.sweep_limit);
118320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        mSweepWarning = (ChartSweepView) findViewById(R.id.sweep_warning);
119320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
120320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        // prevent sweeps from crossing each other
121320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        mSweepWarning.setValidRangeDynamic(null, mSweepLimit);
122320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        mSweepLimit.setValidRangeDynamic(mSweepWarning, null);
123320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
124320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        // mark neighbors for checking touch events against
125f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes        mSweepLimit.setNeighbors(mSweepWarning);
126320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        mSweepWarning.setNeighbors(mSweepLimit);
127320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
128320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        mSweepWarning.addOnSweepListener(mVertListener);
129320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        mSweepLimit.addOnSweepListener(mVertListener);
130320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
131320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        mSweepWarning.setDragInterval(5 * MB_IN_BYTES);
132320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        mSweepLimit.setDragInterval(5 * MB_IN_BYTES);
133f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes
134320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        // tell everyone about our axis
135320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        mGrid.init(mHoriz, mVert);
136320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        mSeries.init(mHoriz, mVert);
137f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes        mDetailSeries.init(mHoriz, mVert);
138320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        mSweepWarning.init(mVert);
139320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        mSweepLimit.init(mVert);
140320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
141320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        setActivated(false);
142adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
143320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
144320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson    public void setListener(DataUsageChartListener listener) {
145320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        mListener = listener;
146320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson    }
147320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
148320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson    public void bindNetworkStats(NetworkStatsHistory stats) {
149320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        mSeries.bindNetworkStats(stats);
150320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        mHistory = stats;
151320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        updateVertAxisBounds(null);
152320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        updateEstimateVisible();
153320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        updatePrimaryRange();
154320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        requestLayout();
155adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
156320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
157320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson    public void bindDetailNetworkStats(NetworkStatsHistory stats) {
158320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        mDetailSeries.bindNetworkStats(stats);
159adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        mDetailSeries.setVisibility(stats != null ? View.VISIBLE : View.GONE);
160adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        if (mHistory != null) {
161adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            mDetailSeries.setEndTime(mHistory.getEnd());
162320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        }
163320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        updateVertAxisBounds(null);
164320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        updateEstimateVisible();
165320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        updatePrimaryRange();
166320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        requestLayout();
167320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson    }
168adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
169f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes    public void bindNetworkPolicy(NetworkPolicy policy) {
170320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        if (policy == null) {
171320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            mSweepLimit.setVisibility(View.INVISIBLE);
172adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            mSweepLimit.setValue(-1);
173adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            mSweepWarning.setVisibility(View.INVISIBLE);
174adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            mSweepWarning.setValue(-1);
175320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            return;
176320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        }
177320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
178f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes        if (policy.limitBytes != NetworkPolicy.LIMIT_DISABLED) {
179320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            mSweepLimit.setVisibility(View.VISIBLE);
180adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            mSweepLimit.setEnabled(true);
181f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes            mSweepLimit.setValue(policy.limitBytes);
182320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        } else {
183320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            mSweepLimit.setVisibility(View.INVISIBLE);
184adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            mSweepLimit.setEnabled(false);
185adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            mSweepLimit.setValue(-1);
186adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
187320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
188320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        if (policy.warningBytes != NetworkPolicy.WARNING_DISABLED) {
189320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            mSweepWarning.setVisibility(View.VISIBLE);
190320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            mSweepWarning.setValue(policy.warningBytes);
191320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        } else {
192320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            mSweepWarning.setVisibility(View.INVISIBLE);
193320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            mSweepWarning.setValue(-1);
194320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        }
195adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
196f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes        updateVertAxisBounds(null);
197320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        requestLayout();
198320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        invalidate();
199adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
200adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
201adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
202320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson     * Update {@link #mVert} to both show data from {@link NetworkStatsHistory}
203320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson     * and controls from {@link NetworkPolicy}.
204320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson     */
205320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson    private void updateVertAxisBounds(ChartSweepView activeSweep) {
206320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        final long max = mVertMax;
207adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
208f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes        long newMax = 0;
209adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        if (activeSweep != null) {
210adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            final int adjustAxis = activeSweep.shouldAdjustAxis();
211adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            if (adjustAxis > 0) {
212adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project                // hovering around upper edge, grow axis
213adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project                newMax = max * 11 / 10;
214320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            } else if (adjustAxis < 0) {
215320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson                // hovering around lower edge, shrink axis
216320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson                newMax = max * 9 / 10;
217320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            } else {
218320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson                newMax = max;
219adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            }
220f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes        }
221adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
222320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        // always show known data and policy lines
223adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        final long maxSweep = Math.max(mSweepWarning.getValue(), mSweepLimit.getValue());
224adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        final long maxSeries = Math.max(mSeries.getMaxVisible(), mDetailSeries.getMaxVisible());
225adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        final long maxVisible = Math.max(maxSeries, maxSweep) * 12 / 10;
226320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        final long maxDefault = Math.max(maxVisible, 50 * MB_IN_BYTES);
227320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        newMax = Math.max(maxDefault, newMax);
228320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
229320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        // only invalidate when vertMax actually changed
230320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        if (newMax != mVertMax) {
231320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            mVertMax = newMax;
232adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
233f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes            final boolean changed = mVert.setBounds(0L, newMax);
234320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            mSweepWarning.setValidRange(0L, newMax);
235320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            mSweepLimit.setValidRange(0L, newMax);
236adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
237adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            if (changed) {
238adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project                mSeries.invalidatePath();
239320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson                mDetailSeries.invalidatePath();
240320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            }
241320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
242320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            mGrid.invalidate();
243320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
244adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            // since we just changed axis, make sweep recalculate its value
245f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes            if (activeSweep != null) {
246320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson                activeSweep.updateValueFromPosition();
247320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            }
248320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
249adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            // layout other sweeps to match changed axis
250adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            // TODO: find cleaner way of doing this, such as requesting full
251320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            // layout and making activeSweep discard its tracking MotionEvent.
252320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            if (mSweepLimit != activeSweep) {
253320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson                layoutSweep(mSweepLimit);
254320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            }
255320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            if (mSweepWarning != activeSweep) {
256adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project                layoutSweep(mSweepWarning);
257f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes            }
258320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        }
259320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson    }
260adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
261adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
262adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * Control {@link ChartNetworkSeriesView#setEstimateVisible(boolean)} based
263320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson     * on how close estimate comes to {@link #mSweepWarning}.
264320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson     */
265320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson    private void updateEstimateVisible() {
266320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        final long maxEstimate = mSeries.getMaxEstimate();
267320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
268320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        // show estimate when near warning/limit
269320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        long interestLine = Long.MAX_VALUE;
270adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        if (mSweepWarning.isEnabled()) {
271f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes            interestLine = mSweepWarning.getValue();
272320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        } else if (mSweepLimit.isEnabled()) {
273320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            interestLine = mSweepLimit.getValue();
274320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        }
275adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
276adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        if (interestLine < 0) {
277320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            interestLine = Long.MAX_VALUE;
278320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        }
279320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
280320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        final boolean estimateVisible = (maxEstimate >= interestLine * 7 / 10);
281320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        mSeries.setEstimateVisible(estimateVisible);
282320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson    }
283adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
284320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson    private void sendUpdateAxisDelayed(ChartSweepView sweep, boolean force) {
285320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        if (force || !mHandler.hasMessages(MSG_UPDATE_AXIS, sweep)) {
286adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            mHandler.sendMessageDelayed(
287adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project                    mHandler.obtainMessage(MSG_UPDATE_AXIS, sweep), DELAY_MILLIS);
288320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        }
289320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson    }
290320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
291320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson    private void clearUpdateAxisDelayed(ChartSweepView sweep) {
292320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        mHandler.removeMessages(MSG_UPDATE_AXIS, sweep);
293320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson    }
294adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
295320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson    private OnSweepListener mVertListener = new OnSweepListener() {
296320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        @Override
297f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes        public void onSweep(ChartSweepView sweep, boolean sweepDone) {
298d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes            if (sweepDone) {
299d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes                clearUpdateAxisDelayed(sweep);
300f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes                updateEstimateVisible();
301d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes
302d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes                if (sweep == mSweepWarning && mListener != null) {
303d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes                    mListener.onWarningChanged();
304d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes                } else if (sweep == mSweepLimit && mListener != null) {
305d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes                    mListener.onLimitChanged();
306d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes                }
307d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes            } else {
308f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes                // while moving, kick off delayed grow/shrink axis updates
309d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes                sendUpdateAxisDelayed(sweep, false);
310d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes            }
311d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes        }
312d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes
313d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes        @Override
314d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes        public void requestEdit(ChartSweepView sweep) {
315d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes            if (sweep == mSweepWarning && mListener != null) {
316d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes                mListener.requestWarningEdit();
317d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes            } else if (sweep == mSweepLimit && mListener != null) {
318d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes                mListener.requestLimitEdit();
319d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes            }
320d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes        }
321d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes    };
322d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes
323d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes    @Override
324d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes    public boolean onTouchEvent(MotionEvent event) {
325f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes        if (isActivated()) return false;
326d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes        switch (event.getAction()) {
327d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes            case MotionEvent.ACTION_DOWN: {
328f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes                return true;
329d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes            }
330d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes            case MotionEvent.ACTION_UP: {
331320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson                setActivated(true);
332d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes                return true;
333d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes            }
334d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes            default: {
335320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson                return false;
336d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes            }
337d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes        }
338f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes    }
339d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes
340d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes    public long getInspectStart() {
341d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes        return mInspectStart;
342d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes    }
343d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes
344d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes    public long getInspectEnd() {
345d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes        return mInspectEnd;
346f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes    }
347d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes
348f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes    public long getWarningBytes() {
349d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes        return mSweepWarning.getLabelValue();
350f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes    }
351d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes
352d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes    public long getLimitBytes() {
353d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes        return mSweepLimit.getLabelValue();
354d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes    }
355d21d78fd49a2d798218e8c8aefbddb26a0e71bbbElliott Hughes
356f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes    /**
357adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * Set the exact time range that should be displayed, updating how
358320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson     * {@link ChartNetworkSeriesView} paints. Moves inspection ranges to be the
359320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson     * last "week" of available data, without triggering listener events.
360f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes     */
361f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes    public void setVisibleRange(long visibleStart, long visibleEnd) {
362320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        final boolean changed = mHoriz.setBounds(visibleStart, visibleEnd);
363320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        mGrid.setBounds(visibleStart, visibleEnd);
364320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        mSeries.setBounds(visibleStart, visibleEnd);
365320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        mDetailSeries.setBounds(visibleStart, visibleEnd);
366f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes
367320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        mInspectStart = visibleStart;
368320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        mInspectEnd = visibleEnd;
369320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
370320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        requestLayout();
371320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        if (changed) {
372f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes            mSeries.invalidatePath();
373320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            mDetailSeries.invalidatePath();
374adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
375320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
376320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        updateVertAxisBounds(null);
377320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        updateEstimateVisible();
378320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        updatePrimaryRange();
379320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson    }
380320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
381320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson    private void updatePrimaryRange() {
382320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        // prefer showing primary range on detail series, when available
383adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        if (mDetailSeries.getVisibility() == View.VISIBLE) {
384adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            mSeries.setSecondary(true);
385f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes        } else {
386adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            mSeries.setSecondary(false);
387320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        }
388320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson    }
389f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes
390320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson    public static class TimeAxis implements ChartAxis {
391320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        private static final int FIRST_DAY_OF_WEEK = Calendar.getInstance().getFirstDayOfWeek() - 1;
392320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
393320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        private long mMin;
394f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes        private long mMax;
395320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        private float mSize;
396320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
397320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        public TimeAxis() {
398320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            final long currentTime = System.currentTimeMillis();
399320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            setBounds(currentTime - DateUtils.DAY_IN_MILLIS * 30, currentTime);
400320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        }
401320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
402320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        @Override
403f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes        public int hashCode() {
404320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            return Objects.hash(mMin, mMax, mSize);
405320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        }
406320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
407320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        @Override
408f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes        public boolean setBounds(long min, long max) {
409f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes            if (mMin != min || mMax != max) {
410320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson                mMin = min;
411f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes                mMax = max;
412f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes                return true;
413320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            } else {
414320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson                return false;
415320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            }
416f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes        }
417320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
418320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        @Override
419320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        public boolean setSize(float size) {
420320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            if (mSize != size) {
421320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson                mSize = size;
422320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson                return true;
423320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            } else {
424f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes                return false;
425320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            }
426320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        }
427320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
428320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        @Override
429320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        public float convertToPoint(long value) {
430320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            return (mSize * (value - mMin)) / (mMax - mMin);
431f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes        }
432320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
433f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes        @Override
434320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        public long convertToValue(float point) {
435320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            return (long) (mMin + ((point * (mMax - mMin)) / mSize));
436320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        }
437320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
438f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes        @Override
439320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        public long buildLabel(Resources res, SpannableStringBuilder builder, long value) {
440adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            // TODO: convert to better string
441320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            builder.replace(0, builder.length(), Long.toString(value));
442320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            return value;
443320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        }
444320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
445320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        @Override
446320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        public float[] getTickPoints() {
447320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            final float[] ticks = new float[32];
448320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            int i = 0;
449adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
450f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes            // tick mark for first day of each week
451adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            final Time time = new Time();
452320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            time.set(mMax);
453f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes            time.monthDay -= time.weekDay - FIRST_DAY_OF_WEEK;
454320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            time.hour = time.minute = time.second = 0;
455320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
456320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            time.normalize(true);
457f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes            long timeMillis = time.toMillis(true);
458320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            while (timeMillis > mMin) {
459f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes                if (timeMillis <= mMax) {
460320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson                    ticks[i++] = convertToPoint(timeMillis);
461320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson                }
462f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes                time.monthDay -= 7;
463320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson                time.normalize(true);
464320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson                timeMillis = time.toMillis(true);
465320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            }
466320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
467f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes            return Arrays.copyOf(ticks, i);
468320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        }
469adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
470320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        @Override
471320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        public int shouldAdjustAxis(long value) {
472320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            // time axis never adjusts
473320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            return 0;
474320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        }
475320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson    }
476320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
477320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson    public static class DataAxis implements ChartAxis {
478adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        private long mMin;
479adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        private long mMax;
480adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        private float mSize;
481320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
482f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes        private static final boolean LOG_SCALE = false;
483320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
484f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes        @Override
485320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        public int hashCode() {
486320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            return Objects.hash(mMin, mMax, mSize);
487320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        }
488320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
489f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes        @Override
490320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        public boolean setBounds(long min, long max) {
491adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            if (mMin != min || mMax != max) {
492320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson                mMin = min;
493320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson                mMax = max;
494320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson                return true;
495320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            } else {
496320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson                return false;
497320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson            }
498320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson        }
499320c9890e8241fb0ad05de6fa5e6c3eb3aece159Jesse Wilson
500adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        @Override
501adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        public boolean setSize(float size) {
502            if (mSize != size) {
503                mSize = size;
504                return true;
505            } else {
506                return false;
507            }
508        }
509
510        @Override
511        public float convertToPoint(long value) {
512            if (LOG_SCALE) {
513                // derived polynomial fit to make lower values more visible
514                final double normalized = ((double) value - mMin) / (mMax - mMin);
515                final double fraction = Math.pow(10,
516                        0.36884343106175121463 * Math.log10(normalized) + -0.04328199452018252624);
517                return (float) (fraction * mSize);
518            } else {
519                return (mSize * (value - mMin)) / (mMax - mMin);
520            }
521        }
522
523        @Override
524        public long convertToValue(float point) {
525            if (LOG_SCALE) {
526                final double normalized = point / mSize;
527                final double fraction = 1.3102228476089056629
528                        * Math.pow(normalized, 2.7111774693164631640);
529                return (long) (mMin + (fraction * (mMax - mMin)));
530            } else {
531                return (long) (mMin + ((point * (mMax - mMin)) / mSize));
532            }
533        }
534
535        private static final Object sSpanSize = new Object();
536        private static final Object sSpanUnit = new Object();
537
538        @Override
539        public long buildLabel(Resources res, SpannableStringBuilder builder, long value) {
540            value = MathUtils.constrain(value, 0, TrafficStats.TB_IN_BYTES);
541            final BytesResult result = Formatter.formatBytes(res, value,
542                    Formatter.FLAG_SHORTER | Formatter.FLAG_CALCULATE_ROUNDED);
543            setText(builder, sSpanSize, result.value, "^1");
544            setText(builder, sSpanUnit, result.units, "^2");
545            return result.roundedBytes;
546        }
547
548        @Override
549        public float[] getTickPoints() {
550            final long range = mMax - mMin;
551
552            // target about 16 ticks on screen, rounded to nearest power of 2
553            final long tickJump = roundUpToPowerOfTwo(range / 16);
554            final int tickCount = (int) (range / tickJump);
555            final float[] tickPoints = new float[tickCount];
556            long value = mMin;
557            for (int i = 0; i < tickPoints.length; i++) {
558                tickPoints[i] = convertToPoint(value);
559                value += tickJump;
560            }
561
562            return tickPoints;
563        }
564
565        @Override
566        public int shouldAdjustAxis(long value) {
567            final float point = convertToPoint(value);
568            if (point < mSize * 0.1) {
569                return -1;
570            } else if (point > mSize * 0.85) {
571                return 1;
572            } else {
573                return 0;
574            }
575        }
576    }
577
578    private static void setText(
579            SpannableStringBuilder builder, Object key, CharSequence text, String bootstrap) {
580        int start = builder.getSpanStart(key);
581        int end = builder.getSpanEnd(key);
582        if (start == -1) {
583            start = TextUtils.indexOf(builder, bootstrap);
584            end = start + bootstrap.length();
585            builder.setSpan(key, start, end, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
586        }
587        builder.replace(start, end, text);
588    }
589
590    private static long roundUpToPowerOfTwo(long i) {
591        // NOTE: borrowed from Hashtable.roundUpToPowerOfTwo()
592
593        i--; // If input is a power of two, shift its high-order bit right
594
595        // "Smear" the high-order bit all the way to the right
596        i |= i >>>  1;
597        i |= i >>>  2;
598        i |= i >>>  4;
599        i |= i >>>  8;
600        i |= i >>> 16;
601        i |= i >>> 32;
602
603        i++;
604
605        return i > 0 ? i : Long.MAX_VALUE;
606    }
607}
608