• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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.settings.accessibility;
18 
19 import static com.android.settings.Utils.isNightMode;
20 
21 import android.content.Context;
22 import android.content.res.ColorStateList;
23 import android.content.res.Resources;
24 import android.graphics.Canvas;
25 import android.graphics.Color;
26 import android.graphics.Paint;
27 import android.graphics.Rect;
28 import android.os.UserHandle;
29 import android.provider.Settings;
30 import android.util.AttributeSet;
31 import android.widget.SeekBar;
32 
33 import androidx.annotation.VisibleForTesting;
34 
35 import com.android.settings.R;
36 
37 /**
38  * A custom seekbar for the balance setting.
39  *
40  * Adds a center line indicator between left and right, which snaps to if close.
41  * Updates Settings.System for balance on progress changed.
42  */
43 public class BalanceSeekBar extends SeekBar {
44     private final Context mContext;
45     private final Object mListenerLock = new Object();
46     private OnSeekBarChangeListener mOnSeekBarChangeListener;
47     private final OnSeekBarChangeListener mProxySeekBarListener = new OnSeekBarChangeListener() {
48         @Override
49         public void onStopTrackingTouch(SeekBar seekBar) {
50             synchronized (mListenerLock) {
51                 if (mOnSeekBarChangeListener != null) {
52                     mOnSeekBarChangeListener.onStopTrackingTouch(seekBar);
53                 }
54             }
55         }
56 
57         @Override
58         public void onStartTrackingTouch(SeekBar seekBar) {
59             synchronized (mListenerLock) {
60                 if (mOnSeekBarChangeListener != null) {
61                     mOnSeekBarChangeListener.onStartTrackingTouch(seekBar);
62                 }
63             }
64         }
65 
66         @Override
67         public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
68             if (fromUser) {
69                 // Snap to centre when within the specified threshold
70                 if (progress != mCenter
71                         && progress > mCenter - mSnapThreshold
72                         && progress < mCenter + mSnapThreshold) {
73                     progress = mCenter;
74                     seekBar.setProgress(progress); // direct update (fromUser becomes false)
75                 }
76                 final float balance = (progress - mCenter) * 0.01f;
77                 Settings.System.putFloatForUser(mContext.getContentResolver(),
78                         Settings.System.MASTER_BALANCE, balance, UserHandle.USER_CURRENT);
79             }
80             // If fromUser is false, the call is a set from the framework on creation or on
81             // internal update. The progress may be zero, ignore (don't change system settings).
82 
83             // after adjusting the seekbar, notify downstream listener.
84             // note that progress may have been adjusted in the code above to mCenter.
85             synchronized (mListenerLock) {
86                 if (mOnSeekBarChangeListener != null) {
87                     mOnSeekBarChangeListener.onProgressChanged(seekBar, progress, fromUser);
88                 }
89             }
90         }
91     };
92 
93     // Percentage of max to be used as a snap to threshold
94     @VisibleForTesting
95     static final float SNAP_TO_PERCENTAGE = 0.03f;
96     private final Paint mCenterMarkerPaint;
97     private final Rect mCenterMarkerRect;
98     // changed in setMax()
99     private float mSnapThreshold;
100     private int mCenter;
101 
BalanceSeekBar(Context context, AttributeSet attrs)102     public BalanceSeekBar(Context context, AttributeSet attrs) {
103         this(context, attrs, com.android.internal.R.attr.seekBarStyle);
104     }
105 
BalanceSeekBar(Context context, AttributeSet attrs, int defStyleAttr)106     public BalanceSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
107         this(context, attrs, defStyleAttr, 0 /* defStyleRes */);
108     }
109 
BalanceSeekBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)110     public BalanceSeekBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
111         super(context, attrs, defStyleAttr, defStyleRes);
112         mContext = context;
113         Resources res = getResources();
114         mCenterMarkerRect = new Rect(0 /* left */, 0 /* top */,
115                 res.getDimensionPixelSize(R.dimen.balance_seekbar_center_marker_width),
116                 res.getDimensionPixelSize(R.dimen.balance_seekbar_center_marker_height));
117         mCenterMarkerPaint = new Paint();
118         mCenterMarkerPaint.setColor(isNightMode(context) ? Color.WHITE : Color.BLACK);
119         mCenterMarkerPaint.setStyle(Paint.Style.FILL);
120         // Remove the progress colour
121         setProgressTintList(ColorStateList.valueOf(Color.TRANSPARENT));
122 
123         super.setOnSeekBarChangeListener(mProxySeekBarListener);
124     }
125 
126     @Override
setOnSeekBarChangeListener(OnSeekBarChangeListener listener)127     public void setOnSeekBarChangeListener(OnSeekBarChangeListener listener) {
128         synchronized (mListenerLock) {
129             mOnSeekBarChangeListener = listener;
130         }
131     }
132 
133     // Note: the superclass AbsSeekBar.setMax is synchronized.
134     @Override
setMax(int max)135     public synchronized void setMax(int max) {
136         super.setMax(max);
137         // update snap to threshold
138         mCenter = max / 2;
139         mSnapThreshold = max * SNAP_TO_PERCENTAGE;
140     }
141 
142     // Note: the superclass AbsSeekBar.onDraw is synchronized.
143     @Override
onDraw(Canvas canvas)144     protected synchronized void onDraw(Canvas canvas) {
145         // Draw a vertical line at 50% that represents centred balance
146         int seekBarCenter = (canvas.getHeight() - getPaddingBottom()) / 2;
147         canvas.save();
148         canvas.translate((canvas.getWidth() - mCenterMarkerRect.right) / 2,
149                 seekBarCenter - (mCenterMarkerRect.bottom / 2));
150         canvas.drawRect(mCenterMarkerRect, mCenterMarkerPaint);
151         canvas.restore();
152         super.onDraw(canvas);
153     }
154 
155     @VisibleForTesting
getProxySeekBarListener()156     OnSeekBarChangeListener getProxySeekBarListener() {
157         return mProxySeekBarListener;
158     }
159 }
160 
161