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