1 /* 2 * Copyright (C) 2021 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.android.media.audiotestharness.server; 18 19 import com.android.media.audiotestharness.proto.AudioTestHarnessGrpc; 20 import com.android.media.audiotestharness.server.config.SharedHostConfiguration; 21 import com.android.media.audiotestharness.server.config.SharedHostConfigurationModule; 22 import com.android.media.audiotestharness.server.utility.PortUtility; 23 24 import com.google.common.annotations.VisibleForTesting; 25 import com.google.common.base.Preconditions; 26 import com.google.inject.AbstractModule; 27 import com.google.inject.Guice; 28 import com.google.inject.Injector; 29 import com.google.inject.Key; 30 31 import java.util.concurrent.Executor; 32 import java.util.concurrent.ExecutorService; 33 import java.util.concurrent.Executors; 34 import java.util.logging.Logger; 35 36 import javax.annotation.Nullable; 37 38 /** 39 * Factory for {@link AudioTestHarnessGrpcServer} instances. 40 * 41 * <p>This class is not meant to be extended, however is left non-final for mocking purposes. 42 */ 43 public class AudioTestHarnessGrpcServerFactory implements AutoCloseable { 44 private static final Logger LOGGER = 45 Logger.getLogger(AudioTestHarnessGrpcServerFactory.class.getName()); 46 47 /** Default port used for testing purposes. */ 48 private static final int TESTING_PORT = 8080; 49 50 /** 51 * Default number of threads that should be used for task execution. 52 * 53 * <p>This value is not used when using a provided {@link ExecutorService} and thus can be 54 * overridden in cases where necessary. 55 */ 56 private static final int DEFAULT_THREAD_COUNT = 16; 57 58 /** 59 * {@link Executor} used for task execution throughout the system. 60 * 61 * <p>This executor is used both by the gRPC server as well as in underlying libraries such as 62 * the javasoundlib which uses the Executor to handle background capture while another thread 63 * handles gRPC actions. 64 */ 65 private final ExecutorService mExecutorService; 66 67 /** 68 * {@link AbstractModule} that should be used as the base module for the system's dependency 69 * injection. This can be overridden for testing or to substitute other implementations. 70 * 71 * <p>At the minimum, this module must be able to provide an instance of the {@link 72 * com.android.media.audiotestharness.proto.AudioTestHarnessGrpc.AudioTestHarnessImplBase} which 73 * is required by the system at runtime. 74 */ 75 private final AbstractModule mBaseModule; 76 AudioTestHarnessGrpcServerFactory( ExecutorService executorService, AbstractModule baseModule)77 private AudioTestHarnessGrpcServerFactory( 78 ExecutorService executorService, AbstractModule baseModule) { 79 mExecutorService = executorService; 80 mBaseModule = baseModule; 81 } 82 83 /** 84 * Creates a new {@link AudioTestHarnessGrpcServerFactory} with the default ExecutorService, 85 * which is a {@link java.util.concurrent.ThreadPoolExecutor} with {@link #DEFAULT_THREAD_COUNT} 86 * threads. 87 */ createFactory()88 public static AudioTestHarnessGrpcServerFactory createFactory() { 89 ExecutorService executorService = Executors.newFixedThreadPool(DEFAULT_THREAD_COUNT); 90 return createInternal( 91 Executors.newFixedThreadPool(DEFAULT_THREAD_COUNT), 92 AudioTestHarnessServerModule.create(executorService)); 93 } 94 95 /** 96 * Creates a new {@link AudioTestHarnessGrpcServerFactory} with the provided ExecutorService. 97 * 98 * <p>All created AudioTestHarnessGrpcServer instances will make use of this executor for tasks. 99 * Furthermore, this {@link ExecutorService} will be shutdown whenever the {@link #close()} 100 * method is invoked on this factory. 101 */ createFactoryWithExecutorService( ExecutorService executorService)102 public static AudioTestHarnessGrpcServerFactory createFactoryWithExecutorService( 103 ExecutorService executorService) { 104 return createInternal( 105 executorService, AudioTestHarnessServerModule.create(executorService)); 106 } 107 108 @VisibleForTesting createInternal( ExecutorService executorService, AbstractModule baseModule)109 static AudioTestHarnessGrpcServerFactory createInternal( 110 ExecutorService executorService, AbstractModule baseModule) { 111 return new AudioTestHarnessGrpcServerFactory( 112 Preconditions.checkNotNull(executorService, "ExecutorService cannot be null."), 113 baseModule); 114 } 115 116 /** 117 * Creates a new {@link AudioTestHarnessGrpcServer} on the specified port. 118 * 119 * <p>This port is not reserved or used until the server's {@link 120 * AudioTestHarnessGrpcServer#open()} method is called. 121 * 122 * @param sharedHostConfiguration the {@link SharedHostConfiguration} that should be used in the 123 * system. This can be used to override configurable values (such as the device's capture 124 * device) from their defaults. 125 */ createOnPort( int port, @Nullable SharedHostConfiguration sharedHostConfiguration)126 public AudioTestHarnessGrpcServer createOnPort( 127 int port, @Nullable SharedHostConfiguration sharedHostConfiguration) { 128 LOGGER.finest(String.format("createOnPort(%d, %s)", port, sharedHostConfiguration)); 129 LOGGER.info( 130 String.format( 131 "Shared Host Configuration is (%s)", 132 sharedHostConfiguration == null ? "Default" : sharedHostConfiguration)); 133 134 // Create an injector for the Audio Test Harness server, if a custom sharedHostConfiguration 135 // was provided, then we add another module which provides this configuration, otherwise, 136 // we create the module with mBaseModule only. 137 Injector injector = 138 sharedHostConfiguration == null 139 ? Guice.createInjector(mBaseModule) 140 : Guice.createInjector( 141 mBaseModule, 142 SharedHostConfigurationModule.create(sharedHostConfiguration)); 143 144 // Verify that the AudioTestHarnessImplBase class is bound, without this binding, the server 145 // will not operate. 146 if (injector.getExistingBinding( 147 Key.get(AudioTestHarnessGrpc.AudioTestHarnessImplBase.class)) 148 == null) { 149 throw new IllegalStateException( 150 "Cannot create new AudioTestHarnessGrpcServer because there is no binding for" 151 + " the Audio Test Harness gRPC Service in the module provided at " 152 + "factory creation."); 153 } 154 155 return new AudioTestHarnessGrpcServer(port, mExecutorService, injector); 156 } 157 158 /** 159 * Creates a new {@link AudioTestHarnessGrpcServer} on the {@link #TESTING_PORT}. 160 * 161 * @param sharedHostConfiguration the {@link SharedHostConfiguration} that should be used in the 162 * system. This can be used to override configurable values (such as the device's capture 163 * device) from their defaults. 164 */ createOnTestingPort( @ullable SharedHostConfiguration sharedHostConfiguration)165 public AudioTestHarnessGrpcServer createOnTestingPort( 166 @Nullable SharedHostConfiguration sharedHostConfiguration) { 167 LOGGER.finest("createOnTestingPort()"); 168 return createOnPort(TESTING_PORT, sharedHostConfiguration); 169 } 170 171 /** 172 * Creates a new {@link AudioTestHarnessGrpcServer} on the next available port within the 173 * dynamic port range. 174 * 175 * @param sharedHostConfiguration the {@link SharedHostConfiguration} that should be used in the 176 * system. This can be used to override configurable values (such as the device's capture 177 * device) from their defaults. 178 */ createOnNextAvailablePort( @ullable SharedHostConfiguration sharedHostConfiguration)179 public AudioTestHarnessGrpcServer createOnNextAvailablePort( 180 @Nullable SharedHostConfiguration sharedHostConfiguration) { 181 LOGGER.finest("createOnNextAvailablePort()"); 182 return createOnPort(PortUtility.nextAvailablePort(), sharedHostConfiguration); 183 } 184 185 /** Shuts down the {@link ExecutorService} used by the factory. */ 186 @Override close()187 public void close() { 188 LOGGER.fine("Shutting down internal ExecutorService"); 189 mExecutorService.shutdownNow(); 190 } 191 } 192