1 /*
2  * Copyright (C) 2023 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.google.android.car.kitchensink.audio;
18 
19 import static android.media.AudioManager.AUDIOFOCUS_GAIN;
20 import static android.media.AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
21 
22 import android.car.Car;
23 import android.car.CarOccupantZoneManager;
24 import android.content.Context;
25 import android.media.AudioFocusRequest;
26 import android.media.AudioManager;
27 import android.os.Bundle;
28 import android.util.Log;
29 import android.view.KeyEvent;
30 import android.view.LayoutInflater;
31 import android.view.View;
32 import android.view.ViewGroup;
33 import android.widget.TextView;
34 import android.widget.Toast;
35 
36 import androidx.annotation.NonNull;
37 import androidx.annotation.Nullable;
38 import androidx.fragment.app.Fragment;
39 
40 import com.google.android.car.kitchensink.R;
41 
42 import java.io.BufferedReader;
43 import java.io.IOException;
44 import java.io.InputStreamReader;
45 import java.util.concurrent.ExecutorService;
46 import java.util.concurrent.Executors;
47 
48 public final class OemCarServiceTestFragment extends Fragment {
49     private static final String TAG = OemCarServiceTestFragment.class.getSimpleName();
50     private static final int TEST_ITERATIONS = 10;
51     private Context mContext;
52     private Car mCar;
53     private AudioManager mAudioManager;
54     private VolumeKeyEventsButtonManager mVolumeKeyEventHandler;
55     private final ExecutorService mPool = Executors.newFixedThreadPool(TEST_ITERATIONS);
56     private TextView mAudioFocusResultText;
57     private TextView mAudioVolumeResultText;
58 
connectCar()59     private void connectCar() {
60         mContext = getContext();
61         mCar = Car.createCar(mContext, /* handler= */ null,
62                 Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER, (car, ready) -> {
63                     if (!ready) {
64                         return;
65                     }
66                 });
67 
68         mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
69     }
70 
71     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle)72     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
73         Log.i(TAG, "onCreateView");
74         View view = inflater.inflate(R.layout.oemcarservice, container, /* attachRoot= */ false);
75 
76         connectCar();
77 
78         mVolumeKeyEventHandler = new VolumeKeyEventsButtonManager(
79                 mCar.getCarManager(CarOccupantZoneManager.class));
80 
81         view.findViewById(R.id.oem_car_service_audio_volume_test_button).setOnClickListener(v -> {
82             sendVolumeKeyEvent();
83         });
84 
85         view.findViewById(R.id.oem_car_service_audio_focus_test_button).setOnClickListener(v -> {
86             sendAudioFocusRequest();
87         });
88 
89         mAudioFocusResultText = view.findViewById(R.id.oem_car_service_audio_focus_text);
90 
91         mAudioVolumeResultText = view.findViewById(R.id.oem_car_service_audio_volume_text);
92 
93         return view;
94     }
95 
96     @Override
onViewCreated(@onNull View view, @Nullable Bundle savedInstanceState)97     public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
98         Log.i(TAG, "onViewCreated");
99         super.onViewCreated(view, savedInstanceState);
100     }
101 
102     @Override
onDestroyView()103     public void onDestroyView() {
104         Log.i(TAG, "onDestroyView");
105 
106         if (mCar != null && mCar.isConnected()) {
107             mCar.disconnect();
108             mCar = null;
109         }
110         super.onDestroyView();
111     }
112 
sendAudioFocusRequest()113     private void sendAudioFocusRequest() {
114         for (int i = 0; i < TEST_ITERATIONS; i++) {
115             MediaWithDelayedFocusListener mediaWithDelayedFocusListener =
116                     new MediaWithDelayedFocusListener();
117             AudioFocusRequest mediaAudioFocusRequest = new AudioFocusRequest.Builder(
118                     AUDIOFOCUS_GAIN)
119                     .setAcceptsDelayedFocusGain(true).setOnAudioFocusChangeListener(
120                             mediaWithDelayedFocusListener).build();
121             int delayedFocusRequestResults = mAudioManager.requestAudioFocus(
122                     mediaAudioFocusRequest);
123 
124             if (delayedFocusRequestResults != AUDIOFOCUS_REQUEST_GRANTED) {
125                 Log.i(TAG, "sendAudioFocusRequest not granted " + delayedFocusRequestResults);
126             }
127         }
128 
129         dump("CarOemAudioFocusProxyService", mAudioFocusResultText);
130         Toast.makeText(mContext,
131                 "Test Finished for Audio Focus Requests with " + TEST_ITERATIONS
132                         + " iterations.", Toast.LENGTH_SHORT).show();
133     }
134 
sendVolumeKeyEvent()135     private void sendVolumeKeyEvent() {
136         for (int i = 0; i < TEST_ITERATIONS; i++) {
137             mPool.execute(() -> mVolumeKeyEventHandler
138                     .sendClickEvent(KeyEvent.KEYCODE_VOLUME_UP));
139         }
140 
141         dump("CarOemAudioVolumeProxyService", mAudioVolumeResultText);
142         Toast.makeText(mContext, "Test Finished for Volume Key Events with " + TEST_ITERATIONS
143                 + " iterations.", Toast.LENGTH_SHORT).show();
144     }
145 
dump(String header, TextView textView)146     private void dump(String header, TextView textView) {
147         Process dump;
148         try {
149             dump = Runtime.getRuntime().exec("dumpsys car_service --oem-service");
150         } catch (IOException e) {
151             Log.e(TAG, "Cannot flush", e);
152             return;
153         }
154 
155         try (BufferedReader reader = new BufferedReader(
156                 new InputStreamReader(dump.getInputStream()))) {
157             String line = "";
158             boolean captureDump = false;
159             int sum = 0;
160             float iterations = 0;
161             while ((line = reader.readLine()) != null) {
162                 // End of execution time dump.
163                 if (captureDump) {
164                     if (line.contains("time log complete")) {
165                         break;
166                     }
167                     if (line.contains("startTime, duration")) {
168                         continue;
169                     }
170                     if (line.contains(",")) {
171                         sum += Integer.parseInt(line.split(",")[1].trim());
172                         iterations++;
173                     }
174                 }
175                 if (line.contains(header)) {
176                     captureDump = true;
177                 }
178             }
179             if (iterations == 0) {
180                 textView.setText(getResources().getString(R.string.oem_car_service_no_results));
181                 return;
182             }
183             textView.setText(String.format(
184                     getResources().getString(R.string.oem_car_service_average_execution_time),
185                     sum / iterations));
186         } catch (IOException e) {
187             Log.e(TAG, "Cannot flush", e);
188         }
189     }
190 
191     private static final class MediaWithDelayedFocusListener implements
192             AudioManager.OnAudioFocusChangeListener {
193         @Override
onAudioFocusChange(int focusChange)194         public void onAudioFocusChange(int focusChange) {
195             Log.i(TAG, "Focus changed" + focusChange);
196         }
197     }
198 }
199