1/*
2 * Copyright (C) 2016 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
17package android.media;
18
19import android.annotation.IntDef;
20import android.annotation.NonNull;
21import android.os.Parcel;
22import android.os.Parcelable;
23import android.util.Log;
24
25import java.io.PrintWriter;
26import java.lang.annotation.Retention;
27import java.lang.annotation.RetentionPolicy;
28import java.util.ArrayList;
29import java.util.Objects;
30
31/**
32 * The AudioRecordingConfiguration class collects the information describing an audio recording
33 * session.
34 * <p>Direct polling (see {@link AudioManager#getActiveRecordingConfigurations()}) or callback
35 * (see {@link AudioManager#registerAudioRecordingCallback(android.media.AudioManager.AudioRecordingCallback, android.os.Handler)}
36 * methods are ways to receive information about the current recording configuration of the device.
37 * <p>An audio recording configuration contains information about the recording format as used by
38 * the application ({@link #getClientFormat()}, as well as the recording format actually used by
39 * the device ({@link #getFormat()}). The two recording formats may, for instance, be at different
40 * sampling rates due to hardware limitations (e.g. application recording at 44.1kHz whereas the
41 * device always records at 48kHz, and the Android framework resamples for the application).
42 * <p>The configuration also contains the use case for which audio is recorded
43 * ({@link #getClientAudioSource()}), enabling the ability to distinguish between different
44 * activities such as ongoing voice recognition or camcorder recording.
45 *
46 */
47public final class AudioRecordingConfiguration implements Parcelable {
48    private final static String TAG = new String("AudioRecordingConfiguration");
49
50    private final int mSessionId;
51
52    private final int mClientSource;
53
54    private final AudioFormat mDeviceFormat;
55    private final AudioFormat mClientFormat;
56
57    @NonNull private final String mClientPackageName;
58    private final int mClientUid;
59
60    private final int mPatchHandle;
61
62    /**
63     * @hide
64     */
65    public AudioRecordingConfiguration(int uid, int session, int source, AudioFormat clientFormat,
66            AudioFormat devFormat, int patchHandle, String packageName) {
67        mClientUid = uid;
68        mSessionId = session;
69        mClientSource = source;
70        mClientFormat = clientFormat;
71        mDeviceFormat = devFormat;
72        mPatchHandle = patchHandle;
73        mClientPackageName = packageName;
74    }
75
76    /**
77     * @hide
78     * For AudioService dump
79     * @param pw
80     */
81    public void dump(PrintWriter pw) {
82        pw.println("  " + toLogFriendlyString(this));
83    }
84
85    /**
86     * @hide
87     */
88    public static String toLogFriendlyString(AudioRecordingConfiguration arc) {
89        return new String("session:" + arc.mSessionId
90                + " -- source:" + MediaRecorder.toLogFriendlyAudioSource(arc.mClientSource)
91                + " -- uid:" + arc.mClientUid
92                + " -- patch:" + arc.mPatchHandle
93                + " -- pack:" + arc.mClientPackageName
94                + " -- format client=" + arc.mClientFormat.toLogFriendlyString()
95                    + ", dev=" + arc.mDeviceFormat.toLogFriendlyString());
96    }
97
98    // Note that this method is called server side, so no "privileged" information is ever sent
99    // to a client that is not supposed to have access to it.
100    /**
101     * @hide
102     * Creates a copy of the recording configuration that is stripped of any data enabling
103     * identification of which application it is associated with ("anonymized").
104     * @param in
105     */
106    public static AudioRecordingConfiguration anonymizedCopy(AudioRecordingConfiguration in) {
107        return new AudioRecordingConfiguration( /*anonymized uid*/ -1,
108                in.mSessionId, in.mClientSource, in.mClientFormat,
109                in.mDeviceFormat, in.mPatchHandle, "" /*empty package name*/);
110    }
111
112    // matches the sources that return false in MediaRecorder.isSystemOnlyAudioSource(source)
113    /** @hide */
114    @IntDef({
115        MediaRecorder.AudioSource.DEFAULT,
116        MediaRecorder.AudioSource.MIC,
117        MediaRecorder.AudioSource.VOICE_UPLINK,
118        MediaRecorder.AudioSource.VOICE_DOWNLINK,
119        MediaRecorder.AudioSource.VOICE_CALL,
120        MediaRecorder.AudioSource.CAMCORDER,
121        MediaRecorder.AudioSource.VOICE_RECOGNITION,
122        MediaRecorder.AudioSource.VOICE_COMMUNICATION,
123        MediaRecorder.AudioSource.UNPROCESSED
124    })
125    @Retention(RetentionPolicy.SOURCE)
126    public @interface AudioSource {}
127
128    // documented return values match the sources that return false
129    //   in MediaRecorder.isSystemOnlyAudioSource(source)
130    /**
131     * Returns the audio source being used for the recording.
132     * @return one of {@link MediaRecorder.AudioSource#DEFAULT},
133     *       {@link MediaRecorder.AudioSource#MIC},
134     *       {@link MediaRecorder.AudioSource#VOICE_UPLINK},
135     *       {@link MediaRecorder.AudioSource#VOICE_DOWNLINK},
136     *       {@link MediaRecorder.AudioSource#VOICE_CALL},
137     *       {@link MediaRecorder.AudioSource#CAMCORDER},
138     *       {@link MediaRecorder.AudioSource#VOICE_RECOGNITION},
139     *       {@link MediaRecorder.AudioSource#VOICE_COMMUNICATION},
140     *       {@link MediaRecorder.AudioSource#UNPROCESSED}.
141     */
142    public @AudioSource int getClientAudioSource() { return mClientSource; }
143
144    /**
145     * Returns the session number of the recording, see {@link AudioRecord#getAudioSessionId()}.
146     * @return the session number.
147     */
148    public int getClientAudioSessionId() { return mSessionId; }
149
150    /**
151     * Returns the audio format at which audio is recorded on this Android device.
152     * Note that it may differ from the client application recording format
153     * (see {@link #getClientFormat()}).
154     * @return the device recording format
155     */
156    public AudioFormat getFormat() { return mDeviceFormat; }
157
158    /**
159     * Returns the audio format at which the client application is recording audio.
160     * Note that it may differ from the actual recording format (see {@link #getFormat()}).
161     * @return the recording format
162     */
163    public AudioFormat getClientFormat() { return mClientFormat; }
164
165    /**
166     * @pending for SystemApi
167     * Returns the package name of the application performing the recording.
168     * Where there are multiple packages sharing the same user id through the "sharedUserId"
169     * mechanism, only the first one with that id will be returned
170     * (see {@link PackageManager#getPackagesForUid(int)}).
171     * <p>This information is only available if the caller has the
172     * {@link android.Manifest.permission.MODIFY_AUDIO_ROUTING} permission.
173     * <br>When called without the permission, the result is an empty string.
174     * @return the package name
175     */
176    public String getClientPackageName() { return mClientPackageName; }
177
178    /**
179     * @pending for SystemApi
180     * Returns the user id of the application performing the recording.
181     * <p>This information is only available if the caller has the
182     * {@link android.Manifest.permission.MODIFY_AUDIO_ROUTING}
183     * permission.
184     * <br>The result is -1 without the permission.
185     * @return the user id
186     */
187    public int getClientUid() { return mClientUid; }
188
189    /**
190     * Returns information about the audio input device used for this recording.
191     * @return the audio recording device or null if this information cannot be retrieved
192     */
193    public AudioDeviceInfo getAudioDevice() {
194        // build the AudioDeviceInfo from the patch handle
195        ArrayList<AudioPatch> patches = new ArrayList<AudioPatch>();
196        if (AudioManager.listAudioPatches(patches) != AudioManager.SUCCESS) {
197            Log.e(TAG, "Error retrieving list of audio patches");
198            return null;
199        }
200        for (int i = 0 ; i < patches.size() ; i++) {
201            final AudioPatch patch = patches.get(i);
202            if (patch.id() == mPatchHandle) {
203                final AudioPortConfig[] sources = patch.sources();
204                if ((sources != null) && (sources.length > 0)) {
205                    // not supporting multiple sources, so just look at the first source
206                    final int devId = sources[0].port().id();
207                    final AudioDeviceInfo[] devices =
208                            AudioManager.getDevicesStatic(AudioManager.GET_DEVICES_INPUTS);
209                    for (int j = 0; j < devices.length; j++) {
210                        if (devices[j].getId() == devId) {
211                            return devices[j];
212                        }
213                    }
214                }
215                // patch handle is unique, there won't be another with the same handle
216                break;
217            }
218        }
219        Log.e(TAG, "Couldn't find device for recording, did recording end already?");
220        return null;
221    }
222
223    public static final Parcelable.Creator<AudioRecordingConfiguration> CREATOR
224            = new Parcelable.Creator<AudioRecordingConfiguration>() {
225        /**
226         * Rebuilds an AudioRecordingConfiguration previously stored with writeToParcel().
227         * @param p Parcel object to read the AudioRecordingConfiguration from
228         * @return a new AudioRecordingConfiguration created from the data in the parcel
229         */
230        public AudioRecordingConfiguration createFromParcel(Parcel p) {
231            return new AudioRecordingConfiguration(p);
232        }
233        public AudioRecordingConfiguration[] newArray(int size) {
234            return new AudioRecordingConfiguration[size];
235        }
236    };
237
238    @Override
239    public int hashCode() {
240        return Objects.hash(mSessionId, mClientSource);
241    }
242
243    @Override
244    public int describeContents() {
245        return 0;
246    }
247
248    @Override
249    public void writeToParcel(Parcel dest, int flags) {
250        dest.writeInt(mSessionId);
251        dest.writeInt(mClientSource);
252        mClientFormat.writeToParcel(dest, 0);
253        mDeviceFormat.writeToParcel(dest, 0);
254        dest.writeInt(mPatchHandle);
255        dest.writeString(mClientPackageName);
256        dest.writeInt(mClientUid);
257    }
258
259    private AudioRecordingConfiguration(Parcel in) {
260        mSessionId = in.readInt();
261        mClientSource = in.readInt();
262        mClientFormat = AudioFormat.CREATOR.createFromParcel(in);
263        mDeviceFormat = AudioFormat.CREATOR.createFromParcel(in);
264        mPatchHandle = in.readInt();
265        mClientPackageName = in.readString();
266        mClientUid = in.readInt();
267    }
268
269    @Override
270    public boolean equals(Object o) {
271        if (this == o) return true;
272        if (o == null || !(o instanceof AudioRecordingConfiguration)) return false;
273
274        AudioRecordingConfiguration that = (AudioRecordingConfiguration) o;
275
276        return ((mClientUid == that.mClientUid)
277                && (mSessionId == that.mSessionId)
278                && (mClientSource == that.mClientSource)
279                && (mPatchHandle == that.mPatchHandle)
280                && (mClientFormat.equals(that.mClientFormat))
281                && (mDeviceFormat.equals(that.mDeviceFormat))
282                && (mClientPackageName.equals(that.mClientPackageName)));
283    }
284}
285