Introduce new volume dialog.

- New VolumeDialog (presentation) + VolumeDialogController (state)
   to implement a volume dialog that keeps track of multiple audio
   streams, including all remote streams.
 - The dialog starts out with a single stream, with more detail available
   behind an expand chevron.
 - Existing zen options reorganized under a master switch bar
   named "Block interruptions", with "None" renamed to "No interruptions"
   and "Priority" renamed to "Priority only".
 - Combined "Block interruptions" icon replaces the now-obsolete star/no-smoking
   icons in the status bar.
 - New icons for all sliders.
 - All sliders present a continuous facade, mapped to discrete integer units
   under the hood.
 - All interesting volume events and state changes piped through one central
   helper for future routing.
 - VolumePanel is obsolete, still accessible via a sysprop if needed.
   Complete removal / garbage collection deferred until all needed
   functionality is ported over.

Bug: 19260237
Change-Id: I6689de3e4d14ae666d3e8da302cc9da2d4d77b9b
This commit is contained in:
John Spurlock
2015-03-25 18:09:51 -04:00
parent 356c628e1b
commit f88d8082a8
60 changed files with 4773 additions and 197 deletions

View File

@@ -0,0 +1,25 @@
<!--
Copyright (C) 2015 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.
-->
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<objectAnimator
android:duration="250"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:pathData="M 12.0,9.0 c 0.0,0.66667 0.0,5.0 0.0,6.0"
android:propertyXName="translateX"
android:propertyYName="translateY" />
</set>

View File

@@ -0,0 +1,26 @@
<!--
Copyright (C) 2015 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.
-->
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<objectAnimator
android:duration="200"
android:interpolator="@interpolator/ic_volume_collapse_animation_interpolator_0"
android:propertyName="rotation"
android:valueFrom="45.0"
android:valueTo="-45.0"
android:valueType="floatType" />
</set>

View File

@@ -0,0 +1,26 @@
<!--
Copyright (C) 2015 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.
-->
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<objectAnimator
android:duration="200"
android:interpolator="@interpolator/ic_volume_collapse_animation_interpolator_0"
android:propertyName="rotation"
android:valueFrom="-45.0"
android:valueTo="45.0"
android:valueType="floatType" />
</set>

View File

@@ -0,0 +1,25 @@
<!--
Copyright (C) 2015 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.
-->
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<objectAnimator
android:duration="250"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:pathData="M 12.0,15.0 c 0.0,-1.0 0.0,-5.33333 0.0,-6.0"
android:propertyXName="translateX"
android:propertyYName="translateY" />
</set>

View File

@@ -0,0 +1,26 @@
<!--
Copyright (C) 2015 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.
-->
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<objectAnimator
android:duration="200"
android:interpolator="@interpolator/ic_volume_expand_animation_interpolator_0"
android:propertyName="rotation"
android:valueFrom="45.0"
android:valueTo="-45.0"
android:valueType="floatType" />
</set>

View File

@@ -0,0 +1,26 @@
<!--
Copyright (C) 2015 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.
-->
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<objectAnimator
android:duration="200"
android:interpolator="@interpolator/ic_volume_expand_animation_interpolator_0"
android:propertyName="rotation"
android:valueFrom="-45.0"
android:valueTo="45.0"
android:valueType="floatType" />
</set>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2014 The Android Open Source Project
<!--
Copyright (C) 2015 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.
@@ -13,13 +13,15 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?android:attr/colorControlHighlight">
android:color="?android:attr/colorControlHighlight" >
<item android:id="@android:id/mask">
<shape>
<corners android:radius="@dimen/borderless_button_radius" />
<solid android:color="@android:color/white" />
</shape>
</item>
</ripple>

View File

@@ -0,0 +1,26 @@
<!--
Copyright (C) 2015 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:height="24dp"
android:viewportHeight="48.0"
android:viewportWidth="48.0"
android:width="24dp" >
<path
android:fillColor="#FFFFFFFF"
android:pathData="M24.0,4.0C12.95,4.0 4.0,12.95 4.0,24.0s8.95,20.0 20.0,20.0 20.0,-8.95 20.0,-20.0S35.05,4.0 24.0,4.0zm10.0,22.0L14.0,26.0l0.0,-4.0l20.0,0.0l0.0,4.0z" />
</vector>

View File

@@ -1,25 +1,26 @@
<!--
Copyright (C) 2014 The Android Open Source Project
Copyright (C) 2015 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
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
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.
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="24dp"
android:height="24dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
android:height="24.0dp"
android:viewportHeight="48.0"
android:viewportWidth="48.0"
android:width="24.0dp" >
<path
android:fillColor="#FFFFFFFF"
android:pathData="M24.0,4.0C13.0,4.0 4.0,13.0 4.0,24.0s9.0,20.0 20.0,20.0c11.0,0.0 20.0,-9.0 20.0,-20.0S35.0,4.0 24.0,4.0zM34.0,26.0L14.0,26.0l0.0,-4.0l20.0,0.0L34.0,26.0z"/>
</vector>
android:pathData="M38.0,26.0L10.0,26.0l0.0,-4.0l28.0,0.0l0.0,4.0z" />
</vector>

View File

@@ -1,25 +1,26 @@
<!--
Copyright (C) 2014 The Android Open Source Project
Copyright (C) 2015 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
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
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.
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="24dp"
android:height="24dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
android:height="24.0dp"
android:viewportHeight="48.0"
android:viewportWidth="48.0"
android:width="24.0dp" >
<path
android:fillColor="#FFFFFFFF"
android:pathData="M24.0,4.0C13.0,4.0 4.0,13.0 4.0,24.0s9.0,20.0 20.0,20.0c11.0,0.0 20.0,-9.0 20.0,-20.0S35.0,4.0 24.0,4.0zM34.0,26.0l-8.0,0.0l0.0,8.0l-4.0,0.0l0.0,-8.0l-8.0,0.0l0.0,-4.0l8.0,0.0l0.0,-8.0l4.0,0.0l0.0,8.0l8.0,0.0L34.0,26.0z"/>
</vector>
android:pathData="M38.0,26.0L26.0,26.0l0.0,12.0l-4.0,0.0L22.0,26.0L10.0,26.0l0.0,-4.0l12.0,0.0L22.0,10.0l4.0,0.0l0.0,12.0l12.0,0.0l0.0,4.0z" />
</vector>

View File

@@ -0,0 +1,26 @@
<!--
Copyright (C) 2015 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:height="24.0dp"
android:viewportHeight="48.0"
android:viewportWidth="48.0"
android:width="24.0dp" >
<path
android:fillColor="@color/volume_icon_color"
android:pathData="M44.0,11.44l-9.19,-7.71 -2.57,3.06 9.19,7.71 2.57,-3.06zm-28.24,-4.66l-2.57,-3.06 -9.19,7.71 2.57,3.06 9.19,-7.71zm9.24,9.22l-3.0,0.0l0.0,12.0l9.49,5.71 1.51,-2.47 -8.0,-4.74l0.0,-10.5zm-1.01,-8.0c-9.95,0.0 -17.99,8.06 -17.99,18.0s8.04,18.0 17.99,18.0 18.01,-8.06 18.01,-18.0 -8.06,-18.0 -18.01,-18.0zm0.01,32.0c-7.73,0.0 -14.0,-6.27 -14.0,-14.0s6.27,-14.0 14.0,-14.0 14.0,6.27 14.0,14.0 -6.26,14.0 -14.0,14.0z" />
</vector>

View File

@@ -0,0 +1,26 @@
<!--
Copyright (C) 2015 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:height="24.0dp"
android:viewportHeight="48.0"
android:viewportWidth="48.0"
android:width="24.0dp" >
<path
android:fillColor="@color/volume_icon_color"
android:pathData="M24.0,12.0c7.73,0.0 14.0,6.27 14.0,14.0 0.0,1.69 -0.31,3.3 -0.86,4.8l3.04,3.04c1.16,-2.37 1.82,-5.03 1.82,-7.84 0.0,-9.94 -8.06,-18.0 -18.01,-18.0 -2.81,0.0 -5.46,0.66 -7.84,1.81l3.05,3.05c1.5,-0.55 3.11,-0.86 4.8,-0.86zm20.0,-0.56l-9.19,-7.71 -2.57,3.06 9.19,7.71 2.57,-3.06zm-38.16,-6.85l-2.55,2.54 2.66,2.66 -2.22,1.86 2.84,2.84 2.22,-1.86 1.6,1.6c-2.73,3.16 -4.39,7.27 -4.39,11.77 0.0,9.94 8.04,18.0 17.99,18.0 4.51,0.0 8.62,-1.67 11.77,-4.4l4.4,4.4 2.54,-2.55 -34.91,-34.91 -1.95,-1.95zm27.1,32.19c-2.43,2.01 -5.54,3.22 -8.94,3.22 -7.73,0.0 -14.0,-6.27 -14.0,-14.0 0.0,-3.4 1.21,-6.51 3.22,-8.94l19.72,19.72zm-16.91,-30.23l-2.84,-2.84 -1.7,1.43 2.84,2.84 1.7,-1.43z" />
</vector>

View File

@@ -0,0 +1,26 @@
<!--
Copyright (C) 2015 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:height="24dp"
android:viewportHeight="48.0"
android:viewportWidth="48.0"
android:width="24dp" >
<path
android:fillColor="@color/volume_icon_color"
android:pathData="M35.4,15.4L24.0,4.0l-2.0,0.0l0.0,15.2L12.8,10.0L10.0,12.8L21.2,24.0L10.0,35.2l2.8,2.8l9.2,-9.2L22.0,44.0l2.0,0.0l11.4,-11.4L26.8,24.0L35.4,15.4zM26.0,11.7l3.8,3.8L26.0,19.2L26.0,11.7zM29.8,32.6L26.0,36.3l0.0,-7.5L29.8,32.6z" />
</vector>

View File

@@ -0,0 +1,26 @@
<!--
Copyright (C) 2015 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:height="24dp"
android:viewportHeight="48.0"
android:viewportWidth="48.0"
android:width="24dp" >
<path
android:fillColor="@color/volume_icon_color"
android:pathData="M26.0,11.8l3.8,3.8l-3.2,3.2l2.8,2.8l6.0,-6.0L24.0,4.2l-2.0,0.0l0.0,10.1l4.0,4.0L26.0,11.8zM10.8,8.2L8.0,11.0l13.2,13.2L10.0,35.3l2.8,2.8L22.0,29.0l0.0,15.2l2.0,0.0l8.6,-8.6l4.6,4.6l2.8,-2.8L10.8,8.2zM26.0,36.5L26.0,29.0l3.8,3.8L26.0,36.5z" />
</vector>

View File

@@ -0,0 +1,26 @@
<!--
Copyright (C) 2015 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:height="24.0dp"
android:viewportHeight="48.0"
android:viewportWidth="48.0"
android:width="24.0dp" >
<path
android:fillColor="@color/volume_icon_color"
android:pathData="M29.41,19.0L34.0,14.41L34.0,22.0l1.0,0.0l5.71,-5.71 -4.3,-4.29 4.29,-4.29L35.0,2.0l-1.0,0.0l0.0,7.59L29.41,5.0 28.0,6.41 33.59,12.0 28.0,17.59 29.41,19.0zM36.0,5.83l1.88,1.88L36.0,9.59L36.0,5.83zm0.0,8.58l1.88,1.88L36.0,18.17l0.0,-3.76zM40.0,31.0c-2.49,0.0 -4.89,-0.4 -7.14,-1.14 -0.69,-0.22 -1.48,-0.06 -2.0,0.49l-4.4,4.41c-5.67,-2.88 -10.29,-7.51 -13.18,-13.17l4.4,-4.41c0.55,-0.5 0.71,-1.3 0.49,-2.03C17.4,12.9 17.0,10.49 17.0,8.0c0.0,-1.11 -0.89,-2.0 -2.0,-2.0L8.0,6.0c-1.11,0.0 -2.0,0.89 -2.0,2.0 0.0,18.78 15.22,34.0 34.0,34.0 1.11,0.0 2.0,-0.89 2.0,-2.0l0.0,-7.0c0.0,-1.11 -0.89,-2.0 -2.0,-2.0z" />
</vector>

View File

@@ -0,0 +1,62 @@
<!--
Copyright (C) 2015 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:name="ic_volume_collapse"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24"
android:width="24dp" >
<group
android:name="chevron_02"
android:rotation="90"
android:translateX="12"
android:translateY="9" >
<group
android:name="rectangle_2"
android:rotation="-45" >
<group
android:name="rectangle_2_pivot"
android:translateY="4" >
<group
android:name="rectangle_path_2_position"
android:translateY="-1" >
<path
android:name="rectangle_path_2"
android:fillColor="#FFFFFFFF"
android:pathData="M -1.0,-4.0 l 2.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,8.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -2.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-8.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z" />
</group>
</group>
</group>
<group
android:name="rectangle_1"
android:rotation="45" >
<group
android:name="rectangle_1_pivot"
android:translateY="-4" >
<group
android:name="rectangle_path_1_position"
android:translateY="1" >
<path
android:name="rectangle_path_1"
android:fillColor="#FFFFFFFF"
android:pathData="M -1.0,-4.0 l 2.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,8.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -2.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-8.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z" />
</group>
</group>
</group>
</group>
</vector>

View File

@@ -0,0 +1,29 @@
<!--
Copyright (C) 2015 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.
-->
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ic_volume_collapse" >
<target
android:name="chevron_02"
android:animation="@anim/ic_volume_collapse_chevron_02_animation" />
<target
android:name="rectangle_2"
android:animation="@anim/ic_volume_collapse_rectangle_2_animation" />
<target
android:name="rectangle_1"
android:animation="@anim/ic_volume_collapse_rectangle_1_animation" />
</animated-vector>

View File

@@ -0,0 +1,62 @@
<!--
Copyright (C) 2015 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:name="ic_volume_expand"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24"
android:width="24dp" >
<group
android:name="chevron_01"
android:rotation="90"
android:translateX="12"
android:translateY="15" >
<group
android:name="rectangle_3"
android:rotation="45" >
<group
android:name="rectangle_2_pivot_0"
android:translateY="4" >
<group
android:name="rectangle_path_3_position"
android:translateY="-1" >
<path
android:name="rectangle_path_3"
android:fillColor="#FFFFFFFF"
android:pathData="M -1.0,-4.0 l 2.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,8.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -2.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-8.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z" />
</group>
</group>
</group>
<group
android:name="rectangle_4"
android:rotation="-45" >
<group
android:name="rectangle_1_pivot_0"
android:translateY="-4" >
<group
android:name="rectangle_path_4_position"
android:translateY="1" >
<path
android:name="rectangle_path_4"
android:fillColor="#FFFFFFFF"
android:pathData="M -1.0,-4.0 l 2.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,8.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -2.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-8.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z" />
</group>
</group>
</group>
</group>
</vector>

View File

@@ -0,0 +1,29 @@
<!--
Copyright (C) 2015 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.
-->
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ic_volume_expand" >
<target
android:name="chevron_01"
android:animation="@anim/ic_volume_expand_chevron_01_animation" />
<target
android:name="rectangle_3"
android:animation="@anim/ic_volume_expand_rectangle_3_animation" />
<target
android:name="rectangle_4"
android:animation="@anim/ic_volume_expand_rectangle_4_animation" />
</animated-vector>

View File

@@ -0,0 +1,26 @@
<!--
Copyright (C) 2015 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:height="24.0dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0"
android:width="24.0dp" >
<path
android:fillColor="@color/volume_icon_color"
android:pathData="M12.0,3.0l0.0,9.28c-0.47,-0.17 -0.97,-0.28 -1.5,-0.28C8.01,12.0 6.0,14.01 6.0,16.5S8.01,21.0 10.5,21.0c2.31,0.0 4.2,-1.75 4.45,-4.0L15.0,17.0L15.0,6.0l4.0,0.0L19.0,3.0l-7.0,0.0z" />
</vector>

View File

@@ -0,0 +1,29 @@
<!--
Copyright (C) 2015 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:height="24.0dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0"
android:width="24.0dp" >
<path
android:fillColor="@color/volume_icon_color"
android:pathData="M15.0,6.0l4.0,0.0L19.0,3.0l-7.0,0.0l0.0,5.6l3.0,3.0C15.0,8.8 15.0,6.0 15.0,6.0z" />
<path
android:fillColor="@color/volume_icon_color"
android:pathData="M4.8,3.9L3.5,5.1l6.9,6.9C8.0,12.1 6.0,14.0 6.0,16.5C6.0,19.0 8.0,21.0 10.5,21.0c2.7,0.0 4.5,-2.3 4.5,-4.3c0.0,0.0 0.0,-0.1 0.0,-0.1l4.0,4.0l1.3,-1.3L4.8,3.9z" />
</vector>

View File

@@ -0,0 +1,26 @@
<!--
Copyright (C) 2015 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:height="24dp"
android:viewportHeight="48.0"
android:viewportWidth="48.0"
android:width="24dp" >
<path
android:fillColor="@color/volume_icon_color"
android:pathData="M2.0,36.0l0.0,6.0l6.0,0.0C8.0,38.7 5.3,36.0 2.0,36.0zM2.0,28.0l0.0,4.0c5.5,0.0 10.0,4.5 10.0,10.0l4.0,0.0C16.0,34.3 9.7,28.0 2.0,28.0zM38.0,14.0L10.0,14.0l0.0,3.3c7.9,2.6 14.2,8.8 16.7,16.7L38.0,34.0L38.0,14.0zM2.0,20.0l0.0,4.0c9.9,0.0 18.0,8.1 18.0,18.0l4.0,0.0C24.0,29.8 14.1,20.0 2.0,20.0zM42.0,6.0L6.0,6.0c-2.2,0.0 -4.0,1.8 -4.0,4.0l0.0,6.0l4.0,0.0l0.0,-6.0l36.0,0.0l0.0,28.0L28.0,38.0l0.0,4.0l14.0,0.0c2.2,0.0 4.0,-1.8 4.0,-4.0L46.0,10.0C46.0,7.8 44.2,6.0 42.0,6.0z" />
</vector>

View File

@@ -0,0 +1,38 @@
<!--
Copyright (C) 2015 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:height="24.0dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0"
android:width="24.0dp" >
<path
android:fillColor="@color/volume_icon_color"
android:pathData="M23.7,21.3l-1.1,-1.0c0.0,0.0 0.0,0.0 0.0,0.0L21.0,18.8l0.0,0.0L5.8,5.0l0.0,0.0L3.6,3.0l0.0,0.0L1.7,1.3L0.3,2.7l1.1,1.0C1.2,4.1 1.0,4.5 1.0,5.0l0.0,3.0l2.0,0.0L3.0,5.2L18.2,19.0L14.0,19.0l0.0,2.0l6.4,0.0l1.9,1.7L23.7,21.3z" />
<path
android:fillColor="@color/volume_icon_color"
android:pathData="M21.0,5.0l0.0,11.1l2.0,1.8L23.0,5.0c0.0,-1.1 -0.9,-2.0 -2.0,-2.0L6.6,3.0l2.2,2.0L21.0,5.0z" />
<path
android:fillColor="@color/volume_icon_color"
android:pathData="M1.0,18.0l0.0,3.0l3.0,0.0C4.0,19.3 2.7,18.0 1.0,18.0z" />
<path
android:fillColor="@color/volume_icon_color"
android:pathData="M1.0,14.0l0.0,2.0c2.8,0.0 5.0,2.2 5.0,5.0l2.0,0.0C8.0,17.1 4.9,14.0 1.0,14.0z" />
<path
android:fillColor="@color/volume_icon_color"
android:pathData="M1.0,10.0l0.0,2.0c5.0,0.0 9.0,4.0 9.0,9.0l2.0,0.0C12.0,14.9 7.1,10.0 1.0,10.0z" />
</vector>

View File

@@ -0,0 +1,26 @@
<!--
Copyright (C) 2015 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:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0"
android:width="24dp" >
<path
android:fillColor="@color/volume_icon_color"
android:pathData="M11.5,22.0c1.1,0.0 2.0,-0.9 2.0,-2.0l-4.0,0.0C9.5,21.1 10.4,22.0 11.5,22.0zM18.0,16.0l0.0,-5.5c0.0,-3.1 -2.1,-5.6 -5.0,-6.3L13.0,3.5C13.0,2.7 12.3,2.0 11.5,2.0C10.7,2.0 10.0,2.7 10.0,3.5l0.0,0.7c-2.9,0.7 -5.0,3.2 -5.0,6.3L5.0,16.0l-2.0,2.0l0.0,1.0l17.0,0.0l0.0,-1.0L18.0,16.0z" />
</vector>

View File

@@ -0,0 +1,26 @@
<!--
Copyright (C) 2015 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:height="24dp"
android:viewportHeight="48.0"
android:viewportWidth="48.0"
android:width="24dp" >
<path
android:fillColor="@color/volume_icon_color"
android:pathData="M23.000000,44.000000c2.200000,0.000000 4.000000,-1.800000 4.000000,-4.000000l-8.000000,0.000000C19.000000,42.200001 20.799999,44.000000 23.000000,44.000000zM36.000000,21.000000c0.000000,-6.100000 -4.300000,-11.300000 -10.000000,-12.600000L26.000000,7.000000c0.000000,-1.700000 -1.300000,-3.000000 -3.000000,-3.000000c-1.700000,0.000000 -3.000000,1.300000 -3.000000,3.000000l0.000000,1.400000c-1.000000,0.200000 -2.000000,0.600000 -2.900000,1.100000L36.000000,28.400000L36.000000,21.000000zM35.500000,38.000000l4.000000,4.000000l2.500000,-2.500000L8.500000,6.000000L6.000000,8.500000l5.800000,5.800000C10.700000,16.299999 10.000000,18.600000 10.000000,21.000000l0.000000,11.000000l-4.000000,4.000000l0.000000,2.000000L35.500000,38.000000z" />
</vector>

View File

@@ -0,0 +1,26 @@
<!--
Copyright (C) 2015 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:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0"
android:width="24dp" >
<path
android:fillColor="@color/volume_icon_color"
android:pathData="M0.0,15.0l2.0,0.0L2.0,9.0L0.0,9.0L0.0,15.0zM3.0,17.0l2.0,0.0L5.0,7.0L3.0,7.0L3.0,17.0zM22.0,9.0l0.0,6.0l2.0,0.0L24.0,9.0L22.0,9.0zM19.0,17.0l2.0,0.0L21.0,7.0l-2.0,0.0L19.0,17.0zM16.5,3.0l-9.0,0.0C6.7,3.0 6.0,3.7 6.0,4.5l0.0,15.0C6.0,20.3 6.7,21.0 7.5,21.0l9.0,0.0c0.8,0.0 1.5,-0.7 1.5,-1.5l0.0,-15.0C18.0,3.7 17.3,3.0 16.5,3.0zM16.0,19.0L8.0,19.0L8.0,5.0l8.0,0.0L16.0,19.0z" />
</vector>

View File

@@ -0,0 +1,26 @@
<!--
Copyright (C) 2015 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:height="20dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0"
android:width="20dp" >
<path
android:fillColor="@color/volume_settings_icon_color"
android:pathData="M19.4,13.0c0.0,-0.3 0.1,-0.6 0.1,-1.0s0.0,-0.7 -0.1,-1.0l2.1,-1.7c0.2,-0.2 0.2,-0.4 0.1,-0.6l-2.0,-3.5C19.5,5.1 19.3,5.0 19.0,5.1l-2.5,1.0c-0.5,-0.4 -1.1,-0.7 -1.7,-1.0l-0.4,-2.6C14.5,2.2 14.2,2.0 14.0,2.0l-4.0,0.0C9.8,2.0 9.5,2.2 9.5,2.4L9.1,5.1C8.5,5.3 8.0,5.7 7.4,6.1L5.0,5.1C4.7,5.0 4.5,5.1 4.3,5.3l-2.0,3.5C2.2,8.9 2.3,9.2 2.5,9.4L4.6,11.0c0.0,0.3 -0.1,0.6 -0.1,1.0s0.0,0.7 0.1,1.0l-2.1,1.7c-0.2,0.2 -0.2,0.4 -0.1,0.6l2.0,3.5C4.5,18.9 4.7,19.0 5.0,18.9l2.5,-1.0c0.5,0.4 1.1,0.7 1.7,1.0l0.4,2.6c0.0,0.2 0.2,0.4 0.5,0.4l4.0,0.0c0.2,0.0 0.5,-0.2 0.5,-0.4l0.4,-2.6c0.6,-0.3 1.2,-0.6 1.7,-1.0l2.5,1.0c0.2,0.1 0.5,0.0 0.6,-0.2l2.0,-3.5c0.1,-0.2 0.1,-0.5 -0.1,-0.6L19.4,13.0zM12.0,15.5c-1.9,0.0 -3.5,-1.6 -3.5,-3.5s1.6,-3.5 3.5,-3.5s3.5,1.6 3.5,3.5S13.9,15.5 12.0,15.5z" />
</vector>

View File

@@ -0,0 +1,32 @@
<!--
Copyright (C) 2015 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:height="24.0dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0"
android:width="24.0dp" >
<path
android:fillColor="@color/volume_icon_color"
android:pathData="M17.7,14.8l-4.5,-2.3c-0.2,-0.1 -0.4,-0.1 -0.5,-0.1l-0.8,0.0l0.0,-6.0c0.0,-0.8 -0.7,-1.5 -1.5,-1.5S8.9,5.6 8.9,6.4l0.0,10.7l-3.4,-0.7c-0.1,0.0 -0.2,0.0 -0.2,0.0c-0.3,0.0 -0.6,0.1 -0.8,0.3l-0.8,0.8l4.9,4.9c0.3,0.3 0.6,0.4 1.1,0.4l6.8,0.0c0.8,0.0 1.3,-0.6 1.4,-1.3l0.8,-5.3c0.0,-0.1 0.0,-0.1 0.0,-0.2C18.6,15.5 18.2,15.0 17.7,14.8z" />
<path
android:fillColor="@color/volume_icon_color"
android:pathData="M14.3,8.8l1.8,0.9c1.5,-2.0 1.4,-4.8 -0.4,-6.6l-1.4,1.4C15.5,5.7 15.5,7.6 14.3,8.8z" />
<path
android:fillColor="@color/volume_icon_color"
android:pathData="M17.9,10.6l1.8,0.9C22.0,8.0 21.6,3.3 18.5,0.3l-1.4,1.4C19.5,4.1 19.8,7.9 17.9,10.6z" />
</vector>

View File

@@ -0,0 +1,35 @@
<!--
Copyright (C) 2015 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:height="24.0dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0"
android:width="24.0dp" >
<path
android:fillColor="@color/volume_icon_color"
android:pathData="M14.3,8.8l1.8,0.9c1.5,-2.0 1.4,-4.8 -0.4,-6.6l-1.4,1.4C15.5,5.7 15.5,7.6 14.3,8.8z" />
<path
android:fillColor="@color/volume_icon_color"
android:pathData="M17.9,10.6l1.8,0.9C22.0,8.0 21.6,3.3 18.5,0.3l-1.4,1.4C19.5,4.1 19.8,7.9 17.9,10.6z" />
<path
android:fillColor="@color/volume_icon_color"
android:pathData="M22.0,20.6l-3.5,-2.8l0.0,0.0l-9.6,-7.6l0.0,0.0L3.2,5.7L2.0,7.3l6.9,5.4L8.9,17.0l-3.4,-0.7c-0.1,0.0 -0.2,0.0 -0.2,0.0c-0.3,0.0 -0.6,0.1 -0.8,0.3l-0.8,0.8l4.9,4.9c0.3,0.3 0.6,0.4 1.1,0.4l6.8,0.0c0.8,0.0 1.3,-0.6 1.4,-1.3l0.2,-1.5l2.6,2.1L22.0,20.6z" />
<path
android:fillColor="@color/volume_icon_color"
android:pathData="M11.9,6.4c0.0,-0.8 -0.7,-1.5 -1.5,-1.5S8.9,5.6 8.9,6.4l0.0,1.5l3.0,2.4L11.9,6.4z" />
</vector>

View File

@@ -0,0 +1,26 @@
<!--
Copyright (C) 2015 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:height="24.0dp"
android:viewportHeight="48.0"
android:viewportWidth="48.0"
android:width="24.0dp" >
<path
android:fillColor="@color/volume_icon_color"
android:pathData="M13.25,21.59c2.88,5.66 7.51,10.29 13.18,13.17l4.4,-4.41c0.55,-0.55 1.34,-0.71 2.03,-0.49C35.1,30.6 37.51,31.0 40.0,31.0c1.11,0.0 2.0,0.89 2.0,2.0l0.0,7.0c0.0,1.11 -0.89,2.0 -2.0,2.0C21.22,42.0 6.0,26.78 6.0,8.0c0.0,-1.1 0.9,-2.0 2.0,-2.0l7.0,0.0c1.11,0.0 2.0,0.89 2.0,2.0 0.0,2.4 0.4,4.9 1.14,7.1 0.2,0.6 0.06,1.48 -0.49,2.03l-4.4,4.42z" />
</vector>

View File

@@ -0,0 +1,22 @@
<!--
Copyright (C) 2015 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.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
<solid android:color="@color/system_primary_color" />
<corners android:radius="@dimen/notification_material_rounded_rect_radius" />
</shape>

View File

@@ -0,0 +1,17 @@
<!--
Copyright (C) 2015 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.
-->
<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
android:pathData="M 0.0,0.0 c 0.0001,0.0 0.0,1.0 1.0,1.0" />

View File

@@ -0,0 +1,17 @@
<!--
Copyright (C) 2015 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.
-->
<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
android:pathData="M 0.0,0.0 c 0.0001,0.0 0.0,1.0 1.0,1.0" />

View File

@@ -19,10 +19,8 @@
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/segmented_button_spacing"
android:layout_weight="1"
android:gravity="center_horizontal|top"
android:gravity="center"
android:textColor="@color/segmented_button_text_selector"
android:background="@drawable/btn_borderless_rect"
android:textAppearance="@style/TextAppearance.QS.SegmentedButton"
android:minHeight="64dp"
android:paddingTop="11dp"
android:drawablePadding="6dp" />
android:textAppearance="@style/TextAppearance.Volume.ZenSwitchSummary"
android:minHeight="48dp" />

View File

@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2014 The Android Open Source Project
Copyright (C) 2015 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.
@@ -14,15 +13,50 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/volume_dialog"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:layout_marginLeft="@dimen/notification_side_padding"
android:layout_marginRight="@dimen/notification_side_padding"
android:background="@drawable/qs_background_primary"
android:translationZ="@dimen/volume_panel_z"
android:layout_marginBottom="@dimen/volume_panel_z">
android:layout_marginTop="4dp"
android:background="@drawable/volume_dialog_background"
android:translationZ="4dp" >
<include layout="@layout/volume_panel" />
<com.android.keyguard.AlphaOptimizedImageButton
android:id="@+id/volume_expand_button"
style="@style/VolumeButtons"
android:layout_alignParentLeft="true"
android:layout_width="@dimen/volume_button_size"
android:layout_height="@dimen/volume_button_size"
android:clickable="true"
android:soundEffectsEnabled="false"
android:src="@drawable/ic_volume_collapse_animation" />
</FrameLayout>
<LinearLayout
android:id="@+id/volume_dialog_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="4dp"
android:paddingEnd="4dp"
android:paddingStart="4dp"
android:paddingTop="6dp" >
<!-- volume rows added and removed here! :-) -->
<FrameLayout
android:id="@+id/volume_footer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:ignore="UselessParent" >
<include layout="@layout/volume_text_footer" />
<include layout="@layout/volume_zen_footer" />
</FrameLayout>
</LinearLayout>
</RelativeLayout>

View File

@@ -0,0 +1,64 @@
<!--
Copyright (C) 2015 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"
android:clipChildren="false" >
<TextView
android:id="@+id/volume_row_header"
style="?android:attr/textAppearanceButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="@dimen/volume_secondary_alpha"
android:ellipsize="end"
android:maxLines="1"
android:paddingBottom="0dp"
android:paddingEnd="12dp"
android:paddingStart="13dp"
android:paddingTop="8dp" />
<com.android.keyguard.AlphaOptimizedImageButton
android:id="@+id/volume_row_icon"
style="@style/VolumeButtons"
android:layout_width="@dimen/volume_button_size"
android:layout_height="@dimen/volume_button_size"
android:layout_below="@id/volume_row_header"
android:soundEffectsEnabled="false" />
<SeekBar
android:id="@+id/volume_row_slider"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/volume_row_icon"
android:layout_alignWithParentIfMissing="true"
android:layout_below="@id/volume_row_header"
android:layout_toEndOf="@id/volume_row_icon"
android:layout_toStartOf="@+id/volume_settings_button"
android:paddingEnd="4dp"
android:paddingStart="4dp"
android:progressTint="@android:color/white"
android:thumbTint="@android:color/white" />
<com.android.keyguard.AlphaOptimizedImageButton
android:id="@+id/volume_settings_button"
style="@style/VolumeButtons"
android:layout_width="@dimen/volume_button_size"
android:layout_height="@dimen/volume_button_size"
android:layout_alignParentEnd="true"
android:layout_below="@id/volume_row_header" />
</RelativeLayout>

View File

@@ -0,0 +1,27 @@
<!--
Copyright (C) 2015 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.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/volume_panel_z"
android:layout_marginLeft="@dimen/notification_side_padding"
android:layout_marginRight="@dimen/notification_side_padding"
android:background="@drawable/qs_background_primary"
android:translationZ="@dimen/volume_panel_z" >
<include layout="@layout/volume_panel" />
</FrameLayout>

View File

@@ -0,0 +1,54 @@
<!--
Copyright (C) 2015 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"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/volume_text_footer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
tools:ignore="UselessParent" >
<TextView
android:id="@+id/volume_footline_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/volume_footline_action_button"
android:alpha="@dimen/volume_secondary_alpha"
android:fontFamily="sans-serif"
android:paddingEnd="8dp"
android:paddingStart="13dp"
android:textColor="?android:attr/textColorPrimary" />
<Button
android:id="@+id/volume_footline_action_button"
style="@android:style/Widget.Material.Button.Borderless"
android:layout_width="wrap_content"
android:layout_height="@dimen/volume_button_size"
android:layout_toEndOf="@id/volume_footline_text"
android:layout_toStartOf="@+id/volume_settings_button"
android:alpha="@dimen/volume_secondary_alpha"
android:paddingEnd="0dp"
android:paddingStart="0dp" />
<com.android.keyguard.AlphaOptimizedImageButton
android:id="@+id/volume_settings_button"
style="@style/VolumeButtons"
android:layout_width="@dimen/volume_button_size"
android:layout_height="@dimen/volume_button_size"
android:layout_alignParentEnd="true"
android:src="@drawable/ic_volume_settings" />
</RelativeLayout>

View File

@@ -0,0 +1,108 @@
<!--
Copyright (C) 2015 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.
-->
<com.android.systemui.volume.ZenFooter xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/volume_zen_footer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" > <!-- extends LinearLayout -->
<LinearLayout
android:id="@+id/volume_zen_switch_bar"
android:layout_width="match_parent"
android:layout_height="@dimen/volume_button_size"
android:clickable="true"
android:orientation="horizontal" >
<ImageView
android:id="@+id/volume_zen_switch_bar_icon"
android:layout_width="@dimen/volume_button_size"
android:layout_height="@dimen/volume_button_size"
android:scaleType="center"
android:src="@drawable/ic_dnd" />
<TextView
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="1"
android:gravity="center_vertical"
android:textDirection="locale"
android:padding="3dp"
android:text="@string/volume_zen_switch_text"
android:textAppearance="@style/TextAppearance.Volume.ZenSwitch" />
<Switch
android:id="@+id/volume_zen_switch"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_marginEnd="11dp" />
</LinearLayout>
<RelativeLayout
android:id="@+id/volume_zen_panel_summary"
android:layout_width="match_parent"
android:layout_height="@dimen/volume_button_size"
android:layout_marginStart="@dimen/volume_button_size"
android:paddingEnd="3dp"
android:paddingStart="3dp" >
<TextView
android:id="@+id/volume_zen_panel_summary_line_1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.Volume.ZenSwitchSummary" />
<TextView
android:id="@+id/volume_zen_panel_summary_line_2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/volume_zen_panel_summary_line_1"
android:textAppearance="@style/TextAppearance.Volume.ZenSwitchDetail" />
</RelativeLayout>
<include layout="@layout/zen_mode_panel" />
<LinearLayout
android:id="@+id/volume_zen_mode_panel_buttons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end" >
<TextView
android:id="@+id/volume_zen_mode_panel_more"
style="@style/QSBorderlessButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:clickable="true"
android:focusable="true"
android:minWidth="132dp"
android:text="@string/quick_settings_more_settings"
android:textAppearance="@style/TextAppearance.QS.DetailButton" />
<TextView
android:id="@+id/volume_zen_mode_panel_done"
style="@style/QSBorderlessButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
android:minWidth="84dp"
android:text="@string/quick_settings_done"
android:textAppearance="@style/TextAppearance.QS.DetailButton" />
</LinearLayout>
</com.android.systemui.volume.ZenFooter>

View File

@@ -34,8 +34,8 @@
android:id="@+id/zen_buttons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginLeft="40dp"
android:layout_marginRight="40dp"
android:layout_marginBottom="8dp"
android:clipChildren="false" />
</FrameLayout>

View File

@@ -758,10 +758,10 @@
<string name="camera_hint">Swipe left for camera</string>
<!-- Interruption level: None. [CHAR LIMIT=20] -->
<string name="interruption_level_none">None</string>
<string name="interruption_level_none">No interruptions</string>
<!-- Interruption level: Priority. [CHAR LIMIT=20] -->
<string name="interruption_level_priority">Priority</string>
<string name="interruption_level_priority">Priority only</string>
<!-- Interruption level: All. [CHAR LIMIT=20] -->
<string name="interruption_level_all">All</string>
@@ -965,4 +965,7 @@
<!-- VolumeUI restoration notification: text -->
<string name="volumeui_notification_text">Touch to restore the original.</string>
<!-- Volume dialog zen toggle switch title -->
<string name="volume_zen_switch_text">Block interruptions</string>
</resources>

View File

@@ -0,0 +1,87 @@
<!--
Copyright (C) 2015 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.
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<item name="volume_expand_animation_duration" type="integer">300</item>
<color name="volume_icon_color">#ffffffff</color>
<color name="volume_settings_icon_color">#7fffffff</color>
<dimen name="volume_slider_interspacing">2dp</dimen>
<dimen name="volume_offset_top">0dp</dimen>
<dimen name="volume_button_size">48dp</dimen>
<item name="volume_secondary_alpha" format="float" type="dimen">0.3</item>
<style name="VolumeDialogAnimations">
<item name="android:windowEnterAnimation">@android:anim/fade_in</item>
<item name="android:windowExitAnimation">@android:anim/fade_out</item>
</style>
<style name="VolumeButtons" parent="@android:style/Widget.Material.Button.Borderless">
<item name="android:background">@drawable/btn_borderless_rect</item>
</style>
<style name="TextAppearance" />
<style name="TextAppearance.Volume">
<item name="android:textStyle">normal</item>
<item name="android:textColor">#ffffffff</item>
<item name="android:fontFamily">sans-serif</item>
</style>
<style name="TextAppearance.Volume.ZenSwitch">
<item name="android:textSize">16sp</item>
<item name="android:fontFamily">sans-serif-medium</item>
</style>
<style name="TextAppearance.Volume.ZenSwitchSummary">
<item name="android:textSize">14sp</item>
<item name="android:fontFamily">sans-serif-medium</item>
</style>
<style name="TextAppearance.Volume.ZenSwitchDetail">
<item name="android:textSize">14sp</item>
<item name="android:fontFamily">sans-serif</item>
<item name="android:textColor">#ffb0b3c5</item>
</style>
<string-array name="volume_stream_titles" translatable="false">
<item>Voice calls</item> <!-- STREAM_VOICE_CALL -->
<item>System</item> <!-- STREAM_SYSTEM -->
<item>Notifications</item> <!-- STREAM_RING -->
<item>Media</item> <!-- STREAM_MUSIC -->
<item>Alarms</item> <!-- STREAM_ALARM -->
<item></item> <!-- STREAM_NOTIFICATION -->
<item>Bluetooth calls</item> <!-- STREAM_BLUETOOTH_SCO -->
<item></item> <!-- STREAM_SYSTEM_ENFORCED -->
<item></item> <!-- STREAM_DTMF -->
<item></item> <!-- STREAM_TTS -->
</string-array>
<string name="volume_dnd_is_on" translatable="false">Do not disturb is on</string>
<string name="volume_turn_off" translatable="false">Turn off</string>
<string name="volume_stream_muted" translatable="false">%s silent</string>
<string name="volume_stream_vibrate" translatable="false">%s vibrate</string>
<string name="volume_stream_suppressed" translatable="false">%1$s silent — %2$s</string>
<string name="volume_stream_muted_dnd" translatable="false">%s silent — No interruptions</string>
<string name="volume_stream_limited_dnd" translatable="false">%s — Priority only</string>
<string name="volume_stream_vibrate_dnd" translatable="false">%s vibrate — Priority only</string>
<string name="volume_dnd_ends_in" translatable="false">Do not disturb ends in %s</string>
<string name="volume_dnd_ends_at" translatable="false">Do not disturb ends at %s</string>
<string name="volume_end_now" translatable="false">End now</string>
</resources>

View File

@@ -40,6 +40,7 @@ public class DndTile extends QSTile<QSTile.BooleanState> {
private static final String ACTION_SET_VISIBLE = "com.android.systemui.dndtile.SET_VISIBLE";
private static final String EXTRA_VISIBLE = "visible";
private static final String PREF_KEY_VISIBLE = "DndTileVisible";
private static final String PREF_KEY_COMBINED_ICON = "DndTileCombinedIcon";
private final ZenModeController mController;
private final DndDetailAdapter mDetailAdapter;
@@ -52,7 +53,7 @@ public class DndTile extends QSTile<QSTile.BooleanState> {
super(host);
mController = host.getZenModeController();
mDetailAdapter = new DndDetailAdapter();
mVisible = getSharedPrefs(mContext).getBoolean(PREF_KEY_VISIBLE, false);
mVisible = isVisible(host.getContext());
mContext.registerReceiver(mReceiver, new IntentFilter(ACTION_SET_VISIBLE));
}
@@ -65,6 +66,14 @@ public class DndTile extends QSTile<QSTile.BooleanState> {
return getSharedPrefs(context).getBoolean(PREF_KEY_VISIBLE, false);
}
public static void setCombinedIcon(Context context, boolean combined) {
getSharedPrefs(context).edit().putBoolean(PREF_KEY_COMBINED_ICON, combined).commit();
}
public static boolean isCombinedIcon(Context context) {
return getSharedPrefs(context).getBoolean(PREF_KEY_COMBINED_ICON, false);
}
@Override
public DetailAdapter getDetailAdapter() {
return mDetailAdapter;

View File

@@ -199,7 +199,7 @@ public class PhoneStatusBarPolicy {
int volumeIconId = 0;
String volumeDescription = null;
if (DndTile.isVisible(mContext)) {
if (DndTile.isVisible(mContext) || DndTile.isCombinedIcon(mContext)) {
zenVisible = mZen != Global.ZEN_MODE_OFF;
zenIconId = R.drawable.stat_sys_dnd;
zenDescription = mContext.getString(R.string.quick_settings_dnd_label);

View File

@@ -0,0 +1,23 @@
/*
* Copyright (C) 2015 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.systemui.volume;
import android.util.Log;
class D {
public static boolean BUG = Log.isLoggable("volume", Log.DEBUG);
}

View File

@@ -0,0 +1,189 @@
/*
* Copyright (C) 2015 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.systemui.volume;
import android.media.AudioManager;
import android.media.AudioSystem;
import android.provider.Settings.Global;
import android.util.Log;
import com.android.systemui.volume.VolumeDialogController.State;
import java.util.Arrays;
/**
* Interesting events related to the volume.
*/
public class Events {
private static final String TAG = Util.logTag(Events.class);
public static final int EVENT_SHOW_DIALOG = 0; // (reason|int) (keyguard|bool)
public static final int EVENT_DISMISS_DIALOG = 1; // (reason|int)
public static final int EVENT_ACTIVE_STREAM_CHANGED = 2; // (stream|int)
public static final int EVENT_EXPAND = 3; // (expand|bool)
public static final int EVENT_KEY = 4;
public static final int EVENT_COLLECTION_STARTED = 5;
public static final int EVENT_COLLECTION_STOPPED = 6;
public static final int EVENT_ICON_CLICK = 7; // (stream|int) (icon_state|int)
public static final int EVENT_SETTINGS_CLICK = 8;
public static final int EVENT_TOUCH_LEVEL_CHANGED = 9; // (stream|int) (level|int)
public static final int EVENT_LEVEL_CHANGED = 10; // (stream|int) (level|int)
public static final int EVENT_INTERNAL_RINGER_MODE_CHANGED = 11; // (mode|int)
public static final int EVENT_EXTERNAL_RINGER_MODE_CHANGED = 12; // (mode|int)
public static final int EVENT_ZEN_MODE_CHANGED = 13; // (mode|int)
public static final int EVENT_SUPPRESSOR_CHANGED = 14; // (component|string) (name|string)
public static final int EVENT_MUTE_CHANGED = 15; // (stream|int) (muted|bool)
private static final String[] EVENT_TAGS = {
"show_dialog",
"dismiss_dialog",
"active_stream_changed",
"expand",
"key",
"collection_started",
"collection_stopped",
"icon_click",
"settings_click",
"touch_level_changed",
"level_changed",
"internal_ringer_mode_changed",
"external_ringer_mode_changed",
"zen_mode_changed",
"suppressor_changed",
"mute_changed",
};
public static final int DISMISS_REASON_UNKNOWN = 0;
public static final int DISMISS_REASON_TOUCH_OUTSIDE = 1;
public static final int DISMISS_REASON_VOLUME_CONTROLLER = 2;
public static final int DISMISS_REASON_TIMEOUT = 3;
public static final int DISMISS_REASON_SCREEN_OFF = 4;
public static final int DISMISS_REASON_SETTINGS_CLICKED = 5;
public static final int DISMISS_REASON_DONE_CLICKED = 6;
public static final String[] DISMISS_REASONS = {
"unknown",
"touch_outside",
"volume_controller",
"timeout",
"screen_off",
"settings_clicked",
"done_clicked",
};
public static final int SHOW_REASON_UNKNOWN = 0;
public static final int SHOW_REASON_VOLUME_CHANGED = 1;
public static final int SHOW_REASON_REMOTE_VOLUME_CHANGED = 2;
public static final String[] SHOW_REASONS = {
"unknown",
"volume_changed",
"remote_volume_changed"
};
public static final int ICON_STATE_UNKNOWN = 0;
public static final int ICON_STATE_UNMUTE = 1;
public static final int ICON_STATE_MUTE = 2;
public static final int ICON_STATE_VIBRATE = 3;
public static Callback sCallback;
public static void writeEvent(int tag, Object... list) {
final long time = System.currentTimeMillis();
final StringBuilder sb = new StringBuilder("writeEvent ").append(EVENT_TAGS[tag]);
if (list != null && list.length > 0) {
sb.append(" ");
switch (tag) {
case EVENT_SHOW_DIALOG:
sb.append(SHOW_REASONS[(Integer) list[0]]).append(" keyguard=").append(list[1]);
break;
case EVENT_EXPAND:
sb.append(list[0]);
break;
case EVENT_DISMISS_DIALOG:
sb.append(DISMISS_REASONS[(Integer) list[0]]);
break;
case EVENT_ACTIVE_STREAM_CHANGED:
sb.append(AudioSystem.streamToString((Integer) list[0]));
break;
case EVENT_ICON_CLICK:
sb.append(AudioSystem.streamToString((Integer) list[0])).append(' ')
.append(iconStateToString((Integer) list[1]));
break;
case EVENT_TOUCH_LEVEL_CHANGED:
case EVENT_LEVEL_CHANGED:
case EVENT_MUTE_CHANGED:
sb.append(AudioSystem.streamToString((Integer) list[0])).append(' ')
.append(list[1]);
break;
case EVENT_INTERNAL_RINGER_MODE_CHANGED:
case EVENT_EXTERNAL_RINGER_MODE_CHANGED:
sb.append(ringerModeToString((Integer) list[0]));
break;
case EVENT_ZEN_MODE_CHANGED:
sb.append(zenModeToString((Integer) list[0]));
break;
case EVENT_SUPPRESSOR_CHANGED:
sb.append(list[0]).append(' ').append(list[1]);
break;
default:
sb.append(Arrays.asList(list));
break;
}
}
Log.i(TAG, sb.toString());
if (sCallback != null) {
sCallback.writeEvent(time, tag, list);
}
}
public static void writeState(long time, State state) {
if (sCallback != null) {
sCallback.writeState(time, state);
}
}
private static String iconStateToString(int iconState) {
switch (iconState) {
case ICON_STATE_UNMUTE: return "unmute";
case ICON_STATE_MUTE: return "mute";
case ICON_STATE_VIBRATE: return "vibrate";
default: return "unknown_state_" + iconState;
}
}
private static String ringerModeToString(int ringerMode) {
switch (ringerMode) {
case AudioManager.RINGER_MODE_SILENT: return "silent";
case AudioManager.RINGER_MODE_VIBRATE: return "vibrate";
case AudioManager.RINGER_MODE_NORMAL: return "normal";
default: return "unknown";
}
}
private static String zenModeToString(int zenMode) {
switch (zenMode) {
case Global.ZEN_MODE_OFF: return "off";
case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: return "important_interruptions";
case Global.ZEN_MODE_NO_INTERRUPTIONS: return "no_interruptions";
default: return "unknown";
}
}
public interface Callback {
void writeEvent(long time, int tag, Object[] list);
void writeState(long time, State state);
}
}

View File

@@ -0,0 +1,378 @@
/*
* Copyright (C) 2015 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.systemui.volume;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.media.IRemoteVolumeController;
import android.media.MediaMetadata;
import android.media.session.ISessionController;
import android.media.session.MediaController;
import android.media.session.MediaController.PlaybackInfo;
import android.media.session.MediaSession.QueueItem;
import android.media.session.MediaSession.Token;
import android.media.session.MediaSessionManager;
import android.media.session.MediaSessionManager.OnActiveSessionsChangedListener;
import android.media.session.PlaybackState;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
* Convenience client for all media session updates. Provides a callback interface for events
* related to remote media sessions.
*/
public class MediaSessions {
private static final String TAG = Util.logTag(MediaSessions.class);
private static final boolean USE_SERVICE_LABEL = false;
private final Context mContext;
private final H mHandler;
private final MediaSessionManager mMgr;
private final Map<Token, MediaControllerRecord> mRecords = new HashMap<>();
private final Callbacks mCallbacks;
private boolean mInit;
public MediaSessions(Context context, Looper looper, Callbacks callbacks) {
mContext = context;
mHandler = new H(looper);
mMgr = (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
mCallbacks = callbacks;
}
public void dump(PrintWriter writer) {
writer.println(getClass().getSimpleName() + " state:");
writer.print(" mInit: "); writer.println(mInit);
writer.print(" mRecords.size: "); writer.println(mRecords.size());
int i = 0;
for (MediaControllerRecord r : mRecords.values()) {
dump(++i, writer, r.controller);
}
}
public void init() {
if (D.BUG) Log.d(TAG, "init");
// will throw if no permission
mMgr.addOnActiveSessionsChangedListener(mSessionsListener, null, mHandler);
mInit = true;
postUpdateSessions();
mMgr.setRemoteVolumeController(mRvc);
}
protected void postUpdateSessions() {
if (!mInit) return;
mHandler.sendEmptyMessage(H.UPDATE_SESSIONS);
}
public void destroy() {
if (D.BUG) Log.d(TAG, "destroy");
mInit = false;
mMgr.removeOnActiveSessionsChangedListener(mSessionsListener);
}
public void setVolume(Token token, int level) {
final MediaControllerRecord r = mRecords.get(token);
if (r == null) {
Log.w(TAG, "setVolume: No record found for token " + token);
return;
}
if (D.BUG) Log.d(TAG, "Setting level to " + level);
r.controller.setVolumeTo(level, 0);
}
private void onRemoteVolumeChangedH(ISessionController session, int flags) {
final MediaController controller = new MediaController(mContext, session);
if (D.BUG) Log.d(TAG, "remoteVolumeChangedH " + controller.getPackageName() + " "
+ Util.audioManagerFlagsToString(flags));
final Token token = controller.getSessionToken();
mCallbacks.onRemoteVolumeChanged(token, flags);
}
private void onUpdateRemoteControllerH(ISessionController session) {
final MediaController controller = session != null ? new MediaController(mContext, session)
: null;
final String pkg = controller != null ? controller.getPackageName() : null;
if (D.BUG) Log.d(TAG, "updateRemoteControllerH " + pkg);
// this may be our only indication that a remote session is changed, refresh
postUpdateSessions();
}
protected void onActiveSessionsUpdatedH(List<MediaController> controllers) {
if (D.BUG) Log.d(TAG, "onActiveSessionsUpdatedH n=" + controllers.size());
final Set<Token> toRemove = new HashSet<Token>(mRecords.keySet());
for (MediaController controller : controllers) {
final Token token = controller.getSessionToken();
final PlaybackInfo pi = controller.getPlaybackInfo();
toRemove.remove(token);
if (!mRecords.containsKey(token)) {
final MediaControllerRecord r = new MediaControllerRecord(controller);
r.name = getControllerName(controller);
mRecords.put(token, r);
controller.registerCallback(r, mHandler);
}
final MediaControllerRecord r = mRecords.get(token);
final boolean remote = isRemote(pi);
if (remote) {
updateRemoteH(token, r.name, pi);
r.sentRemote = true;
}
}
for (Token t : toRemove) {
final MediaControllerRecord r = mRecords.get(t);
r.controller.unregisterCallback(r);
mRecords.remove(t);
if (D.BUG) Log.d(TAG, "Removing " + r.name + " sentRemote=" + r.sentRemote);
if (r.sentRemote) {
mCallbacks.onRemoteRemoved(t);
r.sentRemote = false;
}
}
}
private static boolean isRemote(PlaybackInfo pi) {
return pi != null && pi.getPlaybackType() == PlaybackInfo.PLAYBACK_TYPE_REMOTE;
}
protected String getControllerName(MediaController controller) {
final PackageManager pm = mContext.getPackageManager();
final String pkg = controller.getPackageName();
try {
if (USE_SERVICE_LABEL) {
final List<ResolveInfo> ris = pm.queryIntentServices(
new Intent("android.media.MediaRouteProviderService").setPackage(pkg), 0);
if (ris != null) {
for (ResolveInfo ri : ris) {
if (ri.serviceInfo == null) continue;
if (pkg.equals(ri.serviceInfo.packageName)) {
final String serviceLabel =
Objects.toString(ri.serviceInfo.loadLabel(pm), "").trim();
if (serviceLabel.length() > 0) {
return serviceLabel;
}
}
}
}
}
final ApplicationInfo ai = pm.getApplicationInfo(pkg, 0);
final String appLabel = Objects.toString(ai.loadLabel(pm), "").trim();
if (appLabel.length() > 0) {
return appLabel;
}
} catch (NameNotFoundException e) { }
return pkg;
}
private void updateRemoteH(Token token, String name, PlaybackInfo pi) {
if (mCallbacks != null) {
mCallbacks.onRemoteUpdate(token, name, pi);
}
}
private static void dump(int n, PrintWriter writer, MediaController c) {
writer.println(" Controller " + n + ": " + c.getPackageName());
final Bundle extras = c.getExtras();
final long flags = c.getFlags();
final MediaMetadata mm = c.getMetadata();
final PlaybackInfo pi = c.getPlaybackInfo();
final PlaybackState playbackState = c.getPlaybackState();
final List<QueueItem> queue = c.getQueue();
final CharSequence queueTitle = c.getQueueTitle();
final int ratingType = c.getRatingType();
final PendingIntent sessionActivity = c.getSessionActivity();
writer.println(" PlaybackState: " + Util.playbackStateToString(playbackState));
writer.println(" PlaybackInfo: " + Util.playbackInfoToString(pi));
if (mm != null) {
writer.println(" MediaMetadata.desc=" + mm.getDescription());
}
writer.println(" RatingType: " + ratingType);
writer.println(" Flags: " + flags);
if (extras != null) {
writer.println(" Extras:");
for (String key : extras.keySet()) {
writer.println(" " + key + "=" + extras.get(key));
}
}
if (queueTitle != null) {
writer.println(" QueueTitle: " + queueTitle);
}
if (queue != null && !queue.isEmpty()) {
writer.println(" Queue:");
for (QueueItem qi : queue) {
writer.println(" " + qi);
}
}
if (pi != null) {
writer.println(" sessionActivity: " + sessionActivity);
}
}
public static void dumpMediaSessions(Context context) {
final MediaSessionManager mgr = (MediaSessionManager) context
.getSystemService(Context.MEDIA_SESSION_SERVICE);
try {
final List<MediaController> controllers = mgr.getActiveSessions(null);
final int N = controllers.size();
if (D.BUG) Log.d(TAG, N + " controllers");
for (int i = 0; i < N; i++) {
final StringWriter sw = new StringWriter();
final PrintWriter pw = new PrintWriter(sw, true);
dump(i + 1, pw, controllers.get(i));
if (D.BUG) Log.d(TAG, sw.toString());
}
} catch (SecurityException e) {
Log.w(TAG, "Not allowed to get sessions", e);
}
}
private final class MediaControllerRecord extends MediaController.Callback {
private final MediaController controller;
private boolean sentRemote;
private String name;
private MediaControllerRecord(MediaController controller) {
this.controller = controller;
}
private String cb(String method) {
return method + " " + controller.getPackageName() + " ";
}
@Override
public void onAudioInfoChanged(PlaybackInfo info) {
if (D.BUG) Log.d(TAG, cb("onAudioInfoChanged") + Util.playbackInfoToString(info)
+ " sentRemote=" + sentRemote);
final boolean remote = isRemote(info);
if (!remote && sentRemote) {
mCallbacks.onRemoteRemoved(controller.getSessionToken());
sentRemote = false;
} else if (remote) {
updateRemoteH(controller.getSessionToken(), name, info);
sentRemote = true;
}
}
@Override
public void onExtrasChanged(Bundle extras) {
if (D.BUG) Log.d(TAG, cb("onExtrasChanged") + extras);
}
@Override
public void onMetadataChanged(MediaMetadata metadata) {
if (D.BUG) Log.d(TAG, cb("onMetadataChanged") + Util.mediaMetadataToString(metadata));
}
@Override
public void onPlaybackStateChanged(PlaybackState state) {
if (D.BUG) Log.d(TAG, cb("onPlaybackStateChanged") + Util.playbackStateToString(state));
}
@Override
public void onQueueChanged(List<QueueItem> queue) {
if (D.BUG) Log.d(TAG, cb("onQueueChanged") + queue);
}
@Override
public void onQueueTitleChanged(CharSequence title) {
if (D.BUG) Log.d(TAG, cb("onQueueTitleChanged") + title);
}
@Override
public void onSessionDestroyed() {
if (D.BUG) Log.d(TAG, cb("onSessionDestroyed"));
}
@Override
public void onSessionEvent(String event, Bundle extras) {
if (D.BUG) Log.d(TAG, cb("onSessionEvent") + "event=" + event + " extras=" + extras);
}
}
private final OnActiveSessionsChangedListener mSessionsListener =
new OnActiveSessionsChangedListener() {
@Override
public void onActiveSessionsChanged(List<MediaController> controllers) {
onActiveSessionsUpdatedH(controllers);
}
};
private final IRemoteVolumeController mRvc = new IRemoteVolumeController.Stub() {
@Override
public void remoteVolumeChanged(ISessionController session, int flags)
throws RemoteException {
mHandler.obtainMessage(H.REMOTE_VOLUME_CHANGED, flags, 0, session).sendToTarget();
}
@Override
public void updateRemoteController(final ISessionController session)
throws RemoteException {
mHandler.obtainMessage(H.UPDATE_REMOTE_CONTROLLER, session).sendToTarget();
}
};
private final class H extends Handler {
private static final int UPDATE_SESSIONS = 1;
private static final int REMOTE_VOLUME_CHANGED = 2;
private static final int UPDATE_REMOTE_CONTROLLER = 3;
private H(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case UPDATE_SESSIONS:
onActiveSessionsUpdatedH(mMgr.getActiveSessions(null));
break;
case REMOTE_VOLUME_CHANGED:
onRemoteVolumeChangedH((ISessionController) msg.obj, msg.arg1);
break;
case UPDATE_REMOTE_CONTROLLER:
onUpdateRemoteControllerH((ISessionController) msg.obj);
break;
}
}
}
public interface Callbacks {
void onRemoteUpdate(Token token, String name, PlaybackInfo pi);
void onRemoteRemoved(Token t);
void onRemoteVolumeChanged(Token token, int flags);
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright (C) 2015 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.systemui.volume;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.preference.PreferenceManager;
/**
* Configuration for the volume dialog + related policy.
*/
public class Prefs {
public static final String PREF_ENABLE_PROTOTYPE = "pref_enable_prototype"; // not persistent
public static final String PREF_SHOW_ALARMS = "pref_show_alarms";
public static final String PREF_SHOW_SYSTEM = "pref_show_system";
public static final String PREF_SHOW_HEADERS = "pref_show_headers";
public static final String PREF_SHOW_FAKE_REMOTE_1 = "pref_show_fake_remote_1";
public static final String PREF_SHOW_FAKE_REMOTE_2 = "pref_show_fake_remote_2";
public static final String PREF_SHOW_FOOTER = "pref_show_footer";
public static final String PREF_ZEN_FOOTER = "pref_zen_footer";
public static final String PREF_ENABLE_AUTOMUTE = "pref_enable_automute";
public static final String PREF_ENABLE_SILENT_MODE = "pref_enable_silent_mode";
public static final String PREF_DEBUG_LOGGING = "pref_debug_logging";
public static final String PREF_SEND_LOGS = "pref_send_logs";
public static final String PREF_ADJUST_SYSTEM = "pref_adjust_system";
public static final String PREF_ADJUST_VOICE_CALLS = "pref_adjust_voice_calls";
public static final String PREF_ADJUST_BLUETOOTH_SCO = "pref_adjust_bluetooth_sco";
public static final String PREF_ADJUST_MEDIA = "pref_adjust_media";
public static final String PREF_ADJUST_ALARMS = "pref_adjust_alarms";
public static final String PREF_ADJUST_NOTIFICATION = "pref_adjust_notification";
public static final boolean DEFAULT_SHOW_HEADERS = true;
public static final boolean DEFAULT_SHOW_FOOTER = true;
public static final boolean DEFAULT_ENABLE_AUTOMUTE = true;
public static final boolean DEFAULT_ENABLE_SILENT_MODE = true;
public static final boolean DEFAULT_ZEN_FOOTER = true;
public static void unregisterCallbacks(Context c, OnSharedPreferenceChangeListener listener) {
prefs(c).unregisterOnSharedPreferenceChangeListener(listener);
}
public static void registerCallbacks(Context c, OnSharedPreferenceChangeListener listener) {
prefs(c).registerOnSharedPreferenceChangeListener(listener);
}
private static SharedPreferences prefs(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context);
}
public static boolean get(Context context, String key, boolean def) {
return prefs(context).getBoolean(key, def);
}
}

View File

@@ -60,17 +60,14 @@ public class SegmentedButtons extends LinearLayout {
final Object tag = c.getTag();
final boolean selected = Objects.equals(mSelectedValue, tag);
c.setSelected(selected);
c.getCompoundDrawables()[1].setTint(mContext.getColor(selected
? R.color.segmented_button_selected : R.color.segmented_button_unselected));
}
fireOnSelected();
}
public void addButton(int labelResId, int iconResId, Object value) {
public void addButton(int labelResId, Object value) {
final Button b = (Button) mInflater.inflate(R.layout.segmented_button, this, false);
b.setTag(LABEL_RES_KEY, labelResId);
b.setText(labelResId);
b.setCompoundDrawablesWithIntrinsicBounds(0, iconResId, 0, 0);
final LayoutParams lp = (LayoutParams) b.getLayoutParams();
if (getChildCount() == 0) {
lp.leftMargin = lp.rightMargin = 0; // first button has no margin

View File

@@ -0,0 +1,78 @@
/*
* Copyright (C) 2015 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.systemui.volume;
import android.content.Context;
import android.content.res.Resources;
import android.util.ArrayMap;
import android.util.TypedValue;
import android.view.View;
import android.view.View.OnAttachStateChangeListener;
import android.widget.TextView;
/**
* Capture initial sp values for registered textviews, and update properly when configuration
* changes.
*/
public class SpTexts {
private final Context mContext;
private final ArrayMap<TextView, Integer> mTexts = new ArrayMap<>();
public SpTexts(Context context) {
mContext = context;
}
public int add(final TextView text) {
if (text == null) return 0;
final Resources res = mContext.getResources();
final float fontScale = res.getConfiguration().fontScale;
final float density = res.getDisplayMetrics().density;
final float px = text.getTextSize();
final int sp = (int)(px / fontScale / density);
mTexts.put(text, sp);
text.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
@Override
public void onViewDetachedFromWindow(View v) {
}
@Override
public void onViewAttachedToWindow(View v) {
setTextSizeH(text, sp);
}
});
return sp;
}
public void update() {
if (mTexts.isEmpty()) return;
mTexts.keyAt(0).post(mUpdateAll);
}
private void setTextSizeH(TextView text, int sp) {
text.setTextSize(TypedValue.COMPLEX_UNIT_SP, sp);
}
private final Runnable mUpdateAll = new Runnable() {
@Override
public void run() {
for (int i = 0; i < mTexts.size(); i++) {
setTextSizeH(mTexts.keyAt(i), mTexts.valueAt(i));
}
}
};
}

View File

@@ -0,0 +1,165 @@
/*
* Copyright (C) 2015 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.systemui.volume;
import android.media.AudioManager;
import android.media.MediaMetadata;
import android.media.VolumeProvider;
import android.media.session.MediaController.PlaybackInfo;
import android.media.session.PlaybackState;
import android.service.notification.ZenModeConfig.DowntimeInfo;
import android.view.View;
import android.widget.TextView;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Objects;
/**
* Static helpers for the volume dialog.
*/
class Util {
// Note: currently not shown (only used in the text footer)
private static final SimpleDateFormat HMMAA = new SimpleDateFormat("h:mm aa", Locale.US);
private static int[] AUDIO_MANAGER_FLAGS = new int[] {
AudioManager.FLAG_SHOW_UI,
AudioManager.FLAG_VIBRATE,
AudioManager.FLAG_PLAY_SOUND,
AudioManager.FLAG_ALLOW_RINGER_MODES,
AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE,
AudioManager.FLAG_SHOW_VIBRATE_HINT,
AudioManager.FLAG_SHOW_SILENT_HINT,
AudioManager.FLAG_FROM_KEY,
};
private static String[] AUDIO_MANAGER_FLAG_NAMES = new String[] {
"SHOW_UI",
"VIBRATE",
"PLAY_SOUND",
"ALLOW_RINGER_MODES",
"REMOVE_SOUND_AND_VIBRATE",
"SHOW_VIBRATE_HINT",
"SHOW_SILENT_HINT",
"FROM_KEY",
};
public static String logTag(Class<?> c) {
final String tag = "vol." + c.getSimpleName();
return tag.length() < 23 ? tag : tag.substring(0, 23);
}
public static String ringerModeToString(int ringerMode) {
switch (ringerMode) {
case AudioManager.RINGER_MODE_SILENT: return "RINGER_MODE_SILENT";
case AudioManager.RINGER_MODE_VIBRATE: return "RINGER_MODE_VIBRATE";
case AudioManager.RINGER_MODE_NORMAL: return "RINGER_MODE_NORMAL";
default: return "RINGER_MODE_UNKNOWN_" + ringerMode;
}
}
public static String mediaMetadataToString(MediaMetadata metadata) {
return metadata.getDescription().toString();
}
public static String playbackInfoToString(PlaybackInfo info) {
if (info == null) return null;
final String type = playbackInfoTypeToString(info.getPlaybackType());
final String vc = volumeProviderControlToString(info.getVolumeControl());
return String.format("PlaybackInfo[vol=%s,max=%s,type=%s,vc=%s],atts=%s",
info.getCurrentVolume(), info.getMaxVolume(), type, vc, info.getAudioAttributes());
}
public static String playbackInfoTypeToString(int type) {
switch (type) {
case PlaybackInfo.PLAYBACK_TYPE_LOCAL: return "LOCAL";
case PlaybackInfo.PLAYBACK_TYPE_REMOTE: return "REMOTE";
default: return "UNKNOWN_" + type;
}
}
public static String playbackStateStateToString(int state) {
switch (state) {
case PlaybackState.STATE_NONE: return "STATE_NONE";
case PlaybackState.STATE_STOPPED: return "STATE_STOPPED";
case PlaybackState.STATE_PAUSED: return "STATE_PAUSED";
case PlaybackState.STATE_PLAYING: return "STATE_PLAYING";
default: return "UNKNOWN_" + state;
}
}
public static String volumeProviderControlToString(int control) {
switch (control) {
case VolumeProvider.VOLUME_CONTROL_ABSOLUTE: return "VOLUME_CONTROL_ABSOLUTE";
case VolumeProvider.VOLUME_CONTROL_FIXED: return "VOLUME_CONTROL_FIXED";
case VolumeProvider.VOLUME_CONTROL_RELATIVE: return "VOLUME_CONTROL_RELATIVE";
default: return "VOLUME_CONTROL_UNKNOWN_" + control;
}
}
public static String playbackStateToString(PlaybackState playbackState) {
if (playbackState == null) return null;
return playbackStateStateToString(playbackState.getState()) + " " + playbackState;
}
public static String audioManagerFlagsToString(int value) {
return bitFieldToString(value, AUDIO_MANAGER_FLAGS, AUDIO_MANAGER_FLAG_NAMES);
}
private static String bitFieldToString(int value, int[] values, String[] names) {
if (value == 0) return "";
final StringBuilder sb = new StringBuilder();
for (int i = 0; i < values.length; i++) {
if ((value & values[i]) != 0) {
if (sb.length() > 0) sb.append(',');
sb.append(names[i]);
}
value &= ~values[i];
}
if (value != 0) {
if (sb.length() > 0) sb.append(',');
sb.append("UNKNOWN_").append(value);
}
return sb.toString();
}
public static String getShortTime(long millis) {
return HMMAA.format(new Date(millis));
}
public static String getShortTime(DowntimeInfo info) {
return ((info.endHour + 1) % 12) + ":" + (info.endMinute < 10 ? " " : "") + info.endMinute;
}
public static void setText(TextView tv, CharSequence text) {
if (Objects.equals(tv.getText(), text)) return;
tv.setText(text);
}
public static final void setVisOrGone(View v, boolean vis) {
if (v == null || (v.getVisibility() == View.VISIBLE) == vis) return;
v.setVisibility(vis ? View.VISIBLE : View.GONE);
}
public static final void setVisOrInvis(View v, boolean vis) {
if (v == null || (v.getVisibility() == View.VISIBLE) == vis) return;
v.setVisibility(vis ? View.VISIBLE : View.INVISIBLE);
}
}

View File

@@ -16,10 +16,18 @@
package com.android.systemui.volume;
import android.content.res.Configuration;
import com.android.systemui.DemoMode;
import com.android.systemui.statusbar.policy.ZenModeController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
public interface VolumeComponent extends DemoMode {
ZenModeController getZenController();
void dismissNow();
void onConfigurationChanged(Configuration newConfig);
void dump(FileDescriptor fd, PrintWriter pw, String[] args);
void register();
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,120 @@
/*
* Copyright (C) 2015 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.systemui.volume;
import android.content.Context;
import android.content.res.Configuration;
import android.media.AudioManager;
import android.media.VolumePolicy;
import android.os.Bundle;
import android.os.Handler;
import com.android.systemui.SystemUI;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.qs.tiles.DndTile;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
import com.android.systemui.statusbar.policy.ZenModeController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
/**
* Implementation of VolumeComponent backed by the new volume dialog.
*/
public class VolumeDialogComponent implements VolumeComponent {
private final SystemUI mSysui;
private final Context mContext;
private final VolumeDialogController mController;
private final ZenModeController mZenModeController;
private final VolumeDialog mDialog;
public VolumeDialogComponent(SystemUI sysui, Context context, Handler handler,
ZenModeController zen) {
mSysui = sysui;
mContext = context;
mController = new VolumeDialogController(context, null) {
@Override
protected void onUserActivityW() {
sendUserActivity();
}
};
mZenModeController = zen;
mDialog = new VolumeDialog(context, mController, zen) {
@Override
protected void onZenSettingsClickedH() {
startZenSettings();
}
};
applyConfiguration();
}
private void sendUserActivity() {
final KeyguardViewMediator kvm = mSysui.getComponent(KeyguardViewMediator.class);
if (kvm != null) {
kvm.userActivity();
}
}
private void applyConfiguration() {
mDialog.setStreamImportant(AudioManager.STREAM_ALARM, true);
mDialog.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
mDialog.setShowHeaders(false);
mDialog.setShowFooter(true);
mDialog.setZenFooter(true);
mDialog.setAutomute(true);
mDialog.setSilentMode(false);
mController.setVolumePolicy(VolumePolicy.DEFAULT);
mController.showDndTile(false);
}
@Override
public ZenModeController getZenController() {
return mZenModeController;
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
// noop
}
@Override
public void dismissNow() {
mController.dismiss();
}
@Override
public void dispatchDemoCommand(String command, Bundle args) {
// noop
}
@Override
public void register() {
mController.register();
DndTile.setCombinedIcon(mContext, true);
}
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
mController.dump(fd, pw, args);
mDialog.dump(pw);
}
private void startZenSettings() {
mSysui.getComponent(PhoneStatusBar.class).startActivityDismissingKeyguard(
ZenModePanel.ZEN_SETTINGS, true /* onlyProvisioned */, true /* dismissShade */);
}
}

View File

@@ -0,0 +1,962 @@
/*
* Copyright (C) 2015 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.systemui.volume;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.database.ContentObserver;
import android.media.AudioManager;
import android.media.AudioSystem;
import android.media.IVolumeController;
import android.media.VolumePolicy;
import android.media.session.MediaController.PlaybackInfo;
import android.media.session.MediaSession.Token;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.Vibrator;
import android.provider.Settings;
import android.service.notification.Condition;
import android.util.Log;
import android.util.SparseArray;
import com.android.systemui.R;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* Source of truth for all state / events related to the volume dialog. No presentation.
*
* All work done on a dedicated background worker thread & associated worker.
*
* Methods ending in "W" must be called on the worker thread.
*/
public class VolumeDialogController {
private static final String TAG = Util.logTag(VolumeDialogController.class);
private static final int DYNAMIC_STREAM_START_INDEX = 100;
private static final int VIBRATE_HINT_DURATION = 50;
private static final int[] STREAMS = {
AudioSystem.STREAM_ALARM,
AudioSystem.STREAM_BLUETOOTH_SCO,
AudioSystem.STREAM_DTMF,
AudioSystem.STREAM_MUSIC,
AudioSystem.STREAM_NOTIFICATION,
AudioSystem.STREAM_RING,
AudioSystem.STREAM_SYSTEM,
AudioSystem.STREAM_SYSTEM_ENFORCED,
AudioSystem.STREAM_TTS,
AudioSystem.STREAM_VOICE_CALL,
};
private final HandlerThread mWorkerThread;
private final W mWorker;
private final Context mContext;
private final AudioManager mAudio;
private final NotificationManager mNoMan;
private final ComponentName mComponent;
private final SettingObserver mObserver;
private final Receiver mReceiver = new Receiver();
private final MediaSessions mMediaSessions;
private final VC mVolumeController = new VC();
private final C mCallbacks = new C();
private final State mState = new State();
private final String[] mStreamTitles;
private final MediaSessionsCallbacks mMediaSessionsCallbacksW = new MediaSessionsCallbacks();
private final Vibrator mVibrator;
private final boolean mHasVibrator;
private boolean mEnabled;
private boolean mDestroyed;
private VolumePolicy mVolumePolicy = new VolumePolicy(true, true, false, 400);
private boolean mShowDndTile = false;
public VolumeDialogController(Context context, ComponentName component) {
mContext = context.getApplicationContext();
Events.writeEvent(Events.EVENT_COLLECTION_STARTED);
mComponent = component;
mWorkerThread = new HandlerThread(VolumeDialogController.class.getSimpleName());
mWorkerThread.start();
mWorker = new W(mWorkerThread.getLooper());
mMediaSessions = createMediaSessions(mContext, mWorkerThread.getLooper(),
mMediaSessionsCallbacksW);
mAudio = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
mNoMan = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
mObserver = new SettingObserver(mWorker);
mObserver.init();
mReceiver.init();
mStreamTitles = mContext.getResources().getStringArray(R.array.volume_stream_titles);
mVibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
mHasVibrator = mVibrator != null && mVibrator.hasVibrator();
}
public void dismiss() {
mCallbacks.onDismissRequested(Events.DISMISS_REASON_VOLUME_CONTROLLER);
}
public void register() {
try {
mAudio.setVolumeController(mVolumeController);
} catch (SecurityException e) {
Log.w(TAG, "Unable to set the volume controller", e);
return;
}
setVolumePolicy(mVolumePolicy);
showDndTile(mShowDndTile);
try {
mMediaSessions.init();
} catch (SecurityException e) {
Log.w(TAG, "No access to media sessions", e);
}
}
public void setVolumePolicy(VolumePolicy policy) {
mVolumePolicy = policy;
try {
mAudio.setVolumePolicy(mVolumePolicy);
} catch (NoSuchMethodError e) {
Log.w(TAG, "No volume policy api");
}
}
protected MediaSessions createMediaSessions(Context context, Looper looper,
MediaSessions.Callbacks callbacks) {
return new MediaSessions(context, looper, callbacks);
}
public void destroy() {
if (D.BUG) Log.d(TAG, "destroy");
if (mDestroyed) return;
mDestroyed = true;
Events.writeEvent(Events.EVENT_COLLECTION_STOPPED);
mMediaSessions.destroy();
mObserver.destroy();
mReceiver.destroy();
mWorkerThread.quitSafely();
}
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println(VolumeDialogController.class.getSimpleName() + " state:");
pw.print(" mEnabled: "); pw.println(mEnabled);
pw.print(" mDestroyed: "); pw.println(mDestroyed);
pw.print(" mVolumePolicy: "); pw.println(mVolumePolicy);
pw.print(" mEnabled: "); pw.println(mEnabled);
pw.print(" mShowDndTile: "); pw.println(mShowDndTile);
pw.print(" mHasVibrator: "); pw.println(mHasVibrator);
pw.print(" mRemoteStreams: "); pw.println(mMediaSessionsCallbacksW.mRemoteStreams
.values());
pw.println();
mMediaSessions.dump(pw);
}
public void addCallback(Callbacks callback, Handler handler) {
mCallbacks.add(callback, handler);
}
public void removeCallback(Callbacks callback) {
mCallbacks.remove(callback);
}
public void getState() {
if (mDestroyed) return;
mWorker.sendEmptyMessage(W.GET_STATE);
}
public void notifyVisible(boolean visible) {
if (mDestroyed) return;
mWorker.obtainMessage(W.NOTIFY_VISIBLE, visible ? 1 : 0, 0).sendToTarget();
}
public void userActivity() {
if (mDestroyed) return;
mWorker.removeMessages(W.USER_ACTIVITY);
mWorker.sendEmptyMessage(W.USER_ACTIVITY);
}
public void setRingerMode(int value, boolean external) {
if (mDestroyed) return;
mWorker.obtainMessage(W.SET_RINGER_MODE, value, external ? 1 : 0).sendToTarget();
}
public void setZenMode(int value) {
if (mDestroyed) return;
mWorker.obtainMessage(W.SET_ZEN_MODE, value, 0).sendToTarget();
}
public void setExitCondition(Condition condition) {
if (mDestroyed) return;
mWorker.obtainMessage(W.SET_EXIT_CONDITION, condition).sendToTarget();
}
public void setStreamMute(int stream, boolean mute) {
if (mDestroyed) return;
mWorker.obtainMessage(W.SET_STREAM_MUTE, stream, mute ? 1 : 0).sendToTarget();
}
public void setStreamVolume(int stream, int level) {
if (mDestroyed) return;
mWorker.obtainMessage(W.SET_STREAM_VOLUME, stream, level).sendToTarget();
}
public void setActiveStream(int stream) {
if (mDestroyed) return;
mWorker.obtainMessage(W.SET_ACTIVE_STREAM, stream, 0).sendToTarget();
}
public void vibrate() {
if (mHasVibrator) {
mVibrator.vibrate(VIBRATE_HINT_DURATION);
}
}
public boolean hasVibrator() {
return mHasVibrator;
}
private void onNotifyVisibleW(boolean visible) {
if (mDestroyed) return;
mAudio.notifyVolumeControllerVisible(mVolumeController, visible);
if (!visible) {
if (updateActiveStreamW(-1)) {
mCallbacks.onStateChanged(mState);
}
}
}
protected void onUserActivityW() {
// hook for subclasses
}
private boolean checkRoutedToBluetoothW(int stream) {
boolean changed = false;
if (stream == AudioManager.STREAM_MUSIC) {
final boolean routedToBluetooth =
(mAudio.getDevicesForStream(AudioManager.STREAM_MUSIC) &
(AudioManager.DEVICE_OUT_BLUETOOTH_A2DP |
AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES |
AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER)) != 0;
changed |= updateStreamRoutedToBluetoothW(stream, routedToBluetooth);
}
return changed;
}
private void onVolumeChangedW(int stream, int flags) {
final boolean showUI = (flags & AudioManager.FLAG_SHOW_UI) != 0;
final boolean fromKey = (flags & AudioManager.FLAG_FROM_KEY) != 0;
final boolean showVibrateHint = (flags & AudioManager.FLAG_SHOW_VIBRATE_HINT) != 0;
final boolean showSilentHint = (flags & AudioManager.FLAG_SHOW_SILENT_HINT) != 0;
boolean changed = false;
if (showUI) {
changed |= updateActiveStreamW(stream);
}
changed |= updateStreamLevelW(stream, mAudio.getLastAudibleStreamVolume(stream));
changed |= checkRoutedToBluetoothW(showUI ? AudioManager.STREAM_MUSIC : stream);
if (changed) {
mCallbacks.onStateChanged(mState);
}
if (showUI) {
mCallbacks.onShowRequested(Events.SHOW_REASON_VOLUME_CHANGED);
}
if (showVibrateHint) {
mCallbacks.onShowVibrateHint();
}
if (showSilentHint) {
mCallbacks.onShowSilentHint();
}
if (changed && fromKey) {
Events.writeEvent(Events.EVENT_KEY);
}
}
private boolean updateActiveStreamW(int activeStream) {
if (activeStream == mState.activeStream) return false;
mState.activeStream = activeStream;
Events.writeEvent(Events.EVENT_ACTIVE_STREAM_CHANGED, activeStream);
if (D.BUG) Log.d(TAG, "updateActiveStreamW " + activeStream);
final int s = activeStream < DYNAMIC_STREAM_START_INDEX ? activeStream : -1;
if (D.BUG) Log.d(TAG, "forceVolumeControlStream " + s);
mAudio.forceVolumeControlStream(s);
return true;
}
private StreamState streamStateW(int stream) {
StreamState ss = mState.states.get(stream);
if (ss == null) {
ss = new StreamState();
mState.states.put(stream, ss);
}
return ss;
}
private void onGetStateW() {
for (int stream : STREAMS) {
updateStreamLevelW(stream, mAudio.getLastAudibleStreamVolume(stream));
streamStateW(stream).levelMin = mAudio.getStreamMinVolume(stream);
streamStateW(stream).levelMax = mAudio.getStreamMaxVolume(stream);
updateStreamMuteW(stream, mAudio.isStreamMute(stream));
final StreamState ss = streamStateW(stream);
ss.muteSupported = mAudio.isStreamAffectedByMute(stream);
ss.name = mStreamTitles[stream];
checkRoutedToBluetoothW(stream);
}
updateRingerModeExternalW(mAudio.getRingerMode());
updateZenModeW();
updateEffectsSuppressorW(mNoMan.getEffectsSuppressor());
updateExitConditionW();
mCallbacks.onStateChanged(mState);
}
private boolean updateStreamRoutedToBluetoothW(int stream, boolean routedToBluetooth) {
final StreamState ss = streamStateW(stream);
if (ss.routedToBluetooth == routedToBluetooth) return false;
ss.routedToBluetooth = routedToBluetooth;
if (D.BUG) Log.d(TAG, "updateStreamRoutedToBluetoothW stream=" + stream
+ " routedToBluetooth=" + routedToBluetooth);
return true;
}
private boolean updateStreamLevelW(int stream, int level) {
final StreamState ss = streamStateW(stream);
if (ss.level == level) return false;
ss.level = level;
if (isLogWorthy(stream)) {
Events.writeEvent(Events.EVENT_LEVEL_CHANGED, stream, level);
}
return true;
}
private static boolean isLogWorthy(int stream) {
switch (stream) {
case AudioSystem.STREAM_ALARM:
case AudioSystem.STREAM_BLUETOOTH_SCO:
case AudioSystem.STREAM_MUSIC:
case AudioSystem.STREAM_RING:
case AudioSystem.STREAM_SYSTEM:
case AudioSystem.STREAM_VOICE_CALL:
return true;
}
return false;
}
private boolean updateStreamMuteW(int stream, boolean muted) {
final StreamState ss = streamStateW(stream);
if (ss.muted == muted) return false;
ss.muted = muted;
if (isLogWorthy(stream)) {
Events.writeEvent(Events.EVENT_MUTE_CHANGED, stream, muted);
}
if (muted && isRinger(stream)) {
updateRingerModeInternalW(mAudio.getRingerModeInternal());
}
return true;
}
private static boolean isRinger(int stream) {
return stream == AudioManager.STREAM_RING || stream == AudioManager.STREAM_NOTIFICATION;
}
private boolean updateExitConditionW() {
final Condition exitCondition = mNoMan.getZenModeCondition();
if (Objects.equals(mState.exitCondition, exitCondition)) return false;
mState.exitCondition = exitCondition;
return true;
}
private boolean updateEffectsSuppressorW(ComponentName effectsSuppressor) {
if (Objects.equals(mState.effectsSuppressor, effectsSuppressor)) return false;
mState.effectsSuppressor = effectsSuppressor;
mState.effectsSuppressorName = getApplicationName(mContext, mState.effectsSuppressor);
Events.writeEvent(Events.EVENT_SUPPRESSOR_CHANGED, mState.effectsSuppressor,
mState.effectsSuppressorName);
return true;
}
private static String getApplicationName(Context context, ComponentName component) {
if (component == null) return null;
final PackageManager pm = context.getPackageManager();
final String pkg = component.getPackageName();
try {
final ApplicationInfo ai = pm.getApplicationInfo(pkg, 0);
final String rt = Objects.toString(ai.loadLabel(pm), "").trim();
if (rt.length() > 0) {
return rt;
}
} catch (NameNotFoundException e) {}
return pkg;
}
private boolean updateZenModeW() {
final int zen = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF);
if (mState.zenMode == zen) return false;
mState.zenMode = zen;
Events.writeEvent(Events.EVENT_ZEN_MODE_CHANGED, zen);
return true;
}
private boolean updateRingerModeExternalW(int rm) {
if (rm == mState.ringerModeExternal) return false;
mState.ringerModeExternal = rm;
Events.writeEvent(Events.EVENT_EXTERNAL_RINGER_MODE_CHANGED, rm);
return true;
}
private boolean updateRingerModeInternalW(int rm) {
if (rm == mState.ringerModeInternal) return false;
mState.ringerModeInternal = rm;
Events.writeEvent(Events.EVENT_INTERNAL_RINGER_MODE_CHANGED, rm);
return true;
}
private void onSetRingerModeW(int mode, boolean external) {
if (external) {
mAudio.setRingerMode(mode);
} else {
mAudio.setRingerModeInternal(mode);
}
}
private void onSetStreamMuteW(int stream, boolean mute) {
mAudio.adjustStreamVolume(stream, mute ? AudioManager.ADJUST_MUTE
: AudioManager.ADJUST_UNMUTE, 0);
}
private void onSetStreamVolumeW(int stream, int level) {
if (D.BUG) Log.d(TAG, "onSetStreamVolume " + stream + " level=" + level);
if (stream >= DYNAMIC_STREAM_START_INDEX) {
mMediaSessionsCallbacksW.setStreamVolume(stream, level);
return;
}
mAudio.setStreamVolume(stream, level, 0);
}
private void onSetActiveStreamW(int stream) {
boolean changed = updateActiveStreamW(stream);
if (changed) {
mCallbacks.onStateChanged(mState);
}
}
private void onSetExitConditionW(Condition condition) {
mNoMan.setZenModeCondition(condition);
}
private void onSetZenModeW(int mode) {
if (D.BUG) Log.d(TAG, "onSetZenModeW " + mode);
mNoMan.setZenMode(mode);
}
private void onDismissRequestedW(int reason) {
mCallbacks.onDismissRequested(reason);
}
public void showDndTile(boolean visible) {
if (D.BUG) Log.d(TAG, "showDndTile");
mContext.sendBroadcast(new Intent("com.android.systemui.dndtile.SET_VISIBLE")
.putExtra("visible", visible));
}
private final class VC extends IVolumeController.Stub {
private final String TAG = VolumeDialogController.TAG + ".VC";
@Override
public void displaySafeVolumeWarning(int flags) throws RemoteException {
// noop
}
@Override
public void volumeChanged(int streamType, int flags) throws RemoteException {
if (D.BUG) Log.d(TAG, "volumeChanged " + AudioSystem.streamToString(streamType)
+ " " + Util.audioManagerFlagsToString(flags));
if (mDestroyed) return;
mWorker.obtainMessage(W.VOLUME_CHANGED, streamType, flags).sendToTarget();
}
@Override
public void masterMuteChanged(int flags) throws RemoteException {
if (D.BUG) Log.d(TAG, "masterMuteChanged");
}
@Override
public void setLayoutDirection(int layoutDirection) throws RemoteException {
if (D.BUG) Log.d(TAG, "setLayoutDirection");
if (mDestroyed) return;
mWorker.obtainMessage(W.LAYOUT_DIRECTION_CHANGED, layoutDirection, 0).sendToTarget();
}
@Override
public void dismiss() throws RemoteException {
if (D.BUG) Log.d(TAG, "dismiss requested");
if (mDestroyed) return;
mWorker.obtainMessage(W.DISMISS_REQUESTED, Events.DISMISS_REASON_VOLUME_CONTROLLER, 0)
.sendToTarget();
mWorker.sendEmptyMessage(W.DISMISS_REQUESTED);
}
}
private final class W extends Handler {
private static final int VOLUME_CHANGED = 1;
private static final int DISMISS_REQUESTED = 2;
private static final int GET_STATE = 3;
private static final int SET_RINGER_MODE = 4;
private static final int SET_ZEN_MODE = 5;
private static final int SET_EXIT_CONDITION = 6;
private static final int SET_STREAM_MUTE = 7;
private static final int LAYOUT_DIRECTION_CHANGED = 8;
private static final int CONFIGURATION_CHANGED = 9;
private static final int SET_STREAM_VOLUME = 10;
private static final int SET_ACTIVE_STREAM = 11;
private static final int NOTIFY_VISIBLE = 12;
private static final int USER_ACTIVITY = 13;
W(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case VOLUME_CHANGED: onVolumeChangedW(msg.arg1, msg.arg2); break;
case DISMISS_REQUESTED: onDismissRequestedW(msg.arg1); break;
case GET_STATE: onGetStateW(); break;
case SET_RINGER_MODE: onSetRingerModeW(msg.arg1, msg.arg2 != 0); break;
case SET_ZEN_MODE: onSetZenModeW(msg.arg1); break;
case SET_EXIT_CONDITION: onSetExitConditionW((Condition) msg.obj); break;
case SET_STREAM_MUTE: onSetStreamMuteW(msg.arg1, msg.arg2 != 0); break;
case LAYOUT_DIRECTION_CHANGED: mCallbacks.onLayoutDirectionChanged(msg.arg1); break;
case CONFIGURATION_CHANGED: mCallbacks.onConfigurationChanged(); break;
case SET_STREAM_VOLUME: onSetStreamVolumeW(msg.arg1, msg.arg2); break;
case SET_ACTIVE_STREAM: onSetActiveStreamW(msg.arg1); break;
case NOTIFY_VISIBLE: onNotifyVisibleW(msg.arg1 != 0);
case USER_ACTIVITY: onUserActivityW();
}
}
}
private final class C implements Callbacks {
private final HashMap<Callbacks, Handler> mCallbackMap = new HashMap<>();
public void add(Callbacks callback, Handler handler) {
if (callback == null || handler == null) throw new IllegalArgumentException();
mCallbackMap.put(callback, handler);
}
public void remove(Callbacks callback) {
mCallbackMap.remove(callback);
}
@Override
public void onShowRequested(final int reason) {
for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
entry.getValue().post(new Runnable() {
@Override
public void run() {
entry.getKey().onShowRequested(reason);
}
});
}
}
@Override
public void onDismissRequested(final int reason) {
for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
entry.getValue().post(new Runnable() {
@Override
public void run() {
entry.getKey().onDismissRequested(reason);
}
});
}
}
@Override
public void onStateChanged(final State state) {
final long time = System.currentTimeMillis();
final State copy = state.copy();
for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
entry.getValue().post(new Runnable() {
@Override
public void run() {
entry.getKey().onStateChanged(copy);
}
});
}
Events.writeState(time, copy);
}
@Override
public void onLayoutDirectionChanged(final int layoutDirection) {
for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
entry.getValue().post(new Runnable() {
@Override
public void run() {
entry.getKey().onLayoutDirectionChanged(layoutDirection);
}
});
}
}
@Override
public void onConfigurationChanged() {
for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
entry.getValue().post(new Runnable() {
@Override
public void run() {
entry.getKey().onConfigurationChanged();
}
});
}
}
@Override
public void onShowVibrateHint() {
for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
entry.getValue().post(new Runnable() {
@Override
public void run() {
entry.getKey().onShowVibrateHint();
}
});
}
}
@Override
public void onShowSilentHint() {
for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
entry.getValue().post(new Runnable() {
@Override
public void run() {
entry.getKey().onShowSilentHint();
}
});
}
}
@Override
public void onScreenOff() {
for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
entry.getValue().post(new Runnable() {
@Override
public void run() {
entry.getKey().onScreenOff();
}
});
}
}
}
private final class SettingObserver extends ContentObserver {
private final Uri SERVICE_URI = Settings.Secure.getUriFor(
Settings.Secure.VOLUME_CONTROLLER_SERVICE_COMPONENT);
private final Uri ZEN_MODE_URI =
Settings.Global.getUriFor(Settings.Global.ZEN_MODE);
private final Uri ZEN_MODE_CONFIG_URI =
Settings.Global.getUriFor(Settings.Global.ZEN_MODE_CONFIG_ETAG);
public SettingObserver(Handler handler) {
super(handler);
}
public void init() {
mContext.getContentResolver().registerContentObserver(SERVICE_URI, false, this);
mContext.getContentResolver().registerContentObserver(ZEN_MODE_URI, false, this);
mContext.getContentResolver().registerContentObserver(ZEN_MODE_CONFIG_URI, false, this);
onChange(true, SERVICE_URI);
}
public void destroy() {
mContext.getContentResolver().unregisterContentObserver(this);
}
@Override
public void onChange(boolean selfChange, Uri uri) {
boolean changed = false;
if (SERVICE_URI.equals(uri)) {
final String setting = Settings.Secure.getString(mContext.getContentResolver(),
Settings.Secure.VOLUME_CONTROLLER_SERVICE_COMPONENT);
final boolean enabled = setting != null && mComponent != null
&& mComponent.equals(ComponentName.unflattenFromString(setting));
if (enabled == mEnabled) return;
if (enabled) {
register();
}
mEnabled = enabled;
}
if (ZEN_MODE_URI.equals(uri)) {
changed = updateZenModeW();
}
if (ZEN_MODE_CONFIG_URI.equals(uri)) {
changed = updateExitConditionW();
}
if (changed) {
mCallbacks.onStateChanged(mState);
}
}
}
private final class Receiver extends BroadcastReceiver {
public void init() {
final IntentFilter filter = new IntentFilter();
filter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
filter.addAction(AudioManager.STREAM_DEVICES_CHANGED_ACTION);
filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
filter.addAction(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION);
filter.addAction(AudioManager.STREAM_MUTE_CHANGED_ACTION);
filter.addAction(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED);
filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
filter.addAction(Intent.ACTION_SCREEN_OFF);
mContext.registerReceiver(this, filter, null, mWorker);
}
public void destroy() {
mContext.unregisterReceiver(this);
}
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
boolean changed = false;
if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {
final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
final int level = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);
final int oldLevel = intent
.getIntExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, -1);
if (D.BUG) Log.d(TAG, "onReceive VOLUME_CHANGED_ACTION stream=" + stream
+ " level=" + level + " oldLevel=" + oldLevel);
changed = updateStreamLevelW(stream, level);
} else if (action.equals(AudioManager.STREAM_DEVICES_CHANGED_ACTION)) {
final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
final int devices = intent
.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_DEVICES, -1);
final int oldDevices = intent
.getIntExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_DEVICES, -1);
if (D.BUG) Log.d(TAG, "onReceive STREAM_DEVICES_CHANGED_ACTION stream="
+ stream + " devices=" + devices + " oldDevices=" + oldDevices);
changed = checkRoutedToBluetoothW(stream);
} else if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) {
final int rm = intent.getIntExtra(AudioManager.EXTRA_RINGER_MODE, -1);
if (D.BUG) Log.d(TAG, "onReceive RINGER_MODE_CHANGED_ACTION rm="
+ Util.ringerModeToString(rm));
changed = updateRingerModeExternalW(rm);
} else if (action.equals(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION)) {
final int rm = intent.getIntExtra(AudioManager.EXTRA_RINGER_MODE, -1);
if (D.BUG) Log.d(TAG, "onReceive INTERNAL_RINGER_MODE_CHANGED_ACTION rm="
+ Util.ringerModeToString(rm));
changed = updateRingerModeInternalW(rm);
} else if (action.equals(AudioManager.STREAM_MUTE_CHANGED_ACTION)) {
final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
final boolean muted = intent
.getBooleanExtra(AudioManager.EXTRA_STREAM_VOLUME_MUTED, false);
if (D.BUG) Log.d(TAG, "onReceive STREAM_MUTE_CHANGED_ACTION stream=" + stream
+ " muted=" + muted);
changed = updateStreamMuteW(stream, muted);
} else if (action.equals(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED)) {
if (D.BUG) Log.d(TAG, "onReceive ACTION_EFFECTS_SUPPRESSOR_CHANGED");
changed = updateEffectsSuppressorW(mNoMan.getEffectsSuppressor());
} else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
if (D.BUG) Log.d(TAG, "onReceive ACTION_CONFIGURATION_CHANGED");
mCallbacks.onConfigurationChanged();
} else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
if (D.BUG) Log.d(TAG, "onReceive ACTION_SCREEN_OFF");
mCallbacks.onScreenOff();
}
if (changed) {
mCallbacks.onStateChanged(mState);
}
}
}
private final class MediaSessionsCallbacks implements MediaSessions.Callbacks {
private final HashMap<Token, Integer> mRemoteStreams = new HashMap<>();
private int mNextStream = DYNAMIC_STREAM_START_INDEX;
@Override
public void onRemoteUpdate(Token token, String name, PlaybackInfo pi) {
if (!mRemoteStreams.containsKey(token)) {
mRemoteStreams.put(token, mNextStream);
if (D.BUG) Log.d(TAG, "onRemoteUpdate: " + name + " is stream " + mNextStream);
mNextStream++;
}
final int stream = mRemoteStreams.get(token);
boolean changed = mState.states.indexOfKey(stream) < 0;
final StreamState ss = streamStateW(stream);
ss.dynamic = true;
ss.levelMin = 0;
ss.levelMax = pi.getMaxVolume();
if (ss.level != pi.getCurrentVolume()) {
ss.level = pi.getCurrentVolume();
changed = true;
}
if (!Objects.equals(ss.name, name)) {
ss.name = name;
changed = true;
}
if (changed) {
if (D.BUG) Log.d(TAG, "onRemoteUpdate: " + name + ": " + ss.level
+ " of " + ss.levelMax);
mCallbacks.onStateChanged(mState);
}
}
@Override
public void onRemoteVolumeChanged(Token token, int flags) {
final int stream = mRemoteStreams.get(token);
final boolean showUI = (flags & AudioManager.FLAG_SHOW_UI) != 0;
boolean changed = updateActiveStreamW(stream);
if (showUI) {
changed |= checkRoutedToBluetoothW(AudioManager.STREAM_MUSIC);
}
if (changed) {
mCallbacks.onStateChanged(mState);
}
if (showUI) {
mCallbacks.onShowRequested(Events.SHOW_REASON_REMOTE_VOLUME_CHANGED);
}
}
@Override
public void onRemoteRemoved(Token token) {
final int stream = mRemoteStreams.get(token);
mState.states.remove(stream);
if (mState.activeStream == stream) {
updateActiveStreamW(-1);
}
mCallbacks.onStateChanged(mState);
}
public void setStreamVolume(int stream, int level) {
final Token t = findToken(stream);
if (t == null) {
Log.w(TAG, "setStreamVolume: No token found for stream: " + stream);
return;
}
mMediaSessions.setVolume(t, level);
}
private Token findToken(int stream) {
for (Map.Entry<Token, Integer> entry : mRemoteStreams.entrySet()) {
if (entry.getValue().equals(stream)) {
return entry.getKey();
}
}
return null;
}
}
public static final class StreamState {
public boolean dynamic;
public int level;
public int levelMin;
public int levelMax;
public boolean muted;
public boolean muteSupported;
public String name;
public boolean routedToBluetooth;
public StreamState copy() {
final StreamState rt = new StreamState();
rt.dynamic = dynamic;
rt.level = level;
rt.levelMin = levelMin;
rt.levelMax = levelMax;
rt.muted = muted;
rt.muteSupported = muteSupported;
rt.name = name;
rt.routedToBluetooth = routedToBluetooth;
return rt;
}
}
public static final class State {
public static int NO_ACTIVE_STREAM = -1;
public final SparseArray<StreamState> states = new SparseArray<StreamState>();
public int ringerModeInternal;
public int ringerModeExternal;
public int zenMode;
public ComponentName effectsSuppressor;
public String effectsSuppressorName;
public Condition exitCondition;
public int activeStream = NO_ACTIVE_STREAM;
public State copy() {
final State rt = new State();
for (int i = 0; i < states.size(); i++) {
rt.states.put(states.keyAt(i), states.valueAt(i).copy());
}
rt.ringerModeExternal = ringerModeExternal;
rt.ringerModeInternal = ringerModeInternal;
rt.zenMode = zenMode;
if (effectsSuppressor != null) rt.effectsSuppressor = effectsSuppressor.clone();
rt.effectsSuppressorName = effectsSuppressorName;
if (exitCondition != null) rt.exitCondition = exitCondition.copy();
rt.activeStream = activeStream;
return rt;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("{");
for (int i = 0; i < states.size(); i++) {
if (i > 0) sb.append(',');
final int stream = states.keyAt(i);
final StreamState ss = states.valueAt(i);
sb.append(AudioSystem.streamToString(stream)).append(":").append(ss.level)
.append("[").append(ss.levelMin).append("..").append(ss.levelMax);
if (ss.muted) sb.append(" [MUTED]");
}
sb.append(",ringerModeExternal:").append(ringerModeExternal);
sb.append(",ringerModeInternal:").append(ringerModeInternal);
sb.append(",zenMode:").append(zenMode);
sb.append(",effectsSuppressor:").append(effectsSuppressor);
sb.append(",effectsSuppressorName:").append(effectsSuppressorName);
sb.append(",exitCondition:").append(exitCondition);
sb.append(",activeStream:").append(activeStream);
return sb.append('}').toString();
}
}
public interface Callbacks {
void onShowRequested(int reason);
void onDismissRequested(int reason);
void onStateChanged(State state);
void onLayoutDirectionChanged(int layoutDirection);
void onConfigurationChanged();
void onShowVibrateHint();
void onShowSilentHint();
void onScreenOff();
}
}

View File

@@ -384,7 +384,7 @@ public class VolumePanel extends Handler implements DemoMode {
final Window window = mDialog.getWindow();
window.requestFeature(Window.FEATURE_NO_TITLE);
mDialog.setCanceledOnTouchOutside(true);
mDialog.setContentView(com.android.systemui.R.layout.volume_dialog);
mDialog.setContentView(com.android.systemui.R.layout.volume_panel_dialog);
mDialog.setOnDismissListener(new OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {

View File

@@ -0,0 +1,184 @@
/*
* Copyright (C) 2015 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.systemui.volume;
import android.content.Context;
import android.content.res.Configuration;
import android.media.AudioManager;
import android.media.IRemoteVolumeController;
import android.media.IVolumeController;
import android.media.VolumePolicy;
import android.media.session.ISessionController;
import android.media.session.MediaController;
import android.media.session.MediaSessionManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.qs.tiles.DndTile;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
import com.android.systemui.statusbar.policy.ZenModeController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
/**
* Implementation of VolumeComponent backed by the old volume panel.
*/
public class VolumePanelComponent implements VolumeComponent {
private final SystemUI mSysui;
private final Context mContext;
private final Handler mHandler;
private final VolumeController mVolumeController;
private final RemoteVolumeController mRemoteVolumeController;
private final AudioManager mAudioManager;
private final MediaSessionManager mMediaSessionManager;
private VolumePanel mPanel;
private int mDismissDelay;
public VolumePanelComponent(SystemUI sysui, Context context, Handler handler,
ZenModeController controller) {
mSysui = sysui;
mContext = context;
mHandler = handler;
mAudioManager = context.getSystemService(AudioManager.class);
mMediaSessionManager = context.getSystemService(MediaSessionManager.class);
mVolumeController = new VolumeController();
mRemoteVolumeController = new RemoteVolumeController();
mDismissDelay = mContext.getResources().getInteger(R.integer.volume_panel_dismiss_delay);
mPanel = new VolumePanel(mContext, controller);
mPanel.setCallback(new VolumePanel.Callback() {
@Override
public void onZenSettings() {
mHandler.removeCallbacks(mStartZenSettings);
mHandler.post(mStartZenSettings);
}
@Override
public void onInteraction() {
final KeyguardViewMediator kvm = mSysui.getComponent(KeyguardViewMediator.class);
if (kvm != null) {
kvm.userActivity();
}
}
@Override
public void onVisible(boolean visible) {
if (mAudioManager != null && mVolumeController != null) {
mAudioManager.notifyVolumeControllerVisible(mVolumeController, visible);
}
}
});
}
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mPanel != null) {
mPanel.dump(fd, pw, args);
}
}
public void register() {
mAudioManager.setVolumeController(mVolumeController);
mAudioManager.setVolumePolicy(VolumePolicy.DEFAULT);
mMediaSessionManager.setRemoteVolumeController(mRemoteVolumeController);
DndTile.setVisible(mContext, false);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
if (mPanel != null) {
mPanel.onConfigurationChanged(newConfig);
}
}
@Override
public ZenModeController getZenController() {
return mPanel.getZenController();
}
@Override
public void dispatchDemoCommand(String command, Bundle args) {
mPanel.dispatchDemoCommand(command, args);
}
@Override
public void dismissNow() {
mPanel.postDismiss(0);
}
private final Runnable mStartZenSettings = new Runnable() {
@Override
public void run() {
mSysui.getComponent(PhoneStatusBar.class).startActivityDismissingKeyguard(
ZenModePanel.ZEN_SETTINGS, true /* onlyProvisioned */, true /* dismissShade */);
mPanel.postDismiss(mDismissDelay);
}
};
private final class RemoteVolumeController extends IRemoteVolumeController.Stub {
@Override
public void remoteVolumeChanged(ISessionController binder, int flags)
throws RemoteException {
MediaController controller = new MediaController(mContext, binder);
mPanel.postRemoteVolumeChanged(controller, flags);
}
@Override
public void updateRemoteController(ISessionController session) throws RemoteException {
mPanel.postRemoteSliderVisibility(session != null);
// TODO stash default session in case the slider can be opened other
// than by remoteVolumeChanged.
}
}
/** For now, simply host an unmodified base volume panel in this process. */
private final class VolumeController extends IVolumeController.Stub {
@Override
public void displaySafeVolumeWarning(int flags) throws RemoteException {
mPanel.postDisplaySafeVolumeWarning(flags);
}
@Override
public void volumeChanged(int streamType, int flags)
throws RemoteException {
mPanel.postVolumeChanged(streamType, flags);
}
@Override
public void masterMuteChanged(int flags) throws RemoteException {
// no-op
}
@Override
public void setLayoutDirection(int layoutDirection)
throws RemoteException {
mPanel.postLayoutDirection(layoutDirection);
}
@Override
public void dismiss() throws RemoteException {
dismissNow();
}
}
}

View File

@@ -29,25 +29,17 @@ import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
import android.media.AudioManager;
import android.media.IRemoteVolumeController;
import android.media.IVolumeController;
import android.media.VolumePolicy;
import android.media.session.ISessionController;
import android.media.session.MediaController;
import android.media.session.MediaSessionManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.qs.tiles.DndTile;
import com.android.systemui.statusbar.ServiceMonitor;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.statusbar.policy.ZenModeControllerImpl;
@@ -59,6 +51,8 @@ public class VolumeUI extends SystemUI {
private static final String TAG = "VolumeUI";
private static boolean LOGD = Log.isLoggable(TAG, Log.DEBUG);
private static final boolean USE_OLD_VOLUME = SystemProperties.getBoolean("volume.old", false);
private final Handler mHandler = new Handler();
private final Receiver mReceiver = new Receiver();
private final RestorationNotification mRestorationNotification = new RestorationNotification();
@@ -67,12 +61,10 @@ public class VolumeUI extends SystemUI {
private AudioManager mAudioManager;
private NotificationManager mNotificationManager;
private MediaSessionManager mMediaSessionManager;
private VolumeController mVolumeController;
private RemoteVolumeController mRemoteVolumeController;
private ServiceMonitor mVolumeControllerService;
private VolumePanel mPanel;
private int mDismissDelay;
private VolumePanelComponent mOldVolume;
private VolumeDialogComponent mNewVolume;
@Override
public void start() {
@@ -83,10 +75,10 @@ public class VolumeUI extends SystemUI {
(NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
mMediaSessionManager = (MediaSessionManager) mContext
.getSystemService(Context.MEDIA_SESSION_SERVICE);
initPanel();
mVolumeController = new VolumeController();
mRemoteVolumeController = new RemoteVolumeController();
putComponent(VolumeComponent.class, mVolumeController);
final ZenModeController zenController = new ZenModeControllerImpl(mContext, mHandler);
mOldVolume = new VolumePanelComponent(this, mContext, mHandler, zenController);
mNewVolume = new VolumeDialogComponent(this, mContext, null, zenController);
putComponent(VolumeComponent.class, getVolumeComponent());
mReceiver.start();
mVolumeControllerService = new ServiceMonitor(TAG, LOGD,
mContext, Settings.Secure.VOLUME_CONTROLLER_SERVICE_COMPONENT,
@@ -94,30 +86,30 @@ public class VolumeUI extends SystemUI {
mVolumeControllerService.start();
}
private VolumeComponent getVolumeComponent() {
return USE_OLD_VOLUME ? mOldVolume : mNewVolume;
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (mPanel != null) {
mPanel.onConfigurationChanged(newConfig);
}
if (!mEnabled) return;
getVolumeComponent().onConfigurationChanged(newConfig);
}
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.print("mEnabled="); pw.println(mEnabled);
if (!mEnabled) return;
pw.print("mVolumeControllerService="); pw.println(mVolumeControllerService.getComponent());
if (mPanel != null) {
mPanel.dump(fd, pw, args);
}
getVolumeComponent().dump(fd, pw, args);
}
private void setVolumeController(boolean register) {
private void setDefaultVolumeController(boolean register) {
if (register) {
if (LOGD) Log.d(TAG, "Registering default volume controller");
mAudioManager.setVolumeController(mVolumeController);
mAudioManager.setVolumePolicy(VolumePolicy.DEFAULT);
mMediaSessionManager.setRemoteVolumeController(mRemoteVolumeController);
DndTile.setVisible(mContext, false);
if (LOGD) Log.d(TAG, "Registering default volume controller");
getVolumeComponent().register();
} else {
if (LOGD) Log.d(TAG, "Unregistering default volume controller");
mAudioManager.setVolumeController(null);
@@ -125,33 +117,6 @@ public class VolumeUI extends SystemUI {
}
}
private void initPanel() {
mDismissDelay = mContext.getResources().getInteger(R.integer.volume_panel_dismiss_delay);
mPanel = new VolumePanel(mContext, new ZenModeControllerImpl(mContext, mHandler));
mPanel.setCallback(new VolumePanel.Callback() {
@Override
public void onZenSettings() {
mHandler.removeCallbacks(mStartZenSettings);
mHandler.post(mStartZenSettings);
}
@Override
public void onInteraction() {
final KeyguardViewMediator kvm = getComponent(KeyguardViewMediator.class);
if (kvm != null) {
kvm.userActivity();
}
}
@Override
public void onVisible(boolean visible) {
if (mAudioManager != null && mVolumeController != null) {
mAudioManager.notifyVolumeControllerVisible(mVolumeController, visible);
}
}
});
}
private String getAppLabel(ComponentName component) {
final String pkg = component.getPackageName();
try {
@@ -179,83 +144,11 @@ public class VolumeUI extends SystemUI {
d.show();
}
private final Runnable mStartZenSettings = new Runnable() {
@Override
public void run() {
getComponent(PhoneStatusBar.class).startActivityDismissingKeyguard(
ZenModePanel.ZEN_SETTINGS, true /* onlyProvisioned */, true /* dismissShade */);
mPanel.postDismiss(mDismissDelay);
}
};
/** For now, simply host an unmodified base volume panel in this process. */
private final class VolumeController extends IVolumeController.Stub implements VolumeComponent {
@Override
public void displaySafeVolumeWarning(int flags) throws RemoteException {
mPanel.postDisplaySafeVolumeWarning(flags);
}
@Override
public void volumeChanged(int streamType, int flags)
throws RemoteException {
mPanel.postVolumeChanged(streamType, flags);
}
@Override
public void masterMuteChanged(int flags) throws RemoteException {
// no-op
}
@Override
public void setLayoutDirection(int layoutDirection)
throws RemoteException {
mPanel.postLayoutDirection(layoutDirection);
}
@Override
public void dismiss() throws RemoteException {
dismissNow();
}
@Override
public ZenModeController getZenController() {
return mPanel.getZenController();
}
@Override
public void dispatchDemoCommand(String command, Bundle args) {
mPanel.dispatchDemoCommand(command, args);
}
@Override
public void dismissNow() {
mPanel.postDismiss(0);
}
}
private final class RemoteVolumeController extends IRemoteVolumeController.Stub {
@Override
public void remoteVolumeChanged(ISessionController binder, int flags)
throws RemoteException {
MediaController controller = new MediaController(mContext, binder);
mPanel.postRemoteVolumeChanged(controller, flags);
}
@Override
public void updateRemoteController(ISessionController session) throws RemoteException {
mPanel.postRemoteSliderVisibility(session != null);
// TODO stash default session in case the slider can be opened other
// than by remoteVolumeChanged.
}
}
private final class ServiceMonitorCallbacks implements ServiceMonitor.Callbacks {
@Override
public void onNoService() {
if (LOGD) Log.d(TAG, "onNoService");
setVolumeController(true);
setDefaultVolumeController(true);
mRestorationNotification.hide();
if (!mVolumeControllerService.isPackageAvailable()) {
mVolumeControllerService.setComponent(null);
@@ -267,8 +160,8 @@ public class VolumeUI extends SystemUI {
if (LOGD) Log.d(TAG, "onServiceStartAttempt");
// poke the setting to update the uid
mVolumeControllerService.setComponent(mVolumeControllerService.getComponent());
setVolumeController(false);
mVolumeController.dismissNow();
setDefaultVolumeController(false);
getVolumeComponent().dismissNow();
mRestorationNotification.show();
return 0;
}

View File

@@ -0,0 +1,216 @@
/*
* Copyright (C) 2015 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.systemui.volume;
import android.animation.LayoutTransition;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.provider.Settings.Global;
import android.service.notification.Condition;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.LinearLayout;
import android.widget.Switch;
import android.widget.TextView;
import com.android.systemui.R;
import com.android.systemui.statusbar.policy.ZenModeController;
/**
* Switch bar + zen mode panel (conditions) attached to the bottom of the volume dialog.
*/
public class ZenFooter extends LinearLayout {
private static final String TAG = Util.logTag(ZenFooter.class);
private final Context mContext;
private final float mSecondaryAlpha;
private final LayoutTransition mLayoutTransition;
private ZenModeController mController;
private Switch mSwitch;
private ZenModePanel mZenModePanel;
private View mZenModePanelButtons;
private View mZenModePanelMoreButton;
private View mZenModePanelDoneButton;
private View mSwitchBar;
private View mSwitchBarIcon;
private View mSummary;
private TextView mSummaryLine1;
private TextView mSummaryLine2;
private boolean mFooterExpanded;
private int mZen = -1;
private Callback mCallback;
public ZenFooter(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
mSecondaryAlpha = getFloat(context.getResources(), R.dimen.volume_secondary_alpha);
mLayoutTransition = new LayoutTransition();
mLayoutTransition.setDuration(new ValueAnimator().getDuration() / 2);
mLayoutTransition.disableTransitionType(LayoutTransition.DISAPPEARING);
mLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
}
private static float getFloat(Resources r, int resId) {
final TypedValue tv = new TypedValue();
r.getValue(resId, tv, true);
return tv.getFloat();
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mSwitchBar = findViewById(R.id.volume_zen_switch_bar);
mSwitchBarIcon = findViewById(R.id.volume_zen_switch_bar_icon);
mSwitch = (Switch) findViewById(R.id.volume_zen_switch);
mZenModePanel = (ZenModePanel) findViewById(R.id.zen_mode_panel);
mZenModePanelButtons = findViewById(R.id.volume_zen_mode_panel_buttons);
mZenModePanelMoreButton = findViewById(R.id.volume_zen_mode_panel_more);
mZenModePanelDoneButton = findViewById(R.id.volume_zen_mode_panel_done);
mSummary = findViewById(R.id.volume_zen_panel_summary);
mSummaryLine1 = (TextView) findViewById(R.id.volume_zen_panel_summary_line_1);
mSummaryLine2 = (TextView) findViewById(R.id.volume_zen_panel_summary_line_2);
}
public void init(ZenModeController controller, Callback callback) {
mCallback = callback;
mController = controller;
mZenModePanel.init(controller);
mZenModePanel.setEmbedded(true);
mSwitch.setOnCheckedChangeListener(mCheckedListener);
mController.addCallback(new ZenModeController.Callback() {
@Override
public void onZenChanged(int zen) {
setZen(zen);
}
@Override
public void onExitConditionChanged(Condition exitCondition) {
update();
}
});
mSwitchBar.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mSwitch.setChecked(!mSwitch.isChecked());
}
});
mZenModePanelMoreButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (mCallback != null) {
mCallback.onSettingsClicked();
}
}
});
mZenModePanelDoneButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (mCallback != null) {
mCallback.onDoneClicked();
}
}
});
mZen = mController.getZen();
update();
}
private void setZen(int zen) {
if (mZen == zen) return;
mZen = zen;
update();
}
public boolean isZen() {
return isZenPriority() || isZenNone();
}
private boolean isZenPriority() {
return mZen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
}
private boolean isZenNone() {
return mZen == Global.ZEN_MODE_NO_INTERRUPTIONS;
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
setLayoutTransition(null);
setFooterExpanded(false);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
setLayoutTransition(mLayoutTransition);
}
private boolean setFooterExpanded(boolean expanded) {
if (mFooterExpanded == expanded) return false;
mFooterExpanded = expanded;
update();
if (mCallback != null) {
mCallback.onFooterExpanded();
}
return true;
}
public boolean isFooterExpanded() {
return mFooterExpanded;
}
public void update() {
final boolean isZen = isZen();
mSwitch.setOnCheckedChangeListener(null);
mSwitch.setChecked(isZen);
mSwitch.setOnCheckedChangeListener(mCheckedListener);
Util.setVisOrGone(mZenModePanel, isZen && mFooterExpanded);
Util.setVisOrGone(mZenModePanelButtons, isZen && mFooterExpanded);
Util.setVisOrGone(mSummary, isZen && !mFooterExpanded);
mSwitchBarIcon.setAlpha(isZen ? 1 : mSecondaryAlpha);
final String line1 =
isZenPriority() ? mContext.getString(R.string.interruption_level_priority)
: isZenNone() ? mContext.getString(R.string.interruption_level_none)
: null;
Util.setText(mSummaryLine1, line1);
Util.setText(mSummaryLine2, mZenModePanel.getExitConditionText());
}
private final OnCheckedChangeListener mCheckedListener = new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (D.BUG) Log.d(TAG, "onCheckedChanged " + isChecked);
if (isChecked != isZen()) {
final int newZen = isChecked ? Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
: Global.ZEN_MODE_OFF;
mZen = newZen; // this one's optimistic
setFooterExpanded(isChecked);
mController.setZen(newZen);
}
}
};
public interface Callback {
void onFooterExpanded();
void onSettingsClicked();
void onDoneClicked();
}
}

View File

@@ -23,7 +23,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.res.Resources;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Handler;
@@ -150,6 +149,7 @@ public class ZenModePanel extends LinearLayout {
if (mEmbedded == embedded) return;
mEmbedded = embedded;
mZenButtonsContainer.setLayoutTransition(mEmbedded ? null : newLayoutTransition(null));
setLayoutTransition(mEmbedded ? null : newLayoutTransition(null));
if (mEmbedded) {
mZenButtonsContainer.setBackground(null);
} else {
@@ -166,12 +166,10 @@ public class ZenModePanel extends LinearLayout {
super.onFinishInflate();
mZenButtons = (SegmentedButtons) findViewById(R.id.zen_buttons);
mZenButtons.addButton(R.string.interruption_level_none, R.drawable.ic_zen_none,
Global.ZEN_MODE_NO_INTERRUPTIONS);
mZenButtons.addButton(R.string.interruption_level_priority, R.drawable.ic_zen_important,
mZenButtons.addButton(R.string.interruption_level_none, Global.ZEN_MODE_NO_INTERRUPTIONS);
mZenButtons.addButton(R.string.interruption_level_priority,
Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS);
mZenButtons.addButton(R.string.interruption_level_all, R.drawable.ic_zen_all,
Global.ZEN_MODE_OFF);
mZenButtons.addButton(R.string.interruption_level_all, Global.ZEN_MODE_OFF);
mZenButtons.setCallback(mZenButtonsCallback);
mZenButtonsContainer = (ViewGroup) findViewById(R.id.zen_buttons_container);
@@ -275,6 +273,7 @@ public class ZenModePanel extends LinearLayout {
private void setExpanded(boolean expanded) {
if (expanded == mExpanded) return;
if (DEBUG) Log.d(mTag, "setExpanded " + expanded);
mExpanded = expanded;
if (mExpanded) {
ensureSelection();
@@ -358,6 +357,10 @@ public class ZenModePanel extends LinearLayout {
return condition == null ? null : condition.copy();
}
public String getExitConditionText() {
return mExitConditionText;
}
private void refreshExitConditionText() {
if (mExitCondition == null) {
mExitConditionText = foreverSummary();
@@ -428,7 +431,7 @@ public class ZenModePanel extends LinearLayout {
mZenSubheadExpanded.setVisibility(expanded ? VISIBLE : GONE);
mZenSubheadCollapsed.setVisibility(!expanded ? VISIBLE : GONE);
mMoreSettings.setVisibility(zenImportant && expanded ? VISIBLE : GONE);
mZenConditions.setVisibility(!zenOff && expanded ? VISIBLE : GONE);
mZenConditions.setVisibility(mEmbedded || !zenOff && expanded ? VISIBLE : GONE);
if (zenNone) {
mZenSubheadExpanded.setText(R.string.zen_no_interruptions_with_warning);