diff options
44 files changed, 1609 insertions, 260 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 3db168566870..ab1805890215 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -19679,10 +19679,13 @@ package android.media { method public android.media.AudioAttributes.Builder setUsage(int); } - public class AudioDescriptor { + public class AudioDescriptor implements android.os.Parcelable { + method public int describeContents(); method @NonNull public byte[] getDescriptor(); method public int getEncapsulationType(); method public int getStandard(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.media.AudioDescriptor> CREATOR; field public static final int STANDARD_EDID = 1; // 0x1 field public static final int STANDARD_NONE = 0; // 0x0 } @@ -20198,14 +20201,17 @@ package android.media { method @NonNull public android.media.AudioPresentation.Builder setProgramId(int); } - public class AudioProfile { + public class AudioProfile implements android.os.Parcelable { + method public int describeContents(); method @NonNull public int[] getChannelIndexMasks(); method @NonNull public int[] getChannelMasks(); method public int getEncapsulationType(); method public int getFormat(); method @NonNull public int[] getSampleRates(); + method public void writeToParcel(@NonNull android.os.Parcel, int); field public static final int AUDIO_ENCAPSULATION_TYPE_IEC61937 = 1; // 0x1 field public static final int AUDIO_ENCAPSULATION_TYPE_NONE = 0; // 0x0 + field @NonNull public static final android.os.Parcelable.Creator<android.media.AudioProfile> CREATOR; } public class AudioRecord implements android.media.AudioRecordingMonitor android.media.AudioRouting android.media.MicrophoneDirection { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 252d579d4939..a97ccc91d1ff 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -1085,6 +1085,7 @@ package android.app.admin { method public boolean isDeviceManaged(); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioned(); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioningConfigApplied(); + method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public boolean isDpcDownloaded(); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public boolean isManagedKiosk(); method public boolean isSecondaryLockscreenEnabled(@NonNull android.os.UserHandle); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public boolean isUnattendedManagedKiosk(); @@ -1097,6 +1098,7 @@ package android.app.admin { method @RequiresPermission(android.Manifest.permission.SEND_LOST_MODE_LOCATION_UPDATES) public void sendLostModeLocationUpdate(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>); method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public boolean setActiveProfileOwner(@NonNull android.content.ComponentName, String) throws java.lang.IllegalArgumentException; method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setDeviceProvisioningConfigApplied(); + method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setDpcDownloaded(boolean); method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES) public void setDrawables(@NonNull java.util.Set<android.app.admin.DevicePolicyDrawableResource>); method @Deprecated @RequiresPermission(value=android.Manifest.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS, conditional=true) public void setProfileOwnerCanAccessDeviceIds(@NonNull android.content.ComponentName); method public void setSecondaryLockscreenEnabled(@NonNull android.content.ComponentName, boolean); @@ -5933,11 +5935,20 @@ package android.media { method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.AudioAttributes.Builder setSystemUsage(int); } + public class AudioDescriptor implements android.os.Parcelable { + ctor public AudioDescriptor(int, int, @NonNull byte[]); + } + public final class AudioDeviceAttributes implements android.os.Parcelable { ctor public AudioDeviceAttributes(@NonNull android.media.AudioDeviceInfo); ctor public AudioDeviceAttributes(int, int, @NonNull String); + ctor public AudioDeviceAttributes(int, int, @NonNull String, @NonNull String, @NonNull java.util.List<android.media.AudioProfile>, @NonNull java.util.List<android.media.AudioDescriptor>); method public int describeContents(); + method public boolean equalTypeAddress(@Nullable Object); method @NonNull public String getAddress(); + method @NonNull public java.util.List<android.media.AudioDescriptor> getAudioDescriptors(); + method @NonNull public java.util.List<android.media.AudioProfile> getAudioProfiles(); + method @NonNull public String getName(); method public int getRole(); method public int getType(); method public void writeToParcel(@NonNull android.os.Parcel, int); @@ -6094,6 +6105,10 @@ package android.media { field public static final int PLAYER_TYPE_UNKNOWN = -1; // 0xffffffff } + public class AudioProfile implements android.os.Parcelable { + ctor public AudioProfile(int, @NonNull int[], @NonNull int[], @NonNull int[], int); + } + public class AudioRecord implements android.media.AudioRecordingMonitor android.media.AudioRouting android.media.MicrophoneDirection { ctor @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public AudioRecord(android.media.AudioAttributes, android.media.AudioFormat, int, int) throws java.lang.IllegalArgumentException; method public static long getMaxSharedAudioHistoryMillis(); diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 6cd991bc49b8..a709afe9910d 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -15367,4 +15367,45 @@ public class DevicePolicyManager { } return ParcelableResource.loadDefaultString(defaultStringLoader); } + + /** + * Returns a boolean for whether the DPC has been downloaded during provisioning. + * + * <p>If true is returned, then any attempts to begin setup again should result in factory reset + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) + public boolean isDpcDownloaded() { + throwIfParentInstance("isDpcDownloaded"); + if (mService != null) { + try { + return mService.isDpcDownloaded(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return false; + } + + /** + * Use to indicate that the DPC has or has not been downloaded during provisioning. + * + * @param downloaded {@code true} if the dpc has been downloaded during provisioning. false otherwise. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) + public void setDpcDownloaded(boolean downloaded) { + throwIfParentInstance("setDpcDownloaded"); + if (mService != null) { + try { + mService.setDpcDownloaded(downloaded); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index a7a51f8f6caa..0e1caca2670a 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -555,6 +555,9 @@ interface IDevicePolicyManager { void resetDrawables(in String[] drawableIds); ParcelableResource getDrawable(String drawableId, String drawableStyle, String drawableSource); + boolean isDpcDownloaded(); + void setDpcDownloaded(boolean downloaded); + void setStrings(in List<DevicePolicyStringResource> strings); void resetStrings(in String[] stringIds); ParcelableResource getString(String stringId); diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java index 3914a3c963b6..fadbdbbe8746 100644 --- a/core/java/android/view/animation/Animation.java +++ b/core/java/android/view/animation/Animation.java @@ -1251,18 +1251,19 @@ public abstract class Animation implements Cloneable { public float value; /** - * Size descriptions can appear inthree forms: + * Size descriptions can appear in four forms: * <ol> * <li>An absolute size. This is represented by a number.</li> * <li>A size relative to the size of the object being animated. This - * is represented by a number followed by "%".</li> * + * is represented by a number followed by "%".</li> * <li>A size relative to the size of the parent of object being * animated. This is represented by a number followed by "%p".</li> + * <li>(Starting from API 32) A complex number.</li> * </ol> * @param value The typed value to parse * @return The parsed version of the description */ - static Description parseValue(TypedValue value) { + static Description parseValue(TypedValue value, Context context) { Description d = new Description(); if (value == null) { d.type = ABSOLUTE; @@ -1283,6 +1284,11 @@ public abstract class Animation implements Cloneable { d.type = ABSOLUTE; d.value = value.data; return d; + } else if (value.type == TypedValue.TYPE_DIMENSION) { + d.type = ABSOLUTE; + d.value = TypedValue.complexToDimension(value.data, + context.getResources().getDisplayMetrics()); + return d; } } diff --git a/core/java/android/view/animation/ClipRectAnimation.java b/core/java/android/view/animation/ClipRectAnimation.java index 21509d3a1159..3f4b3e7b4c80 100644 --- a/core/java/android/view/animation/ClipRectAnimation.java +++ b/core/java/android/view/animation/ClipRectAnimation.java @@ -20,7 +20,6 @@ import android.content.Context; import android.content.res.TypedArray; import android.graphics.Rect; import android.util.AttributeSet; -import android.util.DisplayMetrics; /** * An animation that controls the clip of an object. See the @@ -66,43 +65,43 @@ public class ClipRectAnimation extends Animation { com.android.internal.R.styleable.ClipRectAnimation); Description d = Description.parseValue(a.peekValue( - com.android.internal.R.styleable.ClipRectAnimation_fromLeft)); + com.android.internal.R.styleable.ClipRectAnimation_fromLeft), context); mFromLeftType = d.type; mFromLeftValue = d.value; d = Description.parseValue(a.peekValue( - com.android.internal.R.styleable.ClipRectAnimation_fromTop)); + com.android.internal.R.styleable.ClipRectAnimation_fromTop), context); mFromTopType = d.type; mFromTopValue = d.value; d = Description.parseValue(a.peekValue( - com.android.internal.R.styleable.ClipRectAnimation_fromRight)); + com.android.internal.R.styleable.ClipRectAnimation_fromRight), context); mFromRightType = d.type; mFromRightValue = d.value; d = Description.parseValue(a.peekValue( - com.android.internal.R.styleable.ClipRectAnimation_fromBottom)); + com.android.internal.R.styleable.ClipRectAnimation_fromBottom), context); mFromBottomType = d.type; mFromBottomValue = d.value; d = Description.parseValue(a.peekValue( - com.android.internal.R.styleable.ClipRectAnimation_toLeft)); + com.android.internal.R.styleable.ClipRectAnimation_toLeft), context); mToLeftType = d.type; mToLeftValue = d.value; d = Description.parseValue(a.peekValue( - com.android.internal.R.styleable.ClipRectAnimation_toTop)); + com.android.internal.R.styleable.ClipRectAnimation_toTop), context); mToTopType = d.type; mToTopValue = d.value; d = Description.parseValue(a.peekValue( - com.android.internal.R.styleable.ClipRectAnimation_toRight)); + com.android.internal.R.styleable.ClipRectAnimation_toRight), context); mToRightType = d.type; mToRightValue = d.value; d = Description.parseValue(a.peekValue( - com.android.internal.R.styleable.ClipRectAnimation_toBottom)); + com.android.internal.R.styleable.ClipRectAnimation_toBottom), context); mToBottomType = d.type; mToBottomValue = d.value; diff --git a/core/java/android/view/animation/ExtendAnimation.java b/core/java/android/view/animation/ExtendAnimation.java index fd627e50ab0e..210eb8a1ca9d 100644 --- a/core/java/android/view/animation/ExtendAnimation.java +++ b/core/java/android/view/animation/ExtendAnimation.java @@ -63,43 +63,43 @@ public class ExtendAnimation extends Animation { com.android.internal.R.styleable.ExtendAnimation); Description d = Description.parseValue(a.peekValue( - com.android.internal.R.styleable.ExtendAnimation_fromExtendLeft)); + com.android.internal.R.styleable.ExtendAnimation_fromExtendLeft), context); mFromLeftType = d.type; mFromLeftValue = d.value; d = Description.parseValue(a.peekValue( - com.android.internal.R.styleable.ExtendAnimation_fromExtendTop)); + com.android.internal.R.styleable.ExtendAnimation_fromExtendTop), context); mFromTopType = d.type; mFromTopValue = d.value; d = Description.parseValue(a.peekValue( - com.android.internal.R.styleable.ExtendAnimation_fromExtendRight)); + com.android.internal.R.styleable.ExtendAnimation_fromExtendRight), context); mFromRightType = d.type; mFromRightValue = d.value; d = Description.parseValue(a.peekValue( - com.android.internal.R.styleable.ExtendAnimation_fromExtendBottom)); + com.android.internal.R.styleable.ExtendAnimation_fromExtendBottom), context); mFromBottomType = d.type; mFromBottomValue = d.value; d = Description.parseValue(a.peekValue( - com.android.internal.R.styleable.ExtendAnimation_toExtendLeft)); + com.android.internal.R.styleable.ExtendAnimation_toExtendLeft), context); mToLeftType = d.type; mToLeftValue = d.value; d = Description.parseValue(a.peekValue( - com.android.internal.R.styleable.ExtendAnimation_toExtendTop)); + com.android.internal.R.styleable.ExtendAnimation_toExtendTop), context); mToTopType = d.type; mToTopValue = d.value; d = Description.parseValue(a.peekValue( - com.android.internal.R.styleable.ExtendAnimation_toExtendRight)); + com.android.internal.R.styleable.ExtendAnimation_toExtendRight), context); mToRightType = d.type; mToRightValue = d.value; d = Description.parseValue(a.peekValue( - com.android.internal.R.styleable.ExtendAnimation_toExtendBottom)); + com.android.internal.R.styleable.ExtendAnimation_toExtendBottom), context); mToBottomType = d.type; mToBottomValue = d.value; diff --git a/core/java/android/view/animation/GridLayoutAnimationController.java b/core/java/android/view/animation/GridLayoutAnimationController.java index 0f189ae98030..c77f54fa889e 100644 --- a/core/java/android/view/animation/GridLayoutAnimationController.java +++ b/core/java/android/view/animation/GridLayoutAnimationController.java @@ -116,10 +116,12 @@ public class GridLayoutAnimationController extends LayoutAnimationController { com.android.internal.R.styleable.GridLayoutAnimation); Animation.Description d = Animation.Description.parseValue( - a.peekValue(com.android.internal.R.styleable.GridLayoutAnimation_columnDelay)); + a.peekValue(com.android.internal.R.styleable.GridLayoutAnimation_columnDelay), + context); mColumnDelay = d.value; d = Animation.Description.parseValue( - a.peekValue(com.android.internal.R.styleable.GridLayoutAnimation_rowDelay)); + a.peekValue(com.android.internal.R.styleable.GridLayoutAnimation_rowDelay), + context); mRowDelay = d.value; //noinspection PointlessBitwiseExpression mDirection = a.getInt(com.android.internal.R.styleable.GridLayoutAnimation_direction, diff --git a/core/java/android/view/animation/LayoutAnimationController.java b/core/java/android/view/animation/LayoutAnimationController.java index e2b7519b1912..1d56d293e7df 100644 --- a/core/java/android/view/animation/LayoutAnimationController.java +++ b/core/java/android/view/animation/LayoutAnimationController.java @@ -106,7 +106,7 @@ public class LayoutAnimationController { TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.LayoutAnimation); Animation.Description d = Animation.Description.parseValue( - a.peekValue(com.android.internal.R.styleable.LayoutAnimation_delay)); + a.peekValue(com.android.internal.R.styleable.LayoutAnimation_delay), context); mDelay = d.value; mOrder = a.getInt(com.android.internal.R.styleable.LayoutAnimation_animationOrder, ORDER_NORMAL); diff --git a/core/java/android/view/animation/RotateAnimation.java b/core/java/android/view/animation/RotateAnimation.java index 3c325d9b2aa9..0613cd2ea5ad 100644 --- a/core/java/android/view/animation/RotateAnimation.java +++ b/core/java/android/view/animation/RotateAnimation.java @@ -56,12 +56,12 @@ public class RotateAnimation extends Animation { mToDegrees = a.getFloat(com.android.internal.R.styleable.RotateAnimation_toDegrees, 0.0f); Description d = Description.parseValue(a.peekValue( - com.android.internal.R.styleable.RotateAnimation_pivotX)); + com.android.internal.R.styleable.RotateAnimation_pivotX), context); mPivotXType = d.type; mPivotXValue = d.value; d = Description.parseValue(a.peekValue( - com.android.internal.R.styleable.RotateAnimation_pivotY)); + com.android.internal.R.styleable.RotateAnimation_pivotY), context); mPivotYType = d.type; mPivotYValue = d.value; diff --git a/core/java/android/view/animation/ScaleAnimation.java b/core/java/android/view/animation/ScaleAnimation.java index e9a84364452c..533ef45e7fe5 100644 --- a/core/java/android/view/animation/ScaleAnimation.java +++ b/core/java/android/view/animation/ScaleAnimation.java @@ -118,12 +118,12 @@ public class ScaleAnimation extends Animation { } Description d = Description.parseValue(a.peekValue( - com.android.internal.R.styleable.ScaleAnimation_pivotX)); + com.android.internal.R.styleable.ScaleAnimation_pivotX), context); mPivotXType = d.type; mPivotXValue = d.value; d = Description.parseValue(a.peekValue( - com.android.internal.R.styleable.ScaleAnimation_pivotY)); + com.android.internal.R.styleable.ScaleAnimation_pivotY), context); mPivotYType = d.type; mPivotYValue = d.value; diff --git a/core/java/android/view/animation/TranslateAnimation.java b/core/java/android/view/animation/TranslateAnimation.java index 3365c70b5b34..e27469c0729a 100644 --- a/core/java/android/view/animation/TranslateAnimation.java +++ b/core/java/android/view/animation/TranslateAnimation.java @@ -73,22 +73,22 @@ public class TranslateAnimation extends Animation { com.android.internal.R.styleable.TranslateAnimation); Description d = Description.parseValue(a.peekValue( - com.android.internal.R.styleable.TranslateAnimation_fromXDelta)); + com.android.internal.R.styleable.TranslateAnimation_fromXDelta), context); mFromXType = d.type; mFromXValue = d.value; d = Description.parseValue(a.peekValue( - com.android.internal.R.styleable.TranslateAnimation_toXDelta)); + com.android.internal.R.styleable.TranslateAnimation_toXDelta), context); mToXType = d.type; mToXValue = d.value; d = Description.parseValue(a.peekValue( - com.android.internal.R.styleable.TranslateAnimation_fromYDelta)); + com.android.internal.R.styleable.TranslateAnimation_fromYDelta), context); mFromYType = d.type; mFromYValue = d.value; d = Description.parseValue(a.peekValue( - com.android.internal.R.styleable.TranslateAnimation_toYDelta)); + com.android.internal.R.styleable.TranslateAnimation_toYDelta), context); mToYType = d.type; mToYValue = d.value; diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index edc8c5b99ebe..2bec733d954c 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -18,25 +18,25 @@ //#define LOG_NDEBUG 0 #define LOG_TAG "AudioSystem-JNI" -#include <utils/Log.h> - -#include <sstream> -#include <vector> -#include <jni.h> -#include <nativehelper/JNIHelp.h> -#include "core_jni_helpers.h" - #include <android/media/AudioVibratorInfo.h> #include <android/media/INativeSpatializerCallback.h> #include <android/media/ISpatializer.h> +#include <android_os_Parcel.h> #include <audiomanager/AudioManager.h> +#include <jni.h> #include <media/AudioContainers.h> #include <media/AudioPolicy.h> #include <media/AudioSystem.h> #include <media/MicrophoneInfo.h> +#include <nativehelper/JNIHelp.h> #include <nativehelper/ScopedLocalRef.h> #include <system/audio.h> #include <system/audio_policy.h> +#include <utils/Log.h> + +#include <sstream> +#include <vector> + #include "android_media_AudioAttributes.h" #include "android_media_AudioDescriptor.h" #include "android_media_AudioDeviceAttributes.h" @@ -46,6 +46,7 @@ #include "android_media_AudioProfile.h" #include "android_media_MicrophoneInfo.h" #include "android_util_Binder.h" +#include "core_jni_helpers.h" // ---------------------------------------------------------------------------- @@ -584,18 +585,26 @@ android_media_AudioSystem_routing_callback() env->DeleteLocalRef(clazz); } -static jint -android_media_AudioSystem_setDeviceConnectionState(JNIEnv *env, jobject thiz, jint device, jint state, jstring device_address, jstring device_name, - jint codec) -{ - const char *c_address = env->GetStringUTFChars(device_address, NULL); - const char *c_name = env->GetStringUTFChars(device_name, NULL); - int status = check_AudioSystem_Command(AudioSystem::setDeviceConnectionState(static_cast <audio_devices_t>(device), - static_cast <audio_policy_dev_state_t>(state), - c_address, c_name, - static_cast <audio_format_t>(codec))); - env->ReleaseStringUTFChars(device_address, c_address); - env->ReleaseStringUTFChars(device_name, c_name); +static jint android_media_AudioSystem_setDeviceConnectionState(JNIEnv *env, jobject thiz, + jint state, jobject jParcel, + jint codec) { + int status; + if (Parcel *parcel = parcelForJavaObject(env, jParcel); parcel != nullptr) { + android::media::audio::common::AudioPort port{}; + if (status_t statusOfParcel = port.readFromParcel(parcel); statusOfParcel == OK) { + status = check_AudioSystem_Command( + AudioSystem::setDeviceConnectionState(static_cast<audio_policy_dev_state_t>( + state), + port, + static_cast<audio_format_t>(codec))); + } else { + ALOGE("Failed to read from parcel: %s", statusToString(statusOfParcel).c_str()); + status = kAudioStatusError; + } + } else { + ALOGE("Failed to retrieve the native parcel from Java parcel"); + status = kAudioStatusError; + } return (jint) status; } @@ -2912,7 +2921,7 @@ static const JNINativeMethod gMethods[] = {"newAudioSessionId", "()I", (void *)android_media_AudioSystem_newAudioSessionId}, {"newAudioPlayerId", "()I", (void *)android_media_AudioSystem_newAudioPlayerId}, {"newAudioRecorderId", "()I", (void *)android_media_AudioSystem_newAudioRecorderId}, - {"setDeviceConnectionState", "(IILjava/lang/String;Ljava/lang/String;I)I", + {"setDeviceConnectionState", "(ILandroid/os/Parcel;I)I", (void *)android_media_AudioSystem_setDeviceConnectionState}, {"getDeviceConnectionState", "(ILjava/lang/String;)I", (void *)android_media_AudioSystem_getDeviceConnectionState}, diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index afe0f1bf0001..d774fd4e397a 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -6953,10 +6953,10 @@ </declare-styleable> <declare-styleable name="TranslateAnimation"> - <attr name="fromXDelta" format="float|fraction" /> - <attr name="toXDelta" format="float|fraction" /> - <attr name="fromYDelta" format="float|fraction" /> - <attr name="toYDelta" format="float|fraction" /> + <attr name="fromXDelta" format="float|fraction|dimension" /> + <attr name="toXDelta" format="float|fraction|dimension" /> + <attr name="fromYDelta" format="float|fraction|dimension" /> + <attr name="toYDelta" format="float|fraction|dimension" /> </declare-styleable> <declare-styleable name="AlphaAnimation"> diff --git a/media/java/android/media/AudioDescriptor.java b/media/java/android/media/AudioDescriptor.java index 11371b11e905..df648be4c157 100644 --- a/media/java/android/media/AudioDescriptor.java +++ b/media/java/android/media/AudioDescriptor.java @@ -18,16 +18,21 @@ package android.media; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Arrays; +import java.util.Objects; /** * The AudioDescriptor contains the information to describe the audio playback/capture * capabilities. The capabilities are described by a byte array, which is defined by a * particular standard. This is used when the format is unrecognized to the platform. */ -public class AudioDescriptor { +public class AudioDescriptor implements Parcelable { /** * The audio standard is not specified. */ @@ -49,7 +54,15 @@ public class AudioDescriptor { private final byte[] mDescriptor; private final int mEncapsulationType; - AudioDescriptor(int standard, int encapsulationType, @NonNull byte[] descriptor) { + /** + * @hide + * Constructor from standard, encapsulation type and descriptor + * @param standard the standard of the audio descriptor + * @param encapsulationType the encapsulation type of the audio descriptor + * @param descriptor the audio descriptor + */ + @SystemApi + public AudioDescriptor(int standard, int encapsulationType, @NonNull byte[] descriptor) { mStandard = standard; mEncapsulationType = encapsulationType; mDescriptor = descriptor; @@ -87,4 +100,66 @@ public class AudioDescriptor { public @AudioProfile.EncapsulationType int getEncapsulationType() { return mEncapsulationType; } + + @Override + public int hashCode() { + return Objects.hash(mStandard, mEncapsulationType, Arrays.hashCode(mDescriptor)); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + AudioDescriptor that = (AudioDescriptor) o; + return ((mStandard == that.mStandard) + && (mEncapsulationType == that.mEncapsulationType) + && (Arrays.equals(mDescriptor, that.mDescriptor))); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("{"); + sb.append("standard=" + mStandard); + sb.append(", encapsulation type=" + mEncapsulationType); + if (mDescriptor != null && mDescriptor.length > 0) { + sb.append(", descriptor=").append(Arrays.toString(mDescriptor)); + } + sb.append("}"); + return sb.toString(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mStandard); + dest.writeInt(mEncapsulationType); + dest.writeByteArray(mDescriptor); + } + + private AudioDescriptor(@NonNull Parcel in) { + mStandard = in.readInt(); + mEncapsulationType = in.readInt(); + mDescriptor = in.createByteArray(); + } + + public static final @NonNull Parcelable.Creator<AudioDescriptor> CREATOR = + new Parcelable.Creator<AudioDescriptor>() { + /** + * Rebuilds an AudioDescriptor previously stored with writeToParcel(). + * @param p Parcel object to read the AudioDescriptor from + * @return a new AudioDescriptor created from the data in the parcel + */ + public AudioDescriptor createFromParcel(Parcel p) { + return new AudioDescriptor(p); + } + + public AudioDescriptor[] newArray(int size) { + return new AudioDescriptor[size]; + } + }; } diff --git a/media/java/android/media/AudioDeviceAttributes.java b/media/java/android/media/AudioDeviceAttributes.java index 1448c49105b2..af3c295b8d6c 100644 --- a/media/java/android/media/AudioDeviceAttributes.java +++ b/media/java/android/media/AudioDeviceAttributes.java @@ -18,12 +18,16 @@ package android.media; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; 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; /** @@ -65,16 +69,27 @@ public final class AudioDeviceAttributes implements Parcelable { * The unique address of the device. Some devices don't have addresses, only an empty string. */ private final @NonNull String mAddress; - + /** + * The non-unique name of the device. Some devices don't have names, only an empty string. + * Should not be used as a unique identifier for a device. + */ + private final @NonNull String mName; /** * Is input or output device */ private final @Role int mRole; - /** * The internal audio device type */ private final int mNativeType; + /** + * List of AudioProfiles supported by the device + */ + private final @NonNull List<AudioProfile> mAudioProfiles; + /** + * List of AudioDescriptors supported by the device + */ + private final @NonNull List<AudioDescriptor> mAudioDescriptors; /** * @hide @@ -88,7 +103,10 @@ public final class AudioDeviceAttributes implements Parcelable { mRole = deviceInfo.isSink() ? ROLE_OUTPUT : ROLE_INPUT; mType = deviceInfo.getType(); mAddress = deviceInfo.getAddress(); + mName = String.valueOf(deviceInfo.getProductName()); mNativeType = deviceInfo.getInternalType(); + mAudioProfiles = deviceInfo.getAudioProfiles(); + mAudioDescriptors = deviceInfo.getAudioDescriptors(); } /** @@ -100,7 +118,24 @@ public final class AudioDeviceAttributes implements Parcelable { */ @SystemApi public AudioDeviceAttributes(@Role int role, @AudioDeviceInfo.AudioDeviceType int type, - @NonNull String address) { + @NonNull String address) { + this(role, type, address, "", new ArrayList<>(), new ArrayList<>()); + } + + /** + * @hide + * Constructor with specification of all attributes + * @param role indicates input or output role + * @param type the device type, as defined in {@link AudioDeviceInfo} + * @param address the address of the device, or an empty string for devices without one + * @param name the name of the device, or an empty string for devices without one + * @param profiles the list of AudioProfiles supported by the device + * @param descriptors the list of AudioDescriptors supported by the device + */ + @SystemApi + public AudioDeviceAttributes(@Role int role, @AudioDeviceInfo.AudioDeviceType int type, + @NonNull String address, @NonNull String name, @NonNull List<AudioProfile> profiles, + @NonNull List<AudioDescriptor> descriptors) { Objects.requireNonNull(address); if (role != ROLE_OUTPUT && role != ROLE_INPUT) { throw new IllegalArgumentException("Invalid role " + role); @@ -118,19 +153,37 @@ public final class AudioDeviceAttributes implements Parcelable { mRole = role; mType = type; mAddress = address; + mName = name; + mAudioProfiles = profiles; + mAudioDescriptors = descriptors; } /** * @hide - * Constructor from internal device type and address - * @param type the internal device type, as defined in {@link AudioSystem} + * Constructor called from AudioSystem JNI when creating an AudioDeviceAttributes from a native + * AudioDeviceTypeAddr instance. + * @param nativeType the internal device type, as defined in {@link AudioSystem} * @param address the address of the device, or an empty string for devices without one */ public AudioDeviceAttributes(int nativeType, @NonNull String address) { + this(nativeType, address, ""); + } + + /** + * @hide + * Constructor called from BtHelper to connect or disconnect a Bluetooth device. + * @param nativeType the internal device type, as defined in {@link AudioSystem} + * @param address the address of the device, or an empty string for devices without one + * @param name the name of the device, or an empty string for devices without one + */ + public AudioDeviceAttributes(int nativeType, @NonNull String address, @NonNull String name) { mRole = (nativeType & AudioSystem.DEVICE_BIT_IN) != 0 ? ROLE_INPUT : ROLE_OUTPUT; mType = AudioDeviceInfo.convertInternalDeviceToDeviceType(nativeType); mAddress = address; + mName = name; mNativeType = nativeType; + mAudioProfiles = new ArrayList<>(); + mAudioDescriptors = new ArrayList<>(); } /** @@ -165,6 +218,16 @@ public final class AudioDeviceAttributes implements Parcelable { /** * @hide + * Returns the name of the audio device, or an empty string for devices without one + * @return the device name + */ + @SystemApi + public @NonNull String getName() { + return mName; + } + + /** + * @hide * Returns the internal device type of a device * @return the internal device type */ @@ -172,9 +235,29 @@ public final class AudioDeviceAttributes implements Parcelable { return mNativeType; } + /** + * @hide + * Returns the list of AudioProfiles supported by the device + * @return the list of AudioProfiles + */ + @SystemApi + public @NonNull List<AudioProfile> getAudioProfiles() { + return mAudioProfiles; + } + + /** + * @hide + * Returns the list of AudioDescriptors supported by the device + * @return the list of AudioDescriptors + */ + @SystemApi + public @NonNull List<AudioDescriptor> getAudioDescriptors() { + return mAudioDescriptors; + } + @Override public int hashCode() { - return Objects.hash(mRole, mType, mAddress); + return Objects.hash(mRole, mType, mAddress, mName, mAudioProfiles, mAudioDescriptors); } @Override @@ -185,6 +268,25 @@ public final class AudioDeviceAttributes implements Parcelable { AudioDeviceAttributes that = (AudioDeviceAttributes) o; return ((mRole == that.mRole) && (mType == that.mType) + && mAddress.equals(that.mAddress) + && mName.equals(that.mName) + && mAudioProfiles.equals(that.mAudioProfiles) + && mAudioDescriptors.equals(that.mAudioDescriptors)); + } + + /** + * Returns true if the role, type and address are equal. Called to compare with an + * AudioDeviceAttributes that was created from a native AudioDeviceTypeAddr instance. + * @param o object to compare with + * @return whether role, type and address are equal + */ + public boolean equalTypeAddress(@Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + AudioDeviceAttributes that = (AudioDeviceAttributes) o; + return ((mRole == that.mRole) + && (mType == that.mType) && mAddress.equals(that.mAddress)); } @@ -199,7 +301,10 @@ public final class AudioDeviceAttributes implements Parcelable { + " role:" + roleToString(mRole) + " type:" + (mRole == ROLE_OUTPUT ? AudioSystem.getOutputDeviceName(mNativeType) : AudioSystem.getInputDeviceName(mNativeType)) - + " addr:" + mAddress); + + " addr:" + mAddress + + " name:" + mName + + " profiles:" + mAudioProfiles.toString() + + " descriptors:" + mAudioDescriptors.toString()); } @Override @@ -212,14 +317,26 @@ public final class AudioDeviceAttributes implements Parcelable { dest.writeInt(mRole); dest.writeInt(mType); dest.writeString(mAddress); + dest.writeString(mName); dest.writeInt(mNativeType); + dest.writeParcelableArray( + mAudioProfiles.toArray(new AudioProfile[mAudioProfiles.size()]), flags); + dest.writeParcelableArray( + mAudioDescriptors.toArray(new AudioDescriptor[mAudioDescriptors.size()]), flags); } private AudioDeviceAttributes(@NonNull Parcel in) { mRole = in.readInt(); mType = in.readInt(); mAddress = in.readString(); + mName = in.readString(); mNativeType = in.readInt(); + AudioProfile[] audioProfilesArray = + in.readParcelableArray(AudioProfile.class.getClassLoader(), AudioProfile.class); + mAudioProfiles = new ArrayList<AudioProfile>(Arrays.asList(audioProfilesArray)); + AudioDescriptor[] audioDescriptorsArray = in.readParcelableArray( + AudioDescriptor.class.getClassLoader(), AudioDescriptor.class); + mAudioDescriptors = new ArrayList<AudioDescriptor>(Arrays.asList(audioDescriptorsArray)); } public static final @NonNull Parcelable.Creator<AudioDeviceAttributes> CREATOR = diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java index dd17dc649bc7..3d08959901b1 100644 --- a/media/java/android/media/AudioDeviceInfo.java +++ b/media/java/android/media/AudioDeviceInfo.java @@ -421,7 +421,7 @@ public final class AudioDeviceInfo { */ public CharSequence getProductName() { String portName = mPort.name(); - return portName.length() != 0 ? portName : android.os.Build.MODEL; + return (portName != null && portName.length() != 0) ? portName : android.os.Build.MODEL; } /** diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index b8333fb57848..cdc31631637e 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -5874,7 +5874,7 @@ public class AudioManager { return false; } - /** + /** * Indicate wired accessory connection state change. * @param device type of device connected/disconnected (AudioManager.DEVICE_OUT_xxx) * @param state new connection state: 1 connected, 0 disconnected @@ -5883,10 +5883,29 @@ public class AudioManager { */ @UnsupportedAppUsage @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) - public void setWiredDeviceConnectionState(int type, int state, String address, String name) { + public void setWiredDeviceConnectionState(int device, int state, String address, + String name) { + final IAudioService service = getService(); + int role = isOutputDevice(device) + ? AudioDeviceAttributes.ROLE_OUTPUT : AudioDeviceAttributes.ROLE_INPUT; + AudioDeviceAttributes attributes = new AudioDeviceAttributes( + role, AudioDeviceInfo.convertInternalDeviceToDeviceType(device), address, + name, new ArrayList<>()/*mAudioProfiles*/, new ArrayList<>()/*mAudioDescriptors*/); + setWiredDeviceConnectionState(attributes, state); + } + + /** + * Indicate wired accessory connection state change and attributes. + * @param state new connection state: 1 connected, 0 disconnected + * @param attributes attributes of the connected device + * {@hide} + */ + @UnsupportedAppUsage + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public void setWiredDeviceConnectionState(AudioDeviceAttributes attributes, int state) { final IAudioService service = getService(); try { - service.setWiredDeviceConnectionState(type, state, address, name, + service.setWiredDeviceConnectionState(attributes, state, mApplicationContext.getOpPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); diff --git a/media/java/android/media/AudioProfile.java b/media/java/android/media/AudioProfile.java index ae8d0a5ad4ab..5c5f837dd07a 100644 --- a/media/java/android/media/AudioProfile.java +++ b/media/java/android/media/AudioProfile.java @@ -18,10 +18,14 @@ package android.media; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; +import java.util.Objects; import java.util.stream.Collectors; /** @@ -33,7 +37,7 @@ import java.util.stream.Collectors; * be reported in different audio profiles. The application can choose any of the encapsulation * types. */ -public class AudioProfile { +public class AudioProfile implements Parcelable { /** * No encapsulation type is specified. */ @@ -57,9 +61,19 @@ public class AudioProfile { private final int[] mChannelIndexMasks; private final int mEncapsulationType; - AudioProfile(int format, @NonNull int[] samplingRates, @NonNull int[] channelMasks, - @NonNull int[] channelIndexMasks, - int encapsulationType) { + /** + * @hide + * Constructor from format, sampling rates, channel masks, channel index masks and + * encapsulation type. + * @param format the audio format + * @param samplingRates the supported sampling rates + * @param channelMasks the supported channel masks + * @param channelIndexMasks the supported channel index masks + * @param encapsulationType the encapsulation type of the encoding format + */ + @SystemApi + public AudioProfile(int format, @NonNull int[] samplingRates, @NonNull int[] channelMasks, + @NonNull int[] channelIndexMasks, int encapsulationType) { mFormat = format; mSamplingRates = samplingRates; mChannelMasks = channelMasks; @@ -114,6 +128,26 @@ public class AudioProfile { } @Override + public int hashCode() { + return Objects.hash(mFormat, Arrays.hashCode(mSamplingRates), + Arrays.hashCode(mChannelMasks), Arrays.hashCode(mChannelIndexMasks), + mEncapsulationType); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + AudioProfile that = (AudioProfile) o; + return ((mFormat == that.mFormat) + && (hasIdenticalElements(mSamplingRates, that.mSamplingRates)) + && (hasIdenticalElements(mChannelMasks, that.mChannelMasks)) + && (hasIdenticalElements(mChannelIndexMasks, that.mChannelIndexMasks)) + && (mEncapsulationType == that.mEncapsulationType)); + } + + @Override public String toString() { StringBuilder sb = new StringBuilder("{"); sb.append(AudioFormat.toLogFriendlyEncoding(mFormat)); @@ -126,6 +160,7 @@ public class AudioProfile { if (mChannelIndexMasks != null && mChannelIndexMasks.length > 0) { sb.append(", channel index masks=").append(Arrays.toString(mChannelIndexMasks)); } + sb.append(", encapsulation type=" + mEncapsulationType); sb.append("}"); return sb.toString(); } @@ -137,4 +172,50 @@ public class AudioProfile { return Arrays.stream(ints).mapToObj(anInt -> String.format("0x%02X", anInt)) .collect(Collectors.joining(", ")); } + + private static boolean hasIdenticalElements(int[] array1, int[] array2) { + int[] sortedArray1 = Arrays.copyOf(array1, array1.length); + Arrays.sort(sortedArray1); + int[] sortedArray2 = Arrays.copyOf(array2, array2.length); + Arrays.sort(sortedArray2); + return Arrays.equals(sortedArray1, sortedArray2); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mFormat); + dest.writeIntArray(mSamplingRates); + dest.writeIntArray(mChannelMasks); + dest.writeIntArray(mChannelIndexMasks); + dest.writeInt(mEncapsulationType); + } + + private AudioProfile(@NonNull Parcel in) { + mFormat = in.readInt(); + mSamplingRates = in.createIntArray(); + mChannelMasks = in.createIntArray(); + mChannelIndexMasks = in.createIntArray(); + mEncapsulationType = in.readInt(); + } + + public static final @NonNull Parcelable.Creator<AudioProfile> CREATOR = + new Parcelable.Creator<AudioProfile>() { + /** + * Rebuilds an AudioProfile previously stored with writeToParcel(). + * @param p Parcel object to read the AudioProfile from + * @return a new AudioProfile created from the data in the parcel + */ + public AudioProfile createFromParcel(Parcel p) { + return new AudioProfile(p); + } + + public AudioProfile[] newArray(int size) { + return new AudioProfile[size]; + } + }; } diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index 1b46a50d7886..536b4ad71285 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -26,10 +26,12 @@ import android.bluetooth.BluetoothLeAudioCodecConfig; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.pm.PackageManager; +import android.media.audio.common.AidlConversion; import android.media.audiofx.AudioEffect; import android.media.audiopolicy.AudioMix; import android.os.Build; import android.os.IBinder; +import android.os.Parcel; import android.os.Vibrator; import android.telephony.TelephonyManager; import android.util.Log; @@ -1555,9 +1557,24 @@ public class AudioSystem * {@link #AUDIO_STATUS_ERROR} or {@link #AUDIO_STATUS_SERVER_DIED} */ @UnsupportedAppUsage - public static native int setDeviceConnectionState(int device, int state, - String device_address, String device_name, - int codecFormat); + public static int setDeviceConnectionState(AudioDeviceAttributes attributes, int state, + int codecFormat) { + android.media.audio.common.AudioPort port = + AidlConversion.api2aidl_AudioDeviceAttributes_AudioPort(attributes); + Parcel parcel = Parcel.obtain(); + port.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + try { + return setDeviceConnectionState(state, parcel, codecFormat); + } finally { + parcel.recycle(); + } + } + /** + * @hide + */ + @UnsupportedAppUsage + public static native int setDeviceConnectionState(int state, Parcel parcel, int codecFormat); /** @hide */ @UnsupportedAppUsage public static native int getDeviceConnectionState(int device, String device_address); diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 4451c64b6548..fec14def618c 100755 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -215,8 +215,7 @@ interface IAudioService { IRingtonePlayer getRingtonePlayer(); int getUiSoundsStreamType(); - void setWiredDeviceConnectionState(int type, int state, String address, String name, - String caller); + void setWiredDeviceConnectionState(in AudioDeviceAttributes aa, int state, String caller); @UnsupportedAppUsage AudioRoutesInfo startWatchingRoutes(in IAudioRoutesObserver observer); diff --git a/media/java/android/media/audio/common/AidlConversion.java b/media/java/android/media/audio/common/AidlConversion.java index 1053fb717fda..f17189dedcba 100644 --- a/media/java/android/media/audio/common/AidlConversion.java +++ b/media/java/android/media/audio/common/AidlConversion.java @@ -17,12 +17,17 @@ package android.media.audio.common; import android.annotation.NonNull; +import android.media.AudioDescriptor; +import android.media.AudioDeviceAttributes; import android.media.AudioFormat; +import android.media.AudioSystem; import android.media.MediaFormat; import android.os.Parcel; import com.android.internal.annotations.VisibleForTesting; +import java.util.stream.Collectors; + /** * This class provides utility functions for converting between * the AIDL types defined in 'android.media.audio.common' and: @@ -525,6 +530,351 @@ public class AidlConversion { } } + /** + * Convert from SDK AudioDeviceAttributes to AIDL AudioPort. + */ + public static AudioPort api2aidl_AudioDeviceAttributes_AudioPort( + @NonNull AudioDeviceAttributes attributes) { + AudioPort port = new AudioPort(); + port.name = attributes.getName(); + // TO DO: b/211611504 Convert attributes.getAudioProfiles() to AIDL as well. + port.profiles = new AudioProfile[]{}; + port.extraAudioDescriptors = attributes.getAudioDescriptors().stream() + .map(descriptor -> api2aidl_AudioDescriptor_ExtraAudioDescriptor(descriptor)) + .collect(Collectors.toList()).toArray(ExtraAudioDescriptor[]::new); + port.flags = new AudioIoFlags(); + port.gains = new AudioGain[]{}; + AudioPortDeviceExt deviceExt = new AudioPortDeviceExt(); + deviceExt.device = new AudioDevice(); + deviceExt.encodedFormats = new AudioFormatDescription[]{}; + deviceExt.device.type = + api2aidl_NativeType_AudioDeviceDescription(attributes.getInternalType()); + deviceExt.device.address = AudioDeviceAddress.id(attributes.getAddress()); + port.ext = AudioPortExt.device(deviceExt); + return port; + } + + /** + * Convert from SDK AudioDescriptor to AIDL ExtraAudioDescriptor. + */ + public static ExtraAudioDescriptor api2aidl_AudioDescriptor_ExtraAudioDescriptor( + @NonNull AudioDescriptor descriptor) { + ExtraAudioDescriptor extraDescriptor = new ExtraAudioDescriptor(); + extraDescriptor.standard = + api2aidl_AudioDescriptorStandard_AudioStandard(descriptor.getStandard()); + extraDescriptor.audioDescriptor = descriptor.getDescriptor(); + extraDescriptor.encapsulationType = + api2aidl_AudioProfileEncapsulationType_AudioEncapsulationType( + descriptor.getEncapsulationType()); + return extraDescriptor; + } + + /** + * Convert from SDK AudioDescriptor to AIDL ExtraAudioDescriptor. + */ + public static @NonNull AudioDescriptor aidl2api_ExtraAudioDescriptor_AudioDescriptor( + @NonNull ExtraAudioDescriptor extraDescriptor) { + AudioDescriptor descriptor = new AudioDescriptor( + aidl2api_AudioStandard_AudioDescriptorStandard(extraDescriptor.standard), + aidl2api_AudioEncapsulationType_AudioProfileEncapsulationType( + extraDescriptor.encapsulationType), + extraDescriptor.audioDescriptor); + return descriptor; + } + + /** + * Convert from SDK AudioDescriptor#mStandard to AIDL AudioStandard + */ + @AudioStandard + public static int api2aidl_AudioDescriptorStandard_AudioStandard( + @AudioDescriptor.AudioDescriptorStandard int standard) { + switch (standard) { + case AudioDescriptor.STANDARD_EDID: + return AudioStandard.EDID; + case AudioDescriptor.STANDARD_NONE: + default: + return AudioStandard.NONE; + } + } + + /** + * Convert from AIDL AudioStandard to SDK AudioDescriptor#mStandard + */ + @AudioDescriptor.AudioDescriptorStandard + public static int aidl2api_AudioStandard_AudioDescriptorStandard(@AudioStandard int standard) { + switch (standard) { + case AudioStandard.EDID: + return AudioDescriptor.STANDARD_EDID; + case AudioStandard.NONE: + default: + return AudioDescriptor.STANDARD_NONE; + } + } + + /** + * Convert from SDK AudioProfile.EncapsulationType to AIDL AudioEncapsulationType + */ + @AudioEncapsulationType + public static int api2aidl_AudioProfileEncapsulationType_AudioEncapsulationType( + @android.media.AudioProfile.EncapsulationType int type) { + switch (type) { + case android.media.AudioProfile.AUDIO_ENCAPSULATION_TYPE_IEC61937: + return AudioEncapsulationType.IEC61937; + case android.media.AudioProfile.AUDIO_ENCAPSULATION_TYPE_NONE: + default: + return AudioEncapsulationType.NONE; + } + } + + /** + * Convert from AIDL AudioEncapsulationType to SDK AudioProfile.EncapsulationType + */ + @android.media.AudioProfile.EncapsulationType + public static int aidl2api_AudioEncapsulationType_AudioProfileEncapsulationType( + @AudioEncapsulationType int type) { + switch (type) { + case AudioEncapsulationType.IEC61937: + return android.media.AudioProfile.AUDIO_ENCAPSULATION_TYPE_IEC61937; + case AudioEncapsulationType.NONE: + default: + return android.media.AudioProfile.AUDIO_ENCAPSULATION_TYPE_NONE; + } + } + + /** + * Convert from SDK native type to AIDL AudioDeviceDescription + */ + public static AudioDeviceDescription api2aidl_NativeType_AudioDeviceDescription( + int nativeType) { + AudioDeviceDescription aidl = new AudioDeviceDescription(); + aidl.connection = ""; + switch (nativeType) { + case AudioSystem.DEVICE_OUT_EARPIECE: + aidl.type = AudioDeviceType.OUT_SPEAKER_EARPIECE; + break; + case AudioSystem.DEVICE_OUT_SPEAKER: + aidl.type = AudioDeviceType.OUT_SPEAKER; + break; + case AudioSystem.DEVICE_OUT_WIRED_HEADPHONE: + aidl.type = AudioDeviceType.OUT_HEADPHONE; + aidl.connection = AudioDeviceDescription.CONNECTION_ANALOG; + break; + case AudioSystem.DEVICE_OUT_BLUETOOTH_SCO: + aidl.type = AudioDeviceType.OUT_DEVICE; + aidl.connection = AudioDeviceDescription.CONNECTION_BT_SCO; + break; + case AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT: + aidl.type = AudioDeviceType.OUT_CARKIT; + aidl.connection = AudioDeviceDescription.CONNECTION_BT_SCO; + break; + case AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES: + aidl.type = AudioDeviceType.OUT_HEADPHONE; + aidl.connection = AudioDeviceDescription.CONNECTION_BT_A2DP; + break; + case AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER: + aidl.type = AudioDeviceType.OUT_SPEAKER; + aidl.connection = AudioDeviceDescription.CONNECTION_BT_A2DP; + break; + case AudioSystem.DEVICE_OUT_TELEPHONY_TX: + aidl.type = AudioDeviceType.OUT_TELEPHONY_TX; + break; + case AudioSystem.DEVICE_OUT_AUX_LINE: + aidl.type = AudioDeviceType.OUT_LINE_AUX; + break; + case AudioSystem.DEVICE_OUT_SPEAKER_SAFE: + aidl.type = AudioDeviceType.OUT_SPEAKER_SAFE; + break; + case AudioSystem.DEVICE_OUT_HEARING_AID: + aidl.type = AudioDeviceType.OUT_HEARING_AID; + aidl.connection = AudioDeviceDescription.CONNECTION_WIRELESS; + break; + case AudioSystem.DEVICE_OUT_ECHO_CANCELLER: + aidl.type = AudioDeviceType.OUT_ECHO_CANCELLER; + break; + case AudioSystem.DEVICE_OUT_BLE_SPEAKER: + aidl.type = AudioDeviceType.OUT_SPEAKER; + aidl.connection = AudioDeviceDescription.CONNECTION_BT_LE; + break; + case AudioSystem.DEVICE_IN_BUILTIN_MIC: + aidl.type = AudioDeviceType.IN_MICROPHONE; + break; + case AudioSystem.DEVICE_IN_BACK_MIC: + aidl.type = AudioDeviceType.IN_MICROPHONE_BACK; + break; + case AudioSystem.DEVICE_IN_TELEPHONY_RX: + aidl.type = AudioDeviceType.IN_TELEPHONY_RX; + break; + case AudioSystem.DEVICE_IN_TV_TUNER: + aidl.type = AudioDeviceType.IN_TV_TUNER; + break; + case AudioSystem.DEVICE_IN_LOOPBACK: + aidl.type = AudioDeviceType.IN_LOOPBACK; + break; + case AudioSystem.DEVICE_IN_BLUETOOTH_BLE: + aidl.type = AudioDeviceType.IN_DEVICE; + aidl.connection = AudioDeviceDescription.CONNECTION_BT_LE; + break; + case AudioSystem.DEVICE_IN_ECHO_REFERENCE: + aidl.type = AudioDeviceType.IN_ECHO_REFERENCE; + break; + case AudioSystem.DEVICE_IN_WIRED_HEADSET: + aidl.type = AudioDeviceType.IN_HEADSET; + aidl.connection = AudioDeviceDescription.CONNECTION_ANALOG; + break; + case AudioSystem.DEVICE_OUT_WIRED_HEADSET: + aidl.type = AudioDeviceType.OUT_HEADSET; + aidl.connection = AudioDeviceDescription.CONNECTION_ANALOG; + break; + case AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET: + aidl.type = AudioDeviceType.IN_HEADSET; + aidl.connection = AudioDeviceDescription.CONNECTION_BT_SCO; + break; + case AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET: + aidl.type = AudioDeviceType.OUT_HEADSET; + aidl.connection = AudioDeviceDescription.CONNECTION_BT_SCO; + break; + case AudioSystem.DEVICE_IN_HDMI: + aidl.type = AudioDeviceType.IN_DEVICE; + aidl.connection = AudioDeviceDescription.CONNECTION_HDMI; + break; + case AudioSystem.DEVICE_OUT_HDMI: + aidl.type = AudioDeviceType.OUT_DEVICE; + aidl.connection = AudioDeviceDescription.CONNECTION_HDMI; + break; + case AudioSystem.DEVICE_IN_REMOTE_SUBMIX: + aidl.type = AudioDeviceType.IN_SUBMIX; + break; + case AudioSystem.DEVICE_OUT_REMOTE_SUBMIX: + aidl.type = AudioDeviceType.OUT_SUBMIX; + break; + case AudioSystem.DEVICE_IN_ANLG_DOCK_HEADSET: + aidl.type = AudioDeviceType.IN_DOCK; + aidl.connection = AudioDeviceDescription.CONNECTION_ANALOG; + break; + case AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET: + aidl.type = AudioDeviceType.OUT_DOCK; + aidl.connection = AudioDeviceDescription.CONNECTION_ANALOG; + break; + case AudioSystem.DEVICE_IN_DGTL_DOCK_HEADSET: + aidl.type = AudioDeviceType.IN_DOCK; + aidl.connection = AudioDeviceDescription.CONNECTION_USB; + break; + case AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET: + aidl.type = AudioDeviceType.OUT_DOCK; + aidl.connection = AudioDeviceDescription.CONNECTION_USB; + break; + case AudioSystem.DEVICE_IN_USB_ACCESSORY: + aidl.type = AudioDeviceType.IN_ACCESSORY; + aidl.connection = AudioDeviceDescription.CONNECTION_USB; + break; + case AudioSystem.DEVICE_OUT_USB_ACCESSORY: + aidl.type = AudioDeviceType.OUT_ACCESSORY; + aidl.connection = AudioDeviceDescription.CONNECTION_USB; + break; + case AudioSystem.DEVICE_IN_USB_DEVICE: + aidl.type = AudioDeviceType.IN_DEVICE; + aidl.connection = AudioDeviceDescription.CONNECTION_USB; + break; + case AudioSystem.DEVICE_OUT_USB_DEVICE: + aidl.type = AudioDeviceType.OUT_DEVICE; + aidl.connection = AudioDeviceDescription.CONNECTION_USB; + break; + case AudioSystem.DEVICE_IN_FM_TUNER: + aidl.type = AudioDeviceType.IN_FM_TUNER; + break; + case AudioSystem.DEVICE_OUT_FM: + aidl.type = AudioDeviceType.OUT_FM; + break; + case AudioSystem.DEVICE_IN_LINE: + aidl.type = AudioDeviceType.IN_DEVICE; + aidl.connection = AudioDeviceDescription.CONNECTION_ANALOG; + break; + case AudioSystem.DEVICE_OUT_LINE: + aidl.type = AudioDeviceType.OUT_DEVICE; + aidl.connection = AudioDeviceDescription.CONNECTION_ANALOG; + break; + case AudioSystem.DEVICE_IN_SPDIF: + aidl.type = AudioDeviceType.IN_DEVICE; + aidl.connection = AudioDeviceDescription.CONNECTION_SPDIF; + break; + case AudioSystem.DEVICE_OUT_SPDIF: + aidl.type = AudioDeviceType.OUT_DEVICE; + aidl.connection = AudioDeviceDescription.CONNECTION_SPDIF; + break; + case AudioSystem.DEVICE_IN_BLUETOOTH_A2DP: + aidl.type = AudioDeviceType.IN_DEVICE; + aidl.connection = AudioDeviceDescription.CONNECTION_BT_A2DP; + break; + case AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP: + aidl.type = AudioDeviceType.OUT_DEVICE; + aidl.connection = AudioDeviceDescription.CONNECTION_BT_A2DP; + break; + case AudioSystem.DEVICE_IN_IP: + aidl.type = AudioDeviceType.IN_DEVICE; + aidl.connection = AudioDeviceDescription.CONNECTION_IP_V4; + break; + case AudioSystem.DEVICE_OUT_IP: + aidl.type = AudioDeviceType.OUT_DEVICE; + aidl.connection = AudioDeviceDescription.CONNECTION_IP_V4; + break; + case AudioSystem.DEVICE_IN_BUS: + aidl.type = AudioDeviceType.IN_DEVICE; + aidl.connection = AudioDeviceDescription.CONNECTION_BUS; + break; + case AudioSystem.DEVICE_OUT_BUS: + aidl.type = AudioDeviceType.OUT_DEVICE; + aidl.connection = AudioDeviceDescription.CONNECTION_BUS; + break; + case AudioSystem.DEVICE_IN_PROXY: + aidl.type = AudioDeviceType.IN_AFE_PROXY; + break; + case AudioSystem.DEVICE_OUT_PROXY: + aidl.type = AudioDeviceType.OUT_AFE_PROXY; + break; + case AudioSystem.DEVICE_IN_USB_HEADSET: + aidl.type = AudioDeviceType.IN_HEADSET; + aidl.connection = AudioDeviceDescription.CONNECTION_USB; + break; + case AudioSystem.DEVICE_OUT_USB_HEADSET: + aidl.type = AudioDeviceType.OUT_HEADSET; + aidl.connection = AudioDeviceDescription.CONNECTION_USB; + break; + case AudioSystem.DEVICE_IN_HDMI_ARC: + aidl.type = AudioDeviceType.IN_DEVICE; + aidl.connection = AudioDeviceDescription.CONNECTION_HDMI_ARC; + break; + case AudioSystem.DEVICE_OUT_HDMI_ARC: + aidl.type = AudioDeviceType.OUT_DEVICE; + aidl.connection = AudioDeviceDescription.CONNECTION_HDMI_ARC; + break; + case AudioSystem.DEVICE_IN_HDMI_EARC: + aidl.type = AudioDeviceType.IN_DEVICE; + aidl.connection = AudioDeviceDescription.CONNECTION_HDMI_EARC; + break; + case AudioSystem.DEVICE_OUT_HDMI_EARC: + aidl.type = AudioDeviceType.OUT_DEVICE; + aidl.connection = AudioDeviceDescription.CONNECTION_HDMI_EARC; + break; + case AudioSystem.DEVICE_IN_BLE_HEADSET: + aidl.type = AudioDeviceType.IN_HEADSET; + aidl.connection = AudioDeviceDescription.CONNECTION_BT_LE; + break; + case AudioSystem.DEVICE_OUT_BLE_HEADSET: + aidl.type = AudioDeviceType.OUT_HEADSET; + aidl.connection = AudioDeviceDescription.CONNECTION_BT_LE; + break; + case AudioSystem.DEVICE_IN_DEFAULT: + aidl.type = AudioDeviceType.IN_DEFAULT; + break; + case AudioSystem.DEVICE_OUT_DEFAULT: + aidl.type = AudioDeviceType.OUT_DEFAULT; + break; + default: + aidl.type = AudioDeviceType.NONE; + } + return aidl; + } + private static native int aidl2legacy_AudioChannelLayout_Parcel_audio_channel_mask_t( Parcel aidl, boolean isInput); private static native Parcel legacy2aidl_audio_channel_mask_t_AudioChannelLayout_Parcel( diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java index d8f48c2cf0c6..20d711cf4c54 100755 --- a/media/java/android/mtp/MtpDatabase.java +++ b/media/java/android/mtp/MtpDatabase.java @@ -103,6 +103,7 @@ public class MtpDatabase implements AutoCloseable { private int mDeviceType; private String mHostType; private boolean mSkipThumbForHost = false; + private volatile boolean mHostIsWindows = false; private MtpServer mServer; private MtpStorageManager mManager; @@ -358,7 +359,7 @@ public class MtpDatabase implements AutoCloseable { } public void addStorage(StorageVolume storage) { - MtpStorage mtpStorage = mManager.addMtpStorage(storage); + MtpStorage mtpStorage = mManager.addMtpStorage(storage, () -> mHostIsWindows); mStorageMap.put(storage.getPath(), mtpStorage); if (mServer != null) { mServer.addStorage(mtpStorage); @@ -413,6 +414,7 @@ public class MtpDatabase implements AutoCloseable { } mHostType = ""; mSkipThumbForHost = false; + mHostIsWindows = false; } @VisibleForNative @@ -736,10 +738,12 @@ public class MtpDatabase implements AutoCloseable { : MtpConstants.RESPONSE_GENERAL_ERROR); case MtpConstants.DEVICE_PROPERTY_SESSION_INITIATOR_VERSION_INFO: mHostType = stringValue; + Log.d(TAG, "setDeviceProperty." + Integer.toHexString(property) + + "=" + stringValue); if (stringValue.startsWith("Android/")) { - Log.d(TAG, "setDeviceProperty." + Integer.toHexString(property) - + "=" + stringValue); mSkipThumbForHost = true; + } else if (stringValue.startsWith("Windows/")) { + mHostIsWindows = true; } return MtpConstants.RESPONSE_OK; } diff --git a/media/java/android/mtp/MtpStorage.java b/media/java/android/mtp/MtpStorage.java index 88c32a3ea72b..a3754e90a875 100644 --- a/media/java/android/mtp/MtpStorage.java +++ b/media/java/android/mtp/MtpStorage.java @@ -19,6 +19,8 @@ package android.mtp; import android.compat.annotation.UnsupportedAppUsage; import android.os.storage.StorageVolume; +import java.util.function.Supplier; + /** * This class represents a storage unit on an MTP device. * Used only for MTP support in USB responder mode. @@ -33,14 +35,16 @@ public class MtpStorage { private final boolean mRemovable; private final long mMaxFileSize; private final String mVolumeName; + private final Supplier<Boolean> mIsHostWindows; - public MtpStorage(StorageVolume volume, int storageId) { + public MtpStorage(StorageVolume volume, int storageId, Supplier<Boolean> isHostWindows) { mStorageId = storageId; mPath = volume.getPath(); mDescription = volume.getDescription(null); mRemovable = volume.isRemovable(); mMaxFileSize = volume.getMaxFileSize(); mVolumeName = volume.getMediaStoreVolumeName(); + mIsHostWindows = isHostWindows; } /** @@ -93,4 +97,13 @@ public class MtpStorage { public String getVolumeName() { return mVolumeName; } + + /** + * Returns true if the mtp host of this storage is Windows. + * + * @return is host Windows + */ + public boolean isHostWindows() { + return mIsHostWindows.get(); + } } diff --git a/media/java/android/mtp/MtpStorageManager.java b/media/java/android/mtp/MtpStorageManager.java index 0bede0dccbed..e9426cf2ce31 100644 --- a/media/java/android/mtp/MtpStorageManager.java +++ b/media/java/android/mtp/MtpStorageManager.java @@ -18,7 +18,11 @@ package android.mtp; import android.media.MediaFile; import android.os.FileObserver; +import android.os.SystemProperties; import android.os.storage.StorageVolume; +import android.system.ErrnoException; +import android.system.Os; +import android.system.StructStat; import android.util.Log; import com.android.internal.util.Preconditions; @@ -35,6 +39,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.function.Supplier; /** * MtpStorageManager provides functionality for listing, tracking, and notifying MtpServer of @@ -199,7 +204,38 @@ public class MtpStorageManager { } public long getSize() { - return mIsDir ? 0 : getPath().toFile().length(); + return mIsDir ? 0 : maybeApplyTranscodeLengthWorkaround(getPath().toFile().length()); + } + + private long maybeApplyTranscodeLengthWorkaround(long length) { + // Windows truncates transferred files to the size advertised in the object property. + if (mStorage.isHostWindows() && isTranscodeMtpEnabled() && isFileTranscodeSupported()) { + // If the file supports transcoding, we double the returned size to accommodate + // the increase in size from transcoding to AVC. This is the same heuristic + // applied in the FUSE daemon (MediaProvider). + return length * 2; + } + return length; + } + + private boolean isTranscodeMtpEnabled() { + return SystemProperties.getBoolean("sys.fuse.transcode_mtp", false); + } + + private boolean isFileTranscodeSupported() { + // Check if the file supports transcoding by reading the |st_nlinks| struct stat + // field. This will be > 1 if the file supports transcoding. The FUSE daemon + // sets the field accordingly to enable the MTP stack workaround some Windows OS + // MTP client bug where they ignore the size returned as part of getting the MTP + // object, see MtpServer#doGetObject. + final Path path = getPath(); + try { + StructStat stat = Os.stat(path.toString()); + return stat.st_nlink > 1; + } catch (ErrnoException e) { + Log.w(TAG, "Failed to stat path: " + getPath() + ". Ignoring transcoding."); + return false; + } } public Path getPath() { @@ -420,10 +456,12 @@ public class MtpStorageManager { * @param volume Storage to add. * @return the associated MtpStorage */ - public synchronized MtpStorage addMtpStorage(StorageVolume volume) { + public synchronized MtpStorage addMtpStorage(StorageVolume volume, + Supplier<Boolean> isHostWindows) { int storageId = ((getNextStorageId() & 0x0000FFFF) << 16) + 1; - MtpStorage storage = new MtpStorage(volume, storageId); - MtpObject root = new MtpObject(storage.getPath(), storageId, storage, null, true); + MtpStorage storage = new MtpStorage(volume, storageId, isHostWindows); + MtpObject root = new MtpObject(storage.getPath(), storageId, storage, /* parent= */ null, + /* isDir= */ true); mRoots.put(storageId, root); return storage; } diff --git a/media/tests/MtpTests/src/android/mtp/MtpStorageManagerTest.java b/media/tests/MtpTests/src/android/mtp/MtpStorageManagerTest.java index abdc7e559fb3..a6a5568ac8f4 100644 --- a/media/tests/MtpTests/src/android/mtp/MtpStorageManagerTest.java +++ b/media/tests/MtpTests/src/android/mtp/MtpStorageManagerTest.java @@ -160,10 +160,11 @@ public class MtpStorageManagerTest { Log.d(TAG, "sendObjectInfoChanged: " + id); objectsInfoChanged.add(id); } - }, null); + }, /* subdirectories= */ null); - mainMtpStorage = manager.addMtpStorage(mainStorage); - secondaryMtpStorage = manager.addMtpStorage(secondaryStorage); + mainMtpStorage = manager.addMtpStorage(mainStorage, /* isHostWindows= */ () -> false); + secondaryMtpStorage = manager.addMtpStorage(secondaryStorage, + /* isHostWindows= */ () -> false); } @After diff --git a/media/tests/aidltests/src/com/android/media/AidlConversionUnitTests.java b/media/tests/aidltests/src/com/android/media/AidlConversionUnitTests.java index 414de89f8218..09573909c288 100644 --- a/media/tests/aidltests/src/com/android/media/AidlConversionUnitTests.java +++ b/media/tests/aidltests/src/com/android/media/AidlConversionUnitTests.java @@ -16,8 +16,18 @@ package android.media.audio.common; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; + import android.media.AudioAttributes; +import android.media.AudioDescriptor; +import android.media.AudioDeviceAttributes; +import android.media.AudioDeviceInfo; import android.media.AudioFormat; +import android.media.AudioProfile; import android.media.AudioSystem; import android.media.AudioTrack; import android.media.MediaFormat; @@ -25,11 +35,12 @@ import android.platform.test.annotations.Presubmit; import androidx.test.runner.AndroidJUnit4; -import static org.junit.Assert.*; - import org.junit.Test; import org.junit.runner.RunWith; +import java.util.ArrayList; +import java.util.Arrays; + /** * Unit tests for AidlConversion utilities. * @@ -417,10 +428,102 @@ public final class AidlConversionUnitTests { () -> AidlConversion.legacy2aidl_audio_usage_t_AudioUsage(sInvalidValue)); } + @Test + public void testAudioDescriptorConversion_Default() { + ExtraAudioDescriptor aidl = createDefaultDescriptor(); + AudioDescriptor audioDescriptor = + AidlConversion.aidl2api_ExtraAudioDescriptor_AudioDescriptor(aidl); + assertEquals(AudioDescriptor.STANDARD_NONE, audioDescriptor.getStandard()); + assertEquals( + AudioProfile.AUDIO_ENCAPSULATION_TYPE_NONE, audioDescriptor.getEncapsulationType()); + assertTrue(Arrays.equals(new byte[]{}, audioDescriptor.getDescriptor())); + + ExtraAudioDescriptor reconstructedExtraDescriptor = + AidlConversion.api2aidl_AudioDescriptor_ExtraAudioDescriptor(audioDescriptor); + assertEquals(aidl, reconstructedExtraDescriptor); + } + + @Test + public void testAudioDescriptorConversion() { + ExtraAudioDescriptor aidl = createEncapsulationDescriptor(new byte[]{0x05, 0x18, 0x4A}); + AudioDescriptor audioDescriptor = + AidlConversion.aidl2api_ExtraAudioDescriptor_AudioDescriptor(aidl); + assertEquals(AudioDescriptor.STANDARD_EDID, audioDescriptor.getStandard()); + assertEquals(AudioProfile.AUDIO_ENCAPSULATION_TYPE_IEC61937, + audioDescriptor.getEncapsulationType()); + assertTrue(Arrays.equals(new byte[]{0x05, 0x18, 0x4A}, audioDescriptor.getDescriptor())); + + ExtraAudioDescriptor reconstructedExtraDescriptor = + AidlConversion.api2aidl_AudioDescriptor_ExtraAudioDescriptor(audioDescriptor); + assertEquals(aidl, reconstructedExtraDescriptor); + } + + @Test + public void testAudioDeviceAttributesConversion_Default() { + AudioDeviceAttributes attributes = + new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_DEFAULT, "myAddress"); + AudioPort port = AidlConversion.api2aidl_AudioDeviceAttributes_AudioPort(attributes); + assertEquals("", port.name); + assertEquals(0, port.extraAudioDescriptors.length); + assertEquals("myAddress", port.ext.getDevice().device.address.getId()); + assertEquals("", port.ext.getDevice().device.type.connection); + assertEquals(AudioDeviceType.OUT_DEFAULT, port.ext.getDevice().device.type.type); + } + + @Test + public void testAudioDeviceAttributesConversion() { + AudioDescriptor audioDescriptor1 = + AidlConversion.aidl2api_ExtraAudioDescriptor_AudioDescriptor( + createEncapsulationDescriptor(new byte[]{0x05, 0x18, 0x4A})); + + AudioDescriptor audioDescriptor2 = + AidlConversion.aidl2api_ExtraAudioDescriptor_AudioDescriptor( + createDefaultDescriptor()); + + AudioDeviceAttributes attributes = + new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_HDMI_ARC, "myAddress", "myName", new ArrayList<>(), + new ArrayList<>(Arrays.asList(audioDescriptor1, audioDescriptor2))); + AudioPort port = AidlConversion.api2aidl_AudioDeviceAttributes_AudioPort( + attributes); + assertEquals("myName", port.name); + assertEquals(2, port.extraAudioDescriptors.length); + assertEquals(AudioStandard.EDID, port.extraAudioDescriptors[0].standard); + assertEquals(AudioEncapsulationType.IEC61937, + port.extraAudioDescriptors[0].encapsulationType); + assertTrue(Arrays.equals(new byte[]{0x05, 0x18, 0x4A}, + port.extraAudioDescriptors[0].audioDescriptor)); + assertEquals(AudioStandard.NONE, port.extraAudioDescriptors[1].standard); + assertEquals(AudioEncapsulationType.NONE, + port.extraAudioDescriptors[1].encapsulationType); + assertTrue(Arrays.equals(new byte[]{}, + port.extraAudioDescriptors[1].audioDescriptor)); + assertEquals("myAddress", port.ext.getDevice().device.address.getId()); + assertEquals(AudioDeviceDescription.CONNECTION_HDMI_ARC, + port.ext.getDevice().device.type.connection); + assertEquals(AudioDeviceType.OUT_DEVICE, port.ext.getDevice().device.type.type); + } + private static AudioFormatDescription createPcm16FormatAidl() { final AudioFormatDescription aidl = new AudioFormatDescription(); aidl.type = AudioFormatType.PCM; aidl.pcm = PcmType.INT_16_BIT; return aidl; } + + private static ExtraAudioDescriptor createDefaultDescriptor() { + ExtraAudioDescriptor extraDescriptor = new ExtraAudioDescriptor(); + extraDescriptor.standard = AudioStandard.NONE; + extraDescriptor.encapsulationType = AudioEncapsulationType.NONE; + extraDescriptor.audioDescriptor = new byte[]{}; + return extraDescriptor; + } + + private static ExtraAudioDescriptor createEncapsulationDescriptor(byte[] audioDescriptor) { + ExtraAudioDescriptor extraDescriptor = new ExtraAudioDescriptor(); + extraDescriptor.standard = AudioStandard.EDID; + extraDescriptor.encapsulationType = AudioEncapsulationType.IEC61937; + extraDescriptor.audioDescriptor = audioDescriptor; + return extraDescriptor; + } } diff --git a/packages/ConnectivityT/framework-t/src/android/net/nsd/NsdManager.java b/packages/ConnectivityT/framework-t/src/android/net/nsd/NsdManager.java index 0f21e55b9f27..512fbcee9330 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/nsd/NsdManager.java +++ b/packages/ConnectivityT/framework-t/src/android/net/nsd/NsdManager.java @@ -16,6 +16,9 @@ package android.net.nsd; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemService; @@ -23,15 +26,22 @@ import android.app.compat.CompatChanges; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; import android.content.Context; +import android.net.ConnectivityManager; +import android.net.ConnectivityManager.NetworkCallback; +import android.net.Network; +import android.net.NetworkRequest; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import java.util.Objects; @@ -278,9 +288,180 @@ public final class NsdManager { private final SparseArray mListenerMap = new SparseArray(); private final SparseArray<NsdServiceInfo> mServiceMap = new SparseArray<>(); private final Object mMapLock = new Object(); + // Map of listener key sent by client -> per-network discovery tracker + @GuardedBy("mPerNetworkDiscoveryMap") + private final ArrayMap<Integer, PerNetworkDiscoveryTracker> + mPerNetworkDiscoveryMap = new ArrayMap<>(); private final ServiceHandler mHandler; + private class PerNetworkDiscoveryTracker { + final String mServiceType; + final int mProtocolType; + final DiscoveryListener mBaseListener; + final ArrayMap<Network, DelegatingDiscoveryListener> mPerNetworkListeners = + new ArrayMap<>(); + + final NetworkCallback mNetworkCb = new NetworkCallback() { + @Override + public void onAvailable(@NonNull Network network) { + final DelegatingDiscoveryListener wrappedListener = new DelegatingDiscoveryListener( + network, mBaseListener); + mPerNetworkListeners.put(network, wrappedListener); + discoverServices(mServiceType, mProtocolType, network, wrappedListener); + } + + @Override + public void onLost(@NonNull Network network) { + final DelegatingDiscoveryListener listener = mPerNetworkListeners.get(network); + if (listener == null) return; + listener.notifyAllServicesLost(); + // Listener will be removed from map in discovery stopped callback + stopServiceDiscovery(listener); + } + }; + + // Accessed from mHandler + private boolean mStopRequested; + + public void start(@NonNull NetworkRequest request) { + final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class); + cm.registerNetworkCallback(request, mNetworkCb, mHandler); + mHandler.post(() -> mBaseListener.onDiscoveryStarted(mServiceType)); + } + + /** + * Stop discovery on all networks tracked by this class. + * + * This will request all underlying listeners to stop, and the last one to stop will call + * onDiscoveryStopped or onStopDiscoveryFailed. + * + * Must be called on the handler thread. + */ + public void requestStop() { + mHandler.post(() -> { + mStopRequested = true; + final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class); + cm.unregisterNetworkCallback(mNetworkCb); + if (mPerNetworkListeners.size() == 0) { + mBaseListener.onDiscoveryStopped(mServiceType); + return; + } + for (int i = 0; i < mPerNetworkListeners.size(); i++) { + final DelegatingDiscoveryListener listener = mPerNetworkListeners.valueAt(i); + stopServiceDiscovery(listener); + } + }); + } + + private PerNetworkDiscoveryTracker(String serviceType, int protocolType, + DiscoveryListener baseListener) { + mServiceType = serviceType; + mProtocolType = protocolType; + mBaseListener = baseListener; + } + + /** + * Subset of NsdServiceInfo that is tracked to generate service lost notifications when a + * network is lost. + * + * Service lost notifications only contain service name, type and network, so only track + * that information (Network is known from the listener). This also implements + * equals/hashCode for usage in maps. + */ + private class TrackedNsdInfo { + private final String mServiceName; + private final String mServiceType; + TrackedNsdInfo(NsdServiceInfo info) { + mServiceName = info.getServiceName(); + mServiceType = info.getServiceType(); + } + + @Override + public int hashCode() { + return Objects.hash(mServiceName, mServiceType); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof TrackedNsdInfo)) return false; + final TrackedNsdInfo other = (TrackedNsdInfo) obj; + return Objects.equals(mServiceName, other.mServiceName) + && Objects.equals(mServiceType, other.mServiceType); + } + } + + private class DelegatingDiscoveryListener implements DiscoveryListener { + private final Network mNetwork; + private final DiscoveryListener mWrapped; + private final ArraySet<TrackedNsdInfo> mFoundInfo = new ArraySet<>(); + + private DelegatingDiscoveryListener(Network network, DiscoveryListener listener) { + mNetwork = network; + mWrapped = listener; + } + + void notifyAllServicesLost() { + for (int i = 0; i < mFoundInfo.size(); i++) { + final TrackedNsdInfo trackedInfo = mFoundInfo.valueAt(i); + final NsdServiceInfo serviceInfo = new NsdServiceInfo( + trackedInfo.mServiceName, trackedInfo.mServiceType); + serviceInfo.setNetwork(mNetwork); + mWrapped.onServiceLost(serviceInfo); + } + } + + @Override + public void onStartDiscoveryFailed(String serviceType, int errorCode) { + // The delegated listener is used when NsdManager takes care of starting/stopping + // discovery on multiple networks. Failure to start on one network is not a global + // failure to be reported up, as other networks may succeed: just log. + Log.e(TAG, "Failed to start discovery for " + serviceType + " on " + mNetwork + + " with code " + errorCode); + mPerNetworkListeners.remove(mNetwork); + } + + @Override + public void onDiscoveryStarted(String serviceType) { + // Wrapped listener was called upon registration, it is not called for discovery + // on each network + } + + @Override + public void onStopDiscoveryFailed(String serviceType, int errorCode) { + Log.e(TAG, "Failed to stop discovery for " + serviceType + " on " + mNetwork + + " with code " + errorCode); + mPerNetworkListeners.remove(mNetwork); + if (mStopRequested && mPerNetworkListeners.size() == 0) { + // Do not report onStopDiscoveryFailed when some underlying listeners failed: + // this does not mean that all listeners did, and onStopDiscoveryFailed is not + // actionable anyway. Just report that discovery stopped. + mWrapped.onDiscoveryStopped(serviceType); + } + } + + @Override + public void onDiscoveryStopped(String serviceType) { + mPerNetworkListeners.remove(mNetwork); + if (mStopRequested && mPerNetworkListeners.size() == 0) { + mWrapped.onDiscoveryStopped(serviceType); + } + } + + @Override + public void onServiceFound(NsdServiceInfo serviceInfo) { + mFoundInfo.add(new TrackedNsdInfo(serviceInfo)); + mWrapped.onServiceFound(serviceInfo); + } + + @Override + public void onServiceLost(NsdServiceInfo serviceInfo) { + mFoundInfo.remove(new TrackedNsdInfo(serviceInfo)); + mWrapped.onServiceLost(serviceInfo); + } + } + } + /** * Create a new Nsd instance. Applications use * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve @@ -634,6 +815,14 @@ public final class NsdManager { } /** + * Same as {@link #discoverServices(String, int, Network, DiscoveryListener)} with a null + * {@link Network}. + */ + public void discoverServices(String serviceType, int protocolType, DiscoveryListener listener) { + discoverServices(serviceType, protocolType, (Network) null, listener); + } + + /** * Initiate service discovery to browse for instances of a service type. Service discovery * consumes network bandwidth and will continue until the application calls * {@link #stopServiceDiscovery}. @@ -657,11 +846,13 @@ public final class NsdManager { * @param serviceType The service type being discovered. Examples include "_http._tcp" for * http services or "_ipp._tcp" for printers * @param protocolType The service discovery protocol + * @param network Network to discover services on, or null to discover on all available networks * @param listener The listener notifies of a successful discovery and is used * to stop discovery on this serviceType through a call on {@link #stopServiceDiscovery}. * Cannot be null. Cannot be in use for an active service discovery. */ - public void discoverServices(String serviceType, int protocolType, DiscoveryListener listener) { + public void discoverServices(@NonNull String serviceType, int protocolType, + @Nullable Network network, @NonNull DiscoveryListener listener) { if (TextUtils.isEmpty(serviceType)) { throw new IllegalArgumentException("Service type cannot be empty"); } @@ -669,6 +860,7 @@ public final class NsdManager { NsdServiceInfo s = new NsdServiceInfo(); s.setServiceType(serviceType); + s.setNetwork(network); int key = putListener(listener, s); try { @@ -679,6 +871,67 @@ public final class NsdManager { } /** + * Initiate service discovery to browse for instances of a service type. Service discovery + * consumes network bandwidth and will continue until the application calls + * {@link #stopServiceDiscovery}. + * + * <p> The function call immediately returns after sending a request to start service + * discovery to the framework. The application is notified of a success to initiate + * discovery through the callback {@link DiscoveryListener#onDiscoveryStarted} or a failure + * through {@link DiscoveryListener#onStartDiscoveryFailed}. + * + * <p> Upon successful start, application is notified when a service is found with + * {@link DiscoveryListener#onServiceFound} or when a service is lost with + * {@link DiscoveryListener#onServiceLost}. + * + * <p> Upon failure to start, service discovery is not active and application does + * not need to invoke {@link #stopServiceDiscovery} + * + * <p> The application should call {@link #stopServiceDiscovery} when discovery of this + * service type is no longer required, and/or whenever the application is paused or + * stopped. + * + * <p> During discovery, new networks may connect or existing networks may disconnect - for + * example if wifi is reconnected. When a service was found on a network that disconnects, + * {@link DiscoveryListener#onServiceLost} will be called. If a new network connects that + * matches the {@link NetworkRequest}, {@link DiscoveryListener#onServiceFound} will be called + * for services found on that network. Applications that do not want to track networks + * themselves are encouraged to use this method instead of other overloads of + * {@code discoverServices}, as they will receive proper notifications when a service becomes + * available or unavailable due to network changes. + * + * @param serviceType The service type being discovered. Examples include "_http._tcp" for + * http services or "_ipp._tcp" for printers + * @param protocolType The service discovery protocol + * @param networkRequest Request specifying networks that should be considered when discovering + * @param listener The listener notifies of a successful discovery and is used + * to stop discovery on this serviceType through a call on {@link #stopServiceDiscovery}. + * Cannot be null. Cannot be in use for an active service discovery. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public void discoverServices(@NonNull String serviceType, int protocolType, + @NonNull NetworkRequest networkRequest, @NonNull DiscoveryListener listener) { + if (TextUtils.isEmpty(serviceType)) { + throw new IllegalArgumentException("Service type cannot be empty"); + } + Objects.requireNonNull(networkRequest, "NetworkRequest cannot be null"); + checkProtocol(protocolType); + + NsdServiceInfo s = new NsdServiceInfo(); + s.setServiceType(serviceType); + + final int baseListenerKey = putListener(listener, s); + + final PerNetworkDiscoveryTracker discoveryInfo = new PerNetworkDiscoveryTracker( + serviceType, protocolType, listener); + + synchronized (mPerNetworkDiscoveryMap) { + mPerNetworkDiscoveryMap.put(baseListenerKey, discoveryInfo); + discoveryInfo.start(networkRequest); + } + } + + /** * Stop service discovery initiated with {@link #discoverServices}. An active service * discovery is notified to the application with {@link DiscoveryListener#onDiscoveryStarted} * and it stays active until the application invokes a stop service discovery. A successful @@ -696,6 +949,14 @@ public final class NsdManager { */ public void stopServiceDiscovery(DiscoveryListener listener) { int id = getListenerKey(listener); + // If this is a PerNetworkDiscovery request, handle it as such + synchronized (mPerNetworkDiscoveryMap) { + final PerNetworkDiscoveryTracker info = mPerNetworkDiscoveryMap.get(id); + if (info != null) { + info.requestStop(); + return; + } + } try { mService.stopDiscovery(id); } catch (RemoteException e) { diff --git a/packages/ConnectivityT/framework-t/src/android/net/nsd/NsdServiceInfo.java b/packages/ConnectivityT/framework-t/src/android/net/nsd/NsdServiceInfo.java index 0946499f164f..8506db1fbe01 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/nsd/NsdServiceInfo.java +++ b/packages/ConnectivityT/framework-t/src/android/net/nsd/NsdServiceInfo.java @@ -17,7 +17,9 @@ package android.net.nsd; import android.annotation.NonNull; +import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; +import android.net.Network; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; @@ -49,6 +51,9 @@ public final class NsdServiceInfo implements Parcelable { private int mPort; + @Nullable + private Network mNetwork; + public NsdServiceInfo() { } @@ -307,18 +312,37 @@ public final class NsdServiceInfo implements Parcelable { return txtRecord; } - public String toString() { - StringBuffer sb = new StringBuffer(); + /** + * Get the network where the service can be found. + * + * This is never null if this {@link NsdServiceInfo} was obtained from + * {@link NsdManager#discoverServices} or {@link NsdManager#resolveService}. + */ + @Nullable + public Network getNetwork() { + return mNetwork; + } + /** + * Set the network where the service can be found. + * @param network The network, or null to search for, or to announce, the service on all + * connected networks. + */ + public void setNetwork(@Nullable Network network) { + mNetwork = network; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); sb.append("name: ").append(mServiceName) .append(", type: ").append(mServiceType) .append(", host: ").append(mHost) - .append(", port: ").append(mPort); + .append(", port: ").append(mPort) + .append(", network: ").append(mNetwork); byte[] txtRecord = getTxtRecord(); - if (txtRecord != null) { - sb.append(", txtRecord: ").append(new String(txtRecord, StandardCharsets.UTF_8)); - } + sb.append(", txtRecord: ").append(new String(txtRecord, StandardCharsets.UTF_8)); return sb.toString(); } @@ -352,6 +376,8 @@ public final class NsdServiceInfo implements Parcelable { } dest.writeString(key); } + + dest.writeParcelable(mNetwork, 0); } /** Implement the Parcelable interface */ @@ -381,6 +407,7 @@ public final class NsdServiceInfo implements Parcelable { } info.mTxtRecord.put(in.readString(), valueArray); } + info.mNetwork = in.readParcelable(null, Network.class); return info; } diff --git a/packages/ConnectivityT/service/src/com/android/server/NsdService.java b/packages/ConnectivityT/service/src/com/android/server/NsdService.java index 497107dbf989..ddf6d2c4ab15 100644 --- a/packages/ConnectivityT/service/src/com/android/server/NsdService.java +++ b/packages/ConnectivityT/service/src/com/android/server/NsdService.java @@ -19,6 +19,9 @@ package com.android.server; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.net.ConnectivityManager; +import android.net.LinkProperties; +import android.net.Network; import android.net.nsd.INsdManager; import android.net.nsd.INsdManagerCallback; import android.net.nsd.INsdServiceConnector; @@ -44,6 +47,9 @@ import com.android.net.module.util.DnsSdTxtRecord; import java.io.FileDescriptor; import java.io.PrintWriter; import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.net.UnknownHostException; import java.util.Arrays; import java.util.HashMap; import java.util.concurrent.CountDownLatch; @@ -60,6 +66,7 @@ public class NsdService extends INsdManager.Stub { private static final boolean DBG = true; private static final long CLEANUP_DELAY_MS = 10000; + private static final int IFACE_IDX_ANY = 0; private final Context mContext; private final NsdStateMachine mNsdStateMachine; @@ -297,7 +304,7 @@ public class NsdService extends INsdManager.Stub { maybeStartDaemon(); id = getUniqueId(); - if (discoverServices(id, args.serviceInfo.getServiceType())) { + if (discoverServices(id, args.serviceInfo)) { if (DBG) { Log.d(TAG, "Discover " + msg.arg2 + " " + id + args.serviceInfo.getServiceType()); @@ -430,13 +437,38 @@ public class NsdService extends INsdManager.Stub { } switch (code) { case NativeResponseCode.SERVICE_FOUND: - /* NNN uniqueId serviceName regType domain */ + /* NNN uniqueId serviceName regType domain interfaceIdx netId */ servInfo = new NsdServiceInfo(cooked[2], cooked[3]); + final int foundNetId; + try { + foundNetId = Integer.parseInt(cooked[6]); + } catch (NumberFormatException e) { + Log.wtf(TAG, "Invalid network received from mdnsd: " + cooked[6]); + break; + } + if (foundNetId == 0L) { + // Ignore services that do not have a Network: they are not usable + // by apps, as they would need privileged permissions to use + // interfaces that do not have an associated Network. + break; + } + servInfo.setNetwork(new Network(foundNetId)); clientInfo.onServiceFound(clientId, servInfo); break; case NativeResponseCode.SERVICE_LOST: - /* NNN uniqueId serviceName regType domain */ + /* NNN uniqueId serviceName regType domain interfaceIdx netId */ + final int lostNetId; + try { + lostNetId = Integer.parseInt(cooked[6]); + } catch (NumberFormatException e) { + Log.wtf(TAG, "Invalid network received from mdnsd: " + cooked[6]); + break; + } servInfo = new NsdServiceInfo(cooked[2], cooked[3]); + // The network could be null if it was torn down when the service is lost + // TODO: avoid returning null in that case, possibly by remembering found + // services on the same interface index and their network at the time + servInfo.setNetwork(lostNetId == 0 ? null : new Network(lostNetId)); clientInfo.onServiceLost(clientId, servInfo); break; case NativeResponseCode.SERVICE_DISCOVERY_FAILED: @@ -461,7 +493,7 @@ public class NsdService extends INsdManager.Stub { /* NNN regId errorCode */ break; case NativeResponseCode.SERVICE_RESOLVED: - /* NNN resolveId fullName hostName port txtlen txtdata */ + /* NNN resolveId fullName hostName port txtlen txtdata interfaceIdx */ int index = 0; while (index < cooked[2].length() && cooked[2].charAt(index) != '.') { if (cooked[2].charAt(index) == '\\') { @@ -473,6 +505,7 @@ public class NsdService extends INsdManager.Stub { Log.e(TAG, "Invalid service found " + raw); break; } + String name = cooked[2].substring(0, index); String rest = cooked[2].substring(index); String type = rest.replace(".local.", ""); @@ -483,12 +516,13 @@ public class NsdService extends INsdManager.Stub { clientInfo.mResolvedService.setServiceType(type); clientInfo.mResolvedService.setPort(Integer.parseInt(cooked[4])); clientInfo.mResolvedService.setTxtRecords(cooked[6]); + // Network will be added after SERVICE_GET_ADDR_SUCCESS stopResolveService(id); removeRequestMap(clientId, id, clientInfo); int id2 = getUniqueId(); - if (getAddrInfo(id2, cooked[3])) { + if (getAddrInfo(id2, cooked[3], cooked[7] /* interfaceIdx */)) { storeRequestMap(clientId, id2, clientInfo, NsdManager.RESOLVE_SERVICE); } else { clientInfo.onResolveServiceFailed( @@ -513,12 +547,31 @@ public class NsdService extends INsdManager.Stub { clientId, NsdManager.FAILURE_INTERNAL_ERROR); break; case NativeResponseCode.SERVICE_GET_ADDR_SUCCESS: - /* NNN resolveId hostname ttl addr */ + /* NNN resolveId hostname ttl addr interfaceIdx netId */ + Network network = null; + try { + final int netId = Integer.parseInt(cooked[6]); + network = netId == 0L ? null : new Network(netId); + } catch (NumberFormatException e) { + Log.wtf(TAG, "Invalid network in GET_ADDR_SUCCESS: " + cooked[6], e); + } + + InetAddress serviceHost = null; try { - clientInfo.mResolvedService.setHost(InetAddress.getByName(cooked[4])); + serviceHost = InetAddress.getByName(cooked[4]); + } catch (UnknownHostException e) { + Log.wtf(TAG, "Invalid host in GET_ADDR_SUCCESS", e); + } + + // If the resolved service is on an interface without a network, consider it + // as a failure: it would not be usable by apps as they would need + // privileged permissions. + if (network != null && serviceHost != null) { + clientInfo.mResolvedService.setHost(serviceHost); + clientInfo.mResolvedService.setNetwork(network); clientInfo.onResolveServiceSucceeded( clientId, clientInfo.mResolvedService); - } catch (java.net.UnknownHostException e) { + } else { clientInfo.onResolveServiceFailed( clientId, NsdManager.FAILURE_INTERNAL_ERROR); } @@ -815,8 +868,15 @@ public class NsdService extends INsdManager.Stub { return mDaemon.execute("update", regId, t.size(), t.getRawData()); } - private boolean discoverServices(int discoveryId, String serviceType) { - return mDaemon.execute("discover", discoveryId, serviceType); + private boolean discoverServices(int discoveryId, NsdServiceInfo serviceInfo) { + final Network network = serviceInfo.getNetwork(); + final int discoverInterface = getNetworkInterfaceIndex(network); + if (network != null && discoverInterface == IFACE_IDX_ANY) { + Log.e(TAG, "Interface to discover service on not found"); + return false; + } + return mDaemon.execute("discover", discoveryId, serviceInfo.getServiceType(), + discoverInterface); } private boolean stopServiceDiscovery(int discoveryId) { @@ -824,17 +884,61 @@ public class NsdService extends INsdManager.Stub { } private boolean resolveService(int resolveId, NsdServiceInfo service) { - String name = service.getServiceName(); - String type = service.getServiceType(); - return mDaemon.execute("resolve", resolveId, name, type, "local."); + final String name = service.getServiceName(); + final String type = service.getServiceType(); + final Network network = service.getNetwork(); + final int resolveInterface = getNetworkInterfaceIndex(network); + if (network != null && resolveInterface == IFACE_IDX_ANY) { + Log.e(TAG, "Interface to resolve service on not found"); + return false; + } + return mDaemon.execute("resolve", resolveId, name, type, "local.", resolveInterface); + } + + /** + * Guess the interface to use to resolve or discover a service on a specific network. + * + * This is an imperfect guess, as for example the network may be gone or not yet fully + * registered. This is fine as failing is correct if the network is gone, and a client + * attempting to resolve/discover on a network not yet setup would have a bad time anyway; also + * this is to support the legacy mdnsresponder implementation, which historically resolved + * services on an unspecified network. + */ + private int getNetworkInterfaceIndex(Network network) { + if (network == null) return IFACE_IDX_ANY; + + final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class); + if (cm == null) { + Log.wtf(TAG, "No ConnectivityManager for resolveService"); + return IFACE_IDX_ANY; + } + final LinkProperties lp = cm.getLinkProperties(network); + if (lp == null) return IFACE_IDX_ANY; + + // Only resolve on non-stacked interfaces + final NetworkInterface iface; + try { + iface = NetworkInterface.getByName(lp.getInterfaceName()); + } catch (SocketException e) { + Log.e(TAG, "Error querying interface", e); + return IFACE_IDX_ANY; + } + + if (iface == null) { + Log.e(TAG, "Interface not found: " + lp.getInterfaceName()); + return IFACE_IDX_ANY; + } + + return iface.getIndex(); } private boolean stopResolveService(int resolveId) { return mDaemon.execute("stop-resolve", resolveId); } - private boolean getAddrInfo(int resolveId, String hostname) { - return mDaemon.execute("getaddrinfo", resolveId, hostname); + private boolean getAddrInfo(int resolveId, String hostname, String interfaceIdx) { + // interfaceIdx is always obtained (as string) from the service resolved callback + return mDaemon.execute("getaddrinfo", resolveId, hostname, interfaceIdx); } private boolean stopGetAddrInfo(int resolveId) { diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 5330845907ca..daf3561d75ce 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -500,12 +500,11 @@ import java.util.concurrent.atomic.AtomicBoolean; } } - /*package*/ void setWiredDeviceConnectionState(int type, - @AudioService.ConnectionState int state, String address, String name, - String caller) { + /*package*/ void setWiredDeviceConnectionState(AudioDeviceAttributes attributes, + @AudioService.ConnectionState int state, String caller) { //TODO move logging here just like in setBluetooth* methods synchronized (mDeviceStateLock) { - mDeviceInventory.setWiredDeviceConnectionState(type, state, address, name, caller); + mDeviceInventory.setWiredDeviceConnectionState(attributes, state, caller); } } @@ -1013,11 +1012,9 @@ import java.util.concurrent.atomic.AtomicBoolean; } } - /*package*/ boolean handleDeviceConnection(boolean connect, int device, String address, - String deviceName) { + /*package*/ boolean handleDeviceConnection(AudioDeviceAttributes attributes, boolean connect) { synchronized (mDeviceStateLock) { - return mDeviceInventory.handleDeviceConnection(connect, device, address, deviceName, - false /*for test*/); + return mDeviceInventory.handleDeviceConnection(attributes, connect, false /*for test*/); } } diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index 4ae1bd371690..0e290410d288 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -230,19 +230,15 @@ public class AudioDeviceInventory { * A class just for packaging up a set of connection parameters. */ /*package*/ class WiredDeviceConnectionState { - public final int mType; + public final AudioDeviceAttributes mAttributes; public final @AudioService.ConnectionState int mState; - public final String mAddress; - public final String mName; public final String mCaller; public boolean mForTest = false; - /*package*/ WiredDeviceConnectionState(int type, @AudioService.ConnectionState int state, - String address, String name, String caller) { - mType = type; + /*package*/ WiredDeviceConnectionState(AudioDeviceAttributes attributes, + @AudioService.ConnectionState int state, String caller) { + mAttributes = attributes; mState = state; - mAddress = address; - mName = name; mCaller = caller; } } @@ -280,11 +276,10 @@ public class AudioDeviceInventory { synchronized (mDevicesLock) { //TODO iterate on mApmConnectedDevices instead once it handles all device types for (DeviceInfo di : mConnectedDevices.values()) { - mAudioSystem.setDeviceConnectionState( - di.mDeviceType, - AudioSystem.DEVICE_STATE_AVAILABLE, + mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(di.mDeviceType, di.mDeviceAddress, - di.mDeviceName, + di.mDeviceName), + AudioSystem.DEVICE_STATE_AVAILABLE, di.mDeviceCodecFormat); } } @@ -519,41 +514,45 @@ public class AudioDeviceInventory { /*package*/ void onSetWiredDeviceConnectionState( AudioDeviceInventory.WiredDeviceConnectionState wdcs) { + int type = wdcs.mAttributes.getInternalType(); + AudioService.sDeviceLogger.log(new AudioServiceEvents.WiredDevConnectEvent(wdcs)); MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId + "onSetWiredDeviceConnectionState") - .set(MediaMetrics.Property.ADDRESS, wdcs.mAddress) - .set(MediaMetrics.Property.DEVICE, AudioSystem.getDeviceName(wdcs.mType)) + .set(MediaMetrics.Property.ADDRESS, wdcs.mAttributes.getAddress()) + .set(MediaMetrics.Property.DEVICE, + AudioSystem.getDeviceName(type)) .set(MediaMetrics.Property.STATE, wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED ? MediaMetrics.Value.DISCONNECTED : MediaMetrics.Value.CONNECTED); synchronized (mDevicesLock) { if ((wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED) - && DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.contains(wdcs.mType)) { + && DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.contains(type)) { mDeviceBroker.setBluetoothA2dpOnInt(true, false /*fromA2dp*/, "onSetWiredDeviceConnectionState state DISCONNECTED"); } - if (!handleDeviceConnection(wdcs.mState == AudioService.CONNECTION_STATE_CONNECTED, - wdcs.mType, wdcs.mAddress, wdcs.mName, wdcs.mForTest)) { + if (!handleDeviceConnection(wdcs.mAttributes, + wdcs.mState == AudioService.CONNECTION_STATE_CONNECTED, wdcs.mForTest)) { // change of connection state failed, bailout mmi.set(MediaMetrics.Property.EARLY_RETURN, "change of connection state failed") .record(); return; } if (wdcs.mState != AudioService.CONNECTION_STATE_DISCONNECTED) { - if (DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.contains(wdcs.mType)) { + if (DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.contains(type)) { mDeviceBroker.setBluetoothA2dpOnInt(false, false /*fromA2dp*/, "onSetWiredDeviceConnectionState state not DISCONNECTED"); } - mDeviceBroker.checkMusicActive(wdcs.mType, wdcs.mCaller); + mDeviceBroker.checkMusicActive(type, wdcs.mCaller); } - if (wdcs.mType == AudioSystem.DEVICE_OUT_HDMI) { + if (type == AudioSystem.DEVICE_OUT_HDMI) { mDeviceBroker.checkVolumeCecOnHdmiConnection(wdcs.mState, wdcs.mCaller); } - sendDeviceConnectionIntent(wdcs.mType, wdcs.mState, wdcs.mAddress, wdcs.mName); - updateAudioRoutes(wdcs.mType, wdcs.mState); + sendDeviceConnectionIntent(type, wdcs.mState, + wdcs.mAttributes.getAddress(), wdcs.mAttributes.getName()); + updateAudioRoutes(type, wdcs.mState); } mmi.record(); } @@ -572,12 +571,12 @@ public class AudioDeviceInventory { return; } // Toggle HDMI to retrigger broadcast with proper formats. - setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_HDMI, - AudioSystem.DEVICE_STATE_UNAVAILABLE, "", "", - "android"); // disconnect - setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_HDMI, - AudioSystem.DEVICE_STATE_AVAILABLE, "", "", - "android"); // reconnect + setWiredDeviceConnectionState( + new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_HDMI, ""), + AudioSystem.DEVICE_STATE_UNAVAILABLE, "android"); // disconnect + setWiredDeviceConnectionState( + new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_HDMI, ""), + AudioSystem.DEVICE_STATE_AVAILABLE, "android"); // reconnect } mmi.record(); } @@ -707,16 +706,17 @@ public class AudioDeviceInventory { /** * Implements the communication with AudioSystem to (dis)connect a device in the native layers + * @param attributes the attributes of the device * @param connect true if connection - * @param device the device type - * @param address the address of the device - * @param deviceName human-readable name of device * @param isForTesting if true, not calling AudioSystem for the connection as this is * just for testing * @return false if an error was reported by AudioSystem */ - /*package*/ boolean handleDeviceConnection(boolean connect, int device, String address, - String deviceName, boolean isForTesting) { + /*package*/ boolean handleDeviceConnection(AudioDeviceAttributes attributes, boolean connect, + boolean isForTesting) { + int device = attributes.getInternalType(); + String address = attributes.getAddress(); + String deviceName = attributes.getName(); if (AudioService.DEBUG_DEVICES) { Slog.i(TAG, "handleDeviceConnection(" + connect + " dev:" + Integer.toHexString(device) + " address:" + address @@ -743,9 +743,8 @@ public class AudioDeviceInventory { if (isForTesting) { res = AudioSystem.AUDIO_STATUS_OK; } else { - res = mAudioSystem.setDeviceConnectionState(device, - AudioSystem.DEVICE_STATE_AVAILABLE, address, deviceName, - AudioSystem.AUDIO_FORMAT_DEFAULT); + res = mAudioSystem.setDeviceConnectionState(attributes, + AudioSystem.DEVICE_STATE_AVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT); } if (res != AudioSystem.AUDIO_STATUS_OK) { final String reason = "not connecting device 0x" + Integer.toHexString(device) @@ -762,9 +761,8 @@ public class AudioDeviceInventory { mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record(); return true; } else if (!connect && isConnected) { - mAudioSystem.setDeviceConnectionState(device, - AudioSystem.DEVICE_STATE_UNAVAILABLE, address, deviceName, - AudioSystem.AUDIO_FORMAT_DEFAULT); + mAudioSystem.setDeviceConnectionState(attributes, + AudioSystem.DEVICE_STATE_UNAVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT); // always remove even if disconnection failed mConnectedDevices.remove(deviceKey); mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record(); @@ -941,13 +939,13 @@ public class AudioDeviceInventory { return delay; } - /*package*/ int setWiredDeviceConnectionState(int type, @AudioService.ConnectionState int state, - String address, String name, String caller) { + /*package*/ int setWiredDeviceConnectionState(AudioDeviceAttributes attributes, + @AudioService.ConnectionState int state, String caller) { synchronized (mDevicesLock) { - int delay = checkSendBecomingNoisyIntentInt(type, state, AudioSystem.DEVICE_NONE); + int delay = checkSendBecomingNoisyIntentInt( + attributes.getInternalType(), state, AudioSystem.DEVICE_NONE); mDeviceBroker.postSetWiredDeviceConnectionState( - new WiredDeviceConnectionState(type, state, address, name, caller), - delay); + new WiredDeviceConnectionState(attributes, state, caller), delay); return delay; } } @@ -955,8 +953,7 @@ public class AudioDeviceInventory { /*package*/ void setTestDeviceConnectionState(@NonNull AudioDeviceAttributes device, @AudioService.ConnectionState int state) { final WiredDeviceConnectionState connection = new WiredDeviceConnectionState( - device.getInternalType(), state, device.getAddress(), - "test device", "com.android.server.audio"); + device, state, "com.android.server.audio"); connection.mForTest = true; onSetWiredDeviceConnectionState(connection); } @@ -972,8 +969,9 @@ public class AudioDeviceInventory { mDeviceBroker.setBluetoothA2dpOnInt(true, true /*fromA2dp*/, eventSource); // at this point there could be another A2DP device already connected in APM, but it // doesn't matter as this new one will overwrite the previous one - final int res = mAudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, - AudioSystem.DEVICE_STATE_AVAILABLE, address, name, a2dpCodec); + final int res = mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes( + AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, name), + AudioSystem.DEVICE_STATE_AVAILABLE, a2dpCodec); // TODO: log in MediaMetrics once distinction between connection failure and // double connection is made. @@ -1035,8 +1033,9 @@ public class AudioDeviceInventory { // device to remove was visible by APM, update APM mDeviceBroker.clearAvrcpAbsoluteVolumeSupported(); - final int res = mAudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, - AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", a2dpCodec); + final int res = mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes( + AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address), + AudioSystem.DEVICE_STATE_UNAVAILABLE, a2dpCodec); if (res != AudioSystem.AUDIO_STATUS_OK) { AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( @@ -1074,8 +1073,9 @@ public class AudioDeviceInventory { @GuardedBy("mDevicesLock") private void makeA2dpSrcAvailable(String address) { - mAudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, - AudioSystem.DEVICE_STATE_AVAILABLE, address, "", + mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes( + AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address), + AudioSystem.DEVICE_STATE_AVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT); mConnectedDevices.put( DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address), @@ -1085,8 +1085,9 @@ public class AudioDeviceInventory { @GuardedBy("mDevicesLock") private void makeA2dpSrcUnavailable(String address) { - mAudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, - AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", + mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes( + AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address), + AudioSystem.DEVICE_STATE_UNAVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT); mConnectedDevices.remove( DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address)); @@ -1099,8 +1100,9 @@ public class AudioDeviceInventory { AudioSystem.DEVICE_OUT_HEARING_AID); mDeviceBroker.postSetHearingAidVolumeIndex(hearingAidVolIndex, streamType); - mAudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID, - AudioSystem.DEVICE_STATE_AVAILABLE, address, name, + mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes( + AudioSystem.DEVICE_OUT_HEARING_AID, address, name), + AudioSystem.DEVICE_STATE_AVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT); mConnectedDevices.put( DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address), @@ -1122,8 +1124,9 @@ public class AudioDeviceInventory { @GuardedBy("mDevicesLock") private void makeHearingAidDeviceUnavailable(String address) { - mAudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID, - AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", + mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes( + AudioSystem.DEVICE_OUT_HEARING_AID, address), + AudioSystem.DEVICE_STATE_UNAVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT); mConnectedDevices.remove( DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address)); @@ -1140,14 +1143,14 @@ public class AudioDeviceInventory { private void makeLeAudioDeviceAvailable(String address, String name, int streamType, int device, String eventSource) { if (device != AudioSystem.DEVICE_NONE) { - /* Audio Policy sees Le Audio similar to A2DP. Let's make sure * AUDIO_POLICY_FORCE_NO_BT_A2DP is not set */ mDeviceBroker.setBluetoothA2dpOnInt(true, false /*fromA2dp*/, eventSource); - AudioSystem.setDeviceConnectionState(device, AudioSystem.DEVICE_STATE_AVAILABLE, - address, name, AudioSystem.AUDIO_FORMAT_DEFAULT); + AudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(device, address, name), + AudioSystem.DEVICE_STATE_AVAILABLE, + AudioSystem.AUDIO_FORMAT_DEFAULT); mConnectedDevices.put(DeviceInfo.makeDeviceListKey(device, address), new DeviceInfo(device, name, address, AudioSystem.AUDIO_FORMAT_DEFAULT)); mDeviceBroker.postAccessoryPlugMediaUnmute(device); @@ -1168,8 +1171,9 @@ public class AudioDeviceInventory { @GuardedBy("mDevicesLock") private void makeLeAudioDeviceUnavailable(String address, int device) { if (device != AudioSystem.DEVICE_NONE) { - AudioSystem.setDeviceConnectionState(device, AudioSystem.DEVICE_STATE_UNAVAILABLE, - address, "", AudioSystem.AUDIO_FORMAT_DEFAULT); + AudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(device, address), + AudioSystem.DEVICE_STATE_UNAVAILABLE, + AudioSystem.AUDIO_FORMAT_DEFAULT); mConnectedDevices.remove(DeviceInfo.makeDeviceListKey(device, address)); } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 4494d963418e..05955c3cab44 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -6371,23 +6371,23 @@ public class AudioService extends IAudioService.Stub /** * see AudioManager.setWiredDeviceConnectionState() */ - public void setWiredDeviceConnectionState(int type, - @ConnectionState int state, String address, String name, - String caller) { + public void setWiredDeviceConnectionState(AudioDeviceAttributes attributes, + @ConnectionState int state, String caller) { enforceModifyAudioRoutingPermission(); if (state != CONNECTION_STATE_CONNECTED && state != CONNECTION_STATE_DISCONNECTED) { throw new IllegalArgumentException("Invalid state " + state); } new MediaMetrics.Item(mMetricsId + "setWiredDeviceConnectionState") - .set(MediaMetrics.Property.ADDRESS, address) + .set(MediaMetrics.Property.ADDRESS, attributes.getAddress()) .set(MediaMetrics.Property.CLIENT_NAME, caller) - .set(MediaMetrics.Property.DEVICE, AudioSystem.getDeviceName(type)) - .set(MediaMetrics.Property.NAME, name) + .set(MediaMetrics.Property.DEVICE, + AudioSystem.getDeviceName(attributes.getInternalType())) + .set(MediaMetrics.Property.NAME, attributes.getName()) .set(MediaMetrics.Property.STATE, state == CONNECTION_STATE_CONNECTED ? "connected" : "disconnected") .record(); - mDeviceBroker.setWiredDeviceConnectionState(type, state, address, name, caller); + mDeviceBroker.setWiredDeviceConnectionState(attributes, state, caller); } /** @see AudioManager#setTestDeviceConnectionState(AudioDeviceAttributes, boolean) */ diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java index 3137fa5784d2..3225274a8a9b 100644 --- a/services/core/java/com/android/server/audio/AudioServiceEvents.java +++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java @@ -116,10 +116,11 @@ public class AudioServiceEvents { @Override public String eventToString() { return new StringBuilder("setWiredDeviceConnectionState(") - .append(" type:").append(Integer.toHexString(mState.mType)) + .append(" type:").append( + Integer.toHexString(mState.mAttributes.getInternalType())) .append(" state:").append(AudioSystem.deviceStateToString(mState.mState)) - .append(" addr:").append(mState.mAddress) - .append(" name:").append(mState.mName) + .append(" addr:").append(mState.mAttributes.getAddress()) + .append(" name:").append(mState.mAttributes.getName()) .append(") from ").append(mState.mCaller).toString(); } } diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java index a2ba76b6fd6a..f572261c09e5 100644 --- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java +++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java @@ -258,19 +258,16 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback { } /** - * Same as {@link AudioSystem#setDeviceConnectionState(int, int, String, String, int)} - * @param device + * Same as {@link AudioSystem#setDeviceConnectionState(AudioDeviceAttributes, int, int)} + * @param attributes * @param state - * @param deviceAddress - * @param deviceName * @param codecFormat * @return */ - public int setDeviceConnectionState(int device, int state, String deviceAddress, - String deviceName, int codecFormat) { + public int setDeviceConnectionState(AudioDeviceAttributes attributes, int state, + int codecFormat) { invalidateRoutingCache(); - return AudioSystem.setDeviceConnectionState(device, state, deviceAddress, deviceName, - codecFormat); + return AudioSystem.setDeviceConnectionState(attributes, state, codecFormat); } /** diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java index a006b91c9eb6..3491cd59ebb7 100644 --- a/services/core/java/com/android/server/audio/BtHelper.java +++ b/services/core/java/com/android/server/audio/BtHelper.java @@ -595,8 +595,9 @@ public class BtHelper { String btDeviceName = getName(btDevice); boolean result = false; if (isActive) { - result |= mDeviceBroker.handleDeviceConnection(isActive, audioDevice.getInternalType(), - audioDevice.getAddress(), btDeviceName); + result |= mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes( + audioDevice.getInternalType(), audioDevice.getAddress(), btDeviceName), + isActive); } else { int[] outDeviceTypes = { AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, @@ -604,13 +605,15 @@ public class BtHelper { AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT }; for (int outDeviceType : outDeviceTypes) { - result |= mDeviceBroker.handleDeviceConnection( - isActive, outDeviceType, audioDevice.getAddress(), btDeviceName); + result |= mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes( + outDeviceType, audioDevice.getAddress(), btDeviceName), + isActive); } } // handleDeviceConnection() && result to make sure the method get executed - result = mDeviceBroker.handleDeviceConnection( - isActive, inDevice, audioDevice.getAddress(), btDeviceName) && result; + result = mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes( + inDevice, audioDevice.getAddress(), btDeviceName), + isActive) && result; return result; } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index afcd3dde0633..1ce36b181eb0 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -41,7 +41,10 @@ import android.hardware.hdmi.HdmiRecordSources; import android.hardware.hdmi.HdmiTimerRecordSources; import android.hardware.hdmi.IHdmiControlCallback; import android.hardware.tv.cec.V1_0.SendMessageResult; -import android.media.AudioSystem; +import android.media.AudioDescriptor; +import android.media.AudioDeviceAttributes; +import android.media.AudioDeviceInfo; +import android.media.AudioProfile; import android.media.tv.TvInputInfo; import android.media.tv.TvInputManager.TvInputCallback; import android.util.Slog; @@ -58,6 +61,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; +import java.util.stream.Collectors; /** * Represent a logical device of type TV residing in Android system. @@ -816,12 +820,23 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { HdmiLogger.debug("Set Arc Status[old:%b new:%b]", mArcEstablished, enabled); boolean oldStatus = mArcEstablished; - // 1. Enable/disable ARC circuit. - enableAudioReturnChannel(enabled); - // 2. Notify arc status to audio service. - notifyArcStatusToAudioService(enabled); - // 3. Update arc status; - mArcEstablished = enabled; + if (enabled) { + RequestSadAction action = new RequestSadAction( + this, Constants.ADDR_AUDIO_SYSTEM, + new RequestSadAction.RequestSadCallback() { + @Override + public void onRequestSadDone(List<byte[]> supportedSads) { + enableAudioReturnChannel(enabled); + notifyArcStatusToAudioService(enabled, supportedSads); + mArcEstablished = enabled; + } + }); + addAndStartAction(action); + } else { + enableAudioReturnChannel(enabled); + notifyArcStatusToAudioService(enabled, new ArrayList<>()); + mArcEstablished = enabled; + } return oldStatus; } @@ -843,11 +858,15 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { return mService.isConnected(portId); } - private void notifyArcStatusToAudioService(boolean enabled) { + private void notifyArcStatusToAudioService(boolean enabled, List<byte[]> supportedSads) { // Note that we don't set any name to ARC. - mService.getAudioManager().setWiredDeviceConnectionState( - AudioSystem.DEVICE_OUT_HDMI_ARC, - enabled ? 1 : 0, "", ""); + AudioDeviceAttributes attributes = new AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_HDMI_ARC, "", "", + new ArrayList<AudioProfile>(), supportedSads.stream() + .map(sad -> new AudioDescriptor(AudioDescriptor.STANDARD_EDID, + AudioProfile.AUDIO_ENCAPSULATION_TYPE_NONE, sad)) + .collect(Collectors.toList())); + mService.getAudioManager().setWiredDeviceConnectionState(attributes, enabled ? 1 : 0); } /** diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index bfaa7b37fa33..354d1838709d 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -150,8 +150,6 @@ public class InputManagerService extends IInputManager.Stub static final String TAG = "InputManager"; static final boolean DEBUG = false; - private static final boolean USE_SPY_WINDOW_GESTURE_MONITORS = true; - private static final String EXCLUDED_DEVICES_PATH = "etc/excluded-input-devices.xml"; private static final String PORT_ASSOCIATIONS_PATH = "etc/input-port-associations.xml"; @@ -303,7 +301,7 @@ public class InputManagerService extends IInputManager.Stub int locationKeyCode); private static native InputChannel nativeCreateInputChannel(long ptr, String name); private static native InputChannel nativeCreateInputMonitor(long ptr, int displayId, - boolean isGestureMonitor, String name, int pid); + String name, int pid); private static native void nativeRemoveInputChannel(long ptr, IBinder connectionToken); private static native void nativePilferPointers(long ptr, IBinder token); private static native void nativeSetInputFilterEnabled(long ptr, boolean enable); @@ -720,8 +718,7 @@ public class InputManagerService extends IInputManager.Stub throw new IllegalArgumentException("displayId must >= 0."); } - return nativeCreateInputMonitor(mPtr, displayId, false /* isGestureMonitor */, - inputChannelName, Binder.getCallingPid()); + return nativeCreateInputMonitor(mPtr, displayId, inputChannelName, Binder.getCallingPid()); } @NonNull @@ -790,10 +787,7 @@ public class InputManagerService extends IInputManager.Stub final long ident = Binder.clearCallingIdentity(); try { final InputChannel inputChannel = - USE_SPY_WINDOW_GESTURE_MONITORS - ? createSpyWindowGestureMonitor(monitorToken, name, displayId, pid, uid) - : nativeCreateInputMonitor(mPtr, displayId, true /*isGestureMonitor*/, - requestedName, pid); + createSpyWindowGestureMonitor(monitorToken, name, displayId, pid, uid); return new InputMonitor(inputChannel, new InputMonitorHost(inputChannel.getToken())); } finally { Binder.restoreCallingIdentity(ident); @@ -3375,11 +3369,7 @@ public class InputManagerService extends IInputManager.Stub @Override public void dispose() { - if (USE_SPY_WINDOW_GESTURE_MONITORS) { - removeSpyWindowGestureMonitor(mInputChannelToken); - return; - } - nativeRemoveInputChannel(mPtr, mInputChannelToken); + removeSpyWindowGestureMonitor(mInputChannelToken); } } diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 8cb27e179c19..e5529f1997a3 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -265,7 +265,6 @@ public: base::Result<std::unique_ptr<InputChannel>> createInputChannel(JNIEnv* env, const std::string& name); base::Result<std::unique_ptr<InputChannel>> createInputMonitor(JNIEnv* env, int32_t displayId, - bool isGestureMonitor, const std::string& name, int32_t pid); status_t removeInputChannel(JNIEnv* env, const sp<IBinder>& connectionToken); @@ -522,11 +521,9 @@ base::Result<std::unique_ptr<InputChannel>> NativeInputManager::createInputChann } base::Result<std::unique_ptr<InputChannel>> NativeInputManager::createInputMonitor( - JNIEnv* /* env */, int32_t displayId, bool isGestureMonitor, const std::string& name, - int32_t pid) { + JNIEnv* /* env */, int32_t displayId, const std::string& name, int32_t pid) { ATRACE_CALL(); - return mInputManager->getDispatcher().createInputMonitor(displayId, isGestureMonitor, name, - pid); + return mInputManager->getDispatcher().createInputMonitor(displayId, name, pid); } status_t NativeInputManager::removeInputChannel(JNIEnv* /* env */, @@ -1659,7 +1656,7 @@ static jobject nativeCreateInputChannel(JNIEnv* env, jclass /* clazz */, jlong p } static jobject nativeCreateInputMonitor(JNIEnv* env, jclass /* clazz */, jlong ptr, jint displayId, - jboolean isGestureMonitor, jstring nameObj, jint pid) { + jstring nameObj, jint pid) { NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); if (displayId == ADISPLAY_ID_NONE) { @@ -1672,7 +1669,7 @@ static jobject nativeCreateInputMonitor(JNIEnv* env, jclass /* clazz */, jlong p std::string name = nameChars.c_str(); base::Result<std::unique_ptr<InputChannel>> inputChannel = - im->createInputMonitor(env, displayId, isGestureMonitor, name, pid); + im->createInputMonitor(env, displayId, name, pid); if (!inputChannel.ok()) { std::string message = inputChannel.error().message(); @@ -2381,7 +2378,7 @@ static const JNINativeMethod gInputManagerMethods[] = { {"nativeGetKeyCodeForKeyLocation", "(JII)I", (void*)nativeGetKeyCodeForKeyLocation}, {"nativeCreateInputChannel", "(JLjava/lang/String;)Landroid/view/InputChannel;", (void*)nativeCreateInputChannel}, - {"nativeCreateInputMonitor", "(JIZLjava/lang/String;I)Landroid/view/InputChannel;", + {"nativeCreateInputMonitor", "(JILjava/lang/String;I)Landroid/view/InputChannel;", (void*)nativeCreateInputMonitor}, {"nativeRemoveInputChannel", "(JLandroid/os/IBinder;)V", (void*)nativeRemoveInputChannel}, {"nativePilferPointers", "(JLandroid/os/IBinder;)V", (void*)nativePilferPointers}, diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index e99b1f9a408c..762d4c1b9a78 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -134,6 +134,7 @@ import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPR import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1; import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK; import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER; +import static android.provider.Settings.Secure.MANAGED_PROVISIONING_DPC_DOWNLOADED; import static android.provider.Settings.Secure.USER_SETUP_COMPLETE; import static android.provider.Telephony.Carriers.DPC_URI; import static android.provider.Telephony.Carriers.ENFORCE_KEY; @@ -222,6 +223,7 @@ import android.compat.annotation.EnabledSince; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ComponentName; +import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.IIntentReceiver; @@ -18669,4 +18671,26 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mContext.sendBroadcastAsUser(intent, user); } } + + public boolean isDpcDownloaded() { + Preconditions.checkCallAuthorization(hasCallingOrSelfPermission( + android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); + + ContentResolver cr = mContext.getContentResolver(); + + return mInjector.binderWithCleanCallingIdentity(() -> Settings.Secure.getIntForUser( + cr, MANAGED_PROVISIONING_DPC_DOWNLOADED, + /* def= */ 0, /* userHandle= */ cr.getUserId()) + == 1); + } + + public void setDpcDownloaded(boolean downloaded) { + Preconditions.checkCallAuthorization(hasCallingOrSelfPermission( + android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); + + int setTo = downloaded ? 1 : 0; + + mInjector.binderWithCleanCallingIdentity(() -> Settings.Secure.putInt( + mContext.getContentResolver(), MANAGED_PROVISIONING_DPC_DOWNLOADED, setTo)); + } } diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java index bdea679a3311..dad9fe8648b2 100644 --- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java @@ -28,6 +28,7 @@ import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.content.Context; import android.content.Intent; +import android.media.AudioDeviceAttributes; import android.media.AudioManager; import android.media.AudioSystem; import android.media.BluetoothProfileConnectionInfo; @@ -186,8 +187,9 @@ public class AudioDeviceBrokerTest { doNothing().when(mSpySystemServer).broadcastStickyIntentToCurrentProfileGroup( any(Intent.class)); - mSpyDevInventory.setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_WIRED_HEADSET, - AudioService.CONNECTION_STATE_CONNECTED, address, name, caller); + mSpyDevInventory.setWiredDeviceConnectionState(new AudioDeviceAttributes( + AudioSystem.DEVICE_OUT_WIRED_HEADSET, address, name), + AudioService.CONNECTION_STATE_CONNECTED, caller); Thread.sleep(MAX_MESSAGE_HANDLING_DELAY_MS); // Verify that the sticky intent is broadcasted @@ -246,11 +248,11 @@ public class AudioDeviceBrokerTest { */ private void checkSingleSystemConnection(BluetoothDevice btDevice) throws Exception { final String expectedName = btDevice.getName() == null ? "" : btDevice.getName(); + AudioDeviceAttributes expected = new AudioDeviceAttributes( + AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, btDevice.getAddress(), expectedName); verify(mSpyAudioSystem, times(1)).setDeviceConnectionState( - ArgumentMatchers.eq(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP), + ArgumentMatchers.argThat(x -> x.equalTypeAddress(expected)), ArgumentMatchers.eq(AudioSystem.DEVICE_STATE_AVAILABLE), - ArgumentMatchers.eq(btDevice.getAddress()), - ArgumentMatchers.eq(expectedName), anyInt() /*codec*/); } } diff --git a/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java b/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java index 8d706cb960e9..1f355b096335 100644 --- a/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java +++ b/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java @@ -48,11 +48,10 @@ public class NoOpAudioSystemAdapter extends AudioSystemAdapter { //----------------------------------------------------------------- // Overrides of AudioSystemAdapter @Override - public int setDeviceConnectionState(int device, int state, String deviceAddress, - String deviceName, int codecFormat) { - Log.i(TAG, String.format("setDeviceConnectionState(0x%s, %d, %s, %s, 0x%s", - Integer.toHexString(device), state, deviceAddress, deviceName, - Integer.toHexString(codecFormat))); + public int setDeviceConnectionState(AudioDeviceAttributes attributes, int state, + int codecFormat) { + Log.i(TAG, String.format("setDeviceConnectionState(0x%s, %d, 0x%s", + attributes.toString(), state, Integer.toHexString(codecFormat))); return AudioSystem.AUDIO_STATUS_OK; } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java index b6c4bc23f0e4..a9812ab9bb03 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java @@ -62,6 +62,23 @@ import java.util.concurrent.TimeUnit; public class HdmiCecLocalDeviceTvTest { private static final int TIMEOUT_MS = HdmiConfig.TIMEOUT_MS + 1; + private static final String[] SADS_NOT_TO_QUERY = new String[]{ + HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MPEG1, + HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_AAC, + HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DTS, + HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_ATRAC, + HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_ONEBITAUDIO, + HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DDP, + HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DTSHD, + HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_TRUEHD, + HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DST, + HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_WMAPRO, + HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MAX}; + private static final HdmiCecMessage SAD_QUERY = + HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(ADDR_TV, ADDR_AUDIO_SYSTEM, + new int[]{Constants.AUDIO_CODEC_LPCM, Constants.AUDIO_CODEC_DD, + Constants.AUDIO_CODEC_MP3, Constants.AUDIO_CODEC_MPEG2}); + private HdmiControlService mHdmiControlService; private HdmiCecController mHdmiCecController; private HdmiCecLocalDeviceTv mHdmiCecLocalDeviceTv; @@ -136,6 +153,10 @@ public class HdmiCecLocalDeviceTvTest { mNativeWrapper.setPhysicalAddress(mTvPhysicalAddress); mTestLooper.dispatchAll(); mTvLogicalAddress = mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(); + for (String sad : SADS_NOT_TO_QUERY) { + mHdmiControlService.getHdmiCecConfig().setIntValue( + sad, HdmiControlManager.QUERY_SAD_DISABLED); + } mNativeWrapper.clearResultMessages(); } @@ -442,6 +463,7 @@ public class HdmiCecLocalDeviceTvTest { ADDR_TV, ADDR_AUDIO_SYSTEM); assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportArcInitiated); + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(SAD_QUERY); } @Test @@ -463,6 +485,7 @@ public class HdmiCecLocalDeviceTvTest { ADDR_TV, ADDR_AUDIO_SYSTEM); assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportArcInitiated); + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(SAD_QUERY); } @Test @@ -485,6 +508,7 @@ public class HdmiCecLocalDeviceTvTest { ADDR_TV, ADDR_AUDIO_SYSTEM); assertThat(mNativeWrapper.getResultMessages()).contains(reportArcInitiated); + assertThat(mNativeWrapper.getResultMessages()).contains(SAD_QUERY); } @Test diff --git a/services/usb/java/com/android/server/usb/UsbAlsaDevice.java b/services/usb/java/com/android/server/usb/UsbAlsaDevice.java index 85b1de5478e1..337e1f92050c 100644 --- a/services/usb/java/com/android/server/usb/UsbAlsaDevice.java +++ b/services/usb/java/com/android/server/usb/UsbAlsaDevice.java @@ -17,6 +17,7 @@ package com.android.server.usb; import android.annotation.NonNull; +import android.media.AudioDeviceAttributes; import android.media.AudioSystem; import android.media.IAudioService; import android.os.RemoteException; @@ -213,24 +214,25 @@ public final class UsbAlsaDevice { int outputState = (enable && connected) ? 1 : 0; if (outputState != mOutputState) { mOutputState = outputState; - mAudioService.setWiredDeviceConnectionState(device, outputState, - alsaCardDeviceString, - mDeviceName, TAG); + AudioDeviceAttributes attributes = new AudioDeviceAttributes(device, + alsaCardDeviceString, mDeviceName); + mAudioService.setWiredDeviceConnectionState(attributes, outputState, TAG); } } // Input Device if (mHasInput) { - int device = mIsInputHeadset ? AudioSystem.DEVICE_IN_USB_HEADSET + int device = mIsInputHeadset + ? AudioSystem.DEVICE_IN_USB_HEADSET : AudioSystem.DEVICE_IN_USB_DEVICE; boolean connected = isInputJackConnected(); Slog.i(TAG, "INPUT JACK connected: " + connected); int inputState = (enable && connected) ? 1 : 0; if (inputState != mInputState) { mInputState = inputState; - mAudioService.setWiredDeviceConnectionState( - device, inputState, alsaCardDeviceString, - mDeviceName, TAG); + AudioDeviceAttributes attributes = new AudioDeviceAttributes(device, + alsaCardDeviceString, mDeviceName); + mAudioService.setWiredDeviceConnectionState(attributes, inputState, TAG); } } } catch (RemoteException e) { |