• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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 com.android.permissioncontroller.permission.ui.handheld.v31;
18 
19 import android.content.Context;
20 import android.util.AttributeSet;
21 import android.widget.FrameLayout;
22 import android.widget.TextView;
23 
24 import androidx.annotation.NonNull;
25 import androidx.annotation.Nullable;
26 
27 import com.android.permissioncontroller.R;
28 
29 /**
30  * Configured to draw a set of contiguous partial circles via {@link PartialCircleView}, which
31  * are generated from the relative weight of values and corresponding colors given to
32  * {@link #configure(float, int[], int[], int)}.
33  */
34 public class CompositeCircleView extends FrameLayout {
35 
36     /** Spacing between circle segments in degrees. */
37     private static final int SEGMENT_ANGLE_SPACING_DEG = 2;
38 
39     /** How far apart to bump labels so that they have more space. */
40     private static final float LABEL_BUMP_DEGREES = 15;
41 
42     /** Values being represented by this circle. */
43     private int[] mValues;
44 
45     /**
46      * Angles toward the middle of each colored partial circle, calculated in
47      * {@link #configure(float, int[], int[], int)}. Can be used to position text relative to the
48      * partial circles, by index.
49      */
50     private float[] mPartialCircleCenterAngles;
51 
CompositeCircleView(@onNull Context context)52     public CompositeCircleView(@NonNull Context context) {
53         super(context);
54     }
55 
CompositeCircleView(@onNull Context context, @Nullable AttributeSet attrs)56     public CompositeCircleView(@NonNull Context context, @Nullable AttributeSet attrs) {
57         super(context, attrs);
58     }
59 
CompositeCircleView(@onNull Context context, @Nullable AttributeSet attrs, int defStyleAttr)60     public CompositeCircleView(@NonNull Context context, @Nullable AttributeSet attrs,
61             int defStyleAttr) {
62         super(context, attrs, defStyleAttr);
63     }
64 
CompositeCircleView(@onNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)65     public CompositeCircleView(@NonNull Context context, @Nullable AttributeSet attrs,
66             int defStyleAttr, int defStyleRes) {
67         super(context, attrs, defStyleAttr, defStyleRes);
68     }
69 
70     /**
71      * Configures the {@link CompositeCircleView} to draw a set of contiguous partial circles that
72      * are generated from the relative weight of the given values and corresponding colors. The
73      * first segment starts at the top, and drawing proceeds clockwise from there.
74      *
75      * @param startAngle the angle at which to start segments
76      * @param values relative weights, used to size the partial circles
77      * @param colors colors corresponding to relative weights
78      * @param strokeWidth stroke width to apply to all contained partial circles
79      * @param labels the permission labels to set the ContentDescription with % value
80      */
configure(float startAngle, int[] values, int[] colors, int strokeWidth, TextView[] labels)81     public void configure(float startAngle, int[] values, int[] colors, int strokeWidth,
82             TextView[] labels) {
83         removeAllViews();
84         mValues = values;
85 
86         // Get total values and number of values over 0.
87         float total = 0;
88         int numValidValues = 0;
89         for (int i = 0; i < values.length; i++) {
90             total += values[i];
91             if (values[i] > 0) {
92                 numValidValues++;
93             }
94         }
95 
96         // Add small spacing to the first angle to make the little space between segments, but only
97         // if we have more than one segment.
98         if (values.length > 1) {
99             startAngle = startAngle + (SEGMENT_ANGLE_SPACING_DEG * 0.5f);
100         }
101         mPartialCircleCenterAngles = new float[values.length];
102 
103         // Number of degrees allocated to drawing circle segments.
104         float allocatedDegrees = 360;
105         if (values.length > 1) {
106             allocatedDegrees -= (numValidValues * SEGMENT_ANGLE_SPACING_DEG);
107         }
108 
109         // Total label bump degrees so far.
110         float totalBumpDegrees = 0;
111         int labelBumps = 0;
112 
113         for (int i = 0; i < values.length; i++) {
114             if (values[i] <= 0) {
115                 continue;
116             }
117 
118             PartialCircleView pcv = new PartialCircleView(getContext());
119             addView(pcv);
120             pcv.setStartAngle(startAngle);
121             pcv.setColor(colors[i]);
122             pcv.setStrokeWidth(strokeWidth);
123 
124             // Calculate sweep, which is (value / total) * 360, keep track of segment center
125             // angles for later reference.
126             float sweepAngle = (values[i] / total) * allocatedDegrees;
127             pcv.setSweepAngle(sweepAngle);
128 
129             if (labels[i] != null) {
130                 int percentage = Math.round((values[i] / total) * 100);
131                 String contextDescription = getContext().getString(
132                         R.string.privdash_usage_percent, labels[i].getText(), percentage);
133                 labels[i].setContentDescription(contextDescription);
134             }
135 
136             mPartialCircleCenterAngles[i] = (startAngle + (sweepAngle * 0.5f)) % 360;
137             if (i > 0) {
138                 float angleDiff =
139                         ((mPartialCircleCenterAngles[i] - mPartialCircleCenterAngles[i - 1])
140                                 + 360) % 360;
141                 if (angleDiff < LABEL_BUMP_DEGREES) {
142                     float bump = LABEL_BUMP_DEGREES - angleDiff;
143                     mPartialCircleCenterAngles[i] += bump;
144                     totalBumpDegrees += bump;
145                     labelBumps++;
146                 } else {
147                     spreadPreviousLabelBumps(labelBumps, totalBumpDegrees, i);
148                     totalBumpDegrees = 0;
149                     labelBumps = 0;
150                 }
151             }
152 
153             // Move to next segment.
154             startAngle += sweepAngle;
155             startAngle += SEGMENT_ANGLE_SPACING_DEG;
156             startAngle %= 360;
157         }
158 
159         // If any label bumps remaining, spread now.
160         spreadPreviousLabelBumps(labelBumps, totalBumpDegrees, values.length);
161     }
162 
163     /**
164      * If we've been bumping labels further from previous labels to make space, we use this method
165      * to spread the bumps back along the circle, so that labels are as close as possible to their
166      * corresponding segments.
167      *
168      * @param labelBumps total number of previous segments under the size threshold
169      * @param totalBumpDegrees the total degrees to spread along previous labels
170      * @param behindIndex the index behind which we were bumping labels
171      */
spreadPreviousLabelBumps(int labelBumps, float totalBumpDegrees, int behindIndex)172     private void spreadPreviousLabelBumps(int labelBumps, float totalBumpDegrees, int behindIndex) {
173         if (labelBumps > 0) {
174             float spread = totalBumpDegrees * 0.5f;
175             for (int i = 1; i <= labelBumps + 1; i++) {
176                 int index = behindIndex - i;
177                 float angle = mPartialCircleCenterAngles[index];
178                 angle -= spread;
179                 angle += 360;
180                 angle %= 360;
181                 mPartialCircleCenterAngles[index] = angle;
182             }
183         }
184     }
185 
186     /** Returns the value for the given index. */
getValue(int index)187     public int getValue(int index) {
188         return mValues[index];
189     }
190 
191     /** Returns the center angle for the given partial circle index. */
getPartialCircleCenterAngle(int index)192     public float getPartialCircleCenterAngle(int index) {
193         return mPartialCircleCenterAngles[index];
194     }
195 }
196