blob: 42841d178401314369e176996c6b9114cabafbb9 [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.media;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.media.audiofx.AudioEffect;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
/**
* The AudioRecordingConfiguration class collects the information describing an audio recording
* session.
* <p>Direct polling (see {@link AudioManager#getActiveRecordingConfigurations()}) or callback
* (see {@link AudioManager#registerAudioRecordingCallback(android.media.AudioManager.AudioRecordingCallback, android.os.Handler)}
* methods are ways to receive information about the current recording configuration of the device.
* <p>An audio recording configuration contains information about the recording format as used by
* the application ({@link #getClientFormat()}, as well as the recording format actually used by
* the device ({@link #getFormat()}). The two recording formats may, for instance, be at different
* sampling rates due to hardware limitations (e.g. application recording at 44.1kHz whereas the
* device always records at 48kHz, and the Android framework resamples for the application).
* <p>The configuration also contains the use case for which audio is recorded
* ({@link #getClientAudioSource()}), enabling the ability to distinguish between different
* activities such as ongoing voice recognition or camcorder recording.
*
*/
public final class AudioRecordingConfiguration implements Parcelable {
private final static String TAG = new String("AudioRecordingConfiguration");
private final int mClientSessionId;
private final int mClientSource;
private final AudioFormat mDeviceFormat;
private final AudioFormat mClientFormat;
@NonNull private final String mClientPackageName;
private final int mClientUid;
private final int mPatchHandle;
private final int mClientPortId;
private boolean mClientSilenced;
private final int mDeviceSource;
private final AudioEffect.Descriptor[] mClientEffects;
private final AudioEffect.Descriptor[] mDeviceEffects;
/**
* @hide
*/
@TestApi
public AudioRecordingConfiguration(int uid, int session, int source, AudioFormat clientFormat,
AudioFormat devFormat, int patchHandle, String packageName, int clientPortId,
boolean clientSilenced, int deviceSource,
AudioEffect.Descriptor[] clientEffects, AudioEffect.Descriptor[] deviceEffects) {
mClientUid = uid;
mClientSessionId = session;
mClientSource = source;
mClientFormat = clientFormat;
mDeviceFormat = devFormat;
mPatchHandle = patchHandle;
mClientPackageName = packageName;
mClientPortId = clientPortId;
mClientSilenced = clientSilenced;
mDeviceSource = deviceSource;
mClientEffects = clientEffects;
mDeviceEffects = deviceEffects;
}
/**
* @hide
*/
@TestApi
public AudioRecordingConfiguration(int uid, int session, int source,
AudioFormat clientFormat, AudioFormat devFormat,
int patchHandle, String packageName) {
this(uid, session, source, clientFormat,
devFormat, patchHandle, packageName, 0 /*clientPortId*/,
false /*clientSilenced*/, MediaRecorder.AudioSource.DEFAULT /*deviceSource*/,
new AudioEffect.Descriptor[0] /*clientEffects*/,
new AudioEffect.Descriptor[0] /*deviceEffects*/);
}
/**
* @hide
* For AudioService dump
* @param pw
*/
public void dump(PrintWriter pw) {
pw.println(" " + toLogFriendlyString(this));
}
/**
* @hide
*/
public static String toLogFriendlyString(AudioRecordingConfiguration arc) {
String clientEffects = new String();
for (AudioEffect.Descriptor desc : arc.mClientEffects) {
clientEffects += "'" + desc.name + "' ";
}
String deviceEffects = new String();
for (AudioEffect.Descriptor desc : arc.mDeviceEffects) {
deviceEffects += "'" + desc.name + "' ";
}
return new String("session:" + arc.mClientSessionId
+ " -- source client=" + MediaRecorder.toLogFriendlyAudioSource(arc.mClientSource)
+ ", dev=" + arc.mDeviceFormat.toLogFriendlyString()
+ " -- uid:" + arc.mClientUid
+ " -- patch:" + arc.mPatchHandle
+ " -- pack:" + arc.mClientPackageName
+ " -- format client=" + arc.mClientFormat.toLogFriendlyString()
+ ", dev=" + arc.mDeviceFormat.toLogFriendlyString()
+ " -- silenced:" + arc.mClientSilenced
+ " -- effects client=" + clientEffects
+ ", dev=" + deviceEffects);
}
// Note that this method is called server side, so no "privileged" information is ever sent
// to a client that is not supposed to have access to it.
/**
* @hide
* Creates a copy of the recording configuration that is stripped of any data enabling
* identification of which application it is associated with ("anonymized").
* @param in
*/
public static AudioRecordingConfiguration anonymizedCopy(AudioRecordingConfiguration in) {
return new AudioRecordingConfiguration( /*anonymized uid*/ -1,
in.mClientSessionId, in.mClientSource, in.mClientFormat,
in.mDeviceFormat, in.mPatchHandle, "" /*empty package name*/,
in.mClientPortId, in.mClientSilenced, in.mDeviceSource, in.mClientEffects,
in.mDeviceEffects);
}
// matches the sources that return false in MediaRecorder.isSystemOnlyAudioSource(source)
/** @hide */
@IntDef({
MediaRecorder.AudioSource.DEFAULT,
MediaRecorder.AudioSource.MIC,
MediaRecorder.AudioSource.VOICE_UPLINK,
MediaRecorder.AudioSource.VOICE_DOWNLINK,
MediaRecorder.AudioSource.VOICE_CALL,
MediaRecorder.AudioSource.CAMCORDER,
MediaRecorder.AudioSource.VOICE_RECOGNITION,
MediaRecorder.AudioSource.VOICE_COMMUNICATION,
MediaRecorder.AudioSource.UNPROCESSED,
MediaRecorder.AudioSource.VOICE_PERFORMANCE
})
@Retention(RetentionPolicy.SOURCE)
public @interface AudioSource {}
// documented return values match the sources that return false
// in MediaRecorder.isSystemOnlyAudioSource(source)
/**
* Returns the audio source selected by the client.
* @return the audio source selected by the client.
*/
public @AudioSource int getClientAudioSource() { return mClientSource; }
/**
* Returns the session number of the recording, see {@link AudioRecord#getAudioSessionId()}.
* @return the session number.
*/
public int getClientAudioSessionId() {
return mClientSessionId;
}
/**
* Returns the audio format at which audio is recorded on this Android device.
* Note that it may differ from the client application recording format
* (see {@link #getClientFormat()}).
* @return the device recording format
*/
public AudioFormat getFormat() { return mDeviceFormat; }
/**
* Returns the audio format at which the client application is recording audio.
* Note that it may differ from the actual recording format (see {@link #getFormat()}).
* @return the recording format
*/
public AudioFormat getClientFormat() { return mClientFormat; }
/**
* @pending for SystemApi
* Returns the package name of the application performing the recording.
* Where there are multiple packages sharing the same user id through the "sharedUserId"
* mechanism, only the first one with that id will be returned
* (see {@link PackageManager#getPackagesForUid(int)}).
* <p>This information is only available if the caller has the
* {@link android.Manifest.permission.MODIFY_AUDIO_ROUTING} permission.
* <br>When called without the permission, the result is an empty string.
* @return the package name
*/
@UnsupportedAppUsage
public String getClientPackageName() { return mClientPackageName; }
/**
* Returns the user id of the application performing the recording.
* <p>This information is only available if the caller has the
* {@link android.Manifest.permission.MODIFY_AUDIO_ROUTING}
* permission.
* @return the user id
* @throws SecurityException Thrown if the caller is missing the MODIFY_AUDIO_ROUTING permission
*
* @hide
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
public int getClientUid() {
if (mClientUid == -1) {
throw new SecurityException("MODIFY_AUDIO_ROUTING permission is missing");
}
return mClientUid;
}
/**
* Returns information about the audio input device used for this recording.
* @return the audio recording device or null if this information cannot be retrieved
*/
public AudioDeviceInfo getAudioDevice() {
// build the AudioDeviceInfo from the patch handle
ArrayList<AudioPatch> patches = new ArrayList<AudioPatch>();
if (AudioManager.listAudioPatches(patches) != AudioManager.SUCCESS) {
Log.e(TAG, "Error retrieving list of audio patches");
return null;
}
for (int i = 0 ; i < patches.size() ; i++) {
final AudioPatch patch = patches.get(i);
if (patch.id() == mPatchHandle) {
final AudioPortConfig[] sources = patch.sources();
if ((sources != null) && (sources.length > 0)) {
// not supporting multiple sources, so just look at the first source
final int devId = sources[0].port().id();
final AudioDeviceInfo[] devices =
AudioManager.getDevicesStatic(AudioManager.GET_DEVICES_INPUTS);
for (int j = 0; j < devices.length; j++) {
if (devices[j].getId() == devId) {
return devices[j];
}
}
}
// patch handle is unique, there won't be another with the same handle
break;
}
}
Log.e(TAG, "Couldn't find device for recording, did recording end already?");
return null;
}
/**
* @hide
* Returns the system unique ID assigned for the AudioRecord object corresponding to this
* AudioRecordingConfiguration client.
* @return the port ID.
*/
public int getClientPortId() {
return mClientPortId;
}
/**
* Returns true if the audio returned to the client is currently being silenced by the
* audio framework due to concurrent capture policy (e.g the capturing application does not have
* an active foreground process or service anymore).
* @return true if captured audio is silenced, false otherwise .
*/
public boolean isClientSilenced() {
return mClientSilenced;
}
/**
* Returns the audio source currently used to configure the capture path. It can be different
* from the source returned by {@link #getClientAudioSource()} if another capture is active.
* @return the audio source active on the capture path.
*/
public @AudioSource int getAudioSource() {
return mDeviceSource;
}
/**
* Returns the list of {@link AudioEffect.Descriptor} for all effects currently enabled on
* the audio capture client (e.g. {@link AudioRecord} or {@link MediaRecorder}).
* @return List of {@link AudioEffect.Descriptor} containing all effects enabled for the client.
*/
public @NonNull List<AudioEffect.Descriptor> getClientEffects() {
return new ArrayList<AudioEffect.Descriptor>(Arrays.asList(mClientEffects));
}
/**
* Returns the list of {@link AudioEffect.Descriptor} for all effects currently enabled on
* the capture stream.
* @return List of {@link AudioEffect.Descriptor} containing all effects enabled on the
* capture stream. This can be different from the list returned by {@link #getClientEffects()}
* if another capture is active.
*/
public @NonNull List<AudioEffect.Descriptor> getEffects() {
return new ArrayList<AudioEffect.Descriptor>(Arrays.asList(mDeviceEffects));
}
public static final @android.annotation.NonNull Parcelable.Creator<AudioRecordingConfiguration> CREATOR
= new Parcelable.Creator<AudioRecordingConfiguration>() {
/**
* Rebuilds an AudioRecordingConfiguration previously stored with writeToParcel().
* @param p Parcel object to read the AudioRecordingConfiguration from
* @return a new AudioRecordingConfiguration created from the data in the parcel
*/
public AudioRecordingConfiguration createFromParcel(Parcel p) {
return new AudioRecordingConfiguration(p);
}
public AudioRecordingConfiguration[] newArray(int size) {
return new AudioRecordingConfiguration[size];
}
};
@Override
public int hashCode() {
return Objects.hash(mClientSessionId, mClientSource);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mClientSessionId);
dest.writeInt(mClientSource);
mClientFormat.writeToParcel(dest, 0);
mDeviceFormat.writeToParcel(dest, 0);
dest.writeInt(mPatchHandle);
dest.writeString(mClientPackageName);
dest.writeInt(mClientUid);
dest.writeInt(mClientPortId);
dest.writeBoolean(mClientSilenced);
dest.writeInt(mDeviceSource);
dest.writeInt(mClientEffects.length);
for (int i = 0; i < mClientEffects.length; i++) {
mClientEffects[i].writeToParcel(dest);
}
dest.writeInt(mDeviceEffects.length);
for (int i = 0; i < mDeviceEffects.length; i++) {
mDeviceEffects[i].writeToParcel(dest);
}
}
private AudioRecordingConfiguration(Parcel in) {
mClientSessionId = in.readInt();
mClientSource = in.readInt();
mClientFormat = AudioFormat.CREATOR.createFromParcel(in);
mDeviceFormat = AudioFormat.CREATOR.createFromParcel(in);
mPatchHandle = in.readInt();
mClientPackageName = in.readString();
mClientUid = in.readInt();
mClientPortId = in.readInt();
mClientSilenced = in.readBoolean();
mDeviceSource = in.readInt();
mClientEffects = new AudioEffect.Descriptor[in.readInt()];
for (int i = 0; i < mClientEffects.length; i++) {
mClientEffects[i] = new AudioEffect.Descriptor(in);
}
mDeviceEffects = new AudioEffect.Descriptor[in.readInt()];
for (int i = 0; i < mDeviceEffects.length; i++) {
mDeviceEffects[i] = new AudioEffect.Descriptor(in);
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || !(o instanceof AudioRecordingConfiguration)) return false;
AudioRecordingConfiguration that = (AudioRecordingConfiguration) o;
return ((mClientUid == that.mClientUid)
&& (mClientSessionId == that.mClientSessionId)
&& (mClientSource == that.mClientSource)
&& (mPatchHandle == that.mPatchHandle)
&& (mClientFormat.equals(that.mClientFormat))
&& (mDeviceFormat.equals(that.mDeviceFormat))
&& (mClientPackageName.equals(that.mClientPackageName))
&& (mClientPortId == that.mClientPortId)
&& (mClientSilenced == that.mClientSilenced)
&& (mDeviceSource == that.mDeviceSource)
&& (Arrays.equals(mClientEffects, that.mClientEffects))
&& (Arrays.equals(mDeviceEffects, that.mDeviceEffects)));
}
}