• 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.view.View;
22 import android.view.WindowManager;
23 import android.view.WindowMetrics;
24 import android.widget.RelativeLayout;
25 import android.widget.TextView;
26 
27 import androidx.annotation.NonNull;
28 import androidx.annotation.Nullable;
29 
30 /**
31  * Encapsulates a {@link CompositeCircleView}, labeling each of its colored partial circles.
32  */
33 public class CompositeCircleViewLabeler extends RelativeLayout {
34 
35     private int mCircleId;
36     private TextView mCenterLabel;
37     private TextView[] mLabels;
38     private float mLabelRadiusScalar;
39 
CompositeCircleViewLabeler(@onNull Context context)40     public CompositeCircleViewLabeler(@NonNull Context context) {
41         super(context);
42     }
43 
CompositeCircleViewLabeler(@onNull Context context, @Nullable AttributeSet attrs)44     public CompositeCircleViewLabeler(@NonNull Context context, @Nullable AttributeSet attrs) {
45         super(context, attrs);
46     }
47 
CompositeCircleViewLabeler(@onNull Context context, @Nullable AttributeSet attrs, int defStyleAttr)48     public CompositeCircleViewLabeler(@NonNull Context context, @Nullable AttributeSet attrs,
49             int defStyleAttr) {
50         super(context, attrs, defStyleAttr);
51     }
52 
CompositeCircleViewLabeler(@onNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)53     public CompositeCircleViewLabeler(@NonNull Context context, @Nullable AttributeSet attrs,
54             int defStyleAttr, int defStyleRes) {
55         super(context, attrs, defStyleAttr, defStyleRes);
56     }
57 
58     /**
59      * Sets labels to surround the contained {@link CompositeCircleView} with, and the radius
60      * scalar to place them at.
61      *
62      * @param circleId view ID of the circle being labeled
63      * @param centerLabel the center label
64      * @param labels labels labels to position next to each circle value segment
65      * @param labelRadiusScalar scalar to multiply the contained circle radius by to get the
66      *                          radius at which we want to show labels
67      */
configure(int circleId, TextView centerLabel, TextView[] labels, float labelRadiusScalar)68     public void configure(int circleId, TextView centerLabel, TextView[] labels,
69             float labelRadiusScalar) {
70         // Remove previous text content first.
71         for (int i = 0; i < getChildCount(); i++) {
72             if (getChildAt(i) instanceof TextView) {
73                 removeViewAt(i);
74                 i--;
75             }
76         }
77         mCircleId = circleId;
78         mCenterLabel = centerLabel;
79         if (centerLabel != null) {
80             addView(centerLabel);
81         }
82         mLabels = labels;
83         for (int i = 0; i < labels.length; i++) {
84             if (labels[i] != null) {
85                 addView(labels[i]);
86             }
87         }
88         mLabelRadiusScalar = labelRadiusScalar;
89     }
90 
91     @Override
onLayout(boolean changed, int l, int t, int r, int b)92     protected void onLayout(boolean changed, int l, int t, int r, int b) {
93         super.onLayout(changed, l, t, r, b);
94 
95         // Gather CCV geometry.
96         CompositeCircleView ccv = findViewById(mCircleId);
97         int ccvWidth = ccv.getWidth();
98         int ccvHeight = ccv.getHeight();
99         float ccvCenterX = ccv.getX() + (ccvWidth * 0.5f);
100         float ccvCenterY = ccv.getY() + (ccvHeight * 0.5f);
101         float ccvRadius = Math.min(ccvWidth, ccvHeight) * 0.5f;
102         float labelRadius = ccvRadius * mLabelRadiusScalar;
103         int centerLabelX = (int) (ccvCenterX - (mCenterLabel.getWidth() * 0.5f));
104         int centerLabelY = (int) (ccvCenterY - (mCenterLabel.getHeight() * 0.5f));
105 
106         // Position center label.
107         if (mCenterLabel != null) {
108             mCenterLabel.setX(centerLabelX);
109             mCenterLabel.setY(centerLabelY);
110         }
111 
112         // For each provided label, determine position angle.
113         for (int i = 0; i < mLabels.length; i++) {
114             TextView label = mLabels[i];
115             if (label == null) {
116                 continue;
117             }
118             label.setVisibility((ccv.getValue(i) > 0) ? View.VISIBLE : View.GONE);
119             label.measure(0, 0);
120             int width = label.getMeasuredWidth();
121             int height = label.getMeasuredHeight();
122 
123             // For circle path, top angle is 270d. Convert to unit circle rads.
124             double angle = Math.toRadians(360 - ccv.getPartialCircleCenterAngle(i));
125             double x = ccvCenterX + (Math.cos(angle) * labelRadius);
126             double y = ccvCenterY - (Math.sin(angle) * labelRadius);
127 
128             // Determine anchor corner for text, adjust accordingly.
129             if (angle < (Math.PI * 0.5d)) {
130                 y -= height;
131             } else if (angle < Math.PI) {
132                 x -= width;
133                 y -= height;
134             } else if (angle < (Math.PI * 1.5d)) {
135                 x -= width;
136             }
137             WindowManager wm = getContext().getSystemService(WindowManager.class);
138             WindowMetrics metrics = wm.getCurrentWindowMetrics();
139             int maxX = metrics.getBounds().right;
140 
141             double offset = 0;
142             if (x < 0) {
143                 x = 0;
144             } else if ((x + width) > maxX) {
145                 offset = x + width - maxX;
146                 x -= offset;
147             }
148 
149             double labelMinX = x;
150             double labelMaxX = x + width;
151             double labelMinY = y;
152             double labelMaxY = y + height;
153             double centerLabelMinX = centerLabelX;
154             double centerLabelMaxX = centerLabelX + mCenterLabel.getWidth();
155             double centerLabelMinY = centerLabelY;
156             double centerLabelMaxY = centerLabelY + mCenterLabel.getHeight();
157 
158             if (isOverlapping(labelMinX, labelMaxX, labelMinY, labelMaxY,
159                     centerLabelMinX, centerLabelMaxX, centerLabelMinY, centerLabelMaxY)) {
160                 if (shouldMoveLabelUp(labelMinY, labelMaxY, centerLabelMinY, centerLabelMaxY)) {
161                     y += centerLabelMaxY - labelMinY;
162                 } else {
163                     y -= labelMaxY - centerLabelMinY;
164                 }
165             }
166 
167             label.setX((int) x);
168             label.setY((int) y);
169         }
170     }
171 
172     /**
173      * Given the minimum and maximum X and Y values of the label and center label,
174      * determine whether they overlap.
175      * @return whether the label overlaps with the center label
176      */
isOverlapping( double labelMinX, double labelMaxX, double labelMinY, double labelMaxY, double centerLabelMinX, double centerLabelMaxX, double centerLabelMinY, double centerLabelMaxY)177     private boolean isOverlapping(
178             double labelMinX, double labelMaxX, double labelMinY, double labelMaxY,
179             double centerLabelMinX, double centerLabelMaxX,
180             double centerLabelMinY, double centerLabelMaxY) {
181         // If they overlap, the condition inside the parentheses will not be true
182         return !(labelMinY > centerLabelMaxY || labelMaxY < centerLabelMinY
183                 || labelMinX > centerLabelMaxX || labelMaxX < centerLabelMinX);
184     }
185 
186     /**
187      * Determines the minimum distance to move the label along the Y axis in order to make it
188      * not overlap with the center label. Up means the positive direction in Java.
189      * @return whether we should move the label up
190      */
shouldMoveLabelUp( double labelMinY, double labelMaxY, double centerLabelMinY, double centerLabelMaxY)191     private boolean shouldMoveLabelUp(
192             double labelMinY, double labelMaxY, double centerLabelMinY, double centerLabelMaxY) {
193         // this returns (the distance to move the label up) < (the distance to move the label down)
194         return centerLabelMaxY - labelMinY < labelMaxY - centerLabelMinY;
195     }
196 }
197