diff --git a/AudioRotationMonitor/Android.mk b/AudioRotationMonitor/Android.mk
new file mode 100644
index 0000000..992c9d9
--- /dev/null
+++ b/AudioRotationMonitor/Android.mk
@@ -0,0 +1,17 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := AudioRotationMonitor
+LOCAL_CERTIFICATE := platform
+LOCAL_PRIVATE_PLATFORM_APIS := true
+
+LOCAL_PROGUARD_FLAG_FILES := proguard.flags
+
+include $(BUILD_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/AudioRotationMonitor/AndroidManifest.xml b/AudioRotationMonitor/AndroidManifest.xml
new file mode 100644
index 0000000..e179d13
--- /dev/null
+++ b/AudioRotationMonitor/AndroidManifest.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/AudioRotationMonitor/proguard.flags b/AudioRotationMonitor/proguard.flags
new file mode 100644
index 0000000..bc0051c
--- /dev/null
+++ b/AudioRotationMonitor/proguard.flags
@@ -0,0 +1,3 @@
+-keep class org.lineageos.audiorotationmonitor.* {
+ *;
+}
diff --git a/AudioRotationMonitor/service/Android.mk b/AudioRotationMonitor/service/Android.mk
new file mode 100644
index 0000000..6b21877
--- /dev/null
+++ b/AudioRotationMonitor/service/Android.mk
@@ -0,0 +1,14 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := set-audio-rotation
+LOCAL_MODULE_TAGS := optional
+LOCAL_INIT_RC := set-audio-rotation.rc
+LOCAL_SRC_FILES := \
+ set-audio-rotation.cpp
+LOCAL_SHARED_LIBRARIES := \
+ libbase \
+ libtinyalsa
+
+include $(BUILD_EXECUTABLE)
diff --git a/AudioRotationMonitor/service/set-audio-rotation.cpp b/AudioRotationMonitor/service/set-audio-rotation.cpp
new file mode 100644
index 0000000..69f9b09
--- /dev/null
+++ b/AudioRotationMonitor/service/set-audio-rotation.cpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2019 The LineageOS 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.
+ */
+#define LOG_TAG "set-audio-rotation"
+
+#include
+#include
+
+constexpr int SLOT_POSITIONS_0[] = { 0, 1, 0, 1 };
+constexpr int SLOT_POSITIONS_90[] = { 1, 1, 0, 0 };
+constexpr int SLOT_POSITIONS_180[] = { 1, 0, 1, 0 };
+constexpr int SLOT_POSITIONS_270[] = { 0, 0, 1, 1 };
+
+void setMixerValueByName(mixer *mixer, const char *name, int value) {
+ const auto ctl = mixer_get_ctl_by_name(mixer, name);
+
+ if (ctl == nullptr) {
+ LOG(ERROR) << "Failed to find mixer ctl for " << name;
+ return;
+ }
+
+ if (mixer_ctl_set_value(ctl, 0, value) < 0) {
+ LOG(ERROR) << "Failed to set ctl value " << value << " for " << name;
+ return;
+ }
+}
+
+void setSlotPositions(const int *values) {
+ const auto mixer = mixer_open(0);
+
+ if (mixer == nullptr) {
+ LOG(ERROR) << "Failed to open mixer";
+ return;
+ }
+
+ setMixerValueByName(mixer, "ExtSPK LL TDM_ADC_SEL", values[0]);
+ setMixerValueByName(mixer, "ExtSPK LR TDM_ADC_SEL", values[1]);
+ setMixerValueByName(mixer, "ExtSPK UL TDM_ADC_SEL", values[2]);
+ setMixerValueByName(mixer, "ExtSPK UR TDM_ADC_SEL", values[3]);
+
+ setMixerValueByName(mixer, "ExtSPK LL TDM_DAC_SEL", values[0]);
+ setMixerValueByName(mixer, "ExtSPK LR TDM_DAC_SEL", values[1]);
+ setMixerValueByName(mixer, "ExtSPK UL TDM_DAC_SEL", values[2]);
+ setMixerValueByName(mixer, "ExtSPK UR TDM_DAC_SEL", values[3]);
+
+ mixer_close(mixer);
+};
+
+int main(int argc, char **argv) {
+ if (argc != 2) {
+ return -1;
+ }
+
+ if (strcmp(argv[1], "0") == 0) {
+ setSlotPositions(SLOT_POSITIONS_0);
+ } else if (strcmp(argv[1], "1") == 0) {
+ setSlotPositions(SLOT_POSITIONS_90);
+ } else if (strcmp(argv[1], "2") == 0) {
+ setSlotPositions(SLOT_POSITIONS_180);
+ } else if (strcmp(argv[1], "3") == 0) {
+ setSlotPositions(SLOT_POSITIONS_270);
+ } else {
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/AudioRotationMonitor/service/set-audio-rotation.rc b/AudioRotationMonitor/service/set-audio-rotation.rc
new file mode 100644
index 0000000..5dd4329
--- /dev/null
+++ b/AudioRotationMonitor/service/set-audio-rotation.rc
@@ -0,0 +1,2 @@
+on property:sys.audio.rotation=*
+ exec - root audio -- /system/bin/set-audio-rotation ${sys.audio.rotation}
diff --git a/AudioRotationMonitor/src/org/lineageos/audiorotationmonitor/BootCompletedReceiver.java b/AudioRotationMonitor/src/org/lineageos/audiorotationmonitor/BootCompletedReceiver.java
new file mode 100644
index 0000000..3ef3d76
--- /dev/null
+++ b/AudioRotationMonitor/src/org/lineageos/audiorotationmonitor/BootCompletedReceiver.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.lineageos.audiorotationmonitor;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+public class BootCompletedReceiver extends BroadcastReceiver {
+ private static final String TAG = "AudioRotationMonitor";
+
+ @Override
+ public void onReceive(final Context context, Intent intent) {
+ Log.d(TAG, "Starting");
+ context.startService(new Intent(context, DisplayListenerService.class));
+ }
+}
diff --git a/AudioRotationMonitor/src/org/lineageos/audiorotationmonitor/DisplayListener.java b/AudioRotationMonitor/src/org/lineageos/audiorotationmonitor/DisplayListener.java
new file mode 100644
index 0000000..d4d28d9
--- /dev/null
+++ b/AudioRotationMonitor/src/org/lineageos/audiorotationmonitor/DisplayListener.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.lineageos.audiorotationmonitor;
+
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.os.Handler;
+import android.os.SystemProperties;
+import android.util.Log;
+import android.view.Surface;
+import android.view.WindowManager;
+
+public class DisplayListener implements DisplayManager.DisplayListener {
+ private static final boolean DEBUG = true;
+ private static final String TAG = "DisplayListener";
+
+ private static final String AUDIO_ROTATION_PROP = "sys.audio.rotation";
+
+ private Context mContext;
+ private Handler mHandler;
+
+ private DisplayManager mDisplayManager;
+ private WindowManager mWindowManager;
+
+ private final Object mRotationLock = new Object();
+ private int mDeviceRotation = Surface.ROTATION_0;
+
+ public DisplayListener(Context context) {
+ mContext = context;
+ mHandler = new Handler();
+
+ mDisplayManager = mContext.getSystemService(DisplayManager.class);
+ mWindowManager = mContext.getSystemService(WindowManager.class);
+ }
+
+ @Override
+ public void onDisplayAdded(int displayId) {
+ if (DEBUG) Log.d(TAG, "onDisplayAdded");
+ }
+
+ @Override
+ public void onDisplayRemoved(int displayId) {
+ if (DEBUG) Log.d(TAG, "onDisplayRemoved");
+ }
+
+ @Override
+ public void onDisplayChanged(int displayId) {
+ if (DEBUG) Log.d(TAG, "onDisplayChanged");
+ updateOrientation();
+ }
+
+ private void updateOrientation() {
+ // Even though we're responding to device orientation events,
+ // use display rotation so audio stays in sync with video/dialogs
+ int newRotation = mWindowManager.getDefaultDisplay().getRotation();
+
+ synchronized (mRotationLock) {
+ if (newRotation != mDeviceRotation) {
+ mDeviceRotation = newRotation;
+ SystemProperties.set(AUDIO_ROTATION_PROP, String.valueOf(mDeviceRotation));
+ }
+ }
+ }
+
+ public void enable() {
+ if (DEBUG) Log.d(TAG, "Enabling");
+ mDisplayManager.registerDisplayListener(this, mHandler);
+ updateOrientation();
+ }
+
+ public void disable() {
+ if (DEBUG) Log.d(TAG, "Disabling");
+ mDisplayManager.unregisterDisplayListener(this);
+ }
+}
diff --git a/AudioRotationMonitor/src/org/lineageos/audiorotationmonitor/DisplayListenerService.java b/AudioRotationMonitor/src/org/lineageos/audiorotationmonitor/DisplayListenerService.java
new file mode 100644
index 0000000..0897db7
--- /dev/null
+++ b/AudioRotationMonitor/src/org/lineageos/audiorotationmonitor/DisplayListenerService.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.lineageos.audiorotationmonitor;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.util.Log;
+
+public class DisplayListenerService extends Service {
+ private static final String TAG = "DisplayListenerService";
+ private static final boolean DEBUG = true;
+
+ private DisplayListener mDisplayListener;
+
+ @Override
+ public void onCreate() {
+ if (DEBUG) Log.d(TAG, "Creating service");
+ mDisplayListener = new DisplayListener(this);
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ if (DEBUG) Log.d(TAG, "Starting service");
+ mDisplayListener.enable();
+ return START_STICKY;
+ }
+
+ @Override
+ public void onDestroy() {
+ if (DEBUG) Log.d(TAG, "Destroying service");
+ mDisplayListener.disable();
+ super.onDestroy();
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+}
diff --git a/gts3l.mk b/gts3l.mk
index f53475b..0d1445f 100755
--- a/gts3l.mk
+++ b/gts3l.mk
@@ -101,7 +101,9 @@ PRODUCT_PACKAGES += \
libqcomvoiceprocessing \
libvolumelistener \
tinymix \
- libaudioprimary_shim
+ libaudioprimary_shim \
+ AudioRotationMonitor \
+ set-audio-rotation
PRODUCT_COPY_FILES += \
$(LOCAL_PATH)/audio/audio/audio_policy_configuration.xml:$(TARGET_COPY_OUT_VENDOR)/etc/audio/audio_policy_configuration.xml \
diff --git a/sepolicy/file_contexts b/sepolicy/file_contexts
index 1753b9a..203118b 100644
--- a/sepolicy/file_contexts
+++ b/sepolicy/file_contexts
@@ -17,6 +17,7 @@
# Binaries
/(vendor|system/vendor)/bin/hw/macloader u:object_r:macloader_exec:s0
/(vendor|system/vendor)/bin/secril_config_svc u:object_r:secril_config_svc_exec:s0
+/system/bin/set-audio-rotation u:object_r:set-audio-rotation_exec:s0
# Data files
/data/camera(/.*)? u:object_r:camera_socket:s0
diff --git a/sepolicy/property_contexts b/sepolicy/property_contexts
index fb644a9..ae769cc 100644
--- a/sepolicy/property_contexts
+++ b/sepolicy/property_contexts
@@ -32,6 +32,7 @@ ro.vendor.camera.wrapper.hal3TrebleMinorVersion u:object_r:sec_camera_prop:s0
ro.vendor.multisim. u:object_r:vendor_radio_prop:s0
ro.vendor.radio. u:object_r:vendor_radio_prop:s0
service.camera. u:object_r:sec_camera_prop:s0
+sys.audio.rotation u:object_r:exported_system_prop:s0
sys.cameramode. u:object_r:sec_camera_prop:s0
system.camera.CC. u:object_r:sec_camera_prop:s0
vendor.bluetooth_fw_ver u:object_r:vendor_bluetooth_prop:s0
diff --git a/sepolicy/set-audio-rotation.te b/sepolicy/set-audio-rotation.te
new file mode 100644
index 0000000..14410e5
--- /dev/null
+++ b/sepolicy/set-audio-rotation.te
@@ -0,0 +1,8 @@
+type set-audio-rotation, domain, coredomain;
+type set-audio-rotation_exec, exec_type, file_type;
+
+init_daemon_domain(set-audio-rotation)
+
+# Allow set-audio-rotation to read and write to audio_device
+allow set-audio-rotation audio_device:dir r_dir_perms;
+allow set-audio-rotation audio_device:chr_file rw_file_perms;