| /* |
| * 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; |
| private boolean mRequiresInvalidate; |
| |
| 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(); |
| } |
| |
| @Override |
| public void setAlpha(float alpha) { |
| super.setAlpha(alpha); |
| if (alpha > 0f && mRequiresInvalidate) { |
| mRequiresInvalidate = false; |
| invalidate(); |
| } |
| } |
| |
| 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) { |
| // Handle color is same as home handle color. |
| int color = (int) ArgbEvaluator.getInstance().evaluate(darkIntensity, |
| mLightColor, mDarkColor); |
| if (mPaint.getColor() != color) { |
| mPaint.setColor(color); |
| if (getVisibility() == VISIBLE && getAlpha() > 0) { |
| invalidate(); |
| } else { |
| // If we are currently invisible, then invalidate when we are next made visible |
| mRequiresInvalidate = true; |
| } |
| } |
| } |
| |
| @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.systemui.R.dimen.config_rounded_mask_size_bottom); |
| if (radius == 0) { |
| radius = getResources().getDimensionPixelSize( |
| com.android.systemui.R.dimen.config_rounded_mask_size); |
| } |
| if (radius == 0) { |
| radius = getResources().getDimensionPixelSize( |
| com.android.systemui.R.dimen.config_rounded_mask_size_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()); |
| } |
| } |