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.dashboard;
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 
104         // Position center label.
105         if (mCenterLabel != null) {
106             mCenterLabel.setX((int) (ccvCenterX - (mCenterLabel.getWidth() * 0.5f)));
107             mCenterLabel.setY((int) (ccvCenterY - (mCenterLabel.getHeight() * 0.5f)));
108         }
109 
110         // For each provided label, determine position angle.
111         for (int i = 0; i < mLabels.length; i++) {
112             TextView label = mLabels[i];
113             if (label == null) {
114                 continue;
115             }
116             label.setVisibility((ccv.getValue(i) > 0) ? View.VISIBLE : View.GONE);
117             label.measure(0, 0);
118             int width = label.getMeasuredWidth();
119             int height = label.getMeasuredHeight();
120 
121             // For circle path, top angle is 270d. Convert to unit circle rads.
122             double angle = Math.toRadians(360 - ccv.getPartialCircleCenterAngle(i));
123             double x = ccvCenterX + (Math.cos(angle) * labelRadius);
124             double y = ccvCenterY - (Math.sin(angle) * labelRadius);
125 
126             // Determine anchor corner for text, adjust accordingly.
127             if (angle < (Math.PI * 0.5d)) {
128                 y -= height;
129             } else if (angle < Math.PI) {
130                 x -= width;
131                 y -= height;
132             } else if (angle < (Math.PI * 1.5d)) {
133                 x -= width;
134             }
135             WindowManager wm = getContext().getSystemService(WindowManager.class);
136             WindowMetrics metrics = wm.getCurrentWindowMetrics();
137             int maxX = metrics.getBounds().right;
138 
139             double offset = 0;
140             if (x < 0) {
141                 x = 0;
142             } else if ((x + width) > maxX) {
143                 offset = x + width - maxX;
144                 x -= offset;
145             }
146 
147             label.setX((int) x);
148             label.setY((int) y);
149         }
150     }
151 }
152