• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package org.drrickorang.loopback;
18 
19 import android.content.Context;
20 import android.graphics.Canvas;
21 import android.graphics.Color;
22 import android.graphics.Paint;
23 import android.graphics.Rect;
24 import android.util.AttributeSet;
25 import android.util.Log;
26 import android.view.View;
27 
28 
29 /**
30  * This is the histogram used to show recorder/player buffer period.
31  */
32 
33 public class HistogramView extends View {
34     private static final String TAG = "HistogramView";
35 
36 
37     private Paint mHistPaint;
38     private Paint mTextPaint;
39     private Paint mLinePaint;
40     private Paint mXLabelPaint;
41 
42     private int[] mData; // data for buffer period
43     private int[] mDisplayData; // modified data that is used to draw histogram
44     private int mMaxBufferPeriod = 0;
45     // number of x-axis labels excluding the last x-axis label
46     private int mNumberOfXLabel = 5;  // mNumberOfXLabel must > 0
47 
48     private final int mYAxisBase = 10; // base of y-axis log scale
49     private final int mYLabelSize = 40;
50     private final int mXLabelSize = 40;
51     private final int mLineWidth = 3;
52     private final int mMaxNumberOfBeams = 202; // the max amount of beams to display on the screen
53 
54     // Note: if want to change this to base other than 10, must change the way x labels are
55     // displayed. It's currently half-hardcoded.
56     private final int mBucketBase = 10; // a bucket's range
57 
58 
HistogramView(Context context, AttributeSet attrs)59     public HistogramView(Context context, AttributeSet attrs) {
60         super(context, attrs);
61         initPaints();
62     }
63 
64 
65     /** Initiate all the Paint objects. */
initPaints()66     private void initPaints() {
67         mHistPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
68         mHistPaint.setStyle(Paint.Style.FILL);
69         mHistPaint.setColor(Color.BLUE);
70 
71         mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
72         mTextPaint.setColor(Color.BLACK);
73         mTextPaint.setTextSize(mYLabelSize);
74 
75         mXLabelPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
76         mXLabelPaint.setColor(Color.BLACK);
77         mXLabelPaint.setTextSize(mXLabelSize);
78 
79         mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
80         mLinePaint.setColor(Color.BLACK);
81         mLinePaint.setStrokeWidth(mLineWidth);
82     }
83 
84     @Override
onDraw(Canvas canvas)85     protected void onDraw(Canvas canvas) {
86         super.onDraw(canvas);
87         fillCanvas(canvas, this.getRight(), this.getBottom());
88     }
89 
90     // also called in LoopbackActivity.java
fillCanvas(Canvas canvas, int right, int bottom)91     void fillCanvas(Canvas canvas, int right, int bottom) {
92         canvas.drawColor(Color.GRAY);
93 
94         if (mData == null || mData.length == 0) {
95             return;
96         }
97 
98         int arrayLength = mData.length;
99         boolean exceedBufferPeriodRange;
100         if (mMaxBufferPeriod != 0) {
101             final int extraYMargin = 5; // the extra margin between y labels and y-axis
102             final int beamInterval = 2; // separate each beam in the histogram by such amount
103             int range; // the number of beams that's going to be displayed on histogram
104             if (mMaxBufferPeriod > arrayLength - 1) {
105                 range = arrayLength;
106                 exceedBufferPeriodRange = true;
107             } else {
108                 range = mMaxBufferPeriod + 1;
109                 exceedBufferPeriodRange = false;
110             }
111 
112             if (range == 0) {
113                 return;
114             }
115 
116             boolean isUsingDisplayData = false;
117             int oldRange = range;
118             int interval = 1;
119 
120             // if there are more beams than allowed to be displayed on screen,
121             // put beams into buckets
122             if (range > mMaxNumberOfBeams) {
123                 isUsingDisplayData = true;
124                 int bucketOrder = 0;
125                 if (exceedBufferPeriodRange) { // there should be one extra beam for 101+
126                     range -= 2;
127                     while (range > mMaxNumberOfBeams - 2) {
128                         range /= mBucketBase;
129                         bucketOrder++;
130                     }
131                     range += 2; // assuming always XXX1+, not something like 0~473, 474+.
132 
133                 } else {
134                     range--;
135                     int temp = range;
136                     while (range > mMaxNumberOfBeams - 2) {
137                         range /= mBucketBase;
138                         bucketOrder++;
139                     }
140 
141                     if ((temp % mBucketBase) != 0) {
142                         range += 2;
143                     } else {
144                         range++;
145                     }
146                 }
147 
148                 interval = (int) Math.pow(mBucketBase, bucketOrder);
149                 mDisplayData = new int[mMaxNumberOfBeams];
150                 mDisplayData[0] = mData[0];
151 
152                 // putting data into buckets.
153                 for (int i = 1; i < (range - 1); i++) {
154                     for (int j = (((i - 1) * interval) + 1); (j <= (i * interval)); j++) {
155                         mDisplayData[i] += mData[j];
156                     }
157                 }
158 
159                 if (exceedBufferPeriodRange) {
160                     mDisplayData[range - 1] = mData[oldRange - 1];
161                 } else {
162                     for (int i = (((range - 2) * interval) + 1); i < oldRange; i++) {
163                         mDisplayData[range - 1] += mData[i];
164                     }
165                 }
166             } else {
167                 mDisplayData = mData;
168             }
169 
170             // calculate the max frequency among all latencies
171             int maxBufferPeriodFreq = 0;
172             for (int i = 1; i < range; i++) {
173                 if (mDisplayData[i] > maxBufferPeriodFreq) {
174                     maxBufferPeriodFreq = mDisplayData[i];
175                 }
176             }
177 
178             if (maxBufferPeriodFreq == 0) {
179                 return;
180             }
181 
182             // find the closest order of "mYAxisBase" according to maxBufferPeriodFreq
183             int order = (int) Math.ceil((Math.log10(maxBufferPeriodFreq)) /
184                         (Math.log10(mYAxisBase)));
185             float height = ((float) (bottom - mXLabelSize - mLineWidth) / (order + 1));
186 
187             // y labels
188             String[] yLabels = new String[order + 2]; // store {"0", "1", "10", ...} for base = 10
189             yLabels[0] = "0";
190             int yStartPoint = bottom - mXLabelSize - mLineWidth;
191             canvas.drawText(yLabels[0], 0, yStartPoint, mTextPaint);
192             int currentValue = 1;
193             for (int i = 1; i < yLabels.length; i++) {
194                 yLabels[i] = Integer.toString(currentValue);
195                 // Label is displayed at lower than it should be by the amount of "mYLabelSize"
196                 canvas.drawText(yLabels[i], 0, yStartPoint - (i * height) + mYLabelSize,
197                         mTextPaint);
198                 currentValue *= mYAxisBase;
199             }
200 
201             // draw x axis
202             canvas.drawLine(0, bottom - mXLabelSize, right, bottom - mXLabelSize, mLinePaint);
203 
204             // draw y axis
205             int yMargin = getTextWidth(yLabels[order + 1], mTextPaint);
206             canvas.drawLine(yMargin + extraYMargin, bottom, yMargin + extraYMargin,
207                     0, mLinePaint);
208 
209             // width of each beam in the histogram
210             float width = ((float) (right - yMargin - extraYMargin - mLineWidth -
211                           (range * beamInterval)) / range);
212 
213             // draw x labels
214             String lastXLabel;
215             int xLabelInterval;
216             int xStartPoint = yMargin + extraYMargin + mLineWidth;  // position of first beam
217             String[] xLabels;
218 
219             // mNumberOfXLabel includes "0" but excludes the last label, which will be at last beam
220             // if mNumberOfXLabel exceeds the total beams that's going to have, reduce its value
221             if (mNumberOfXLabel - 1 > range - 2) {
222                 mNumberOfXLabel = range - 1;
223             }
224 
225             //
226             if (!isUsingDisplayData) { // in this case each beam represent one buffer period
227                 if ((range - 2) < mNumberOfXLabel) {
228                     xLabelInterval = 1;
229                 } else {
230                     xLabelInterval = (range - 2) / mNumberOfXLabel;
231                 }
232 
233                 xLabels = new String[mNumberOfXLabel];
234                 xLabels[0] = "0";       // first label is for 0
235                 canvas.drawText(xLabels[0], yMargin + extraYMargin + mLineWidth, bottom,
236                         mXLabelPaint);
237 
238                 float xLabelLineStartX;
239                 float xLabelLineStartY;
240                 int xLabelLineLength = 10;
241                 for (int i = 1; i < mNumberOfXLabel; i++) {
242                     xLabelLineStartX = xStartPoint +
243                                        (xLabelInterval * i * (width + beamInterval));
244                     xLabels[i] = Integer.toString(i * xLabelInterval);
245                     canvas.drawText(xLabels[i], xLabelLineStartX, bottom, mXLabelPaint);
246 
247                     //add a vertical line to indicate label's corresponding beams
248                     xLabelLineStartY = bottom - mXLabelSize;
249                     canvas.drawLine(xLabelLineStartX, xLabelLineStartY, xLabelLineStartX,
250                                     xLabelLineStartY - xLabelLineLength, mLinePaint);
251                 }
252 
253                 // last label is for the last beam
254                 if (exceedBufferPeriodRange) {
255                     lastXLabel = Integer.toString(range - 1) + "+";
256                 } else {
257                     lastXLabel = Integer.toString(range - 1);
258                 }
259 
260                 canvas.drawText(lastXLabel, right - getTextWidth(lastXLabel, mXLabelPaint) - 1,
261                         bottom, mXLabelPaint);
262 
263             } else {    // in this case each beam represent a range of buffer period
264                 // if mNumberOfXLabel exceeds amount of beams, decrease mNumberOfXLabel
265                 if ((range - 2) < mNumberOfXLabel) {
266                     xLabelInterval = 1;
267                 } else {
268                     xLabelInterval = (range - 2) / mNumberOfXLabel;
269                 }
270 
271                 xLabels = new String[mNumberOfXLabel];
272                 xLabels[0] = "0";       // first label is for 0ms
273                 canvas.drawText(xLabels[0], yMargin + extraYMargin + mLineWidth, bottom,
274                         mXLabelPaint);
275 
276                 // draw all the middle labels
277                 for (int i = 1; i < mNumberOfXLabel; i++) {
278                     xLabels[i] = Integer.toString((i * xLabelInterval) - 1) + "1-" +
279                                  Integer.toString(i * xLabelInterval) + "0";
280                     canvas.drawText(xLabels[i], xStartPoint + (xLabelInterval * i *
281                             (width + beamInterval)), bottom, mXLabelPaint);
282                 }
283 
284                 // draw the last label for the last beam
285                 if (exceedBufferPeriodRange) {
286                     lastXLabel = Integer.toString(oldRange - 1) + "+";
287                 } else {
288                     if ((((range - 2) * interval) + 1) == oldRange - 1) {
289                         lastXLabel = Integer.toString(oldRange - 1);
290                     } else {
291                         lastXLabel = Integer.toString(range - 2) + "1-" +
292                                 Integer.toString(oldRange - 1);
293                     }
294                 }
295 
296                 canvas.drawText(lastXLabel, right - getTextWidth(lastXLabel, mXLabelPaint) - 1,
297                         bottom, mXLabelPaint);
298             }
299 
300             // draw the histogram
301             float currentLeft = yMargin + extraYMargin + mLineWidth;
302             float currentTop;
303             float currentRight;
304             int currentBottom = bottom - mXLabelSize - mLineWidth;
305             for (int i = 0; i < range; i++) {
306                 currentRight = currentLeft + width;
307                 // calculate the height of the beam. Skip drawing if mDisplayData[i] = 0
308                 if (mDisplayData[i] != 0) {
309                     float units = (float) (((Math.log10((double) mDisplayData[i])) /
310                             Math.log10(mYAxisBase)) + 1.0);
311                     currentTop = currentBottom - (height * units);
312                     canvas.drawRect(currentLeft, currentTop, currentRight,
313                             currentBottom, mHistPaint);
314                 }
315 
316                 currentLeft = currentRight + beamInterval;
317             }
318 
319         }
320     }
321 
322 
323     /** get the width of "text" when using "paint". */
getTextWidth(String text, Paint paint)324     public int getTextWidth(String text, Paint paint) {
325         int width;
326         Rect bounds = new Rect();
327         paint.getTextBounds(text, 0, text.length(), bounds);
328         width = bounds.left + bounds.width();
329         return width;
330     }
331 
332     /** Copy buffer period data into "mData" */
setBufferPeriodArray(int[] data)333     public void setBufferPeriodArray(int[] data) {
334         if (data == null) {
335             return;
336         }
337 
338         if (mData == null || data.length != mData.length) {
339             mData = new int[data.length];
340         }
341 
342         System.arraycopy(data, 0, mData, 0, data.length);
343     }
344 
345 
setMaxBufferPeriod(int ReadBufferPeriod)346     public void setMaxBufferPeriod(int ReadBufferPeriod) {
347         mMaxBufferPeriod = ReadBufferPeriod;
348     }
349 
350 
log(String msg)351     private static void log(String msg) {
352         Log.v(TAG, msg);
353     }
354 
355 }
356