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.view.LayoutInflater;
21 import android.view.MotionEvent;
22 import android.view.View;
23 import android.view.ViewGroup;
24 import android.widget.SeekBar;
25 
26 import androidx.annotation.Nullable;
27 
28 import com.android.internal.logging.UiEventLogger;
29 import com.android.settingslib.RestrictedLockUtils;
30 import com.android.systemui.Gefingerpoken;
31 import com.android.systemui.R;
32 import com.android.systemui.classifier.Classifier;
33 import com.android.systemui.plugins.FalsingManager;
34 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
35 import com.android.systemui.util.ViewController;
36 
37 import javax.inject.Inject;
38 
39 /**
40  * {@code ViewController} for a {@code BrightnessSliderView}
41  *
42  * This class allows to control the views of a {@code BrightnessSliderViewView} and get callbacks
43  * when the views change value. It also propagates and manipulates another {@link ToggleSlider} as a
44  * mirror.
45  *
46  * @see BrightnessMirrorController
47  */
48 public class BrightnessSliderController extends ViewController<BrightnessSliderView> implements
49         ToggleSlider {
50 
51     private Listener mListener;
52     private ToggleSlider mMirror;
53     private BrightnessMirrorController mMirrorController;
54     private boolean mTracking;
55     private final FalsingManager mFalsingManager;
56     private final UiEventLogger mUiEventLogger;
57 
58     private final Gefingerpoken mOnInterceptListener = new Gefingerpoken() {
59         @Override
60         public boolean onInterceptTouchEvent(MotionEvent ev) {
61             int action = ev.getActionMasked();
62             if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
63                 mFalsingManager.isFalseTouch(Classifier.BRIGHTNESS_SLIDER);
64             }
65 
66             return false;
67         }
68 
69         @Override
70         public boolean onTouchEvent(MotionEvent ev) {
71             return false;
72         }
73     };
74 
BrightnessSliderController( BrightnessSliderView brightnessSliderView, FalsingManager falsingManager, UiEventLogger uiEventLogger)75     BrightnessSliderController(
76             BrightnessSliderView brightnessSliderView,
77             FalsingManager falsingManager,
78             UiEventLogger uiEventLogger) {
79         super(brightnessSliderView);
80         mFalsingManager = falsingManager;
81         mUiEventLogger = uiEventLogger;
82     }
83 
84     /**
85      * Returns a top level view in the hierarchy that can be attached where necessary
86      */
getRootView()87     public View getRootView() {
88         return mView;
89     }
90 
91 
92     @Override
onViewAttached()93     protected void onViewAttached() {
94         mView.setOnSeekBarChangeListener(mSeekListener);
95         mView.setOnInterceptListener(mOnInterceptListener);
96     }
97 
98     @Override
onViewDetached()99     protected void onViewDetached() {
100         mView.setOnSeekBarChangeListener(null);
101         mView.setOnDispatchTouchEventListener(null);
102         mView.setOnInterceptListener(null);
103     }
104 
105     @Override
mirrorTouchEvent(MotionEvent ev)106     public boolean mirrorTouchEvent(MotionEvent ev) {
107         if (mMirror != null) {
108             return copyEventToMirror(ev);
109         } else {
110             // We are the mirror, so we have to dispatch the event
111             return mView.dispatchTouchEvent(ev);
112         }
113     }
114 
copyEventToMirror(MotionEvent ev)115     private boolean copyEventToMirror(MotionEvent ev) {
116         MotionEvent copy = ev.copy();
117         boolean out = mMirror.mirrorTouchEvent(copy);
118         copy.recycle();
119         return out;
120     }
121 
122     @Override
setEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin)123     public void setEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin) {
124         mView.setEnforcedAdmin(admin);
125     }
126 
setMirror(ToggleSlider toggleSlider)127     private void setMirror(ToggleSlider toggleSlider) {
128         mMirror = toggleSlider;
129         if (mMirror != null) {
130             mMirror.setMax(mView.getMax());
131             mMirror.setValue(mView.getValue());
132             mView.setOnDispatchTouchEventListener(this::mirrorTouchEvent);
133         } else {
134             // If there's no mirror, we may be the ones dispatching, events but we should not mirror
135             // them
136             mView.setOnDispatchTouchEventListener(null);
137         }
138     }
139 
140     /**
141      * This will set the mirror from the controller using
142      * {@link BrightnessMirrorController#getToggleSlider} as a side-effect.
143      * @param c
144      */
145     @Override
setMirrorControllerAndMirror(BrightnessMirrorController c)146     public void setMirrorControllerAndMirror(BrightnessMirrorController c) {
147         mMirrorController = c;
148         setMirror(c.getToggleSlider());
149     }
150 
151     @Override
setOnChangedListener(Listener l)152     public void setOnChangedListener(Listener l) {
153         mListener = l;
154     }
155 
156     @Override
setMax(int max)157     public void setMax(int max) {
158         mView.setMax(max);
159         if (mMirror != null) {
160             mMirror.setMax(max);
161         }
162     }
163 
164     @Override
getMax()165     public int getMax() {
166         return mView.getMax();
167     }
168 
169     @Override
setValue(int value)170     public void setValue(int value) {
171         mView.setValue(value);
172         if (mMirror != null) {
173             mMirror.setValue(value);
174         }
175     }
176 
177     @Override
getValue()178     public int getValue() {
179         return mView.getValue();
180     }
181 
182     @Override
hideView()183     public void hideView() {
184         mView.setVisibility(View.GONE);
185     }
186 
187     @Override
showView()188     public void showView() {
189         mView.setVisibility(View.VISIBLE);
190     }
191 
192     @Override
isVisible()193     public boolean isVisible() {
194         // this should be called rarely - once or twice per slider's value change, but not for
195         // every value change when user slides finger - only the final one.
196         // If view is not visible this call is quick (around 50 µs) as it sees parent is not visible
197         // otherwise it's slightly longer (70 µs) because there are more checks to be done
198         return mView.isVisibleToUser();
199     }
200 
201     private final SeekBar.OnSeekBarChangeListener mSeekListener =
202             new SeekBar.OnSeekBarChangeListener() {
203         @Override
204         public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
205             if (mListener != null) {
206                 mListener.onChanged(mTracking, progress, false);
207             }
208         }
209 
210         @Override
211         public void onStartTrackingTouch(SeekBar seekBar) {
212             mTracking = true;
213             mUiEventLogger.log(BrightnessSliderEvent.SLIDER_STARTED_TRACKING_TOUCH);
214             if (mListener != null) {
215                 mListener.onChanged(mTracking, getValue(), false);
216             }
217 
218             if (mMirrorController != null) {
219                 mMirrorController.showMirror();
220                 mMirrorController.setLocationAndSize(mView);
221             }
222         }
223 
224         @Override
225         public void onStopTrackingTouch(SeekBar seekBar) {
226             mTracking = false;
227             mUiEventLogger.log(BrightnessSliderEvent.SLIDER_STOPPED_TRACKING_TOUCH);
228             if (mListener != null) {
229                 mListener.onChanged(mTracking, getValue(), true);
230             }
231 
232             if (mMirrorController != null) {
233                 mMirrorController.hideMirror();
234             }
235         }
236     };
237 
238     /**
239      * Creates a {@link BrightnessSliderController} with its associated view.
240      */
241     public static class Factory {
242 
243         private final FalsingManager mFalsingManager;
244         private final UiEventLogger mUiEventLogger;
245 
246         @Inject
Factory(FalsingManager falsingManager, UiEventLogger uiEventLogger)247         public Factory(FalsingManager falsingManager, UiEventLogger uiEventLogger) {
248             mFalsingManager = falsingManager;
249             mUiEventLogger = uiEventLogger;
250         }
251 
252         /**
253          * Creates the view hierarchy and controller
254          *
255          * @param context a {@link Context} to inflate the hierarchy
256          * @param viewRoot the {@link ViewGroup} that will contain the hierarchy. The inflated
257          *                 hierarchy will not be attached
258          */
create( Context context, @Nullable ViewGroup viewRoot)259         public BrightnessSliderController create(
260                 Context context,
261                 @Nullable ViewGroup viewRoot) {
262             int layout = getLayout();
263             BrightnessSliderView root = (BrightnessSliderView) LayoutInflater.from(context)
264                     .inflate(layout, viewRoot, false);
265             return new BrightnessSliderController(root, mFalsingManager, mUiEventLogger);
266         }
267 
268         /** Get the layout to inflate based on what slider to use */
getLayout()269         private int getLayout() {
270             return R.layout.quick_settings_brightness_dialog;
271         }
272     }
273 }
274