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 package com.android.media.audiotestharness.server;
17 
18 import com.android.media.audiotestharness.proto.AudioTestHarnessGrpc;
19 import com.android.media.audiotestharness.server.config.SharedHostConfiguration;
20 
21 import com.google.common.base.Preconditions;
22 import com.google.inject.ConfigurationException;
23 import com.google.inject.Injector;
24 
25 import io.grpc.Server;
26 import io.grpc.ServerBuilder;
27 
28 import java.io.IOException;
29 import java.util.concurrent.Executor;
30 import java.util.concurrent.ExecutorService;
31 import java.util.logging.Level;
32 import java.util.logging.Logger;
33 
34 /**
35  * gRPC Server to the Audio Test Harness Infrastructure.
36  *
37  * <p>This class is designed in such a way that the spin up and tear down of the gRPC server,
38  * including all dependencies are abstracted away from any users of Audio Test Harness. It is
39  * possible to use the service implementation directly and customize the deployment of the service,
40  * however, this is meant as an easy alternative for users who just want a working system and don't
41  * care how it works under the hood.
42  *
43  * <p>This class should not be subclassed or extended, however is left non-final for testing
44  * purposes.
45  */
46 public class AudioTestHarnessGrpcServer implements AutoCloseable {
47 
48     private static final Logger LOGGER =
49             Logger.getLogger(AudioTestHarnessGrpcServer.class.getName());
50 
51     /** The port on which the gRPC Server should be executed on. */
52     private final int mPort;
53 
54     /**
55      * {@link Executor} used for background task execution. These tasks can be the handling of gRPC
56      * calls or other background tasks in the system such as the publishing of audio sample data by
57      * a {@link com.android.media.audiotestharness.server.core.AudioCapturer}.
58      */
59     private final ExecutorService mExecutor;
60 
61     /** {@link Injector} used for dependency management and injection. */
62     private final Injector mInjector;
63 
64     /** Underlying {@link Server} that manages gRPC calls. */
65     private Server mServer;
66 
AudioTestHarnessGrpcServer(int port, ExecutorService executor, Injector injector)67     AudioTestHarnessGrpcServer(int port, ExecutorService executor, Injector injector) {
68         LOGGER.finest(String.format("new AudioTestHarnessGrpcServer on Port (%d)", port));
69 
70         this.mPort = port;
71         this.mExecutor = executor;
72         this.mInjector = injector;
73     }
74 
main(String[] args)75     public static void main(String[] args) {
76         try (AudioTestHarnessGrpcServer server =
77                 AudioTestHarnessGrpcServerFactory.createFactory()
78                         .createOnTestingPort(SharedHostConfiguration.getDefault())) {
79             server.open();
80 
81             // Ensure that resources are cleanly stopped upon JVM shutdown.
82             Runtime.getRuntime().addShutdownHook(new Thread(server::close));
83 
84             // Wait for server execution to complete. So that CTRL + C or some other mechanism must
85             // be used to kill the server.
86             server.mServer.awaitTermination();
87 
88         } catch (Exception e) {
89             LOGGER.log(Level.SEVERE, "Unable to start the Audio Test Harness gRPC Server.", e);
90         }
91     }
92 
93     /**
94      * Starts the Audio Test Harness gRPC Server listening on {@link #mPort}.
95      *
96      * @throws IOException if any errors occur while provisioning or starting the server.
97      */
open()98     public void open() throws IOException {
99         Preconditions.checkState(mServer == null, "Server has already been opened.");
100 
101         ServerBuilder<?> serverBuilder = ServerBuilder.forPort(mPort).executor(mExecutor);
102 
103         try {
104             serverBuilder.addService(
105                     mInjector.getInstance(AudioTestHarnessGrpc.AudioTestHarnessImplBase.class));
106         } catch (ConfigurationException ce) {
107             throw new IOException(
108                     "Unable to create new Audio Test Harness gRPC Server, failed to resolve"
109                             + " required dependency.",
110                     ce);
111         }
112 
113         mServer = serverBuilder.build();
114         LOGGER.fine("Successfully created Audio Test Harness gRPC Server.");
115 
116         mServer.start();
117         LOGGER.info(
118                 String.format(
119                         "Started Audio Test Harness gRPC Server Listening on Port %d", mPort));
120     }
121 
122     /**
123      * Stops the Audio Test Harness gRPC Server immediately and closes any underlying resources
124      * being used by the server.
125      */
126     @Override
close()127     public void close() {
128         LOGGER.info("Stopping Audio Test Harness gRPC Server");
129         if (mServer != null) {
130             mServer.shutdownNow();
131             mServer = null;
132         } else {
133             LOGGER.warning(
134                     "mServer is null indicating that the Audio Test Harness gRPC Server was never"
135                             + " started.");
136         }
137     }
138 
getPort()139     public int getPort() {
140         return mPort;
141     }
142 }
143