1/*
2 * Copyright (C) 2015 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
17package com.google.android.car.kitchensink.audio;
18
19import android.car.Car;
20import android.car.CarAppContextManager;
21import android.car.CarAppContextManager.AppContextChangeListener;
22import android.car.CarAppContextManager.AppContextOwnershipChangeListener;
23import android.car.CarNotConnectedException;
24import android.car.media.CarAudioManager;
25import android.content.ComponentName;
26import android.content.Context;
27import android.content.ServiceConnection;
28import android.media.AudioAttributes;
29import android.media.AudioManager;
30import android.os.Bundle;
31import android.os.Handler;
32import android.os.IBinder;
33import android.os.Looper;
34import android.support.v4.app.Fragment;
35import android.util.Log;
36import android.view.LayoutInflater;
37import android.view.View;
38import android.view.View.OnClickListener;
39import android.view.ViewGroup;
40import android.widget.Button;
41import android.widget.CompoundButton;
42import android.widget.CompoundButton.OnCheckedChangeListener;
43import android.widget.RadioGroup;
44import android.widget.TextView;
45import android.widget.ToggleButton;
46
47import com.google.android.car.kitchensink.CarEmulator;
48import com.google.android.car.kitchensink.R;
49import com.google.android.car.kitchensink.audio.AudioPlayer.PlayStateListener;
50
51public class AudioTestFragment extends Fragment {
52    private static final String TAG = "CAR.AUDIO.KS";
53    private static final boolean DBG = true;
54
55    private AudioManager mAudioManager;
56    private FocusHandler mAudioFocusHandler;
57    private Button mNavPlayOnce;
58    private Button mVrPlayOnce;
59    private Button mSystemPlayOnce;
60    private Button mMediaPlay;
61    private Button mMediaPlayOnce;
62    private Button mMediaStop;
63    private Button mNavStart;
64    private Button mNavEnd;
65    private Button mVrStart;
66    private Button mVrEnd;
67    private Button mRadioStart;
68    private Button mRadioEnd;
69    private Button mSpeakerPhoneOn;
70    private Button mSpeakerPhoneOff;
71    private Button mMicrophoneOn;
72    private Button mMicrophoneOff;
73    private ToggleButton mEnableMocking;
74    private ToggleButton mRejectFocus;
75
76    private AudioPlayer mMusicPlayer;
77    private AudioPlayer mMusicPlayerShort;
78    private AudioPlayer mNavGuidancePlayer;
79    private AudioPlayer mVrPlayer;
80    private AudioPlayer mSystemPlayer;
81    private AudioPlayer[] mAllPlayers;
82
83    private Handler mHandler;
84    private Context mContext;
85
86    private Car mCar;
87    private CarAppContextManager mAppContextManager;
88    private CarAudioManager mCarAudioManager;
89    private AudioAttributes mMusicAudioAttrib;
90    private AudioAttributes mNavAudioAttrib;
91    private AudioAttributes mVrAudioAttrib;
92    private AudioAttributes mRadioAudioAttrib;
93    private AudioAttributes mSystemSoundAudioAttrib;
94    private CarEmulator mCarEmulator;
95
96    private final AudioManager.OnAudioFocusChangeListener mNavFocusListener =
97            new AudioManager.OnAudioFocusChangeListener() {
98                @Override
99                public void onAudioFocusChange(int focusChange) {
100                    Log.i(TAG, "Nav focus change:" + focusChange);
101                }
102    };
103    private final AudioManager.OnAudioFocusChangeListener mVrFocusListener =
104            new AudioManager.OnAudioFocusChangeListener() {
105                @Override
106                public void onAudioFocusChange(int focusChange) {
107                    Log.i(TAG, "VR focus change:" + focusChange);
108                }
109    };
110    private final AudioManager.OnAudioFocusChangeListener mRadioFocusListener =
111            new AudioManager.OnAudioFocusChangeListener() {
112                @Override
113                public void onAudioFocusChange(int focusChange) {
114                    Log.i(TAG, "Radio focus change:" + focusChange);
115                }
116    };
117
118    private final AppContextOwnershipChangeListener mOwnershipListener =
119            new AppContextOwnershipChangeListener() {
120                @Override
121                public void onAppContextOwnershipLoss(int context) {
122                }
123    };
124
125    private void init() {
126        mContext = getContext();
127        mHandler = new Handler(Looper.getMainLooper());
128        mCar = Car.createCar(mContext, new ServiceConnection() {
129            @Override
130            public void onServiceConnected(ComponentName name, IBinder service) {
131                try {
132                    mAppContextManager =
133                            (CarAppContextManager) mCar.getCarManager(Car.APP_CONTEXT_SERVICE);
134                } catch (CarNotConnectedException e) {
135                    throw new RuntimeException("Failed to create app context manager", e);
136                }
137                try {
138                    mAppContextManager.registerContextListener(new AppContextChangeListener() {
139                        @Override
140                        public void onAppContextChange(int activeContexts) {
141                        }
142                    }, CarAppContextManager.APP_CONTEXT_NAVIGATION |
143                     CarAppContextManager.APP_CONTEXT_VOICE_COMMAND);
144                } catch (CarNotConnectedException e) {
145                    Log.e(TAG, "Failed to register context listener", e);
146                }
147                try {
148                    mCarAudioManager = (CarAudioManager) mCar.getCarManager(Car.AUDIO_SERVICE);
149                } catch (CarNotConnectedException e) {
150                    throw new RuntimeException("Failed to create audio manager", e);
151                }
152                mMusicAudioAttrib = mCarAudioManager.getAudioAttributesForCarUsage(
153                        CarAudioManager.CAR_AUDIO_USAGE_MUSIC);
154                mNavAudioAttrib = mCarAudioManager.getAudioAttributesForCarUsage(
155                        CarAudioManager.CAR_AUDIO_USAGE_NAVIGATION_GUIDANCE);
156                mVrAudioAttrib = mCarAudioManager.getAudioAttributesForCarUsage(
157                        CarAudioManager.CAR_AUDIO_USAGE_VOICE_COMMAND);
158                mRadioAudioAttrib = mCarAudioManager.getAudioAttributesForCarUsage(
159                        CarAudioManager.CAR_AUDIO_USAGE_RADIO);
160                mSystemSoundAudioAttrib = mCarAudioManager.getAudioAttributesForCarUsage(
161                        CarAudioManager.CAR_AUDIO_USAGE_SYSTEM_SOUND);
162
163                mMusicPlayer = new AudioPlayer(mContext, R.raw.john_harrison_with_the_wichita_state_university_chamber_players_05_summer_mvt_2_adagio,
164                        mMusicAudioAttrib);
165                mMusicPlayerShort = new AudioPlayer(mContext, R.raw.ring_classic_01,
166                        mMusicAudioAttrib);
167                mNavGuidancePlayer = new AudioPlayer(mContext, R.raw.turnright,
168                        mNavAudioAttrib);
169                // no Usage for voice command yet.
170                mVrPlayer = new AudioPlayer(mContext, R.raw.one2six,
171                        mVrAudioAttrib);
172                mSystemPlayer = new AudioPlayer(mContext, R.raw.ring_classic_01,
173                        mSystemSoundAudioAttrib);
174                mAllPlayers = new AudioPlayer[] {
175                        mMusicPlayer,
176                        mMusicPlayerShort,
177                        mNavGuidancePlayer,
178                        mVrPlayer,
179                        mSystemPlayer
180                };
181            }
182            @Override
183            public void onServiceDisconnected(ComponentName name) {
184            }
185            }, Looper.getMainLooper());
186        mCar.connect();
187    }
188
189    @Override
190    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
191        Log.i(TAG, "onCreateView");
192        init();
193        View view = inflater.inflate(R.layout.audio, container, false);
194        mAudioManager = (AudioManager) mContext.getSystemService(
195                Context.AUDIO_SERVICE);
196        mAudioFocusHandler = new FocusHandler(
197                (RadioGroup) view.findViewById(R.id.button_focus_request_selection),
198                (Button) view.findViewById(R.id.button_audio_focus_request),
199                (TextView) view.findViewById(R.id.text_audio_focus_state));
200        mMediaPlay = (Button) view.findViewById(R.id.button_media_play_start);
201        mMediaPlay.setOnClickListener(new OnClickListener() {
202            @Override
203            public void onClick(View v) {
204                mMusicPlayer.start(false, true, AudioManager.AUDIOFOCUS_GAIN);
205            }
206        });
207        mMediaPlayOnce = (Button) view.findViewById(R.id.button_media_play_once);
208        mMediaPlayOnce.setOnClickListener(new OnClickListener() {
209            @Override
210            public void onClick(View v) {
211                mMusicPlayerShort.start(true, false, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
212                // play only for 1 sec and stop
213                mHandler.postDelayed(new Runnable() {
214                    @Override
215                    public void run() {
216                        mMusicPlayerShort.stop();
217                    }
218                }, 1000);
219            }
220        });
221        mMediaStop = (Button) view.findViewById(R.id.button_media_play_stop);
222        mMediaStop.setOnClickListener(new OnClickListener() {
223            @Override
224            public void onClick(View v) {
225                mMusicPlayer.stop();
226            }
227        });
228        mNavPlayOnce = (Button) view.findViewById(R.id.button_nav_play_once);
229        mNavPlayOnce.setOnClickListener(new OnClickListener() {
230            @Override
231            public void onClick(View v) {
232                if (mAppContextManager == null) {
233                    return;
234                }
235                if (DBG) {
236                    Log.i(TAG, "Nav start");
237                }
238                if (!mNavGuidancePlayer.isPlaying()) {
239                    try {
240                        mAppContextManager.setActiveContexts(mOwnershipListener,
241                                CarAppContextManager.APP_CONTEXT_NAVIGATION);
242                    } catch (CarNotConnectedException e) {
243                        Log.e(TAG, "Failed to set active context", e);
244                    }
245                    mNavGuidancePlayer.start(true, false,
246                            AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,
247                            new PlayStateListener() {
248                        @Override
249                        public void onCompletion() {
250                            try {
251                                mAppContextManager.resetActiveContexts(
252                                        CarAppContextManager.APP_CONTEXT_NAVIGATION);
253                            } catch (CarNotConnectedException e) {
254                                Log.e(TAG, "Failed to reset active context", e);
255                            }
256                        }
257                    });
258                }
259            }
260        });
261        mVrPlayOnce = (Button) view.findViewById(R.id.button_vr_play_once);
262        mVrPlayOnce.setOnClickListener(new OnClickListener() {
263            @Override
264            public void onClick(View v) {
265                if (mAppContextManager == null) {
266                    return;
267                }
268                if (DBG) {
269                    Log.i(TAG, "VR start");
270                }
271                try {
272                    mAppContextManager.setActiveContexts(mOwnershipListener,
273                            CarAppContextManager.APP_CONTEXT_VOICE_COMMAND);
274                } catch (CarNotConnectedException e) {
275                    Log.e(TAG, "Failed to set active context", e);
276                }
277                if (!mVrPlayer.isPlaying()) {
278                    mVrPlayer.start(true, false, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
279                            new PlayStateListener() {
280                        @Override
281                        public void onCompletion() {
282                            try {
283                                mAppContextManager.resetActiveContexts(
284                                        CarAppContextManager.APP_CONTEXT_VOICE_COMMAND);
285                            } catch (CarNotConnectedException e) {
286                                Log.e(TAG, "Failed to reset active context", e);
287                            }
288                        }
289                    });
290                }
291            }
292        });
293        mSystemPlayOnce = (Button) view.findViewById(R.id.button_system_play_once);
294        mSystemPlayOnce.setOnClickListener(new OnClickListener() {
295            @Override
296            public void onClick(View v) {
297                if (DBG) {
298                    Log.i(TAG, "System start");
299                }
300                if (!mSystemPlayer.isPlaying()) {
301                    // system sound played without focus
302                    mSystemPlayer.start(false, false, 0);
303                }
304            }
305        });
306        mNavStart = (Button) view.findViewById(R.id.button_nav_start);
307        mNavStart.setOnClickListener(new OnClickListener() {
308            @Override
309            public void onClick(View v) {
310                handleNavStart();
311            }
312        });
313        mNavEnd = (Button) view.findViewById(R.id.button_nav_end);
314        mNavEnd.setOnClickListener(new OnClickListener() {
315            @Override
316            public void onClick(View v) {
317                handleNavEnd();
318            }
319        });
320        mVrStart = (Button) view.findViewById(R.id.button_vr_start);
321        mVrStart.setOnClickListener(new OnClickListener() {
322            @Override
323            public void onClick(View v) {
324                handleVrStart();
325            }
326        });
327        mVrEnd = (Button) view.findViewById(R.id.button_vr_end);
328        mVrEnd.setOnClickListener(new OnClickListener() {
329            @Override
330            public void onClick(View v) {
331                handleVrEnd();
332            }
333        });
334        mRadioStart = (Button) view.findViewById(R.id.button_radio_start);
335        mRadioStart.setOnClickListener(new OnClickListener() {
336            @Override
337            public void onClick(View v) {
338                handleRadioStart();
339            }
340        });
341        mRadioEnd = (Button) view.findViewById(R.id.button_radio_end);
342        mRadioEnd.setOnClickListener(new OnClickListener() {
343            @Override
344            public void onClick(View v) {
345                handleRadioEnd();
346            }
347        });
348        mSpeakerPhoneOn = (Button) view.findViewById(R.id.button_speaker_phone_on);
349        mSpeakerPhoneOn.setOnClickListener(new OnClickListener() {
350            @Override
351            public void onClick(View v) {
352                mAudioManager.setSpeakerphoneOn(true);
353            }
354        });
355        mSpeakerPhoneOff = (Button) view.findViewById(R.id.button_speaker_phone_off);
356        mSpeakerPhoneOff.setOnClickListener(new OnClickListener() {
357            @Override
358            public void onClick(View v) {
359                mAudioManager.setSpeakerphoneOn(false);
360            }
361        });
362        mMicrophoneOn = (Button) view.findViewById(R.id.button_microphone_on);
363        mMicrophoneOn.setOnClickListener(new OnClickListener() {
364            @Override
365            public void onClick(View v) {
366                mAudioManager.setMicrophoneMute(false); // Turn the microphone on.
367            }
368        });
369        mMicrophoneOff = (Button) view.findViewById(R.id.button_microphone_off);
370        mMicrophoneOff.setOnClickListener(new OnClickListener() {
371            @Override
372            public void onClick(View v) {
373                mAudioManager.setMicrophoneMute(true); // Mute the microphone.
374            }
375        });
376
377
378        mRejectFocus = (ToggleButton) view.findViewById(R.id.button_reject_audio_focus);
379        mRejectFocus.setOnCheckedChangeListener(new OnCheckedChangeListener() {
380            @Override
381            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
382                if (mCarEmulator == null) {
383                    return;
384                }
385                if (!mEnableMocking.isChecked()) {
386                    return;
387                }
388                if (isChecked) {
389                    mCarEmulator.setAudioFocusControl(true);
390                } else {
391                    mCarEmulator.setAudioFocusControl(false);
392                }
393            }
394        });
395        mRejectFocus.setActivated(false);
396        mEnableMocking = (ToggleButton) view.findViewById(R.id.button_mock_audio);
397        mEnableMocking.setOnCheckedChangeListener(new OnCheckedChangeListener() {
398            @Override
399            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
400                if (mCarEmulator == null) {
401                    mCarEmulator = new CarEmulator(mCar);
402                }
403                if (isChecked) {
404                    mRejectFocus.setActivated(true);
405                    mCarEmulator.start();
406                } else {
407                    mRejectFocus.setActivated(false);
408                    mCarEmulator.stop();
409                    mCarEmulator = null;
410                }
411            }
412        });
413        return view;
414    }
415
416    @Override
417    public void onDestroyView() {
418        super.onDestroyView();
419        Log.i(TAG, "onDestroyView");
420        if (mCarEmulator != null) {
421            mCarEmulator.setAudioFocusControl(false);
422            mCarEmulator.stop();
423        }
424        for (AudioPlayer p : mAllPlayers) {
425            p.stop();
426        }
427        if (mAudioFocusHandler != null) {
428            mAudioFocusHandler.release();
429            mAudioFocusHandler = null;
430        }
431        if (mAppContextManager != null) {
432            try {
433                mAppContextManager.resetActiveContexts(CarAppContextManager.APP_CONTEXT_NAVIGATION |
434                        CarAppContextManager.APP_CONTEXT_VOICE_COMMAND);
435            } catch (CarNotConnectedException e) {
436                Log.e(TAG, "Failed to reset active context", e);
437            }
438        }
439    }
440
441    private void handleNavStart() {
442        if (mAppContextManager == null) {
443            return;
444        }
445        if (mCarAudioManager == null) {
446            return;
447        }
448        if (DBG) {
449            Log.i(TAG, "Nav start");
450        }
451        try {
452            mAppContextManager.setActiveContexts(mOwnershipListener,
453                    CarAppContextManager.APP_CONTEXT_NAVIGATION);
454        } catch (CarNotConnectedException e) {
455            Log.e(TAG, "Failed to set active context", e);
456        }
457        mCarAudioManager.requestAudioFocus(mNavFocusListener, mNavAudioAttrib,
458                AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0);
459    }
460
461    private void handleNavEnd() {
462        if (mAppContextManager == null) {
463            return;
464        }
465        if (mCarAudioManager == null) {
466            return;
467        }
468        if (DBG) {
469            Log.i(TAG, "Nav end");
470        }
471        try {
472            mAppContextManager.resetActiveContexts(
473                    CarAppContextManager.APP_CONTEXT_NAVIGATION);
474        } catch (CarNotConnectedException e) {
475            Log.e(TAG, "Failed to reset active context", e);
476        }
477        mCarAudioManager.abandonAudioFocus(mNavFocusListener, mNavAudioAttrib);
478    }
479
480    private void handleVrStart() {
481        if (mAppContextManager == null) {
482            return;
483        }
484        if (mCarAudioManager == null) {
485            return;
486        }
487        if (DBG) {
488            Log.i(TAG, "VR start");
489        }
490        try {
491            mAppContextManager.setActiveContexts(mOwnershipListener,
492                    CarAppContextManager.APP_CONTEXT_VOICE_COMMAND);
493        } catch (CarNotConnectedException e) {
494            Log.e(TAG, "Failed to set active context", e);
495        }
496        mCarAudioManager.requestAudioFocus(mVrFocusListener, mVrAudioAttrib,
497                AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, 0);
498    }
499
500    private void handleVrEnd() {
501        if (mAppContextManager == null) {
502            return;
503        }
504        if (mCarAudioManager == null) {
505            return;
506        }
507        if (DBG) {
508            Log.i(TAG, "VR end");
509        }
510        try {
511            mAppContextManager.resetActiveContexts(
512                    CarAppContextManager.APP_CONTEXT_VOICE_COMMAND);
513        } catch (CarNotConnectedException e) {
514            Log.e(TAG, "Failed to reset active context", e);
515        }
516        mCarAudioManager.abandonAudioFocus(mVrFocusListener, mVrAudioAttrib);
517    }
518
519    private void handleRadioStart() {
520        if (mCarAudioManager == null) {
521            return;
522        }
523        if (DBG) {
524            Log.i(TAG, "Radio start");
525        }
526        mCarAudioManager.requestAudioFocus(mRadioFocusListener, mRadioAudioAttrib,
527                AudioManager.AUDIOFOCUS_GAIN, 0);
528    }
529
530    private void handleRadioEnd() {
531        if (mCarAudioManager == null) {
532            return;
533        }
534        if (DBG) {
535            Log.i(TAG, "Radio end");
536        }
537        mCarAudioManager.abandonAudioFocus(mRadioFocusListener, mRadioAudioAttrib);
538    }
539
540    private class FocusHandler {
541        private static final String AUDIO_FOCUS_STATE_GAIN = "gain";
542        private static final String AUDIO_FOCUS_STATE_RELEASED_UNKNOWN = "released / unknown";
543
544        private final RadioGroup mRequestSelection;
545        private final TextView mText;
546        private final AudioFocusListener mFocusListener;
547
548        public FocusHandler(RadioGroup radioGroup, Button requestButton, TextView text) {
549            mText = text;
550            mRequestSelection = radioGroup;
551            mRequestSelection.check(R.id.focus_gain);
552            setFocusText(AUDIO_FOCUS_STATE_RELEASED_UNKNOWN);
553            mFocusListener = new AudioFocusListener();
554            requestButton.setOnClickListener(new OnClickListener() {
555                @Override
556                public void onClick(View v) {
557                    int selectedButtonId = mRequestSelection.getCheckedRadioButtonId();
558                    int focusRequest = AudioManager.AUDIOFOCUS_GAIN;
559                    if (selectedButtonId == R.id.focus_gain_transient_duck) {
560                        focusRequest = AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK;
561                    } else if (selectedButtonId == R.id.focus_release) {
562                        mAudioManager.abandonAudioFocus(mFocusListener);
563                        setFocusText(AUDIO_FOCUS_STATE_RELEASED_UNKNOWN);
564                        return;
565                    }
566                    int ret = mAudioManager.requestAudioFocus(mFocusListener,
567                            AudioManager.STREAM_MUSIC, focusRequest);
568                    Log.i(TAG, "requestAudioFocus returned " + ret);
569                    if (ret == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
570                        setFocusText(AUDIO_FOCUS_STATE_GAIN);
571                    }
572                }
573            });
574        }
575
576        public void release() {
577            abandonAudioFocus();
578        }
579
580        private void abandonAudioFocus() {
581            if (DBG) {
582                Log.i(TAG, "abandonAudioFocus");
583            }
584            mAudioManager.abandonAudioFocus(mFocusListener);
585            setFocusText(AUDIO_FOCUS_STATE_RELEASED_UNKNOWN);
586        }
587
588        private void setFocusText(String msg) {
589            mText.setText("focus state:" + msg);
590        }
591
592        private class AudioFocusListener implements AudioManager.OnAudioFocusChangeListener {
593            @Override
594            public void onAudioFocusChange(int focusChange) {
595                Log.i(TAG, "onAudioFocusChange " + focusChange);
596                if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
597                    setFocusText(AUDIO_FOCUS_STATE_GAIN);
598                } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
599                    setFocusText("loss");
600                } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
601                    setFocusText("loss,transient");
602                } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
603                    setFocusText("loss,transient,duck");
604                }
605            }
606        }
607    }
608}
609