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