huangcm
2025-08-14 5d6606c55520a76d5bb8297d83fd9bbf967e5244
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 
package com.android.systemui;
 
import android.animation.ArgbEvaluator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.ContextThemeWrapper;
import android.view.View;
 
import com.android.settingslib.Utils;
 
/**
 * CornerHandleView draws an inset arc intended to be displayed within the screen decoration
 * corners.
 */
public class CornerHandleView extends View {
    private static final float STROKE_DP_LARGE = 2f;
    private static final float STROKE_DP_SMALL = 1.95f;
    // Radius to use if none is available.
    private static final int FALLBACK_RADIUS_DP = 15;
    private static final float MARGIN_DP = 8;
    private static final int MAX_ARC_DEGREES = 90;
    // Arc length along the phone's perimeter used to measure the desired angle.
    private static final float ARC_LENGTH_DP = 31f;
 
    private Paint mPaint;
    private int mLightColor;
    private int mDarkColor;
    private Path mPath;
 
    public CornerHandleView(Context context, AttributeSet attrs) {
        super(context, attrs);
 
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        mPaint.setStrokeWidth(getStrokePx());
 
        final int dualToneDarkTheme = Utils.getThemeAttr(mContext, R.attr.darkIconTheme);
        final int dualToneLightTheme = Utils.getThemeAttr(mContext, R.attr.lightIconTheme);
        Context lightContext = new ContextThemeWrapper(mContext, dualToneLightTheme);
        Context darkContext = new ContextThemeWrapper(mContext, dualToneDarkTheme);
        mLightColor = Utils.getColorAttrDefaultColor(lightContext, R.attr.singleToneColor);
        mDarkColor = Utils.getColorAttrDefaultColor(darkContext, R.attr.singleToneColor);
 
        updatePath();
    }
 
    private void updatePath() {
        mPath = new Path();
 
        float marginPx = getMarginPx();
        float radiusPx = getInnerRadiusPx();
        float halfStrokePx = getStrokePx() / 2f;
        float angle = getAngle();
        float startAngle = 180 + ((90 - angle) / 2);
        RectF circle = new RectF(marginPx + halfStrokePx,
                marginPx + halfStrokePx,
                marginPx + 2 * radiusPx - halfStrokePx,
                marginPx + 2 * radiusPx - halfStrokePx);
 
        if (angle >= 90f) {
            float innerCircumferenceDp = convertPixelToDp(radiusPx * 2 * (float) Math.PI,
                    mContext);
            float arcDp = innerCircumferenceDp * getAngle() / 360f;
            // Add additional "arms" to the two ends of the arc. The length computation is
            // hand-tuned.
            float lineLengthPx = convertDpToPixel((ARC_LENGTH_DP - arcDp - MARGIN_DP) / 2,
                    mContext);
 
            mPath.moveTo(marginPx + halfStrokePx, marginPx + radiusPx + lineLengthPx);
            mPath.lineTo(marginPx + halfStrokePx, marginPx + radiusPx);
            mPath.arcTo(circle, startAngle, angle);
            mPath.moveTo(marginPx + radiusPx, marginPx + halfStrokePx);
            mPath.lineTo(marginPx + radiusPx + lineLengthPx, marginPx + halfStrokePx);
        } else {
            mPath.arcTo(circle, startAngle, angle);
        }
    }
 
    /**
     * Receives an intensity from 0 (lightest) to 1 (darkest) and sets the handle color
     * appropriately. Intention is to match the home handle color.
     */
    public void updateDarkness(float darkIntensity) {
        mPaint.setColor((int) ArgbEvaluator.getInstance().evaluate(darkIntensity,
                mLightColor,
                mDarkColor));
        invalidate();
    }
 
    @Override
    public void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawPath(mPath, mPaint);
    }
 
    private static float convertDpToPixel(float dp, Context context) {
        return dp * ((float) context.getResources().getDisplayMetrics().densityDpi
                / DisplayMetrics.DENSITY_DEFAULT);
    }
 
    private static float convertPixelToDp(float px, Context context) {
        return px * DisplayMetrics.DENSITY_DEFAULT
                / ((float) context.getResources().getDisplayMetrics().densityDpi);
    }
 
    private float getAngle() {
        // Measure a length of ARC_LENGTH_DP along the *screen's* perimeter, get the angle and cap
        // it at 90.
        float circumferenceDp = convertPixelToDp((
                getOuterRadiusPx()) * 2 * (float) Math.PI, mContext);
        float angleDeg = (ARC_LENGTH_DP / circumferenceDp) * 360;
        if (angleDeg > MAX_ARC_DEGREES) {
            angleDeg = MAX_ARC_DEGREES;
        }
        return angleDeg;
    }
 
    private float getMarginPx() {
        return convertDpToPixel(MARGIN_DP, mContext);
    }
 
    private float getInnerRadiusPx() {
        return getOuterRadiusPx() - getMarginPx();
    }
 
    private float getOuterRadiusPx() {
        // Attempt to get the bottom corner radius, otherwise fall back on the generic or top
        // values. If none are available, use the FALLBACK_RADIUS_DP.
        int radius = getResources().getDimensionPixelSize(
                com.android.internal.R.dimen.rounded_corner_radius_bottom);
        if (radius == 0) {
            radius = getResources().getDimensionPixelSize(
                    com.android.internal.R.dimen.rounded_corner_radius);
        }
        if (radius == 0) {
            radius = getResources().getDimensionPixelSize(
                    com.android.internal.R.dimen.rounded_corner_radius_top);
        }
        if (radius == 0) {
            radius = (int) convertDpToPixel(FALLBACK_RADIUS_DP, mContext);
        }
        return radius;
    }
 
    private float getStrokePx() {
        // Use a slightly smaller stroke if we need to cover the full corner angle.
        return convertDpToPixel((getAngle() < 90) ? STROKE_DP_LARGE : STROKE_DP_SMALL,
                getContext());
    }
}