chromium/tools/android/audio_focus_grabber/java/src/org/chromium/tools/audio_focus_grabber/AudioFocusGrabberListenerService.java

// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.tools.audio_focus_grabber;

import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.IBinder;
import android.widget.RemoteViews;

import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;

import org.chromium.base.IntentUtils;
import org.chromium.base.Log;

/** The listener service, which listens to intents and perform audio focus actions. */
public class AudioFocusGrabberListenerService extends Service {
    private static final String TAG = "AudioFocusGrabber";

    public static final String ACTION_GAIN = "AUDIO_FOCUS_GRABBER_GAIN";
    public static final String ACTION_TRANSIENT_PAUSE = "AUDIO_FOCUS_GRABBER_TRANSIENT_PAUSE";
    public static final String ACTION_TRANSIENT_DUCK = "AUDIO_FOCUS_GRABBER_TRANSIENT_DUCK";
    public static final String ACTION_SHOW_NOTIFICATION = "AUDIO_FOCUS_GRABBER_SHOW_NOTIFICATION";
    public static final String ACTION_HIDE_NOTIFICATION = "AUDIO_FOCUS_GRABBER_HIDE_NOTIFICATION";

    private static final int NOTIFICATION_ID = 1;

    AudioManager mAudioManager;
    MediaPlayer mMediaPlayer;
    boolean mIsDucking;

    @Override
    public void onCreate() {
        super.onCreate();
        mAudioManager =
                (AudioManager) getApplicationContext().getSystemService(Context.AUDIO_SERVICE);
    }

    @Override
    public void onDestroy() {
        hideNotification();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (intent != null) {
            Log.i(TAG, "received intent: " + intent.getAction());
        } else {
            Log.i(TAG, "received null intent");
            return START_NOT_STICKY;
        }
        processIntent(intent);
        return START_NOT_STICKY;
    }

    private void processIntent(Intent intent) {
        if (mMediaPlayer != null) {
            Log.i(
                    TAG,
                    "There's already a MediaPlayer playing,"
                            + " stopping the existing player and abandon focus");
            releaseAndAbandonAudioFocus();
        }
        String action = intent.getAction();
        if (ACTION_SHOW_NOTIFICATION.equals(action)) {
            showNotification();
        } else if (ACTION_HIDE_NOTIFICATION.equals(action)) {
            hideNotification();
        } else if (ACTION_GAIN.equals(action)) {
            gainFocusAndPlay(AudioManager.AUDIOFOCUS_GAIN);
        } else if (ACTION_TRANSIENT_PAUSE.equals(action)) {
            gainFocusAndPlay(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
        } else if (ACTION_TRANSIENT_DUCK.equals(action)) {
            gainFocusAndPlay(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
        } else {
            assert false;
        }
    }

    private void gainFocusAndPlay(int focusType) {
        int result =
                mAudioManager.requestAudioFocus(
                        mOnAudioFocusChangeListener, AudioManager.STREAM_MUSIC, focusType);
        if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
            playSound();
        } else {
            Log.i(TAG, "cannot request audio focus");
        }
    }

    private void playSound() {
        mMediaPlayer = MediaPlayer.create(getApplicationContext(), R.raw.ping);
        mMediaPlayer.setOnCompletionListener(mOnCompletionListener);
        mMediaPlayer.start();
    }

    private void releaseAndAbandonAudioFocus() {
        mMediaPlayer.release();
        mMediaPlayer = null;
        mAudioManager.abandonAudioFocus(mOnAudioFocusChangeListener);
    }

    MediaPlayer.OnCompletionListener mOnCompletionListener =
            new MediaPlayer.OnCompletionListener() {
                @Override
                public void onCompletion(MediaPlayer mp) {
                    releaseAndAbandonAudioFocus();
                }
            };

    AudioManager.OnAudioFocusChangeListener mOnAudioFocusChangeListener =
            new AudioManager.OnAudioFocusChangeListener() {
                @Override
                public void onAudioFocusChange(int focusChange) {
                    switch (focusChange) {
                        case AudioManager.AUDIOFOCUS_GAIN:
                            if (mIsDucking) {
                                mMediaPlayer.setVolume(1.0f, 1.0f);
                                mIsDucking = false;
                            } else {
                                mMediaPlayer.start();
                            }
                            break;
                        case AudioManager.AUDIOFOCUS_LOSS:
                            mMediaPlayer.stop();
                            mMediaPlayer.release();
                            mMediaPlayer = null;
                            break;
                        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                            mMediaPlayer.pause();
                            break;
                        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                            mMediaPlayer.setVolume(0.1f, 0.1f);
                            mIsDucking = true;
                            break;
                        default:
                            break;
                    }
                }
            };

    private void showNotification() {
        RemoteViews view =
                new RemoteViews(
                        this.getPackageName(), R.layout.audio_focus_grabber_notification_bar);
        view.setOnClickPendingIntent(
                R.id.notification_button_gain, createPendingIntent(ACTION_GAIN));
        view.setOnClickPendingIntent(
                R.id.notification_button_transient_pause,
                createPendingIntent(ACTION_TRANSIENT_PAUSE));
        view.setOnClickPendingIntent(
                R.id.notification_button_transient_duck,
                createPendingIntent(ACTION_TRANSIENT_DUCK));
        view.setOnClickPendingIntent(
                R.id.notification_button_hide, createPendingIntent(ACTION_HIDE_NOTIFICATION));

        NotificationManagerCompat manager = NotificationManagerCompat.from(this);
        NotificationCompat.Builder builder =
                new NotificationCompat.Builder(this)
                        .setContent(view)
                        .setSmallIcon(R.drawable.notification_icon);
        manager.notify(NOTIFICATION_ID, builder.build());
    }

    private void hideNotification() {
        NotificationManagerCompat manager = NotificationManagerCompat.from(this);
        manager.cancel(NOTIFICATION_ID);
    }

    private PendingIntent createPendingIntent(String action) {
        Intent i = new Intent(this, AudioFocusGrabberListenerService.class);
        i.setAction(action);
        return PendingIntent.getService(
                this,
                0,
                i,
                PendingIntent.FLAG_CANCEL_CURRENT
                        | IntentUtils.getPendingIntentMutabilityFlag(false));
    }
}