Adding settings for sound

Test: manually verified
Change-Id: Ia2433830eb5241a0a948e91ff22008ebd87ec728
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index f799255..090712a 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -167,5 +167,22 @@
                 android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
                 android:value="true"/>
         </activity>
+
+        <activity
+          android:name=".sound.SoundSettingsActivity"
+          android:label="@string/sound_settings"
+          android:theme="@style/CarSettingTheme"
+          android:icon="@drawable/ic_settings_sound">
+            <intent-filter android:priority="1">
+                <action android:name="com.android.settings.action.SETTINGS"/>
+            </intent-filter>
+
+            <meta-data
+              android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
+              android:value="true"/>
+            <meta-data
+              android:name="com.android.settings.category"
+              android:value="com.android.settings.category.ia.homepage"/>
+        </activity>
     </application>
 </manifest>
diff --git a/res/drawable/ic_settings_sound.xml b/res/drawable/ic_settings_sound.xml
new file mode 100644
index 0000000..18c6aa1
--- /dev/null
+++ b/res/drawable/ic_settings_sound.xml
@@ -0,0 +1,25 @@
+<!--
+    Copyright (C) 2017 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24.0dp"
+        android:height="24.0dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0"
+        android:tint="?android:attr/colorControlNormal">
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M3.0,9.0l0.0,6.0l4.0,0.0l5.0,5.0L12.0,4.0L7.0,9.0L3.0,9.0zm13.5,3.0c0.0,-1.77 -1.02,-3.29 -2.5,-4.03l0.0,8.05c1.48,-0.73 2.5,-2.25 2.5,-4.02zM14.0,3.23l0.0,2.06c2.8,0.86 5.0,3.54 5.0,6.71s-2.11,5.85 -5.0,6.71l0.0,2.06c4.01,-0.91 7.0,-4.49 7.0,-8.77s-2.99,-7.86 -7.0,-8.77z"/>
+</vector>
diff --git a/res/layout/list_item.xml b/res/layout/list_item.xml
index dd147ff..59a0142 100644
--- a/res/layout/list_item.xml
+++ b/res/layout/list_item.xml
@@ -20,12 +20,12 @@
     android:layout_height="wrap_content">
     <ImageView
         android:id="@+id/icon"
-        android:layout_width="@dimen/tile_icon_size"
-        android:layout_height="@dimen/tile_icon_size"
+        android:layout_width="@dimen/car_list_item_small_icon_size"
+        android:layout_height="@dimen/car_list_item_small_icon_size"
         android:layout_alignParentStart="true"
         android:layout_centerVertical="true"
-        android:layout_marginStart="@dimen/key_line1"
-        android:layout_marginEnd="@dimen/key_line1" />
+        android:layout_marginStart="@dimen/car_list_item_icon_right_margin"
+        android:layout_marginEnd="@dimen/car_list_item_icon_right_margin" />
     <LinearLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
@@ -47,12 +47,12 @@
     </LinearLayout>
     <ImageButton
         android:id="@+id/action"
-        android:layout_width="@dimen/tile_icon_size"
-        android:layout_height="@dimen/tile_icon_size"
+        android:layout_width="@dimen/car_list_item_right_icon_size"
+        android:layout_height="@dimen/car_list_item_right_icon_size"
         android:layout_alignParentEnd="true"
         android:layout_centerVertical="true"
         android:src="@drawable/ic_settings_gear"
         android:background="@null"
         android:visibility="gone"
-        android:layout_marginEnd="@dimen/key_line1" />
+        android:layout_marginEnd="@dimen/car_list_item_right_icon_margin" />
 </RelativeLayout>
\ No newline at end of file
diff --git a/res/layout/tile_item.xml b/res/layout/tile_item.xml
index 277be6e..afcf3b2 100644
--- a/res/layout/tile_item.xml
+++ b/res/layout/tile_item.xml
@@ -27,11 +27,11 @@
 
     <ImageView
             android:id="@+id/icon"
-            android:layout_width="@dimen/dashboard_tile_image_size"
-            android:layout_height="@dimen/dashboard_tile_image_size"
+            android:layout_width="@dimen/car_list_item_small_icon_size"
+            android:layout_height="@dimen/car_list_item_small_icon_size"
             android:scaleType="fitCenter"
-            android:layout_marginStart="@dimen/key_line1"
-            android:layout_marginEnd="@dimen/key_line1" />
+            android:layout_marginStart="@dimen/car_list_item_icon_right_margin"
+            android:layout_marginEnd="@dimen/car_list_item_icon_right_margin" />
 
     <LinearLayout
             android:layout_width="match_parent"
@@ -56,6 +56,6 @@
                   android:gravity="center_vertical"
                   android:maxLines="1"
                   android:ellipsize="end"
-                  android:paddingEnd="@dimen/key_line1" />
+                  android:paddingEnd="@dimen/car_list_item_right_icon_margin" />
     </LinearLayout>
 </LinearLayout>
diff --git a/res/layout/volume_controller_view.xml b/res/layout/volume_controller_view.xml
new file mode 100644
index 0000000..3b9b5d8
--- /dev/null
+++ b/res/layout/volume_controller_view.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 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
+  -->
+
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+    <ImageView
+        android:id="@+id/icon"
+        android:layout_width="@dimen/car_list_item_small_icon_size"
+        android:layout_height="@dimen/car_list_item_small_icon_size"
+        android:layout_alignParentStart="true"
+        android:layout_centerVertical="true"
+        android:layout_marginStart="@dimen/car_list_item_icon_right_margin"
+        android:layout_marginEnd="@dimen/car_list_item_icon_right_margin" />
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:paddingTop="@dimen/tile_top_bottom_padding"
+        android:paddingBottom="@dimen/tile_top_bottom_padding"
+        android:layout_toEndOf="@+id/icon" >
+        <TextView
+          android:id="@+id/stream_name"
+          android:layout_width="match_parent"
+          android:layout_height="wrap_content"
+          android:textSize="@dimen/medium_text_size" />
+        <SeekBar
+          android:id="@+id/seekbar"
+          android:layout_width="match_parent"
+          android:layout_height="wrap_content"/>
+    </LinearLayout>
+</RelativeLayout>
\ No newline at end of file
diff --git a/res/layout/volume_list.xml b/res/layout/volume_list.xml
new file mode 100644
index 0000000..57c6e1d
--- /dev/null
+++ b/res/layout/volume_list.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 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
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical" >
+    <include
+        android:id="@+id/media_volume"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        layout="@layout/volume_controller_view" />
+    <include
+        android:id="@+id/ring_volume"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        layout="@layout/volume_controller_view" />
+</LinearLayout>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 6144915..0019e98 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -105,6 +105,22 @@
     <!-- Bluetooth settings: The sub heading for available devices during and after scanning. [CHAR LIMIT=40] -->
     <string name="bluetooth_preference_found_devices">Available devices</string>
 
+    <!-- sound settings -->
+    <!-- Sound settings screen heading -->
+    <string name="sound_settings">Sound</string>
+    <!-- Sound settings screen, setting option name -->
+    <string name="ring_volume_title">Ring volume</string>
+    <!-- Sound settings screen, the title of the volume bar to adjust the incoming call volume -->
+    <string name="incoming_call_volume_title">Ringtone</string>
+    <!-- Sound settings screen, the title of the volume bar to adjust the notification volume -->
+    <string name="notification_volume_title">Notification</string>
+    <!-- Sound settings screen, setting option name -->
+    <string name="media_volume_title">Media</string>
+    <!-- Sound settings screen, setting option summary text -->
+    <string name="media_volume_summary">Set volume for music and videos</string>
+    <!-- Sound settings screen, alarm volume slider title -->
+    <string name="alarm_volume_title">Alarm</string>
+
     <!-- generic -->
     <!-- Button label for generic forget action [CHAR LIMIT=20] -->
     <string name="forget">Forget</string>
diff --git a/src/com/android/car/settings/sound/SoundSettingsActivity.java b/src/com/android/car/settings/sound/SoundSettingsActivity.java
new file mode 100644
index 0000000..5e13185
--- /dev/null
+++ b/src/com/android/car/settings/sound/SoundSettingsActivity.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.car.settings.sound;
+
+import android.car.Car;
+import android.content.ComponentName;
+import android.content.ServiceConnection;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.view.View;
+
+import com.android.car.settings.CarSettingActivity;
+import com.android.car.settings.R;
+
+/**
+ * Activity hosts sound related settings.
+ */
+public class SoundSettingsActivity extends CarSettingActivity {
+    private static final String TAG = "SoundSettingsActivity";
+    private Car mCar;
+
+    private VolumeControllerPresenter mMediaVolumeControllerPresenter;
+    private VolumeControllerPresenter mRingVolumeControllerPresenter;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        showMenuIcon();
+        setContentView(R.layout.volume_list);
+
+        View mediaVolumeControllerView = findViewById(
+                R.id.media_volume);
+        View ringVolumeControllerView = findViewById(
+                R.id.ring_volume);
+        mMediaVolumeControllerPresenter = new VolumeControllerPresenter(
+                this /* context*/,
+                mediaVolumeControllerView,
+                AudioManager.STREAM_MUSIC,
+                null /* Uri sampleUri */,
+                R.string.media_volume_title,
+                com.android.internal.R.drawable.ic_audio_media);
+        mRingVolumeControllerPresenter = new VolumeControllerPresenter(
+                this /* context*/,
+                ringVolumeControllerView,
+                AudioManager.STREAM_RING,
+                null /* Uri sampleUri */,
+                R.string.ring_volume_title,
+                com.android.internal.R.drawable.ic_audio_ring_notif);
+        mCar = Car.createCar(this /* context */, mServiceConnection);
+    }
+
+    ServiceConnection mServiceConnection = new ServiceConnection() {
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            mMediaVolumeControllerPresenter.onServiceConnected(mCar);
+            mRingVolumeControllerPresenter.onServiceConnected(mCar);
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            mMediaVolumeControllerPresenter.onServiceDisconnected();
+            mRingVolumeControllerPresenter.onServiceDisconnected();
+        }
+    };
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        mCar.connect();
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        mMediaVolumeControllerPresenter.stop();
+        mRingVolumeControllerPresenter.stop();
+        mCar.disconnect();
+    }
+}
diff --git a/src/com/android/car/settings/sound/VolumeControllerPresenter.java b/src/com/android/car/settings/sound/VolumeControllerPresenter.java
new file mode 100644
index 0000000..cd7a2a5
--- /dev/null
+++ b/src/com/android/car/settings/sound/VolumeControllerPresenter.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.car.settings.sound;
+
+import android.car.Car;
+import android.car.CarNotConnectedException;
+import android.car.media.CarAudioManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ServiceConnection;
+import android.media.AudioManager;
+import android.media.IVolumeController;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.SeekBar;
+import android.widget.SeekBar.OnSeekBarChangeListener;
+import android.widget.TextView;
+
+import com.android.car.settings.R;
+
+/**
+ * Contains logic about volume controller UI.
+ */
+public class VolumeControllerPresenter implements OnSeekBarChangeListener {
+
+    private static final String TAG = "SeekBarVolumizer";
+    private static final int AUDIO_FEEDBACK_DELAY_MS = 1500;
+
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+    private final SeekBar mSeekBar;
+    private final int mStreamType;
+    private final Ringtone mRingtone;
+    private final VolumnCallback mVolumeCallback = new VolumnCallback();
+
+    private CarAudioManager mCarAudioManager;
+
+    public void onServiceConnected(Car car) {
+        try {
+            mCarAudioManager = (CarAudioManager) car.getCarManager(Car.AUDIO_SERVICE);
+            mCarAudioManager.setVolumeController(mVolumeCallback);
+            mSeekBar.setMax(mCarAudioManager.getStreamMaxVolume(mStreamType));
+            mSeekBar.setProgress(mCarAudioManager.getStreamVolume(mStreamType));
+            mSeekBar.setOnSeekBarChangeListener(VolumeControllerPresenter.this);
+        } catch (CarNotConnectedException e) {
+            Log.e(TAG, "Car is not connected!", e);
+        }
+    }
+
+    public void onServiceDisconnected() {
+        mSeekBar.setOnSeekBarChangeListener(null);
+        mCarAudioManager = null;
+    }
+
+    public VolumeControllerPresenter(Context context, View volumeControllerView,
+            int streamType, Uri sampleUri, int titleStringResId, int iconResId) {
+        mSeekBar = (SeekBar) volumeControllerView.findViewById(R.id.seekbar);
+        mStreamType = streamType;
+        Uri ringtoneUri;
+
+        if (sampleUri == null) {
+            switch (mStreamType) {
+                case AudioManager.STREAM_RING:
+                    ringtoneUri = Settings.System.DEFAULT_RINGTONE_URI;
+                    break;
+                case AudioManager.STREAM_NOTIFICATION:
+                    ringtoneUri = Settings.System.DEFAULT_NOTIFICATION_URI;
+                    break;
+                default:
+                    ringtoneUri = Settings.System.DEFAULT_ALARM_ALERT_URI;
+            }
+        } else {
+            ringtoneUri = sampleUri;
+        }
+        mRingtone = RingtoneManager.getRingtone(context, ringtoneUri);
+        if (mRingtone != null) {
+            mRingtone.setStreamType(mStreamType);
+        }
+        ((ImageView) volumeControllerView.findViewById(R.id.icon)).setImageResource(iconResId);
+        ((TextView) volumeControllerView.findViewById(R.id.stream_name)).setText(titleStringResId);
+    }
+
+    public void stop() {
+        mHandler.removeCallbacksAndMessages(null);
+        mRingtone.stop();
+    }
+
+    @Override
+    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+        try {
+            if (mCarAudioManager == null) {
+                Log.w(TAG, "CarAudiomanager not available, Car is not connected!");
+                return;
+            }
+            mCarAudioManager.setStreamVolume(mStreamType, progress, AudioManager.FLAG_PLAY_SOUND);
+            playAudioFeedback();
+        } catch (CarNotConnectedException e) {
+            Log.e(TAG, "Car is not connected!", e);
+        }
+    }
+
+    @Override
+    public void onStartTrackingTouch(SeekBar seekBar) {
+
+    }
+
+    @Override
+    public void onStopTrackingTouch(SeekBar seekBar) {
+        playAudioFeedback();
+    }
+
+    private void playAudioFeedback() {
+        mHandler.removeCallbacksAndMessages(null);
+        mRingtone.play();
+        mHandler.postDelayed(() -> {
+            if (mRingtone.isPlaying()) {
+                mRingtone.stop();
+            }
+        }, AUDIO_FEEDBACK_DELAY_MS);
+    }
+
+    /**
+     * The interface has a terrible name, it is actually a callback, so here name it accordingly.
+     */
+    private final class VolumnCallback extends IVolumeController.Stub {
+
+        private final String TAG = VolumeControllerPresenter.TAG + ".cb";
+
+        @Override
+        public void displaySafeVolumeWarning(int flags) throws RemoteException {
+        }
+
+        @Override
+        public void volumeChanged(int streamType, int flags) throws RemoteException {
+            if (streamType != mStreamType) {
+                return;
+            }
+            try {
+                if (mCarAudioManager == null) {
+                    Log.w(TAG, "CarAudiomanager not available, Car is not connected!");
+                    return;
+                }
+                int volume = mCarAudioManager.getStreamVolume(mStreamType);
+                if (mSeekBar.getProgress() == volume) {
+                    return;
+                }
+                mSeekBar.setProgress(volume);
+            } catch (CarNotConnectedException e) {
+                Log.e(TAG, "Car is not connected!", e);
+            }
+        }
+
+        // this is not mute of this stream
+        @Override
+        public void masterMuteChanged(int flags) throws RemoteException {
+        }
+
+        @Override
+        public void setLayoutDirection(int layoutDirection) throws RemoteException {
+        }
+
+        @Override
+        public void dismiss() throws RemoteException {
+        }
+
+        @Override
+        public void setA11yMode(int mode) {
+        }
+    }
+}