Audio routing by strategy SystemApi

  Routing is done per AudioProductStrategy. For instance routing
media is done by using the AudioProductStrategy that supports
the AudioAttributes with USAGE_MEDIA.
  Routing is for a connected device, AudioDeviceInfo, or for on a
device that is not currently connected, uniquely specified by
its role, type and address.
  Preferred routing can be set, removed, queried (get).
  When audioserver crashes, AudioService restores the preferred
routing operating on the audio policy engine.

Bug: 144440677
Test: atest AudioServiceHostTest#PreferredDeviceRoutingTest

Change-Id: I0647608088fe2906e78b71341615975a56747c2f
diff --git a/api/system-current.txt b/api/system-current.txt
index b594eb7..657c7c0 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -3928,16 +3928,19 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioVolumeGroup> getAudioVolumeGroups();
     method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMaxVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
     method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMinVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
+    method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.AudioDeviceAddress getPreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy);
     method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
     method public boolean isAudioServerRunning();
     method public boolean isHdmiSystemAudioSupported();
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int registerAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy);
     method public void registerVolumeGroupCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.VolumeGroupCallback);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean removePreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, @NonNull android.media.AudioAttributes, int, int) throws java.lang.IllegalArgumentException;
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.MODIFY_AUDIO_ROUTING}) public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, @NonNull android.media.AudioAttributes, int, int, android.media.audiopolicy.AudioPolicy) throws java.lang.IllegalArgumentException;
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int requestAudioFocus(@NonNull android.media.AudioFocusRequest, @Nullable android.media.audiopolicy.AudioPolicy);
     method public void setAudioServerStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.AudioServerStateCallback);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setFocusRequestResult(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setPreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull android.media.AudioDeviceAddress);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setVolumeIndexForAttributes(@NonNull android.media.AudioAttributes, int, int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterAudioPolicyAsync(@NonNull android.media.audiopolicy.AudioPolicy);
@@ -4108,9 +4111,11 @@
   }
 
   public final class AudioProductStrategy implements android.os.Parcelable {
+    method @NonNull public static android.media.audiopolicy.AudioProductStrategy createInvalidAudioProductStrategy(int);
     method public int describeContents();
     method @NonNull public android.media.AudioAttributes getAudioAttributes();
     method public int getId();
+    method public boolean supportsAudioAttributes(@NonNull android.media.AudioAttributes);
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.media.audiopolicy.AudioProductStrategy> CREATOR;
   }
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 148b0a2..025de5e 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -152,6 +152,7 @@
                 "android_util_StringBlock.cpp",
                 "android_util_XmlBlock.cpp",
                 "android_util_jar_StrictJarFile.cpp",
+                "android_media_AudioDeviceAddress.cpp",
                 "android_media_AudioEffectDescriptor.cpp",
                 "android_media_AudioRecord.cpp",
                 "android_media_AudioSystem.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 97451a2..d92ab49 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -86,6 +86,7 @@
 extern int register_android_hardware_UsbRequest(JNIEnv *env);
 extern int register_android_hardware_location_ActivityRecognitionHardware(JNIEnv* env);
 
+extern int register_android_media_AudioDeviceAddress(JNIEnv *env);
 extern int register_android_media_AudioEffectDescriptor(JNIEnv *env);
 extern int register_android_media_AudioRecord(JNIEnv *env);
 extern int register_android_media_AudioSystem(JNIEnv *env);
@@ -1510,6 +1511,7 @@
     REG_JNI(register_android_hardware_UsbDeviceConnection),
     REG_JNI(register_android_hardware_UsbRequest),
     REG_JNI(register_android_hardware_location_ActivityRecognitionHardware),
+    REG_JNI(register_android_media_AudioDeviceAddress),
     REG_JNI(register_android_media_AudioEffectDescriptor),
     REG_JNI(register_android_media_AudioSystem),
     REG_JNI(register_android_media_AudioRecord),
diff --git a/core/jni/android_media_AudioDeviceAddress.cpp b/core/jni/android_media_AudioDeviceAddress.cpp
new file mode 100644
index 0000000..5f39f7e
--- /dev/null
+++ b/core/jni/android_media_AudioDeviceAddress.cpp
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "core_jni_helpers.h"
+#include "android_media_AudioDeviceAddress.h"
+#include "android_media_AudioErrors.h"
+
+#include <media/AudioDeviceTypeAddr.h>
+
+using namespace android;
+
+static jclass gAudioDeviceAddressClass;
+static jmethodID gAudioDeviceAddressCstor;
+
+namespace android {
+
+jint createAudioDeviceAddressFromNative(
+        JNIEnv *env, jobject *jAudioDeviceAddress,
+        const AudioDeviceTypeAddr *devTypeAddr) {
+    jint jStatus = (jint)AUDIO_JAVA_SUCCESS;
+    jint jNativeType = (jint)devTypeAddr->mType;
+    ScopedLocalRef<jstring> jAddress(env, env->NewStringUTF(devTypeAddr->mAddress.data()));
+
+    *jAudioDeviceAddress = env->NewObject(gAudioDeviceAddressClass, gAudioDeviceAddressCstor,
+            jNativeType, jAddress.get());
+
+    return jStatus;
+}
+
+}
+
+int register_android_media_AudioDeviceAddress(JNIEnv *env)
+{
+    jclass audioDeviceTypeAddressClass = FindClassOrDie(env, "android/media/AudioDeviceAddress");
+    gAudioDeviceAddressClass = MakeGlobalRefOrDie(env, audioDeviceTypeAddressClass);
+    gAudioDeviceAddressCstor = GetMethodIDOrDie(env, audioDeviceTypeAddressClass, "<init>",
+                                                "(ILjava/lang/String;)V");
+
+    return 0;
+}
diff --git a/core/jni/android_media_AudioDeviceAddress.h b/core/jni/android_media_AudioDeviceAddress.h
new file mode 100644
index 0000000..c66b179
--- /dev/null
+++ b/core/jni/android_media_AudioDeviceAddress.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_MEDIA_AUDIODEVICEADDRESS_H
+#define ANDROID_MEDIA_AUDIODEVICEADDRESS_H
+
+#include <system/audio.h>
+#include <media/AudioDeviceTypeAddr.h>
+
+#include "jni.h"
+
+namespace android {
+
+// Create a Java AudioDeviceAddress instance from a C++ AudioDeviceTypeAddress
+
+extern jint createAudioDeviceAddressFromNative(JNIEnv *env, jobject *jAudioDeviceAddress,
+        const AudioDeviceTypeAddr *devTypeAddr);
+} // namespace android
+
+#endif
\ No newline at end of file
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 01f9d0b0..79cf019 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -26,19 +26,19 @@
 #include <nativehelper/JNIHelp.h>
 #include "core_jni_helpers.h"
 
+#include "android_media_AudioAttributes.h"
+#include "android_media_AudioDeviceAddress.h"
+#include "android_media_AudioEffectDescriptor.h"
+#include "android_media_AudioErrors.h"
+#include "android_media_AudioFormat.h"
+#include "android_media_MicrophoneInfo.h"
 #include <audiomanager/AudioManager.h>
-#include <media/AudioDeviceTypeAddr.h>
-#include <media/AudioSystem.h>
 #include <media/AudioPolicy.h>
+#include <media/AudioSystem.h>
 #include <media/MicrophoneInfo.h>
 #include <nativehelper/ScopedLocalRef.h>
 #include <system/audio.h>
 #include <system/audio_policy.h>
-#include "android_media_AudioEffectDescriptor.h"
-#include "android_media_AudioFormat.h"
-#include "android_media_AudioErrors.h"
-#include "android_media_MicrophoneInfo.h"
-#include "android_media_AudioAttributes.h"
 
 // ----------------------------------------------------------------------------
 
@@ -2254,9 +2254,9 @@
 android_media_AudioSystem_setAudioHalPids(JNIEnv *env, jobject clazz, jintArray jPids)
 {
     if (jPids == NULL) {
-        return (jint)AUDIO_JAVA_BAD_VALUE;
+        return (jint) AUDIO_JAVA_BAD_VALUE;
     }
-    pid_t *nPidsArray = (pid_t *)env->GetIntArrayElements(jPids, NULL);
+    pid_t *nPidsArray = (pid_t *) env->GetIntArrayElements(jPids, NULL);
     std::vector<pid_t> nPids(nPidsArray, nPidsArray + env->GetArrayLength(jPids));
     status_t status = AudioSystem::setAudioHalPids(nPids);
     env->ReleaseIntArrayElements(jPids, nPidsArray, 0);
@@ -2270,6 +2270,48 @@
     return AudioSystem::isCallScreenModeSupported();
 }
 
+static jint
+android_media_AudioSystem_setPreferredDeviceForStrategy(JNIEnv *env, jobject thiz,
+        jint strategy, jint deviceType, jstring deviceAddress) {
+
+    const char *c_address = env->GetStringUTFChars(deviceAddress, NULL);
+    int status = check_AudioSystem_Command(
+            AudioSystem::setPreferredDeviceForStrategy((product_strategy_t) strategy,
+                    AudioDeviceTypeAddr(deviceType, c_address)));
+    env->ReleaseStringUTFChars(deviceAddress, c_address);
+    return (jint) status;
+}
+
+static jint
+android_media_AudioSystem_removePreferredDeviceForStrategy(JNIEnv *env, jobject thiz, jint strategy)
+{
+    return (jint) check_AudioSystem_Command(
+            AudioSystem::removePreferredDeviceForStrategy((product_strategy_t) strategy));
+}
+
+static jint
+android_media_AudioSystem_getPreferredDeviceForStrategy(JNIEnv *env, jobject thiz,
+        jint strategy, jobjectArray jDeviceArray)
+{
+    if (jDeviceArray == nullptr || env->GetArrayLength(jDeviceArray) != 1) {
+        ALOGE("%s invalid array to store AudioDeviceAddress", __FUNCTION__);
+        return (jint)AUDIO_JAVA_BAD_VALUE;
+    }
+
+    AudioDeviceTypeAddr elDevice;
+    status_t status = check_AudioSystem_Command(
+            AudioSystem::getPreferredDeviceForStrategy((product_strategy_t) strategy, elDevice));
+    if (status != NO_ERROR) {
+        return (jint) status;
+    }
+    jobject jAudioDeviceAddress = NULL;
+    jint jStatus = createAudioDeviceAddressFromNative(env, &jAudioDeviceAddress, &elDevice);
+    if (jStatus == AUDIO_JAVA_SUCCESS) {
+        env->SetObjectArrayElement(jDeviceArray, 0, jAudioDeviceAddress);
+    }
+    return jStatus;
+}
+
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod gMethods[] = {
@@ -2350,6 +2392,9 @@
     {"setRttEnabled",       "(Z)I",     (void *)android_media_AudioSystem_setRttEnabled},
     {"setAudioHalPids",  "([I)I", (void *)android_media_AudioSystem_setAudioHalPids},
     {"isCallScreeningModeSupported", "()Z", (void *)android_media_AudioSystem_isCallScreeningModeSupported},
+    {"setPreferredDeviceForStrategy", "(IILjava/lang/String;)I", (void *)android_media_AudioSystem_setPreferredDeviceForStrategy},
+    {"removePreferredDeviceForStrategy", "(I)I", (void *)android_media_AudioSystem_removePreferredDeviceForStrategy},
+    {"getPreferredDeviceForStrategy", "(I[Landroid/media/AudioDeviceAddress;)I", (void *)android_media_AudioSystem_getPreferredDeviceForStrategy},
 };
 
 static const JNINativeMethod gEventHandlerMethods[] = {
diff --git a/media/java/android/media/AudioDeviceAddress.java b/media/java/android/media/AudioDeviceAddress.java
index 415e77d..3d8fc37 100644
--- a/media/java/android/media/AudioDeviceAddress.java
+++ b/media/java/android/media/AudioDeviceAddress.java
@@ -72,10 +72,12 @@
     private final @Role int mRole;
 
     /**
+     * @hide
      * Constructor from a valid {@link AudioDeviceInfo}
      * @param deviceInfo the connected audio device from which to obtain the device-identifying
      *                   type and address.
      */
+    @SystemApi
     public AudioDeviceAddress(@NonNull AudioDeviceInfo deviceInfo) {
         Objects.requireNonNull(deviceInfo);
         mRole = deviceInfo.isSink() ? ROLE_OUTPUT : ROLE_INPUT;
@@ -83,6 +85,14 @@
         mAddress = deviceInfo.getAddress();
     }
 
+    /**
+     * @hide
+     * Constructor from role, device type and address
+     * @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
+     */
+    @SystemApi
     public AudioDeviceAddress(@Role int role, @AudioDeviceInfo.AudioDeviceType int type,
                               @NonNull String address) {
         Objects.requireNonNull(address);
@@ -101,14 +111,38 @@
         mAddress = address;
     }
 
+    /*package*/ AudioDeviceAddress(int nativeType, @NonNull String address) {
+        mRole = (nativeType & AudioSystem.DEVICE_BIT_IN) != 0 ? ROLE_INPUT : ROLE_OUTPUT;
+        mType = AudioDeviceInfo.convertInternalDeviceToDeviceType(nativeType);
+        mAddress = address;
+    }
+
+    /**
+     * @hide
+     * Returns the role of a device
+     * @return the role
+     */
+    @SystemApi
     public @Role int getRole() {
         return mRole;
     }
 
+    /**
+     * @hide
+     * Returns the audio device type of a device
+     * @return the type, as defined in {@link AudioDeviceInfo}
+     */
+    @SystemApi
     public @AudioDeviceInfo.AudioDeviceType int getType() {
         return mType;
     }
 
+    /**
+     * @hide
+     * Returns the address of the audio device, or an empty string for devices without one
+     * @return the device address
+     */
+    @SystemApi
     public @NonNull String getAddress() {
         return mAddress;
     }
diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java
index a39bc51..8293b5f 100644
--- a/media/java/android/media/AudioDeviceInfo.java
+++ b/media/java/android/media/AudioDeviceInfo.java
@@ -155,7 +155,9 @@
             TYPE_TV_TUNER }
     )
     @Retention(RetentionPolicy.SOURCE)
-    public @interface AudioDeviceType {}    /** @hide */
+    public @interface AudioDeviceType {}
+
+    /** @hide */
     @IntDef(flag = false, prefix = "TYPE", value = {
             TYPE_BUILTIN_MIC,
             TYPE_BLUETOOTH_SCO,
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index d552491..fac276c 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -78,6 +78,7 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.TreeMap;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
@@ -1536,6 +1537,76 @@
     }
 
     //====================================================================
+    // Audio Product Strategy routing
+
+    /**
+     * @hide
+     * Set the preferred device for a given strategy, i.e. the audio routing to be used by
+     * this audio strategy. Note that the device may not be available at the time the preferred
+     * device is set, but it will be used once made available.
+     * <p>Use {@link #removePreferredDeviceForStrategy(AudioProductStrategy)} to cancel setting
+     * this preference for this strategy.</p>
+     * @param strategy the audio strategy whose routing will be affected
+     * @param device the audio device to route to when available
+     * @return true if the operation was successful, false otherwise
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public boolean setPreferredDeviceForStrategy(@NonNull AudioProductStrategy strategy,
+            @NonNull AudioDeviceAddress device) {
+        Objects.requireNonNull(strategy);
+        Objects.requireNonNull(device);
+        try {
+            final int status =
+                    getService().setPreferredDeviceForStrategy(strategy.getId(), device);
+            return status == AudioSystem.SUCCESS;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     * Removes the preferred audio device previously set with
+     * {@link #setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAddress)}.
+     * @param strategy the audio strategy whose routing will be affected
+     * @return true if the operation was successful, false otherwise (invalid strategy, or no
+     *     device set for example)
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public boolean removePreferredDeviceForStrategy(@NonNull AudioProductStrategy strategy) {
+        Objects.requireNonNull(strategy);
+        try {
+            final int status =
+                    getService().removePreferredDeviceForStrategy(strategy.getId());
+            return status == AudioSystem.SUCCESS;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     * Return the preferred device for an audio strategy, previously set with
+     * {@link #setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAddress)}
+     * @param strategy the strategy to query
+     * @return the preferred device for that strategy, or null if none was ever set or if the
+     *    strategy is invalid
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public @Nullable AudioDeviceAddress getPreferredDeviceForStrategy(
+            @NonNull AudioProductStrategy strategy) {
+        Objects.requireNonNull(strategy);
+        try {
+            return getService().getPreferredDeviceForStrategy(strategy.getId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    //====================================================================
     // Offload query
     /**
      * Returns whether offloaded playback of an audio format is supported on the device.
@@ -4962,6 +5033,14 @@
      */
     public static final int GET_DEVICES_OUTPUTS   = 0x0002;
 
+    /** @hide */
+    @IntDef(flag = true, prefix = "GET_DEVICES", value = {
+            GET_DEVICES_INPUTS,
+            GET_DEVICES_OUTPUTS }
+    )
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AudioDeviceRole {}
+
     /**
      * Specifies to the {@link AudioManager#getDevices(int)} method to include both
      * source and sink devices.
@@ -4994,7 +5073,7 @@
      * @see #GET_DEVICES_ALL
      * @return A (possibly zero-length) array of AudioDeviceInfo objects.
      */
-    public AudioDeviceInfo[] getDevices(int flags) {
+    public AudioDeviceInfo[] getDevices(@AudioDeviceRole int flags) {
         return getDevicesStatic(flags);
     }
 
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index d64e4ef..066bf25 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -1173,6 +1173,48 @@
      */
     public static native boolean isCallScreeningModeSupported();
 
+    // use case routing by product strategy
+
+    /**
+     * Sets the preferred device to use for a given audio strategy in the audio policy engine
+     * @param strategy the id of the strategy to configure
+     * @param device the device type and address to route to when available
+     * @return {@link #SUCCESS} if successfully set
+     */
+    public static int setPreferredDeviceForStrategy(
+            int strategy, @NonNull AudioDeviceAddress device) {
+        return setPreferredDeviceForStrategy(strategy,
+                AudioDeviceInfo.convertDeviceTypeToInternalDevice(device.getType()),
+                device.getAddress());
+    }
+    /**
+     * Set device routing per product strategy.
+     * @param strategy the id of the strategy to configure
+     * @param deviceType the native device type, NOT AudioDeviceInfo types
+     * @param deviceAddress the address of the device
+     * @return {@link #SUCCESS} if successfully set
+     */
+    private static native int setPreferredDeviceForStrategy(
+            int strategy, int deviceType, String deviceAddress);
+
+    /**
+     * Remove preferred routing for the strategy
+     * @param strategy the id of the strategy to configure
+     * @return {@link #SUCCESS} if successfully removed
+     */
+    public static native int removePreferredDeviceForStrategy(int strategy);
+
+    /**
+     * Query previously set preferred device for a strategy
+     * @param strategy the id of the strategy to query for
+     * @param device an array of size 1 that will contain the preferred device, or null if
+     *               none was set
+     * @return {@link #SUCCESS} if there is a preferred device and it was successfully retrieved
+     *     and written to the array
+     */
+    public static native int getPreferredDeviceForStrategy(int strategy,
+                                                           AudioDeviceAddress[] device);
+
     // Items shared with audio service
 
     /**
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index ef451ce..ad7335e 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -18,6 +18,7 @@
 
 import android.bluetooth.BluetoothDevice;
 import android.media.AudioAttributes;
+import android.media.AudioDeviceAddress;
 import android.media.AudioFocusInfo;
 import android.media.AudioPlaybackConfiguration;
 import android.media.AudioRecordingConfiguration;
@@ -265,6 +266,12 @@
 
     boolean isCallScreeningModeSupported();
 
+    int setPreferredDeviceForStrategy(in int strategy, in AudioDeviceAddress device);
+
+    int removePreferredDeviceForStrategy(in int strategy);
+
+    AudioDeviceAddress getPreferredDeviceForStrategy(in int strategy);
+
     // WARNING: read warning at top of file, new methods that need to be used by native
     // code via IAudioManager.h need to be added to the top section.
 }
diff --git a/media/java/android/media/audiopolicy/AudioProductStrategy.java b/media/java/android/media/audiopolicy/AudioProductStrategy.java
index 2799d46..612f83a 100644
--- a/media/java/android/media/audiopolicy/AudioProductStrategy.java
+++ b/media/java/android/media/audiopolicy/AudioProductStrategy.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.media.AudioAttributes;
 import android.media.AudioSystem;
 import android.media.MediaRecorder;
@@ -82,6 +83,18 @@
 
     /**
      * @hide
+     * Create an invalid AudioProductStrategy instance for testing
+     * @param id the ID for the invalid strategy, always use a different one than in use
+     * @return an invalid instance that cannot successfully be used for volume groups or routing
+     */
+    @TestApi
+    @SystemApi
+    public static @NonNull AudioProductStrategy createInvalidAudioProductStrategy(int id) {
+        return new AudioProductStrategy("dummy strategy", id, new AudioAttributesGroup[0]);
+    }
+
+    /**
+     * @hide
      * @param streamType to match against AudioProductStrategy
      * @return the AudioAttributes for the first strategy found with the associated stream type
      *          If no match is found, returns AudioAttributes with unknown content_type and usage
@@ -222,6 +235,7 @@
      * @return true if the {@link AudioProductStrategy} supports the given {@link AudioAttributes},
      *         false otherwise.
      */
+    @SystemApi
     public boolean supportsAudioAttributes(@NonNull AudioAttributes aa) {
         Preconditions.checkNotNull(aa, "AudioAttributes must not be null");
         for (final AudioAttributesGroup aag : mAudioAttributesGroups) {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 6010b1dc..8144a71 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -24,6 +24,7 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.media.AudioDeviceAddress;
 import android.media.AudioManager;
 import android.media.AudioRoutesInfo;
 import android.media.AudioSystem;
@@ -400,6 +401,15 @@
         }
     }
 
+    /*package*/ int setPreferredDeviceForStrategySync(int strategy,
+                                                      @NonNull AudioDeviceAddress device) {
+        return mDeviceInventory.setPreferredDeviceForStrategySync(strategy, device);
+    }
+
+    /*package*/ int removePreferredDeviceForStrategySync(int strategy) {
+        return mDeviceInventory.removePreferredDeviceForStrategySync(strategy);
+    }
+
     //---------------------------------------------------------------------
     // Communication with (to) AudioService
     //TODO check whether the AudioService methods are candidates to move here
@@ -533,6 +543,15 @@
         sendLMsgNoDelay(MSG_L_SCOCLIENT_DIED, SENDMSG_QUEUE, obj);
     }
 
+    /*package*/ void postSaveSetPreferredDeviceForStrategy(int strategy, AudioDeviceAddress device)
+    {
+        sendILMsgNoDelay(MSG_IL_SAVE_PREF_DEVICE_FOR_STRATEGY, SENDMSG_QUEUE, strategy, device);
+    }
+
+    /*package*/ void postSaveRemovePreferredDeviceForStrategy(int strategy) {
+        sendIMsgNoDelay(MSG_I_SAVE_REMOVE_PREF_DEVICE_FOR_STRATEGY, SENDMSG_QUEUE, strategy);
+    }
+
     //---------------------------------------------------------------------
     // Method forwarding between the helper classes (BtHelper, AudioDeviceInventory)
     // only call from a "handle"* method or "on"* method
@@ -631,6 +650,7 @@
         } else {
             pw.println("Message handler is null");
         }
+        mDeviceInventory.dump(pw, prefix);
     }
 
     //---------------------------------------------------------------------
@@ -890,6 +910,15 @@
                                 info.mDevice, info.mState, info.mSupprNoisy, info.mMusicDevice);
                     }
                 } break;
+                case MSG_IL_SAVE_PREF_DEVICE_FOR_STRATEGY: {
+                    final int strategy = msg.arg1;
+                    final AudioDeviceAddress device = (AudioDeviceAddress) msg.obj;
+                    mDeviceInventory.onSaveSetPreferredDevice(strategy, device);
+                } break;
+                case MSG_I_SAVE_REMOVE_PREF_DEVICE_FOR_STRATEGY: {
+                    final int strategy = msg.arg1;
+                    mDeviceInventory.onSaveRemovePreferredDevice(strategy);
+                } break;
                 default:
                     Log.wtf(TAG, "Invalid message " + msg.what);
             }
@@ -941,6 +970,8 @@
     private static final int MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT = 31;
     // a ScoClient died in BtHelper
     private static final int MSG_L_SCOCLIENT_DIED = 32;
+    private static final int MSG_IL_SAVE_PREF_DEVICE_FOR_STRATEGY = 33;
+    private static final int MSG_I_SAVE_REMOVE_PREF_DEVICE_FOR_STRATEGY = 34;
 
 
     private static boolean isMessageHandledUnderWakelock(int msgId) {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 37add3d..661451b 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -23,6 +23,7 @@
 import android.bluetooth.BluetoothHearingAid;
 import android.bluetooth.BluetoothProfile;
 import android.content.Intent;
+import android.media.AudioDeviceAddress;
 import android.media.AudioDevicePort;
 import android.media.AudioFormat;
 import android.media.AudioManager;
@@ -35,6 +36,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.Slog;
@@ -42,6 +44,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
@@ -60,6 +63,9 @@
     // Key for map created from DeviceInfo.makeDeviceListKey()
     private final LinkedHashMap<String, DeviceInfo> mConnectedDevices = new LinkedHashMap<>();
 
+    // List of preferred devices for strategies
+    private final ArrayMap<Integer, AudioDeviceAddress> mPreferredDevices = new ArrayMap<>();
+
     private @NonNull AudioDeviceBroker mDeviceBroker;
 
     // cache of the address of the last dock the device was connected to
@@ -140,12 +146,20 @@
     }
 
     //------------------------------------------------------------
+    /*package*/ void dump(PrintWriter pw, String prefix) {
+        pw.println("\n" + prefix + "Preferred devices for strategy:");
+        mPreferredDevices.forEach((strategy, device) -> {
+            pw.println("  " + prefix + "strategy:" + strategy + " device:" + device); });
+    }
+
+    //------------------------------------------------------------
     // Message handling from AudioDeviceBroker
 
     /**
      * Restore previously connected devices. Use in case of audio server crash
      * (see AudioService.onAudioServerDied() method)
      */
+    // Always executed on AudioDeviceBroker message queue
     /*package*/ void onRestoreDevices() {
         synchronized (mConnectedDevices) {
             for (DeviceInfo di : mConnectedDevices.values()) {
@@ -157,6 +171,11 @@
                         di.mDeviceCodecFormat);
             }
         }
+
+        synchronized (mPreferredDevices) {
+            mPreferredDevices.forEach((strategy, device) -> {
+                AudioSystem.setPreferredDeviceForStrategy(strategy, device); });
+        }
     }
 
     // only public for mocking/spying
@@ -431,9 +450,41 @@
                     "android"); // reconnect
         }
     }
+
+    /*package*/ void onSaveSetPreferredDevice(int strategy, @NonNull AudioDeviceAddress device) {
+        mPreferredDevices.put(strategy, device);
+    }
+
+    /*package*/ void onSaveRemovePreferredDevice(int strategy) {
+        mPreferredDevices.remove(strategy);
+    }
+
     //------------------------------------------------------------
     //
 
+    /*package*/ int setPreferredDeviceForStrategySync(int strategy,
+                                                      @NonNull AudioDeviceAddress device) {
+        final long identity = Binder.clearCallingIdentity();
+        final int status = AudioSystem.setPreferredDeviceForStrategy(strategy, device);
+        Binder.restoreCallingIdentity(identity);
+
+        if (status == AudioSystem.SUCCESS) {
+            mDeviceBroker.postSaveSetPreferredDeviceForStrategy(strategy, device);
+        }
+        return status;
+    }
+
+    /*package*/ int removePreferredDeviceForStrategySync(int strategy) {
+        final long identity = Binder.clearCallingIdentity();
+        final int status = AudioSystem.removePreferredDeviceForStrategy(strategy);
+        Binder.restoreCallingIdentity(identity);
+
+        if (status == AudioSystem.SUCCESS) {
+            mDeviceBroker.postSaveRemovePreferredDeviceForStrategy(strategy);
+        }
+        return status;
+    }
+
     /**
      * Implements the communication with AudioSystem to (dis)connect a device in the native layers
      * @param connect true if connection
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 4bf1de6..1a62eb2 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -63,6 +63,8 @@
 import android.hardware.usb.UsbManager;
 import android.hidl.manager.V1_0.IServiceManager;
 import android.media.AudioAttributes;
+import android.media.AudioDeviceAddress;
+import android.media.AudioDeviceInfo;
 import android.media.AudioFocusInfo;
 import android.media.AudioFocusRequest;
 import android.media.AudioFormat;
@@ -1633,6 +1635,60 @@
     ///////////////////////////////////////////////////////////////////////////
     // IPC methods
     ///////////////////////////////////////////////////////////////////////////
+    /** @see AudioManager#setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceInfo) */
+    public int setPreferredDeviceForStrategy(int strategy, AudioDeviceAddress device) {
+        if (device == null) {
+            return AudioSystem.ERROR;
+        }
+        enforceModifyAudioRoutingPermission();
+        final String logString = String.format(
+                "setPreferredDeviceForStrategy u/pid:%d/%d strat:%d dev:%s",
+                Binder.getCallingUid(), Binder.getCallingPid(), strategy, device.toString());
+        sDeviceLogger.log(new AudioEventLogger.StringEvent(logString).printLog(TAG));
+        if (device.getRole() == AudioDeviceAddress.ROLE_INPUT) {
+            Log.e(TAG, "Unsupported input routing in " + logString);
+            return AudioSystem.ERROR;
+        }
+
+        final int status = mDeviceBroker.setPreferredDeviceForStrategySync(strategy, device);
+        if (status != AudioSystem.SUCCESS) {
+            Log.e(TAG, String.format("Error %d in %s)", status, logString));
+        }
+
+        return status;
+    }
+
+    /** @see AudioManager#removePreferredDeviceForStrategy(AudioProductStrategy) */
+    public int removePreferredDeviceForStrategy(int strategy) {
+        enforceModifyAudioRoutingPermission();
+        final String logString =
+                String.format("removePreferredDeviceForStrategy strat:%d", strategy);
+        sDeviceLogger.log(new AudioEventLogger.StringEvent(logString).printLog(TAG));
+
+        final int status = mDeviceBroker.removePreferredDeviceForStrategySync(strategy);
+        if (status != AudioSystem.SUCCESS) {
+            Log.e(TAG, String.format("Error %d in %s)", status, logString));
+        }
+        return status;
+    }
+
+    /** @see AudioManager#getPreferredDeviceForStrategy(AudioProductStrategy) */
+    public AudioDeviceAddress getPreferredDeviceForStrategy(int strategy) {
+        enforceModifyAudioRoutingPermission();
+        AudioDeviceAddress[] devices = new AudioDeviceAddress[1];
+        final long identity = Binder.clearCallingIdentity();
+        final int status = AudioSystem.getPreferredDeviceForStrategy(strategy, devices);
+        Binder.restoreCallingIdentity(identity);
+        if (status != AudioSystem.SUCCESS) {
+            Log.e(TAG, String.format("Error %d in getPreferredDeviceForStrategy(%d)",
+                    status, strategy));
+            return null;
+        } else {
+            return devices[0];
+        }
+    }
+
+
     /** @see AudioManager#adjustVolume(int, int) */
     public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
             String callingPackage, String caller) {