diff --git a/res/layout/preference_balance_slider.xml b/res/layout/preference_balance_slider.xml new file mode 100644 index 00000000000..32010c36026 --- /dev/null +++ b/res/layout/preference_balance_slider.xml @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 0a35188287e..d42f0731d98 100755 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -38,6 +38,9 @@ 8dip + 14dp + 1dp + 100sp 3dip diff --git a/res/values/strings.xml b/res/values/strings.xml index ae0b8f0214b..b5592b4a451 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -4717,6 +4717,12 @@ Mono audio Combine channels when playing audio + + Audio balance + + Left + + Right Default diff --git a/res/xml/accessibility_settings.xml b/res/xml/accessibility_settings.xml index cc07ce119e9..9cb73a2ea6b 100644 --- a/res/xml/accessibility_settings.xml +++ b/res/xml/accessibility_settings.xml @@ -130,6 +130,10 @@ android:summary="@string/accessibility_toggle_master_mono_summary" android:persistent="false"/> + + mCenter - mSnapThreshold + && progress < mCenter + mSnapThreshold) { + progress = mCenter; + seekBar.setProgress(progress); // direct update (fromUser becomes false) + } + final float balance = (progress - mCenter) * 0.01f; + Settings.System.putFloatForUser(mContext.getContentResolver(), + Settings.System.MASTER_BALANCE, balance, UserHandle.USER_CURRENT); + } + // If fromUser is false, the call is a set from the framework on creation or on + // internal update. The progress may be zero, ignore (don't change system settings). + + // after adjusting the seekbar, notify downstream listener. + // note that progress may have been adjusted in the code above to mCenter. + synchronized(mListenerLock) { + if (mOnSeekBarChangeListener != null) { + mOnSeekBarChangeListener.onProgressChanged(seekBar, progress, fromUser); + } + } + } + }; + + // Percentage of max to be used as a snap to threshold + private static final float SNAP_TO_PERCENTAGE = 0.03f; + private final Paint mCenterMarkerPaint; + private final Rect mCenterMarkerRect; + // changed in setMax() + private float mSnapThreshold; + private int mCenter; + + public BalanceSeekBar(Context context, AttributeSet attrs) { + this(context, attrs, com.android.internal.R.attr.seekBarStyle); + } + + public BalanceSeekBar(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0 /* defStyleRes */); + } + + public BalanceSeekBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + mContext = context; + Resources res = getResources(); + mCenterMarkerRect = new Rect(0 /* left */, 0 /* top */, + res.getDimensionPixelSize(R.dimen.balance_seekbar_center_marker_width), + res.getDimensionPixelSize(R.dimen.balance_seekbar_center_marker_height)); + mCenterMarkerPaint = new Paint(); + // TODO use a more suitable colour? + mCenterMarkerPaint.setColor(Color.BLACK); + mCenterMarkerPaint.setStyle(Paint.Style.FILL); + // Remove the progress colour + setProgressTintList(ColorStateList.valueOf(Color.TRANSPARENT)); + + super.setOnSeekBarChangeListener(mProxySeekBarListener); + } + + @Override + public void setOnSeekBarChangeListener(OnSeekBarChangeListener listener) { + synchronized(mListenerLock) { + mOnSeekBarChangeListener = listener; + } + } + + // Note: the superclass AbsSeekBar.setMax is synchronized. + @Override + public synchronized void setMax(int max) { + super.setMax(max); + // update snap to threshold + mCenter = max / 2; + mSnapThreshold = max * SNAP_TO_PERCENTAGE; + } + + // Note: the superclass AbsSeekBar.onDraw is synchronized. + @Override + protected synchronized void onDraw(Canvas canvas) { + // Draw a vertical line at 50% that represents centred balance + int seekBarCenter = (canvas.getHeight() - getPaddingBottom()) / 2; + canvas.save(); + canvas.translate((canvas.getWidth() - mCenterMarkerRect.right) / 2, + seekBarCenter - (mCenterMarkerRect.bottom / 2)); + canvas.drawRect(mCenterMarkerRect, mCenterMarkerPaint); + canvas.restore(); + super.onDraw(canvas); + } +} + diff --git a/src/com/android/settings/accessibility/BalanceSeekBarPreference.java b/src/com/android/settings/accessibility/BalanceSeekBarPreference.java new file mode 100644 index 00000000000..a40282ca929 --- /dev/null +++ b/src/com/android/settings/accessibility/BalanceSeekBarPreference.java @@ -0,0 +1,70 @@ +/* + * 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.settings.accessibility; + +import android.content.Context; +import android.media.AudioSystem; +import android.os.UserHandle; +import android.provider.Settings; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ImageView; +import android.widget.SeekBar; + +import androidx.core.content.res.TypedArrayUtils; +import androidx.preference.Preference; +import androidx.preference.PreferenceViewHolder; + +import com.android.settings.R; +import com.android.settings.widget.SeekBarPreference; + +/** A slider preference that directly controls audio balance **/ +public class BalanceSeekBarPreference extends SeekBarPreference { + private static final String TAG = "BalanceSeekBarPreference"; + private final Context mContext; + private BalanceSeekBar mSeekBar; + private ImageView mIconView; + + public BalanceSeekBarPreference(Context context, AttributeSet attrs) { + super(context, attrs, TypedArrayUtils.getAttr(context, + R.attr.preferenceStyle, + android.R.attr.preferenceStyle)); + mContext = context; + setLayoutResource(R.layout.preference_balance_slider); + } + + @Override + public void onBindViewHolder(PreferenceViewHolder view) { + super.onBindViewHolder(view); + mSeekBar = (BalanceSeekBar) view.findViewById(com.android.internal.R.id.seekbar); + mIconView = (ImageView) view.findViewById(com.android.internal.R.id.icon); + init(); + } + + private void init() { + if (mSeekBar == null) { + return; + } + final float balance = Settings.System.getFloatForUser( + mContext.getContentResolver(), Settings.System.MASTER_BALANCE, + 0.f /* default */, UserHandle.USER_CURRENT); + // Rescale balance to range 0-200 centered at 100. + mSeekBar.setMax(200); + mSeekBar.setProgress((int)(balance * 100.f) + 100); + mSeekBar.setEnabled(isEnabled()); + } +}