Search code examples
androidandroid-recyclerviewandroid-tvandroid-audiomanagersoundeffect

How to Disable Sound Effects in Android TV app without Muting all System Sounds


I would like to disable sound effects when browsing over RecycleView items and also clicking sounds in an Android TV app. But, I do not want to disable all other sounds (e.g., There is Exoplayer in the app that its output sounds should not be muted).

I noticed there are some other questions similar to this on Stackoverflow and the suggested solutions are:

  1. Disable Sound effect in the Layout Files by setting android:soundEffectsEnabled="false" (I put this in every Layout). However, this does not have any effect and there is still clicking and item browsing sound effects.

  2. Disable sound effects using AudioManager. I tried the following: audioManager.adjustStreamVolume(AudioManager.STREAM_NOTIFICATION, AudioManager.ADJUST_MUTE, 0); and audioManager.adjustStreamVolume(AudioManager.STREAM_SYSTEM, AudioManager.ADJUST_MUTE, 0); These mute all app sounds including Media sounds.

I would be grateful if someone can help with this issue. Thanks


Solution

  • Finally I found a solution for this problem.

    Issue 1: Disabling sound effect on pressing DPAD_CENTER key. I could resolve this issue by programmatically disabling sound effect in CardPresenter (for Leanback ListRowPresenter) and CardAdapter (for RecyclerView).

    Issue 2: Disabling sound effect on pressing DPAD navigation keys (DPAD_RIGHT, DPAD_LEFT, ...). Digging into the ViewRootImpl.java class, it turns out that navigation sound is always played without checking the soundEffect flag. Here is parts of the code in ViewRootImpl.java

    if (v.requestFocus(direction, mTempRect)) {
         boolean isFastScrolling = event.getRepeatCount() > 0;
         playSoundEffect(
                    SoundEffectConstants.getConstantForFocusDirection(direction,
                                                isFastScrolling));
         return true;
    

    So a workaround that I came up with is to override the requestFocus method in my views and always return false to prevent playing sound effect.

    Code for Leanback ListRowPresenter:

    CardPresenter.java

    public class CardPresenter extends Presenter {
         ....
    
        @Override
        public ViewHolder onCreateViewHolder(ViewGroup parent) {
             ....
    
            Context mContext = parent.getContext();
    
            CustomImageCardView mCardView = new CustomImageCardView(mContext);
    
            mCardView.setSoundEffectsEnabled(false);
    
            return new ViewHolder(mCardView);
        }
    

    CustomImageCardView.java

    public class CustomImageCardView extends ImageCardView {
    
        public CustomImageCardView(Context context, int themeResId) {
            super(context, themeResId);
        }
    
        public CustomImageCardView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    
        public CustomImageCardView(Context context) {
            super(context);
        }
    
        public CustomImageCardView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        @Override
        public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
            super.requestFocus(direction, previouslyFocusedRect);
            return false;
        }
    }
    

    Code for RecyclerView:

    CardAdapter.java

    public class CardAdapter extends RecyclerView.Adapter<CardAdapter.ViewHolder> {
    ...
    
    @NonNull
        @Override
        public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
            View view = mLayoutInflater.inflate(R.layout.recycler_view, viewGroup, false);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                view.setFocusable(true);
                view.setSoundEffectsEnabled(false);
            }
    
            mViewHolder = new ViewHolder(view);
    
            return mViewHolder;
        }
    
    

    CustomLinearLayout.java (Root View for Recycler View)

    public class CustomLinearLayout extends LinearLayout {
        public CustomLinearLayout(Context context) {
            super(context);
        }
    
        public CustomLinearLayout(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
        }
    
        public CustomLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    
        public CustomLinearLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
            super(context, attrs, defStyleAttr, defStyleRes);
        }
    
        @Override
        public void playSoundEffect(int soundConstant) {
            super.playSoundEffect(soundConstant);
        }
    
        @Override
        public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
            super.requestFocus(direction, previouslyFocusedRect);
            return false;
        }
    }