1 /*
2  * Copyright (C) 2022 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 android.companion.virtual.audio;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.RequiresPermission;
22 import android.annotation.SystemApi;
23 import android.companion.virtual.IVirtualDevice;
24 import android.content.Context;
25 import android.hardware.display.VirtualDisplay;
26 import android.media.AudioFormat;
27 import android.media.AudioManager;
28 import android.media.AudioPlaybackConfiguration;
29 import android.media.AudioRecordingConfiguration;
30 import android.os.RemoteException;
31 
32 import java.io.Closeable;
33 import java.util.List;
34 import java.util.Objects;
35 import java.util.concurrent.Executor;
36 
37 /**
38  * The class stores an {@link AudioCapture} for audio capturing and an {@link AudioInjection} for
39  * audio injection.
40  *
41  * @hide
42  */
43 @SystemApi
44 public final class VirtualAudioDevice implements Closeable {
45 
46     /**
47      * Interface to be notified when playback or recording configuration of applications running on
48      * virtual display was changed.
49      *
50      * @hide
51      */
52     @SystemApi
53     public interface AudioConfigurationChangeCallback {
54         /**
55          * Notifies when playback configuration of applications running on virtual display was
56          * changed.
57          */
onPlaybackConfigChanged(@onNull List<AudioPlaybackConfiguration> configs)58         void onPlaybackConfigChanged(@NonNull List<AudioPlaybackConfiguration> configs);
59 
60         /**
61          * Notifies when recording configuration of applications running on virtual display was
62          * changed.
63          */
onRecordingConfigChanged(@onNull List<AudioRecordingConfiguration> configs)64         void onRecordingConfigChanged(@NonNull List<AudioRecordingConfiguration> configs);
65     }
66 
67     /**
68      * Interface to be notified when {@link #close()} is called.
69      *
70      * @hide
71      */
72     public interface CloseListener {
73         /**
74          * Notifies when {@link #close()} is called.
75          */
onClosed()76         void onClosed();
77     }
78 
79     private final Context mContext;
80     private final IVirtualDevice mVirtualDevice;
81     private final VirtualDisplay mVirtualDisplay;
82     private final AudioConfigurationChangeCallback mCallback;
83     private final Executor mExecutor;
84     private final CloseListener mListener;
85     @Nullable
86     private VirtualAudioSession mOngoingSession;
87 
88     /**
89      * @hide
90      */
VirtualAudioDevice(Context context, IVirtualDevice virtualDevice, @NonNull VirtualDisplay virtualDisplay, @Nullable Executor executor, @Nullable AudioConfigurationChangeCallback callback, @Nullable CloseListener listener)91     public VirtualAudioDevice(Context context, IVirtualDevice virtualDevice,
92             @NonNull VirtualDisplay virtualDisplay, @Nullable Executor executor,
93             @Nullable AudioConfigurationChangeCallback callback, @Nullable CloseListener listener) {
94         mContext = context;
95         mVirtualDevice = virtualDevice;
96         mVirtualDisplay = virtualDisplay;
97         mExecutor = executor;
98         mCallback = callback;
99         mListener = listener;
100     }
101 
102     /**
103      * Begins injecting audio from a remote device into this device.
104      *
105      * @return An {@link AudioInjection} containing the injected audio.
106      */
107     @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
108     @NonNull
startAudioInjection(@onNull AudioFormat injectionFormat)109     public AudioInjection startAudioInjection(@NonNull AudioFormat injectionFormat) {
110         Objects.requireNonNull(injectionFormat, "injectionFormat must not be null");
111 
112         if (mOngoingSession != null && mOngoingSession.getAudioInjection() != null) {
113             throw new IllegalStateException("Cannot start an audio injection while a session is "
114                     + "ongoing. Call close() on this device first to end the previous session.");
115         }
116         if (mOngoingSession == null) {
117             mOngoingSession = new VirtualAudioSession(mContext, mCallback, mExecutor);
118         }
119 
120         try {
121             mVirtualDevice.onAudioSessionStarting(mVirtualDisplay.getDisplay().getDisplayId(),
122                     /* routingCallback= */ mOngoingSession,
123                     /* configChangedCallback= */  mOngoingSession.getAudioConfigChangedListener());
124         } catch (RemoteException e) {
125             throw e.rethrowFromSystemServer();
126         }
127         return mOngoingSession.startAudioInjection(injectionFormat);
128     }
129 
130     /**
131      * Begins recording audio emanating from this device.
132      *
133      * <p>Note: This method does not support capturing privileged playback, which means the
134      * application can opt out of capturing by {@link AudioManager#setAllowedCapturePolicy(int)}.
135      *
136      * @return An {@link AudioCapture} containing the recorded audio.
137      */
138     @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
139     @NonNull
startAudioCapture(@onNull AudioFormat captureFormat)140     public AudioCapture startAudioCapture(@NonNull AudioFormat captureFormat) {
141         Objects.requireNonNull(captureFormat, "captureFormat must not be null");
142 
143         if (mOngoingSession != null && mOngoingSession.getAudioCapture() != null) {
144             throw new IllegalStateException("Cannot start an audio capture while a session is "
145                     + "ongoing. Call close() on this device first to end the previous session.");
146         }
147         if (mOngoingSession == null) {
148             mOngoingSession = new VirtualAudioSession(mContext, mCallback, mExecutor);
149         }
150 
151         try {
152             mVirtualDevice.onAudioSessionStarting(mVirtualDisplay.getDisplay().getDisplayId(),
153                     /* routingCallback= */ mOngoingSession,
154                     /* configChangedCallback= */ mOngoingSession.getAudioConfigChangedListener());
155         } catch (RemoteException e) {
156             throw e.rethrowFromSystemServer();
157         }
158         return mOngoingSession.startAudioCapture(captureFormat);
159     }
160 
161     /** Returns the {@link AudioCapture} instance. */
162     @Nullable
getAudioCapture()163     public AudioCapture getAudioCapture() {
164         return mOngoingSession != null ? mOngoingSession.getAudioCapture() : null;
165     }
166 
167     /** Returns the {@link AudioInjection} instance. */
168     @Nullable
getAudioInjection()169     public AudioInjection getAudioInjection() {
170         return mOngoingSession != null ? mOngoingSession.getAudioInjection() : null;
171     }
172 
173     /** Stops audio capture and injection then releases all the resources */
174     @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
175     @Override
close()176     public void close() {
177         if (mOngoingSession != null) {
178             mOngoingSession.close();
179             mOngoingSession = null;
180 
181             try {
182                 mVirtualDevice.onAudioSessionEnded();
183             } catch (RemoteException e) {
184                 throw e.rethrowFromSystemServer();
185             }
186 
187             if (mListener != null) {
188                 mListener.onClosed();
189             }
190         }
191     }
192 }
193