1 /*
2  * Copyright (C) 2020 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.systemui.settings.brightness;
18 
19 import android.content.Context;
20 import android.graphics.Rect;
21 import android.graphics.drawable.Drawable;
22 import android.graphics.drawable.DrawableWrapper;
23 import android.graphics.drawable.LayerDrawable;
24 import android.util.AttributeSet;
25 import android.view.MotionEvent;
26 import android.view.View;
27 import android.widget.FrameLayout;
28 import android.widget.SeekBar.OnSeekBarChangeListener;
29 
30 import androidx.annotation.Keep;
31 import androidx.annotation.NonNull;
32 import androidx.annotation.Nullable;
33 
34 import com.android.systemui.Gefingerpoken;
35 import com.android.systemui.res.R;
36 
37 import java.util.Collections;
38 
39 /**
40  * {@code FrameLayout} used to show and manipulate a {@link ToggleSeekBar}.
41  *
42  */
43 public class BrightnessSliderView extends FrameLayout {
44 
45     @NonNull
46     private ToggleSeekBar mSlider;
47     private DispatchTouchEventListener mListener;
48     private Gefingerpoken mOnInterceptListener;
49     @Nullable
50     private Drawable mProgressDrawable;
51     private float mScale = 1f;
52     private final Rect mSystemGestureExclusionRect = new Rect();
53 
BrightnessSliderView(Context context)54     public BrightnessSliderView(Context context) {
55         this(context, null);
56     }
57 
BrightnessSliderView(Context context, AttributeSet attrs)58     public BrightnessSliderView(Context context, AttributeSet attrs) {
59         super(context, attrs);
60     }
61 
62     // Inflated from quick_settings_brightness_dialog
63     @Override
onFinishInflate()64     protected void onFinishInflate() {
65         super.onFinishInflate();
66         setLayerType(LAYER_TYPE_HARDWARE, null);
67 
68         mSlider = requireViewById(R.id.slider);
69         mSlider.setAccessibilityLabel(getContentDescription().toString());
70         setBoundaryOffset();
71 
72         // Finds the progress drawable. Assumes brightness_progress_drawable.xml
73         try {
74             LayerDrawable progress = (LayerDrawable) mSlider.getProgressDrawable();
75             DrawableWrapper progressSlider = (DrawableWrapper) progress
76                     .findDrawableByLayerId(android.R.id.progress);
77             LayerDrawable actualProgressSlider = (LayerDrawable) progressSlider.getDrawable();
78             mProgressDrawable = actualProgressSlider.findDrawableByLayerId(R.id.slider_foreground);
79         } catch (Exception e) {
80             // Nothing to do, mProgressDrawable will be null.
81         }
82     }
83 
setBoundaryOffset()84     private void setBoundaryOffset() {
85          //  BrightnessSliderView uses hardware layer; if the background of its children exceed its
86          //  boundary, it'll be cropped. We need to expand its boundary so that the background of
87          //  ToggleSeekBar (i.e. the focus state) can be correctly rendered.
88         int offset = getResources().getDimensionPixelSize(R.dimen.rounded_slider_boundary_offset);
89         MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();
90         lp.setMargins(-offset, -offset, -offset, -offset);
91         setLayoutParams(lp);
92         setPadding(offset,  offset, offset,  offset);
93     }
94 
95     /**
96      * Attaches a listener to relay touch events.
97      * @param listener use {@code null} to remove listener
98      */
setOnDispatchTouchEventListener( DispatchTouchEventListener listener)99     public void setOnDispatchTouchEventListener(
100             DispatchTouchEventListener listener) {
101         mListener = listener;
102     }
103 
104     @Override
dispatchTouchEvent(MotionEvent ev)105     public boolean dispatchTouchEvent(MotionEvent ev) {
106         if (mListener != null) {
107             mListener.onDispatchTouchEvent(ev);
108         }
109         return super.dispatchTouchEvent(ev);
110     }
111 
112     @Override
requestDisallowInterceptTouchEvent(boolean disallowIntercept)113     public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
114         // We prevent disallowing on this view, but bubble it up to our parents.
115         // We need interception to handle falsing.
116         if (mParent != null) {
117             mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
118         }
119     }
120 
121     /**
122      * Attaches a listener to the {@link ToggleSeekBar} in the view so changes can be observed
123      * @param seekListener use {@code null} to remove listener
124      */
setOnSeekBarChangeListener(OnSeekBarChangeListener seekListener)125     public void setOnSeekBarChangeListener(OnSeekBarChangeListener seekListener) {
126         mSlider.setOnSeekBarChangeListener(seekListener);
127     }
128 
129     /**
130      * Enforces admin rules for toggling auto-brightness and changing value of brightness
131      * @param admin
132      * @see ToggleSeekBar#setEnforcedAdmin
133      */
setAdminBlocker(ToggleSeekBar.AdminBlocker blocker)134     void setAdminBlocker(ToggleSeekBar.AdminBlocker blocker) {
135         mSlider.setAdminBlocker(blocker);
136     }
137 
138     /**
139      * Enables or disables the slider
140      * @param enable
141      */
enableSlider(boolean enable)142     public void enableSlider(boolean enable) {
143         mSlider.setEnabled(enable);
144     }
145 
146     /**
147      * @return the maximum value of the {@link ToggleSeekBar}.
148      */
getMax()149     public int getMax() {
150         return mSlider.getMax();
151     }
152 
153     /**
154      * Sets the maximum value of the {@link ToggleSeekBar}.
155      * @param max
156      */
setMax(int max)157     public void setMax(int max) {
158         mSlider.setMax(max);
159     }
160 
161     /**
162      * Sets the current value of the {@link ToggleSeekBar}.
163      * @param value
164      */
setValue(int value)165     public void setValue(int value) {
166         mSlider.setProgress(value);
167     }
168 
169     /**
170      * @return the current value of the {@link ToggleSeekBar}
171      */
getValue()172     public int getValue() {
173         return mSlider.getProgress();
174     }
175 
setOnInterceptListener(Gefingerpoken onInterceptListener)176     public void setOnInterceptListener(Gefingerpoken onInterceptListener) {
177         mOnInterceptListener = onInterceptListener;
178     }
179 
180     @Override
onInterceptTouchEvent(MotionEvent ev)181     public boolean onInterceptTouchEvent(MotionEvent ev) {
182         if (mOnInterceptListener != null) {
183             return mOnInterceptListener.onInterceptTouchEvent(ev);
184         }
185         return super.onInterceptTouchEvent(ev);
186     }
187 
188     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)189     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
190         super.onLayout(changed, left, top, right, bottom);
191         applySliderScale();
192         int horizontalMargin =
193                 getResources().getDimensionPixelSize(R.dimen.notification_side_paddings);
194         mSystemGestureExclusionRect.set(-horizontalMargin, 0, right - left + horizontalMargin,
195                 bottom - top);
196         setSystemGestureExclusionRects(Collections.singletonList(mSystemGestureExclusionRect));
197     }
198 
199     /**
200      * Sets the scale for the progress bar (for brightness_progress_drawable.xml)
201      *
202      * This will only scale the thick progress bar and not the icon inside
203      *
204      * Used in {@link com.android.systemui.qs.QSAnimator}.
205      */
206     @Keep
setSliderScaleY(float scale)207     public void setSliderScaleY(float scale) {
208         if (scale != mScale) {
209             mScale = scale;
210             applySliderScale();
211         }
212     }
213 
applySliderScale()214     private void applySliderScale() {
215         if (mProgressDrawable != null) {
216             final Rect r = mProgressDrawable.getBounds();
217             int height = (int) (mProgressDrawable.getIntrinsicHeight() * mScale);
218             int inset = (mProgressDrawable.getIntrinsicHeight() - height) / 2;
219             mProgressDrawable.setBounds(r.left, inset, r.right, inset + height);
220         }
221     }
222 
223     @Keep
getSliderScaleY()224     public float getSliderScaleY() {
225         return mScale;
226     }
227 
228     /**
229      * Interface to attach a listener for {@link View#dispatchTouchEvent}.
230      */
231     @FunctionalInterface
232     interface DispatchTouchEventListener {
onDispatchTouchEvent(MotionEvent ev)233         boolean onDispatchTouchEvent(MotionEvent ev);
234     }
235 }
236 
237