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