blob: cf7ee3a5753ff237f156650579a3141028abbb37 [file] [log] [blame]
/*
* 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());
}
}