1eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng/*
2eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng * Copyright (C) 2018 The Android Open Source Project
3eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng *
4eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng * Licensed under the Apache License, Version 2.0 (the "License");
5eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng * you may not use this file except in compliance with the License.
6eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng * You may obtain a copy of the License at
7eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng *
8eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng *      http://www.apache.org/licenses/LICENSE-2.0
9eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng *
10eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng * Unless required by applicable law or agreed to in writing, software
11eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng * distributed under the License is distributed on an "AS IS" BASIS,
12eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng * See the License for the specific language governing permissions and
14eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng * limitations under the License
15eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng */
16eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng
17eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ngpackage com.android.systemui.statusbar.phone;
18eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng
19eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng
20eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ngimport android.graphics.Bitmap;
21eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ngimport android.graphics.BlurMaskFilter;
22eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ngimport android.graphics.BlurMaskFilter.Blur;
23eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ngimport android.graphics.Canvas;
24eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ngimport android.graphics.Color;
25eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ngimport android.graphics.ColorFilter;
26eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ngimport android.graphics.Paint;
27eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ngimport android.graphics.PixelFormat;
28eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ngimport android.graphics.Rect;
29eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ngimport android.graphics.drawable.Drawable;
30eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng
31eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ngimport com.android.systemui.R;
32eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng
33eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng/**
34eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng * A drawable which adds shadow around a child drawable.
35eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng */
36eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ngpublic class ShadowKeyDrawable extends Drawable {
37eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng    private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
38eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng
39eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng    private final ShadowDrawableState mState;
40eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng
41eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng    public ShadowKeyDrawable(Drawable d) {
42eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        this(d, new ShadowDrawableState());
43eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng    }
44eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng
45eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng    private ShadowKeyDrawable(Drawable d, ShadowDrawableState state) {
46eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        mState = state;
47eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        if (d != null) {
48eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng            mState.mBaseHeight = d.getIntrinsicHeight();
49eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng            mState.mBaseWidth = d.getIntrinsicWidth();
50eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng            mState.mChangingConfigurations = d.getChangingConfigurations();
51eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng            mState.mChildState = d.getConstantState();
52eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        }
53eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng    }
54eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng
55eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng    public void setRotation(float degrees) {
56eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        if (mState.mRotateDegrees != degrees) {
57eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng            mState.mRotateDegrees = degrees;
58eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng            mState.mLastDrawnBitmap = null;
59eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng            invalidateSelf();
60eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        }
61eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng    }
62eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng
63eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng    public void setShadowProperties(int x, int y, int size, int color) {
64eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        if (mState.mShadowOffsetX != x || mState.mShadowOffsetY != y
65eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng                || mState.mShadowSize != size || mState.mShadowColor != color) {
66eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng            mState.mShadowOffsetX = x;
67eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng            mState.mShadowOffsetY = y;
68eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng            mState.mShadowSize = size;
69eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng            mState.mShadowColor = color;
70eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng            mState.mLastDrawnBitmap = null;
71eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng            invalidateSelf();
72eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        }
73eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng    }
74eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng
75eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng    public float getRotation() {
76eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        return mState.mRotateDegrees;
77eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng    }
78eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng
79eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng    @Override
80eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng    public void draw(Canvas canvas) {
81eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        Rect bounds = getBounds();
82eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        if (bounds.isEmpty()) {
83eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng            return;
84eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        }
85eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        if (mState.mLastDrawnBitmap == null) {
86eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng            regenerateBitmapCache();
87eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        }
88eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        canvas.drawBitmap(mState.mLastDrawnBitmap, null, bounds, mPaint);
89eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng    }
90eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng
91eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng    @Override
92eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng    public void setTint(int tintColor) {
93eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        super.setTint(tintColor);
94eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng    }
95eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng
96eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng    @Override
97eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng    public void setAlpha(int alpha) {
98eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        mPaint.setAlpha(alpha);
99eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        invalidateSelf();
100eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng    }
101eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng
102eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng    @Override
103eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng    public void setColorFilter(ColorFilter colorFilter) {
104eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        mPaint.setColorFilter(colorFilter);
105eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        invalidateSelf();
106eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng    }
107eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng
108eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng    @Override
109eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng    public ConstantState getConstantState() {
110eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        return mState;
111eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng    }
112eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng
113eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng    @Override
114eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng    public int getOpacity() {
115eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        return PixelFormat.TRANSLUCENT;
116eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng    }
117eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng
118eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng    @Override
119eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng    public int getIntrinsicHeight() {
120eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        return mState.mBaseHeight;
121eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng    }
122eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng
123eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng    @Override
124eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng    public int getIntrinsicWidth() {
125eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        return mState.mBaseWidth;
126eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng    }
127eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng
128eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng    @Override
129eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng    public boolean canApplyTheme() {
130eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        return mState.canApplyTheme();
131eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng    }
132eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng
133eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng    private void regenerateBitmapCache() {
134eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        final int width = getIntrinsicWidth();
135eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        final int height = getIntrinsicHeight();
136eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
137eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        Canvas canvas = new Canvas(bitmap);
138eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        canvas.save();
139eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        final float radians = (float) (mState.mRotateDegrees * Math.PI / 180);
140eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng
141eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        // Rotate canvas before drawing original drawable if no shadow
142eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        if (mState.mShadowSize == 0) {
143eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng            canvas.rotate(mState.mRotateDegrees, width / 2, height / 2);
144eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        }
145eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng
146eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        // Call mutate, so that the pixel allocation by the underlying vector drawable is cleared.
147eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        final Drawable d = mState.mChildState.newDrawable().mutate();
148eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        d.setBounds(0, 0, mState.mBaseWidth, mState.mBaseHeight);
149eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        d.draw(canvas);
150eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng
151eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        if (mState.mShadowSize > 0) {
152eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng            // Draws the shadow
153eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng            Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
154eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng            paint.setMaskFilter(new BlurMaskFilter(mState.mShadowSize, Blur.NORMAL));
155eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng            int[] offset = new int[2];
156eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng
157eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng            final Bitmap shadow = bitmap.extractAlpha(paint, offset);
158eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng
159eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng            paint.setMaskFilter(null);
160eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng            paint.setColor(mState.mShadowColor);
161eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng            bitmap.eraseColor(Color.TRANSPARENT);
162eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng
163eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng            canvas.rotate(mState.mRotateDegrees, width / 2, height / 2);
164eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng
165eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng            final float shadowOffsetX = (float) (Math.sin(radians) * mState.mShadowOffsetY
166eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng                    + Math.cos(radians) * mState.mShadowOffsetX);
167eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng            final float shadowOffsetY = (float) (Math.cos(radians) * mState.mShadowOffsetY
168eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng                    - Math.sin(radians) * mState.mShadowOffsetX);
169eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng
170eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng            canvas.drawBitmap(shadow, offset[0] + shadowOffsetX, offset[1] + shadowOffsetY, paint);
171eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng            d.draw(canvas);
172eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        }
173eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng
174eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        bitmap = bitmap.copy(Bitmap.Config.HARDWARE, false);
175eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        mState.mLastDrawnBitmap = bitmap;
176eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        canvas.restore();
177eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng    }
178eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng
179eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng    private static class ShadowDrawableState extends ConstantState {
180eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        int mChangingConfigurations;
181eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        int mBaseWidth;
182eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        int mBaseHeight;
183eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        float mRotateDegrees;
184eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        int mShadowOffsetX;
185eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        int mShadowOffsetY;
186eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        int mShadowSize;
187eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        int mShadowColor;
188eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng
189eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        Bitmap mLastDrawnBitmap;
190eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        ConstantState mChildState;
191eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng
192eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        @Override
193eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        public Drawable newDrawable() {
194eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng            return new ShadowKeyDrawable(null, this);
195eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        }
196eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng
197eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        @Override
198eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        public int getChangingConfigurations() {
199eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng            return mChangingConfigurations;
200eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        }
201eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng
202eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        @Override
203eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        public boolean canApplyTheme() {
204eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng            return true;
205eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng        }
206eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng    }
207eb5ce83cb400549dea09458e6e3908fae4457fe4Matthew Ng}
208