VolumeZen: combine ringer/notification volume and zen.

- Implement a new volume panel widget, combining volume and
  zen mode + conditions.
- Show zen mode + conditions when modifying ringer or notification
  streams.
- Host the volume panel widget in a dialog when being controlled
  by the audio service / volume keys.
- Remove support for multiple sliders in the volume panel.
- Remove support for separate ringer + notification volumes
  in the volume panel.
- Move volume panel resources up to SystemUI.
- Create a new combined Notifications quick settings tile.
- Host the volume panel widget in the quick settings panel under
  Notifications.
- When the quick settings detail panel is visible, route the volume
  keys to the embedded widget instead of showing a redundant dialog.
- Create common styles for quick settings text to be closer to spec.
- Update the framework resources for the ringer stream.
- Show the ringer icons in global actions.
- Add "until you turn this off" back as a separate zen condition.
- Disable time condition buttons when they are N/A.
- Don't allow volume changes to set ringer mode silent.

Bug:15186070
Change-Id: Id5e321dd1d5e7c4cf3917027ffbdf7e80d38b00d
This commit is contained in:
John Spurlock
2014-05-23 11:58:00 -04:00
parent 41b170d606
commit 8600534df6
40 changed files with 1207 additions and 823 deletions

View File

@@ -1,23 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
* Copyright 2013, 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.
*/
-->
Copyright (C) 2014 The Android Open Source Project
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/ic_audio_ring_notif_am_alpha"
android:autoMirrored="true"
android:tint="?attr/colorControlNormal" />
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" >
<size
android:width="32dp"
android:height="32dp"/>
<viewport
android:viewportWidth="24.0"
android:viewportHeight="24.0"/>
<path
android:fill="#8A000000"
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

@@ -1,23 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
* Copyright 2013, 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.
*/
-->
Copyright (C) 2014 The Android Open Source Project
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/ic_audio_ring_notif_mute_am_alpha"
android:autoMirrored="true"
android:tint="?attr/colorControlNormal" />
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" >
<size
android:width="32dp"
android:height="32dp"/>
<viewport
android:viewportWidth="24.0"
android:viewportHeight="24.0"/>
<path
android:fill="#8A000000"
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,10.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.7C9.5,4.3 9.0,4.5 8.6,4.7l9.4,9.4L18.0,10.5zM17.7,19.0l2.0,2.0l1.3,-1.3L4.3,3.0L3.0,4.3l2.9,2.9C5.3,8.2 5.0,9.3 5.0,10.5L5.0,16.0l-2.0,2.0l0.0,1.0L17.7,19.0z" />
</vector>

View File

@@ -1,23 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
* Copyright 2013, 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.
*/
-->
Copyright (C) 2014 The Android Open Source Project
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/ic_audio_ring_notif_vibrate_am_alpha"
android:autoMirrored="true"
android:tint="?attr/colorControlNormal" />
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" >
<size
android:width="32dp"
android:height="32dp"/>
<viewport
android:viewportWidth="24.0"
android:viewportHeight="24.0"/>
<path
android:fill="#8A000000"
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

@@ -37,7 +37,7 @@
android:layout_marginEnd="8dp"
android:layout_marginTop="6dp"
android:layout_marginBottom="6dp"
android:src="@drawable/ic_audio_vol_mute"
android:src="@drawable/ic_audio_ring_notif_mute"
android:scaleType="center"
android:duplicateParentState="true"
android:background="@drawable/silent_mode_indicator"
@@ -94,7 +94,7 @@
android:layout_marginEnd="8dp"
android:layout_marginTop="6dp"
android:layout_marginBottom="6dp"
android:src="@drawable/ic_audio_vol"
android:src="@drawable/ic_audio_ring_notif"
android:scaleType="center"
android:duplicateParentState="true"
android:background="@drawable/silent_mode_indicator"

View File

@@ -1,51 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2007 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/visible_panel"
android:orientation="horizontal"
android:layout_width="300dp"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/slider_group"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<!-- Sliders go here -->
</LinearLayout>
<ImageView
android:id="@+id/expand_button_divider"
android:src="?attr/dividerVertical"
android:layout_width="wrap_content"
android:layout_height="32dip"
android:scaleType="fitXY"
android:layout_gravity="top"
android:layout_marginTop="16dip"
android:layout_marginBottom="16dip" />
<ImageView
android:id="@+id/expand_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:padding="16dip"
android:background="?attr/selectableItemBackground"
android:src="@drawable/ic_sysbar_quicksettings" />
</LinearLayout>

View File

@@ -206,9 +206,6 @@
<!-- Default width for a textview error popup -->
<dimen name="textview_error_popup_default_width">240dip</dimen>
<!-- Volume panel y offset -->
<dimen name="volume_panel_top">16dp</dimen>
<!-- Default padding to apply to AppWidgetHostViews containing widgets targeting API level 14 and up. -->
<dimen name="default_app_widget_padding_left">8dp</dimen>
<dimen name="default_app_widget_padding_top">8dp</dimen>

View File

@@ -69,8 +69,6 @@
<java-symbol type="id" name="edittext_container" />
<java-symbol type="id" name="enter_pin_section" />
<java-symbol type="id" name="expand_activities_button" />
<java-symbol type="id" name="expand_button" />
<java-symbol type="id" name="expand_button_divider" />
<java-symbol type="id" name="expires_on" />
<java-symbol type="id" name="find_next" />
<java-symbol type="id" name="find_prev" />
@@ -161,9 +159,7 @@
<java-symbol type="id" name="share" />
<java-symbol type="id" name="shortcut" />
<java-symbol type="id" name="skip_button" />
<java-symbol type="id" name="slider_group" />
<java-symbol type="id" name="split_action_bar" />
<java-symbol type="id" name="stream_icon" />
<java-symbol type="id" name="submit_area" />
<java-symbol type="id" name="switch_new" />
<java-symbol type="id" name="switch_old" />
@@ -181,7 +177,6 @@
<java-symbol type="id" name="topPanel" />
<java-symbol type="id" name="up" />
<java-symbol type="id" name="value" />
<java-symbol type="id" name="visible_panel" />
<java-symbol type="id" name="websearch" />
<java-symbol type="id" name="wifi_p2p_wps_pin" />
<java-symbol type="id" name="year" />
@@ -343,7 +338,6 @@
<java-symbol type="dimen" name="search_view_preferred_width" />
<java-symbol type="dimen" name="textview_error_popup_default_width" />
<java-symbol type="dimen" name="toast_y_offset" />
<java-symbol type="dimen" name="volume_panel_top" />
<java-symbol type="dimen" name="action_bar_stacked_max_height" />
<java-symbol type="dimen" name="action_bar_stacked_tab_max_width" />
<java-symbol type="dimen" name="notification_text_size" />
@@ -1199,8 +1193,6 @@
<java-symbol type="layout" name="time_picker_legacy" />
<java-symbol type="layout" name="time_picker_dialog" />
<java-symbol type="layout" name="transient_notification" />
<java-symbol type="layout" name="volume_adjust" />
<java-symbol type="layout" name="volume_adjust_item" />
<java-symbol type="layout" name="voice_interaction_session" />
<java-symbol type="layout" name="web_text_view_dropdown" />
<java-symbol type="layout" name="webview_find" />

View File

@@ -112,6 +112,9 @@ public class AudioService extends IAudioService.Stub {
private static final boolean USE_SESSIONS = true;
private static final boolean DEBUG_SESSIONS = true;
/** Allow volume changes to set ringer mode to silent? */
private static final boolean VOLUME_SETS_RINGER_MODE_SILENT = false;
/** How long to delay before persisting a change in volume/ringer mode. */
private static final int PERSIST_DELAY = 500;
@@ -1019,7 +1022,8 @@ public class AudioService extends IAudioService.Stub {
int newRingerMode;
if (index == 0) {
newRingerMode = mHasVibrator ? AudioManager.RINGER_MODE_VIBRATE
: AudioManager.RINGER_MODE_SILENT;
: VOLUME_SETS_RINGER_MODE_SILENT ? AudioManager.RINGER_MODE_SILENT
: AudioManager.RINGER_MODE_NORMAL;
} else {
newRingerMode = AudioManager.RINGER_MODE_NORMAL;
}
@@ -2552,7 +2556,9 @@ public class AudioService extends IAudioService.Stub {
}
} else {
// (oldIndex < step) is equivalent to (old UI index == 0)
if ((oldIndex < step) && mPrevVolDirection != AudioManager.ADJUST_LOWER) {
if ((oldIndex < step)
&& VOLUME_SETS_RINGER_MODE_SILENT
&& mPrevVolDirection != AudioManager.ADJUST_LOWER) {
ringerMode = RINGER_MODE_SILENT;
}
}
@@ -2565,7 +2571,8 @@ public class AudioService extends IAudioService.Stub {
break;
}
if ((direction == AudioManager.ADJUST_LOWER)) {
if (mPrevVolDirection != AudioManager.ADJUST_LOWER) {
if (VOLUME_SETS_RINGER_MODE_SILENT
&& mPrevVolDirection != AudioManager.ADJUST_LOWER) {
ringerMode = RINGER_MODE_SILENT;
}
} else if (direction == AudioManager.ADJUST_RAISE) {

View File

@@ -24,5 +24,5 @@ Copyright (C) 2014 The Android Open Source Project
<path
android:fill="#FFFFFFFF"
android:pathData="M6.6,3.6L5.2,2.2C2.8,4.0 1.2,6.8 1.0,10.0l2.0,0.0C3.2,7.3 4.5,5.0 6.6,3.6zM20.0,10.0l2.0,0.0c-0.2,-3.2 -1.7,-6.0 -4.1,-7.8l-1.4,1.4C18.5,5.0 19.8,7.3 20.0,10.0zM18.0,10.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.0l-2.0,-2.0L18.0,10.5zM11.5,22.0c0.1,0.0 0.3,0.0 0.4,0.0c0.7,-0.1 1.2,-0.6 1.4,-1.2c0.1,-0.2 0.2,-0.5 0.2,-0.8l-4.0,0.0C9.5,21.1 10.4,22.0 11.5,22.0z" />
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,28 @@
<!--
Copyright (C) 2014 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" >
<size
android:width="32dp"
android:height="32dp"/>
<viewport
android:viewportWidth="24.0"
android:viewportHeight="24.0"/>
<path
android:fill="#FFFFFFFF"
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,28 @@
<!--
Copyright (C) 2014 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" >
<size
android:width="32dp"
android:height="32dp"/>
<viewport
android:viewportWidth="24.0"
android:viewportHeight="24.0"/>
<path
android:fill="#FFFFFFFF"
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,10.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.7C9.5,4.3 9.0,4.5 8.6,4.7l9.4,9.4L18.0,10.5zM17.7,19.0l2.0,2.0l1.3,-1.3L4.3,3.0L3.0,4.3l2.9,2.9C5.3,8.2 5.0,9.3 5.0,10.5L5.0,16.0l-2.0,2.0l0.0,1.0L17.7,19.0z" />
</vector>

View File

@@ -0,0 +1,28 @@
<!--
Copyright (C) 2014 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" >
<size
android:width="32dp"
android:height="32dp"/>
<viewport
android:viewportWidth="24.0"
android:viewportHeight="24.0"/>
<path
android:fill="#FFFFFFFF"
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,30 @@
<!--
Copyright (C) 2014 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" >
<size
android:width="32dp"
android:height="32dp"/>
<viewport
android:viewportWidth="24.0"
android:viewportHeight="24.0"/>
<path
android:fill="#00000000"
android:stroke="#CCCCCC"
android:strokeWidth="1.0"
android:pathData="M12.0,2.0C6.5,2.0 2.0,6.5 2.0,12.0s4.5,10.0 10.0,10.0c5.5,0.0 10.0,-4.5 10.0,-10.0S17.5,2.0 12.0,2.0zM4.0,12.0c0.0,-4.4 3.6,-8.0 8.0,-8.0c1.8,0.0 3.5,0.6 4.9,1.7L5.7,16.9C4.6,15.5 4.0,13.8 4.0,12.0zM12.0,20.0c-1.8,0.0 -3.5,-0.6 -4.9,-1.7L18.3,7.1C19.4,8.5 20.0,10.2 20.0,12.0C20.0,16.4 16.4,20.0 12.0,20.0z" />
</vector>

View File

@@ -0,0 +1,28 @@
<!--
Copyright (C) 2014 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" >
<size
android:width="32dp"
android:height="32dp"/>
<viewport
android:viewportWidth="24.0"
android:viewportHeight="24.0"/>
<path
android:fill="#FFFFFFFF"
android:pathData="M12.0,2.0C6.5,2.0 2.0,6.5 2.0,12.0s4.5,10.0 10.0,10.0c5.5,0.0 10.0,-4.5 10.0,-10.0S17.5,2.0 12.0,2.0zM4.0,12.0c0.0,-4.4 3.6,-8.0 8.0,-8.0c1.8,0.0 3.5,0.6 4.9,1.7L5.7,16.9C4.6,15.5 4.0,13.8 4.0,12.0zM12.0,20.0c-1.8,0.0 -3.5,-0.6 -4.9,-1.7L18.3,7.1C19.4,8.5 20.0,10.2 20.0,12.0C20.0,16.4 16.4,20.0 12.0,20.0z" />
</vector>

View File

@@ -14,64 +14,44 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<com.android.systemui.qs.tiles.ZenModeDetail xmlns:android="http://schemas.android.com/apk/res/android"
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/system_secondary_color" >
android:background="@color/system_primary_color" >
<ImageView
android:id="@android:id/button1"
android:src="@drawable/ic_qs_close"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_alignParentStart="true"
android:padding="@dimen/qs_panel_padding" />
<Switch
android:id="@android:id/checkbox"
android:layout_width="wrap_content"
android:layout_height="64dp"
android:layout_alignParentEnd="true"
android:gravity="center"
android:padding="@dimen/qs_panel_padding" />
android:contentDescription="@string/accessibility_quick_settings_close"
android:padding="@dimen/qs_panel_padding"
android:src="@drawable/ic_qs_close" />
<TextView
android:id="@android:id/title"
android:layout_width="match_parent"
android:layout_height="64dp"
android:layout_alignParentTop="true"
android:layout_toEndOf="@android:id/button1"
android:layout_toStartOf="@android:id/checkbox"
android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock"
android:gravity="center_vertical"
android:paddingStart="@dimen/qs_panel_padding"
android:text="@string/zen_mode_title" />
android:textAppearance="@style/TextAppearance.QS.DetailHeader" />
<View
<ImageView
android:id="@android:id/custom"
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_height="wrap_content"
android:layout_below="@android:id/title"
android:background="#888" />
android:layout_marginLeft="16dip"
android:layout_marginRight="16dip"
android:scaleType="fitXY"
android:src="?android:attr/dividerHorizontal" />
<ListView
<FrameLayout
android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@android:id/button2"
android:layout_below="@android:id/custom"
android:divider="#00000000"
android:dividerHeight="0px" />
android:layout_below="@android:id/custom" />
<TextView
android:id="@android:id/button2"
style="@style/QSBorderless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock"
android:padding="@dimen/qs_panel_padding"
android:text="@string/quick_settings_more_settings"
android:textAllCaps="true" />
</com.android.systemui.qs.tiles.ZenModeDetail>
</RelativeLayout>

View File

@@ -27,7 +27,7 @@
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@*android:dimen/volume_panel_top"
android:layout_marginTop="@dimen/volume_panel_top"
android:background="@*android:drawable/dialog_full_holo_dark">
<ListView android:id="@android:id/list"
android:layout_width="match_parent"

View File

@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2007 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:id="@+id/visible_panel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<FrameLayout
android:id="@+id/slider_panel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toLeftOf="@+id/expand_button_divider" />
<ImageView
android:id="@+id/expand_button_divider"
android:layout_width="wrap_content"
android:layout_height="32dip"
android:layout_gravity="top"
android:layout_marginBottom="16dip"
android:layout_marginTop="16dip"
android:layout_toLeftOf="@+id/expand_button"
android:scaleType="fitXY"
android:src="?android:attr/dividerVertical" />
<ImageView
android:id="@+id/expand_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_gravity="top"
style="@style/BorderlessButton.Tiny"
android:padding="16dip" />
<ImageView
android:id="@+id/zen_panel_divider"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/slider_panel"
android:layout_marginLeft="16dip"
android:layout_marginRight="16dip"
android:scaleType="fitXY"
android:src="?android:attr/dividerHorizontal" />
<ViewStub
android:id="@+id/zen_panel_stub"
android:layout_below="@+id/zen_panel_divider"
android:inflatedId="@+id/zen_panel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout="@layout/zen_mode_panel" />
</RelativeLayout>

View File

@@ -27,7 +27,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="16dip"
android:background="?attr/selectableItemBackground"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="@null" />
<SeekBar

View File

@@ -16,44 +16,47 @@
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
android:layout_height="wrap_content" >
<RadioButton
android:id="@android:id/checkbox"
android:layout_width="32dp"
android:layout_height="64dp"
android:layout_height="@dimen/zen_mode_condition_height"
android:layout_alignParentStart="true"
android:layout_marginStart="@dimen/qs_panel_padding"
android:gravity="center" />
<TextView
android:id="@android:id/title"
android:layout_width="match_parent"
android:layout_height="64dp"
android:layout_height="@dimen/zen_mode_condition_height"
android:layout_toEndOf="@android:id/checkbox"
android:layout_toStartOf="@android:id/button1"
android:ellipsize="end"
android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock"
android:gravity="center_vertical"
android:maxLines="1"
android:text="@string/accessibility_back" />
android:textAppearance="@style/TextAppearance.QS.DetailItemPrimary" />
<ImageView
android:id="@android:id/button1"
android:src="@drawable/ic_qs_minus"
android:layout_width="64dp"
android:layout_height="64dp"
style="@style/BorderlessButton"
android:layout_width="@dimen/zen_mode_condition_height"
android:layout_height="@dimen/zen_mode_condition_height"
android:layout_alignParentEnd="true"
android:layout_marginEnd="48dp"
android:padding="@dimen/qs_panel_padding"
android:paddingRight="0px" />
android:layout_centerVertical="true"
android:layout_marginEnd="@dimen/zen_mode_condition_height"
android:contentDescription="@string/accessibility_quick_settings_less_time"
android:padding="@dimen/zen_mode_condition_detail_button_padding"
android:src="@drawable/ic_qs_minus" />
<ImageView
android:id="@android:id/button2"
android:src="@drawable/ic_qs_plus"
android:layout_width="64dp"
android:layout_height="64dp"
style="@style/BorderlessButton"
android:layout_width="@dimen/zen_mode_condition_height"
android:layout_height="@dimen/zen_mode_condition_height"
android:layout_alignParentEnd="true"
android:padding="@dimen/qs_panel_padding" />
android:layout_centerVertical="true"
android:contentDescription="@string/accessibility_quick_settings_more_time"
android:padding="@dimen/zen_mode_condition_detail_button_padding"
android:src="@drawable/ic_qs_plus" />
</RelativeLayout>
</RelativeLayout>

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2014 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.
-->
<!-- extends LinearLayout -->
<com.android.systemui.volume.ZenModePanel xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/zen_mode_panel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/system_primary_color"
android:orientation="vertical"
android:padding="@dimen/qs_panel_padding" >
<TextView
android:id="@android:id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_marginBottom="8dp"
android:text="@string/zen_mode_title"
android:textAppearance="@style/TextAppearance.QS.DetailHeader" />
<LinearLayout
android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
<TextView
android:id="@android:id/button2"
style="@style/BorderlessButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_gravity="end"
android:text="@string/quick_settings_more_settings"
android:textAppearance="@style/TextAppearance.QS.DetailButton" />
</com.android.systemui.volume.ZenModePanel>

View File

@@ -135,5 +135,8 @@
<!-- Defines the implementation of the velocity tracker to be used for the panel expansion. Can
be 'platform' or 'noisy' (i.e. for noisy touch screens). -->
<string name="velocity_tracker_impl" translatable="false">platform</string>
<!-- Wait on the touch feedback this long before performing an action. -->
<integer name="feedback_start_delay">300</integer>
</resources>

View File

@@ -199,6 +199,9 @@
<!-- How far the expanded QS panel peeks from the header in collapsed state. -->
<dimen name="qs_peek_height">8dp</dimen>
<dimen name="zen_mode_condition_detail_button_padding">8dp</dimen>
<dimen name="zen_mode_condition_height">48dp</dimen>
<!-- used by DessertCase -->
<dimen name="dessert_case_cell_size">192dp</dimen>
@@ -291,4 +294,9 @@
<dimen name="keyguard_clock_notifications_margin_min">22dp</dimen>
<dimen name="keyguard_clock_notifications_margin_max">36dp</dimen>
<!-- Volume panel dialog y offset -->
<dimen name="volume_panel_top">16dp</dimen>
<!-- Volume panel dialog width -->
<dimen name="volume_panel_width">300dp</dimen>
</resources>

View File

@@ -388,6 +388,12 @@
<string name="accessibility_quick_settings_location">Location <xliff:g id="state" example="Off">%s</xliff:g>.</string>
<!-- Content description of the alarm tile in quick settings (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_quick_settings_alarm">Alarm set for <xliff:g id="time" example="Wed 3:30 PM">%s</xliff:g>.</string>
<!-- Content description of quick settings detail panel close button (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_quick_settings_close">Close panel</string>
<!-- Content description of zen mode time condition plus button (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_quick_settings_more_time">More time</string>
<!-- Content description of zen mode time condition minus button (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_quick_settings_less_time">Less time</string>
<!-- Title of dialog shown when 2G-3G data usage has exceeded limit and has been disabled. [CHAR LIMIT=48] -->
<string name="data_usage_disabled_dialog_3g_title">2G-3G data disabled</string>
@@ -512,6 +518,8 @@
<string name="quick_settings_tethering_label">Tethering</string>
<!-- QuickSettings: Hotspot. [CHAR LIMIT=NONE] -->
<string name="quick_settings_hotspot_label">Hotspot</string>
<!-- QuickSettings: Notifications [CHAR LIMIT=NONE] -->
<string name="quick_settings_notifications_label">Notifications</string>
<!-- Recents: The empty recents string. [CHAR LIMIT=NONE] -->
<string name="recents_empty_message">RECENTS</string>
@@ -563,4 +571,19 @@
<string name="keyguard_unlock">Swipe up to unlock</string>
<string name="bugreport_tile_extended" translatable="false">%s\n%s (%s)</string>
<!-- Zen mode condition: no exit criteria. [CHAR LIMIT=NONE] -->
<string name="zen_mode_forever">Until you turn this off</string>
<!-- Zen mode condition: time duration in minutes. [CHAR LIMIT=NONE] -->
<plurals name="zen_mode_duration_minutes">
<item quantity="one">For one minute</item>
<item quantity="other">For %d minutes</item>
</plurals>
<!-- Zen mode condition: time duration in hours. [CHAR LIMIT=NONE] -->
<plurals name="zen_mode_duration_hours">
<item quantity="one">For one hour</item>
<item quantity="other">For %d hours</item>
</plurals>
</resources>

View File

@@ -133,6 +133,32 @@
<item name="android:fadingEdge">horizontal</item>
</style>
<style name="TextAppearance.QS">
<item name="android:textStyle">normal</item>
<item name="android:textColor">#ffffff</item>
<item name="android:fontFamily">sans-serif</item>
</style>
<style name="TextAppearance.QS.DetailHeader">
<item name="android:textSize">20sp</item>
<item name="android:fontFamily">sans-serif-medium</item>
</style>
<style name="TextAppearance.QS.DetailItemPrimary">
<item name="android:textSize">16sp</item>
</style>
<style name="TextAppearance.QS.DetailItemSecondary">
<item name="android:textSize">14sp</item>
<item name="android:textColor">#7fcac3</item>
</style>
<style name="TextAppearance.QS.DetailButton">
<item name="android:textSize">14sp</item>
<item name="android:textAllCaps">true</item>
<item name="android:fontFamily">sans-serif-medium</item>
</style>
<style name="BaseBrightnessDialogContainer">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
@@ -192,9 +218,9 @@
<item name="android:colorControlActivated">@color/system_accent_color</item>
</style>
<style name="QSBorderless" parent="@android:style/Widget.Quantum.Button.Borderless" />
<style name="BorderlessButton" parent="@android:style/Widget.Quantum.Button.Borderless" />
<style name="QSBorderless.Tiny">
<style name="BorderlessButton.Tiny">
<item name="android:minHeight">12dip</item>
<item name="android:minWidth">12dip</item>
</style>

View File

@@ -42,12 +42,12 @@ public class SystemUIApplication extends Application {
private final Class<?>[] SERVICES = new Class[] {
com.android.systemui.keyguard.KeyguardViewMediator.class,
com.android.systemui.recent.Recents.class,
com.android.systemui.volume.VolumeUI.class,
com.android.systemui.statusbar.SystemBars.class,
com.android.systemui.usb.StorageNotification.class,
com.android.systemui.power.PowerUI.class,
com.android.systemui.media.RingtonePlayer.class,
com.android.systemui.settings.SettingsUI.class,
com.android.systemui.volume.VolumeUI.class,
};
/**

View File

@@ -26,6 +26,7 @@ import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile.State;
import com.android.systemui.statusbar.policy.BluetoothController;
import com.android.systemui.statusbar.policy.CastController;
@@ -35,6 +36,7 @@ import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.RotationLockController;
import com.android.systemui.statusbar.policy.TetheringController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.volume.VolumeComponent;
import java.util.List;
import java.util.Objects;
@@ -49,12 +51,12 @@ import java.util.Objects;
public abstract class QSTile<TState extends State> implements Listenable {
protected final String TAG = "QSTile." + getClass().getSimpleName();
protected static final boolean DEBUG = false;
public static final int FEEDBACK_START_DELAY = 400;
protected final Host mHost;
protected final Context mContext;
protected final H mHandler;
protected final Handler mUiHandler = new Handler(Looper.getMainLooper());
private final int mFeedbackStartDelay;
private Callback mCallback;
protected final TState mState = newTileState();
@@ -68,6 +70,7 @@ public abstract class QSTile<TState extends State> implements Listenable {
mHost = host;
mContext = host.getContext();
mHandler = new H(host.getLooper());
mFeedbackStartDelay = mContext.getResources().getInteger(R.integer.feedback_start_delay);
}
public boolean supportsDualTargets() {
@@ -116,6 +119,10 @@ public abstract class QSTile<TState extends State> implements Listenable {
mHandler.obtainMessage(H.USER_SWITCH, newUserId).sendToTarget();
}
protected void postAfterFeedback(Runnable runnable) {
mHandler.postDelayed(runnable, mFeedbackStartDelay);
}
// call only on tile worker looper
private void handleSetCallback(Callback callback) {
@@ -213,6 +220,7 @@ public abstract class QSTile<TState extends State> implements Listenable {
ZenModeController getZenModeController();
TetheringController getTetheringController();
CastController getCastController();
VolumeComponent getVolumeComponent();
}
public static class State {

View File

@@ -84,7 +84,8 @@ public class QSTileView extends ViewGroup {
removeView(mLabel);
}
final Resources res = mContext.getResources();
mLabel = new TextView(mDual ? new ContextThemeWrapper(mContext, R.style.QSBorderless_Tiny)
mLabel = new TextView(mDual
? new ContextThemeWrapper(mContext, R.style.BorderlessButton_Tiny)
: mContext);
mLabel.setId(android.R.id.title);
mLabel.setTextColor(res.getColor(R.color.qs_tile_text));

View File

@@ -58,13 +58,13 @@ public class BugreportTile extends QSTile<QSTile.State> {
@Override
protected void handleClick() {
mHandler.postDelayed(new Runnable() {
postAfterFeedback(new Runnable() {
@Override
public void run() {
mHost.collapsePanels();
mUiHandler.post(mShowDialog);
}
}, FEEDBACK_START_DELAY);
});
}
@Override

View File

@@ -65,12 +65,12 @@ public class CastTile extends QSTile<QSTile.BooleanState> {
@Override
protected void handleClick() {
mHandler.postDelayed(new Runnable() {
postAfterFeedback(new Runnable() {
public void run() {
mHost.collapsePanels();
mUiHandler.post(mShowDialog);
}
}, FEEDBACK_START_DELAY);
});
}
@Override

View File

@@ -0,0 +1,172 @@
/*
* Copyright (C) 2014 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.qs.tiles;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.util.Log;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnAttachStateChangeListener;
import android.view.ViewGroup;
import android.widget.TextView;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.volume.VolumeComponent;
import com.android.systemui.volume.VolumePanel;
import com.android.systemui.volume.ZenModePanel;
/** Quick settings tile: Notifications **/
public class NotificationsTile extends QSTile<NotificationsTile.NotificationsState> {
private final ZenModeController mZenController;
private final AudioManager mAudioManager;
public NotificationsTile(Host host) {
super(host);
mZenController = host.getZenModeController();
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
}
@Override
public View createDetailView(Context context, ViewGroup root) {
final Context themedContext = new ContextThemeWrapper(mContext, R.style.QSAccentTheme);
final View v = LayoutInflater.from(themedContext).inflate(R.layout.qs_detail, root, false);
final TextView title = (TextView) v.findViewById(android.R.id.title);
title.setText(R.string.quick_settings_notifications_label);
final View close = v.findViewById(android.R.id.button1);
close.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showDetail(false);
}
});
final ViewGroup content = (ViewGroup) v.findViewById(android.R.id.content);
final VolumeComponent volumeComponent = mHost.getVolumeComponent();
final VolumePanel vp = new VolumePanel(mContext, content, mZenController);
v.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
@Override
public void onViewDetachedFromWindow(View v) {
volumeComponent.setVolumePanel(null);
}
@Override
public void onViewAttachedToWindow(View v) {
volumeComponent.setVolumePanel(vp);
}
});
vp.setZenModePanelCallback(new ZenModePanel.Callback() {
@Override
public void onMoreSettings() {
mHost.startSettingsActivity(ZenModePanel.ZEN_SETTINGS);
}
@Override
public void onInteraction() {
// noop
}
});
vp.postVolumeChanged(AudioManager.STREAM_NOTIFICATION, AudioManager.FLAG_SHOW_UI);
return v;
}
@Override
protected NotificationsState newTileState() {
return new NotificationsState();
}
@Override
public void setListening(boolean listening) {
if (listening) {
mZenController.addCallback(mCallback);
final IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION);
mContext.registerReceiver(mReceiver, filter);
} else {
mZenController.removeCallback(mCallback);
mContext.unregisterReceiver(mReceiver);
}
}
@Override
protected void handleClick() {
if (mState.zen) {
mZenController.setZen(false);
} else {
showDetail(true);
}
}
@Override
protected void handleUpdateState(NotificationsState state, Object arg) {
state.visible = true;
state.zen = arg instanceof Boolean ? (Boolean) arg : mZenController.isZen();
state.ringerMode = mAudioManager.getRingerMode();
if (state.zen) {
state.iconId = R.drawable.ic_qs_zen_on;
} else if (state.ringerMode == AudioManager.RINGER_MODE_VIBRATE) {
state.iconId = R.drawable.ic_qs_ringer_vibrate;
} else if (state.ringerMode == AudioManager.RINGER_MODE_SILENT) {
state.iconId = R.drawable.ic_qs_ringer_silent;
} else {
state.iconId = R.drawable.ic_qs_ringer_audible;
}
state.label = mContext.getString(R.string.quick_settings_notifications_label);
}
private final ZenModeController.Callback mCallback = new ZenModeController.Callback() {
@Override
public void onZenChanged(boolean zen) {
if (DEBUG) Log.d(TAG, "onZenChanged " + zen);
refreshState(zen);
}
};
public static final class NotificationsState extends QSTile.State {
public boolean zen;
public int ringerMode;
@Override
public boolean copyTo(State other) {
final NotificationsState o = (NotificationsState) other;
final boolean changed = o.zen != zen || o.ringerMode != ringerMode;
o.zen = zen;
o.ringerMode = ringerMode;
return super.copyTo(other) || changed;
}
@Override
protected StringBuilder toStringBuilder() {
final StringBuilder rt = super.toStringBuilder();
rt.insert(rt.length() - 1, ",zen=" + zen + ",ringerMode=" + ringerMode);
return rt;
}
}
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (AudioManager.RINGER_MODE_CHANGED_ACTION.equals(intent.getAction())) {
refreshState();
}
}
};
}

View File

@@ -1,108 +0,0 @@
/*
* Copyright (C) 2014 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.qs.tiles;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
/** Quick settings tile: Ringer mode **/
public class RingerModeTile extends QSTile<RingerModeTile.IntState> {
private final AudioManager mAudioManager;
public RingerModeTile(Host host) {
super(host);
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
}
@Override
protected IntState newTileState() {
return new IntState();
}
@Override
public void setListening(boolean listening) {
if (listening) {
final IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION);
mContext.registerReceiver(mReceiver, filter);
} else {
mContext.unregisterReceiver(mReceiver);
}
}
@Override
protected void handleClick() {
final int oldValue = (Integer) mState.value;
final int newValue =
oldValue == AudioManager.RINGER_MODE_NORMAL ? AudioManager.RINGER_MODE_VIBRATE
: oldValue == AudioManager.RINGER_MODE_VIBRATE ? AudioManager.RINGER_MODE_SILENT
: AudioManager.RINGER_MODE_NORMAL;
mAudioManager.setRingerMode(newValue);
}
@Override
protected void handleUpdateState(IntState state, Object arg) {
final int ringerMode = mAudioManager.getRingerMode();
state.visible = true;
state.value = ringerMode;
if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) {
state.iconId = R.drawable.ic_qs_ringer_vibrate;
state.label = "Vibrate";
} else if (ringerMode == AudioManager.RINGER_MODE_SILENT) {
state.iconId = R.drawable.ic_qs_ringer_silent;
state.label = "Silent";
} else {
state.iconId = R.drawable.ic_qs_ringer_audible;
state.label = "Audible";
}
}
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (AudioManager.RINGER_MODE_CHANGED_ACTION.equals(intent.getAction())) {
refreshState();
}
}
};
public static class IntState extends QSTile.State {
public int value;
@Override
public boolean copyTo(State other) {
final IntState o = (IntState) other;
final boolean changed = o.value != value;
o.value = value;
return super.copyTo(other) || changed;
}
@Override
protected StringBuilder toStringBuilder() {
final StringBuilder rt = super.toStringBuilder();
rt.insert(rt.length() - 1, ",value=" + value);
return rt;
}
}
}

View File

@@ -1,273 +0,0 @@
/*
* Copyright (C) 2014 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.qs.tiles;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.provider.Settings;
import android.service.notification.Condition;
import android.util.AttributeSet;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.RadioButton;
import android.widget.RelativeLayout;
import android.widget.Switch;
import android.widget.TextView;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.policy.ZenModeController;
import java.util.HashSet;
/** Quick settings control panel: Zen mode **/
public class ZenModeDetail extends RelativeLayout {
private static final String TAG = "ZenModeDetail";
private static final Intent ZEN_SETTINGS = new Intent(Settings.ACTION_ZEN_MODE_SETTINGS);
private static final int[] MINUTES = new int[] { 15, 30, 45, 60, 120, 180, 240, 480 };
private final H mHandler = new H();
private int mMinutesIndex = 3;
private Context mContext;
private ZenModeTile mTile;
private QSTile.Host mHost;
private ZenModeController mController;
private Switch mSwitch;
private ConditionAdapter mAdapter;
public ZenModeDetail(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void init(ZenModeTile tile) {
mTile = tile;
mHost = mTile.getHost();
mContext = getContext();
mController = mHost.getZenModeController();
final ImageView close = (ImageView) findViewById(android.R.id.button1);
close.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mTile.showDetail(false);
}
});
mSwitch = (Switch) findViewById(android.R.id.checkbox);
mSwitch.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
mController.setZen(isChecked);
}
});
mSwitch.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final boolean isChecked = mSwitch.isChecked();
mController.setZen(isChecked);
if (!isChecked) {
mTile.showDetail(false);
}
}
});
final View moreSettings = findViewById(android.R.id.button2);
moreSettings.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mHost.startSettingsActivity(ZEN_SETTINGS);
mTile.showDetail(false);
}
});
final ListView conditions = (ListView) findViewById(android.R.id.content);
mAdapter = new ConditionAdapter(mContext);
conditions.setAdapter(mAdapter);
mAdapter.add(updateTimeCondition());
updateZen(mController.isZen());
}
private Condition updateTimeCondition() {
final int minutes = MINUTES[mMinutesIndex];
final long millis = System.currentTimeMillis() + minutes * 60 * 1000;
final Uri id = new Uri.Builder().scheme(Condition.SCHEME).authority("android")
.appendPath("countdown").appendPath(Long.toString(millis)).build();
final int num = minutes < 60 ? minutes : minutes / 60;
final String units = minutes < 60 ? "minutes" : minutes == 60 ? "hour" : "hours";
return new Condition(id, "For " + num + " " + units, "", "", 0, Condition.STATE_TRUE,
Condition.FLAG_RELEVANT_NOW);
}
private void editTimeCondition(int delta) {
final int i = mMinutesIndex + delta;
if (i < 0 || i >= MINUTES.length) return;
mMinutesIndex = i;
mAdapter.remove(mAdapter.getItem(0));
final Condition c = updateTimeCondition();
mAdapter.insert(c, 0);
select(c);
}
private void select(Condition condition) {
mController.select(condition);
}
private void updateZen(boolean zen) {
mHandler.obtainMessage(H.UPDATE_ZEN, zen ? 1 : 0, 0).sendToTarget();
}
private void updateConditions(Condition[] conditions) {
if (conditions == null) return;
mHandler.obtainMessage(H.UPDATE_CONDITIONS, conditions).sendToTarget();
}
private void handleUpdateZen(boolean zen) {
mSwitch.setChecked(zen);
}
private void handleUpdateConditions(Condition[] conditions) {
for (int i = mAdapter.getCount() - 1; i > 0; i--) {
mAdapter.remove(mAdapter.getItem(i));
}
for (Condition condition : conditions) {
mAdapter.add(condition);
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mController.addCallback(mCallback);
mController.requestConditions(true);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mController.removeCallback(mCallback);
mController.requestConditions(false);
}
private final class H extends Handler {
private static final int UPDATE_ZEN = 1;
private static final int UPDATE_CONDITIONS = 2;
public H() {
super(Looper.getMainLooper());
}
@Override
public void handleMessage(Message msg) {
if (msg.what == UPDATE_ZEN) {
handleUpdateZen(msg.arg1 == 1);
} else if (msg.what == UPDATE_CONDITIONS) {
handleUpdateConditions((Condition[])msg.obj);
}
}
}
private final ZenModeController.Callback mCallback = new ZenModeController.Callback() {
@Override
public void onZenChanged(boolean zen) {
updateZen(zen);
}
public void onConditionsChanged(Condition[] conditions) {
updateConditions(conditions);
}
};
private final class ConditionAdapter extends ArrayAdapter<Condition> {
private final LayoutInflater mInflater;
private final HashSet<RadioButton> mRadioButtons = new HashSet<RadioButton>();
public ConditionAdapter(Context context) {
super(context, 0);
mInflater = LayoutInflater.from(new ContextThemeWrapper(context, R.style.QSWhiteTheme));
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final Condition condition = getItem(position);
final boolean enabled = condition.state == Condition.STATE_TRUE;
final View row = convertView != null ? convertView : mInflater
.inflate(R.layout.qs_zen_mode_detail_condition, parent, false);
final RadioButton rb = (RadioButton) row.findViewById(android.R.id.checkbox);
mRadioButtons.add(rb);
rb.setEnabled(enabled);
rb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
for (RadioButton otherButton : mRadioButtons) {
if (otherButton == rb) continue;
otherButton.setChecked(false);
}
select(condition);
}
}
});
final TextView title = (TextView) row.findViewById(android.R.id.title);
title.setText(condition.summary);
title.setEnabled(enabled);
title.setAlpha(enabled ? 1 : .5f);
final ImageView button1 = (ImageView) row.findViewById(android.R.id.button1);
button1.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
rb.setChecked(true);
editTimeCondition(-1);
}
});
final ImageView button2 = (ImageView) row.findViewById(android.R.id.button2);
button2.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
rb.setChecked(true);
editTimeCondition(1);
}
});
title.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
rb.setChecked(true);
}
});
if (position != 0) {
button1.setVisibility(View.GONE);
button2.setVisibility(View.GONE);
}
if (position == 0 && mRadioButtons.size() == 1) {
rb.setChecked(true);
}
return row;
}
}
}

View File

@@ -1,87 +0,0 @@
/*
* Copyright (C) 2014 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.qs.tiles;
import android.content.Context;
import android.util.Log;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.policy.ZenModeController;
/** Quick settings tile: Zen mode **/
public class ZenModeTile extends QSTile<QSTile.BooleanState> {
private final ZenModeController mController;
public ZenModeTile(Host host) {
super(host);
mController = host.getZenModeController();
}
@Override
public View createDetailView(Context context, ViewGroup root) {
final Context themedContext = new ContextThemeWrapper(mContext, R.style.QSAccentTheme);
final ZenModeDetail v = (ZenModeDetail) LayoutInflater.from(themedContext)
.inflate(R.layout.qs_zen_mode_detail, root, false);
v.init(this);
return v;
}
@Override
protected BooleanState newTileState() {
return new BooleanState();
}
@Override
public void setListening(boolean listening) {
if (listening) {
mController.addCallback(mCallback);
} else {
mController.removeCallback(mCallback);
}
}
@Override
protected void handleClick() {
final boolean newZen = !mState.value;
mController.setZen(newZen);
if (newZen) {
showDetail(true);
}
}
@Override
protected void handleUpdateState(BooleanState state, Object arg) {
final boolean zen = arg instanceof Boolean ? (Boolean)arg : mController.isZen();
state.value = zen;
state.visible = true;
state.iconId = zen ? R.drawable.ic_qs_zen_on : R.drawable.ic_qs_zen_off;
state.label = mContext.getString(R.string.zen_mode_title);
}
private final ZenModeController.Callback mCallback = new ZenModeController.Callback() {
@Override
public void onZenChanged(boolean zen) {
if (DEBUG) Log.d(TAG, "onZenChanged " + zen);
refreshState(zen);
}
};
}

View File

@@ -72,8 +72,7 @@ public class BrightnessDialog extends Dialog implements
window.setGravity(Gravity.TOP);
WindowManager.LayoutParams lp = window.getAttributes();
// Offset from the top
lp.y = getContext().getResources().getDimensionPixelOffset(
com.android.internal.R.dimen.volume_panel_top);
lp.y = getContext().getResources().getDimensionPixelOffset(R.dimen.volume_panel_top);
lp.type = WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
lp.privateFlags |=
WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;

View File

@@ -121,10 +121,12 @@ import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.statusbar.policy.LocationControllerImpl;
import com.android.systemui.statusbar.policy.NetworkControllerImpl;
import com.android.systemui.statusbar.policy.RotationLockControllerImpl;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.statusbar.policy.ZenModeControllerImpl;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout.OnChildLocationsChangedListener;
import com.android.systemui.statusbar.stack.StackScrollState.ViewState;
import com.android.systemui.volume.VolumeComponent;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -196,8 +198,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
NetworkControllerImpl mNetworkController;
RotationLockControllerImpl mRotationLockController;
UserInfoController mUserInfoController;
ZenModeControllerImpl mZenModeController;
ZenModeController mZenModeController;
CastControllerImpl mCastController;
VolumeComponent mVolumeComponent;
int mNaturalBarHeight = -1;
int mIconSize = -1;
@@ -684,7 +687,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mRotationLockController = new RotationLockControllerImpl(mContext);
}
mUserInfoController = new UserInfoController(mContext);
mZenModeController = new ZenModeControllerImpl(mContext, mHandler);
mVolumeComponent = getComponent(VolumeComponent.class);
mZenModeController = mVolumeComponent.getZenController();
mCastController = new CastControllerImpl(mContext);
final SignalClusterView signalCluster =
(SignalClusterView)mStatusBarView.findViewById(R.id.signal_cluster);
@@ -747,7 +751,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
final QSTileHost qsh = new QSTileHost(mContext, this,
mBluetoothController, mLocationController, mRotationLockController,
mNetworkController, mZenModeController, null /*tethering*/,
mCastController);
mCastController, mVolumeComponent);
for (QSTile<?> tile : qsh.getTiles()) {
mQSPanel.addTile(tile);
}

View File

@@ -21,6 +21,7 @@ import android.content.Intent;
import android.os.HandlerThread;
import android.os.Looper;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
import com.android.systemui.qs.tiles.AirplaneModeTile;
import com.android.systemui.qs.tiles.BluetoothTile;
@@ -29,11 +30,10 @@ import com.android.systemui.qs.tiles.CastTile;
import com.android.systemui.qs.tiles.CellularTile;
import com.android.systemui.qs.tiles.ColorInversionTile;
import com.android.systemui.qs.tiles.LocationTile;
import com.android.systemui.qs.tiles.RingerModeTile;
import com.android.systemui.qs.tiles.NotificationsTile;
import com.android.systemui.qs.tiles.RotationLockTile;
import com.android.systemui.qs.tiles.HotspotTile;
import com.android.systemui.qs.tiles.WifiTile;
import com.android.systemui.qs.tiles.ZenModeTile;
import com.android.systemui.settings.CurrentUserTracker;
import com.android.systemui.statusbar.policy.BluetoothController;
import com.android.systemui.statusbar.policy.CastController;
@@ -42,6 +42,7 @@ import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.RotationLockController;
import com.android.systemui.statusbar.policy.TetheringController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.volume.VolumeComponent;
import java.util.ArrayList;
import java.util.List;
@@ -60,13 +61,15 @@ public class QSTileHost implements QSTile.Host {
private final CastController mCast;
private final Looper mLooper;
private final CurrentUserTracker mUserTracker;
private final VolumeComponent mVolume;
private final ArrayList<QSTile<?>> mTiles = new ArrayList<QSTile<?>>();
private final int mFeedbackStartDelay;
public QSTileHost(Context context, PhoneStatusBar statusBar,
BluetoothController bluetooth, LocationController location,
RotationLockController rotation, NetworkController network,
ZenModeController zen, TetheringController tethering,
CastController cast) {
CastController cast, VolumeComponent volume) {
mContext = context;
mStatusBar = statusBar;
mBluetooth = bluetooth;
@@ -76,6 +79,7 @@ public class QSTileHost implements QSTile.Host {
mZen = zen;
mTethering = tethering;
mCast = cast;
mVolume = volume;
final HandlerThread ht = new HandlerThread(QSTileHost.class.getSimpleName());
ht.start();
@@ -86,8 +90,7 @@ public class QSTileHost implements QSTile.Host {
mTiles.add(new ColorInversionTile(this));
mTiles.add(new CellularTile(this));
mTiles.add(new AirplaneModeTile(this));
mTiles.add(new ZenModeTile(this));
mTiles.add(new RingerModeTile(this));
mTiles.add(new NotificationsTile(this));
mTiles.add(new RotationLockTile(this));
mTiles.add(new LocationTile(this));
mTiles.add(new CastTile(this));
@@ -103,6 +106,7 @@ public class QSTileHost implements QSTile.Host {
}
};
mUserTracker.startTracking();
mFeedbackStartDelay = mContext.getResources().getInteger(R.integer.feedback_start_delay);
}
@Override
@@ -112,7 +116,7 @@ public class QSTileHost implements QSTile.Host {
@Override
public void startSettingsActivity(final Intent intent) {
mStatusBar.postStartSettingsActivity(intent, QSTile.FEEDBACK_START_DELAY);
mStatusBar.postStartSettingsActivity(intent, mFeedbackStartDelay);
}
@Override
@@ -169,4 +173,9 @@ public class QSTileHost implements QSTile.Host {
public CastController getCastController() {
return mCast;
}
@Override
public VolumeComponent getVolumeComponent() {
return mVolume;
}
}

View File

@@ -0,0 +1,24 @@
/*
* Copyright (C) 2014 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 com.android.systemui.statusbar.policy.ZenModeController;
public interface VolumeComponent {
ZenModeController getZenController();
void setVolumePanel(VolumePanel panel);
}

View File

@@ -16,14 +16,12 @@
package com.android.systemui.volume;
import com.android.internal.R;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface.OnDismissListener;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnDismissListener;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
@@ -42,6 +40,7 @@ import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.view.Window;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
@@ -49,6 +48,9 @@ import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import com.android.internal.R;
import com.android.systemui.statusbar.policy.ZenModeController;
import java.util.HashMap;
/**
@@ -57,7 +59,6 @@ import java.util.HashMap;
* @hide
*/
public class VolumePanel extends Handler {
private static final String TAG = VolumePanel.class.getSimpleName();
private static boolean LOGD = false;
private static final int PLAY_SOUND_DELAY = AudioService.PLAY_SOUND_DELAY;
@@ -88,33 +89,48 @@ public class VolumePanel extends Handler {
private static final int MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN = 9;
private static final int MSG_SLIDER_VISIBILITY_CHANGED = 10;
private static final int MSG_DISPLAY_SAFE_VOLUME_WARNING = 11;
private static final int MSG_LAYOUT_DIRECTION = 12;
private static final int MSG_ZEN_MODE_CHANGED = 13;
// Pseudo stream type for master volume
private static final int STREAM_MASTER = -100;
// Pseudo stream type for remote volume is defined in AudioService.STREAM_REMOTE_MUSIC
private final String mTag;
protected final Context mContext;
private final AudioManager mAudioManager;
private final ZenModeController mZenController;
private boolean mRingIsSilent;
private boolean mShowCombinedVolumes;
private boolean mVoiceCapable;
private boolean mZenModeCapable;
// True if we want to play tones on the system stream when the master stream is specified.
private final boolean mPlayMasterStreamTones;
/** Dialog containing all the sliders */
private final Dialog mDialog;
/** Dialog's content view */
/** Volume panel content view */
private final View mView;
/** Dialog hosting the panel, if not embedded */
private final Dialog mDialog;
/** Parent view hosting the panel, if embedded */
private final ViewGroup mParent;
/** The visible portion of the volume overlay */
private final ViewGroup mPanel;
/** Contains the sliders and their touchable icons */
private final ViewGroup mSliderGroup;
/** The button that expands the dialog to show all sliders */
private final View mMoreButton;
/** Dummy divider icon that needs to vanish with the more button */
private final View mDivider;
/** Contains the slider and its touchable icons */
private final ViewGroup mSliderPanel;
/** The button that expands the dialog to show the zen panel */
private final ImageView mExpandButton;
/** Dummy divider icon that needs to vanish with the expand button */
private final View mExpandDivider;
/** The zen mode configuration panel view stub */
private final ViewStub mZenPanelStub;
/** The zen mode configuration panel view, once inflated */
private ZenModePanel mZenPanel;
/** Dummy divider icon that needs to vanish with the zen panel */
private final View mZenPanelDivider;
private ZenModePanel.Callback mZenPanelCallback;
/** Currently active stream that shows up at the top of the list of sliders */
private int mActiveStreamType = -1;
@@ -129,8 +145,8 @@ public class VolumePanel extends Handler {
false),
RingerStream(AudioManager.STREAM_RING,
R.string.volume_icon_description_ringer,
R.drawable.ic_audio_ring_notif,
R.drawable.ic_audio_ring_notif_mute,
com.android.systemui.R.drawable.ic_ringer_audible,
com.android.systemui.R.drawable.ic_ringer_silent,
false),
VoiceStream(AudioManager.STREAM_VOICE_CALL,
R.string.volume_icon_description_incall,
@@ -149,8 +165,8 @@ public class VolumePanel extends Handler {
true),
NotificationStream(AudioManager.STREAM_NOTIFICATION,
R.string.volume_icon_description_notification,
R.drawable.ic_audio_notification,
R.drawable.ic_audio_notification_mute,
com.android.systemui.R.drawable.ic_ringer_audible,
com.android.systemui.R.drawable.ic_ringer_silent,
true),
// for now, use media resources for master volume
MasterStream(STREAM_MASTER,
@@ -245,8 +261,11 @@ public class VolumePanel extends Handler {
}
public VolumePanel(Context context) {
public VolumePanel(Context context, ViewGroup parent, ZenModeController zenController) {
mTag = String.format("VolumePanel%s.%08x", parent == null ? "Dialog" : "", hashCode());
mContext = context;
mParent = parent;
mZenController = zenController;
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
// For now, only show master volume if master volume is supported
@@ -258,74 +277,81 @@ public class VolumePanel extends Handler {
streamRes.show = (streamRes.streamType == STREAM_MASTER);
}
}
mDialog = new Dialog(context) {
@Override
public boolean onTouchEvent(MotionEvent event) {
if (isShowing() && event.getAction() == MotionEvent.ACTION_OUTSIDE &&
sConfirmSafeVolumeDialog == null) {
forceTimeout();
return true;
if (LOGD) Log.d(mTag, String.format("new VolumePanel hasParent=%s", parent != null));
final int layoutId = com.android.systemui.R.layout.volume_panel;
if (parent == null) {
// dialog mode
mDialog = new Dialog(context) {
@Override
public boolean onTouchEvent(MotionEvent event) {
if (isShowing() && event.getAction() == MotionEvent.ACTION_OUTSIDE &&
sConfirmSafeVolumeDialog == null) {
forceTimeout();
return true;
}
return false;
}
return false;
}
};
};
// Change some window properties
final Window window = mDialog.getWindow();
final LayoutParams lp = window.getAttributes();
lp.token = null;
// Offset from the top
lp.y = res.getDimensionPixelOffset(R.dimen.volume_panel_top);
lp.type = LayoutParams.TYPE_VOLUME_OVERLAY;
lp.windowAnimations = R.style.Animation_VolumePanel;
window.setAttributes(lp);
window.setGravity(Gravity.TOP);
window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
window.requestFeature(Window.FEATURE_NO_TITLE);
window.addFlags(LayoutParams.FLAG_NOT_FOCUSABLE
| LayoutParams.FLAG_NOT_TOUCH_MODAL
| LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
// Change some window properties
final Window window = mDialog.getWindow();
final LayoutParams lp = window.getAttributes();
lp.token = null;
// Offset from the top
lp.y = res.getDimensionPixelOffset(com.android.systemui.R.dimen.volume_panel_top);
lp.width = res.getDimensionPixelSize(com.android.systemui.R.dimen.volume_panel_width);
lp.type = LayoutParams.TYPE_VOLUME_OVERLAY;
lp.windowAnimations = R.style.Animation_VolumePanel;
window.setBackgroundDrawableResource(com.android.systemui.R.drawable.qs_panel_background);
window.setAttributes(lp);
window.setGravity(Gravity.TOP);
window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
window.requestFeature(Window.FEATURE_NO_TITLE);
window.addFlags(LayoutParams.FLAG_NOT_FOCUSABLE
| LayoutParams.FLAG_NOT_TOUCH_MODAL
| LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
mDialog.setCanceledOnTouchOutside(true);
mDialog.setContentView(layoutId);
mDialog.setOnDismissListener(new OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
mActiveStreamType = -1;
mAudioManager.forceVolumeControlStream(mActiveStreamType);
}
});
mDialog.setCanceledOnTouchOutside(true);
mDialog.setContentView(R.layout.volume_adjust);
mDialog.setOnDismissListener(new OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
mActiveStreamType = -1;
mAudioManager.forceVolumeControlStream(mActiveStreamType);
}
});
mDialog.create();
mDialog.create();
mView = window.findViewById(R.id.content);
mView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
resetTimeout();
return false;
}
});
mView = window.findViewById(R.id.content);
mView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
resetTimeout();
return false;
}
});
mPanel = (ViewGroup) mView.findViewById(R.id.visible_panel);
mSliderGroup = (ViewGroup) mView.findViewById(R.id.slider_group);
mMoreButton = mView.findViewById(R.id.expand_button);
mDivider = mView.findViewById(R.id.expand_button_divider);
} else {
// embedded mode
mDialog = null;
mView = LayoutInflater.from(mContext).inflate(layoutId, parent, true);
}
mPanel = (ViewGroup) mView.findViewById(com.android.systemui.R.id.visible_panel);
mSliderPanel = (ViewGroup) mView.findViewById(com.android.systemui.R.id.slider_panel);
mExpandButton = (ImageView) mView.findViewById(com.android.systemui.R.id.expand_button);
mExpandDivider = mView.findViewById(com.android.systemui.R.id.expand_button_divider);
mZenPanelStub = (ViewStub)mView.findViewById(com.android.systemui.R.id.zen_panel_stub);
mZenPanelDivider = mView.findViewById(com.android.systemui.R.id.zen_panel_divider);
mToneGenerators = new ToneGenerator[AudioSystem.getNumStreamTypes()];
mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
mVoiceCapable = context.getResources().getBoolean(R.bool.config_voice_capable);
// If we don't want to show multiple volumes, hide the settings button
// and divider.
mShowCombinedVolumes = !mVoiceCapable && !useMasterVolume;
if (!mShowCombinedVolumes) {
mMoreButton.setVisibility(View.GONE);
mDivider.setVisibility(View.GONE);
} else {
mMoreButton.setOnClickListener(mClickListener);
}
mZenModeCapable = !useMasterVolume && mZenController != null;
mZenPanelDivider.setVisibility(View.GONE);
mExpandButton.setOnClickListener(mClickListener);
updateZenMode(mZenController == null ? false : mZenController.isZen());
mZenController.addCallback(mZenCallback);
final boolean masterVolumeOnly = res.getBoolean(R.bool.config_useMasterVolume);
final boolean masterVolumeKeySounds = res.getBoolean(R.bool.config_useVolumeKeySounds);
@@ -334,7 +360,7 @@ public class VolumePanel extends Handler {
listenToRingerMode();
}
public void setLayoutDirection(int layoutDirection) {
private void setLayoutDirection(int layoutDirection) {
mPanel.setLayoutDirection(layoutDirection);
updateStates();
}
@@ -406,21 +432,19 @@ public class VolumePanel extends Handler {
StreamResources streamRes = STREAMS[i];
final int streamType = streamRes.streamType;
if (mVoiceCapable && streamRes == StreamResources.NotificationStream) {
streamRes = StreamResources.RingerStream;
}
final StreamControl sc = new StreamControl();
sc.streamType = streamType;
sc.group = (ViewGroup) inflater.inflate(R.layout.volume_adjust_item, null);
sc.group = (ViewGroup) inflater.inflate(
com.android.systemui.R.layout.volume_panel_item, null);
sc.group.setTag(sc);
sc.icon = (ImageView) sc.group.findViewById(R.id.stream_icon);
sc.icon = (ImageView) sc.group.findViewById(com.android.systemui.R.id.stream_icon);
sc.icon.setTag(sc);
sc.icon.setContentDescription(res.getString(streamRes.descRes));
sc.iconRes = streamRes.iconRes;
sc.iconMuteRes = streamRes.iconMuteRes;
sc.icon.setImageResource(sc.iconRes);
sc.seekbarView = (SeekBar) sc.group.findViewById(R.id.seekbar);
sc.seekbarView = (SeekBar) sc.group.findViewById(com.android.systemui.R.id.seekbar);
final int plusOne = (streamType == AudioSystem.STREAM_BLUETOOTH_SCO ||
streamType == AudioSystem.STREAM_VOICE_CALL) ? 1 : 0;
sc.seekbarView.setMax(getStreamMaxVolume(streamType) + plusOne);
@@ -431,34 +455,18 @@ public class VolumePanel extends Handler {
}
private void reorderSliders(int activeStreamType) {
mSliderGroup.removeAllViews();
mSliderPanel.removeAllViews();
final StreamControl active = mStreamControls.get(activeStreamType);
if (active == null) {
Log.e("VolumePanel", "Missing stream type! - " + activeStreamType);
mActiveStreamType = -1;
} else {
mSliderGroup.addView(active.group);
mSliderPanel.addView(active.group);
mActiveStreamType = activeStreamType;
active.group.setVisibility(View.VISIBLE);
updateSlider(active);
}
addOtherVolumes();
}
private void addOtherVolumes() {
if (!mShowCombinedVolumes) return;
for (int i = 0; i < STREAMS.length; i++) {
// Skip the phone specific ones and the active one
final int streamType = STREAMS[i].streamType;
if (!STREAMS[i].show || streamType == mActiveStreamType) {
continue;
}
StreamControl sc = mStreamControls.get(streamType);
mSliderGroup.addView(sc.group);
updateSlider(sc);
updateZenMode(mZenController == null ? false : mZenController.isZen());
}
}
@@ -472,7 +480,7 @@ public class VolumePanel extends Handler {
if (((sc.streamType == AudioManager.STREAM_RING) ||
(sc.streamType == AudioManager.STREAM_NOTIFICATION)) &&
mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE) {
sc.icon.setImageResource(R.drawable.ic_audio_ring_notif_vibrate);
sc.icon.setImageResource(com.android.systemui.R.drawable.ic_ringer_vibrate);
}
if (sc.streamType == AudioService.STREAM_REMOTE_MUSIC) {
// never disable touch interactions for remote playback, the muting is not tied to
@@ -486,32 +494,70 @@ public class VolumePanel extends Handler {
}
}
public void setZenModePanelCallback(ZenModePanel.Callback callback) {
mZenPanelCallback = callback;
}
private void expand() {
final int count = mSliderGroup.getChildCount();
for (int i = 0; i < count; i++) {
mSliderGroup.getChildAt(i).setVisibility(View.VISIBLE);
if (LOGD) Log.d(mTag, "expand mZenPanel=" + mZenPanel);
if (mZenPanel == null) {
mZenPanel = (ZenModePanel) mZenPanelStub.inflate();
mZenPanel.init(mZenController);
mZenPanel.setCallback(new ZenModePanel.Callback() {
@Override
public void onMoreSettings() {
if (mZenPanelCallback != null) {
mZenPanelCallback.onMoreSettings();
}
}
@Override
public void onInteraction() {
if (mZenPanelCallback != null) {
mZenPanelCallback.onInteraction();
}
}
});
}
mMoreButton.setVisibility(View.INVISIBLE);
mDivider.setVisibility(View.INVISIBLE);
mZenPanel.setVisibility(View.VISIBLE);
mZenPanelDivider.setVisibility(View.VISIBLE);
}
private void collapse() {
mMoreButton.setVisibility(View.VISIBLE);
mDivider.setVisibility(View.VISIBLE);
final int count = mSliderGroup.getChildCount();
for (int i = 1; i < count; i++) {
mSliderGroup.getChildAt(i).setVisibility(View.GONE);
if (LOGD) Log.d(mTag, "collapse mZenPanel=" + mZenPanel);
if (mZenPanel != null) {
mZenPanel.setVisibility(View.GONE);
}
mZenPanelDivider.setVisibility(View.GONE);
}
public void updateStates() {
final int count = mSliderGroup.getChildCount();
final int count = mSliderPanel.getChildCount();
for (int i = 0; i < count; i++) {
StreamControl sc = (StreamControl) mSliderGroup.getChildAt(i).getTag();
StreamControl sc = (StreamControl) mSliderPanel.getChildAt(i).getTag();
updateSlider(sc);
}
}
private void updateZenMode(boolean zen) {
if (mZenModeCapable) {
final boolean show = mActiveStreamType == AudioManager.STREAM_NOTIFICATION
|| mActiveStreamType == AudioManager.STREAM_RING;
mExpandButton.setVisibility(show ? View.VISIBLE : View.GONE);
mExpandDivider.setVisibility(show ? View.VISIBLE : View.GONE);
mExpandButton.setImageResource(zen ? com.android.systemui.R.drawable.ic_vol_zen_on
: com.android.systemui.R.drawable.ic_vol_zen_off);
} else {
mExpandButton.setVisibility(View.GONE);
mExpandDivider.setVisibility(View.GONE);
}
}
public void postZenModeChanged(boolean zen) {
removeMessages(MSG_ZEN_MODE_CHANGED);
obtainMessage(MSG_ZEN_MODE_CHANGED, zen ? 1 : 0).sendToTarget();
}
public void postVolumeChanged(int streamType, int flags) {
if (hasMessages(MSG_VOLUME_CHANGED)) return;
synchronized (this) {
@@ -582,8 +628,12 @@ public class VolumePanel extends Handler {
}
public void postDismiss() {
removeMessages(MSG_TIMEOUT);
sendEmptyMessage(MSG_TIMEOUT);
forceTimeout();
}
public void postLayoutDirection(int layoutDirection) {
removeMessages(MSG_LAYOUT_DIRECTION);
obtainMessage(MSG_LAYOUT_DIRECTION, layoutDirection).sendToTarget();
}
/**
@@ -593,7 +643,7 @@ public class VolumePanel extends Handler {
*/
protected void onVolumeChanged(int streamType, int flags) {
if (LOGD) Log.d(TAG, "onVolumeChanged(streamType: " + streamType + ", flags: " + flags + ")");
if (LOGD) Log.d(mTag, "onVolumeChanged(streamType: " + streamType + ", flags: " + flags + ")");
if ((flags & AudioManager.FLAG_SHOW_UI) != 0) {
synchronized (this) {
@@ -622,7 +672,7 @@ public class VolumePanel extends Handler {
protected void onMuteChanged(int streamType, int flags) {
if (LOGD) Log.d(TAG, "onMuteChanged(streamType: " + streamType + ", flags: " + flags + ")");
if (LOGD) Log.d(mTag, "onMuteChanged(streamType: " + streamType + ", flags: " + flags + ")");
StreamControl sc = mStreamControls.get(streamType);
if (sc != null) {
@@ -638,7 +688,7 @@ public class VolumePanel extends Handler {
mRingIsSilent = false;
if (LOGD) {
Log.d(TAG, "onShowVolumeChanged(streamType: " + streamType
Log.d(mTag, "onShowVolumeChanged(streamType: " + streamType
+ ", flags: " + flags + "), index: " + index);
}
@@ -707,7 +757,7 @@ public class VolumePanel extends Handler {
}
case AudioService.STREAM_REMOTE_MUSIC: {
if (LOGD) { Log.d(TAG, "showing remote volume "+index+" over "+ max); }
if (LOGD) { Log.d(mTag, "showing remote volume "+index+" over "+ max); }
break;
}
}
@@ -730,16 +780,18 @@ public class VolumePanel extends Handler {
}
}
if (!mDialog.isShowing()) {
if (!isShowing()) {
int stream = (streamType == AudioService.STREAM_REMOTE_MUSIC) ? -1 : streamType;
// when the stream is for remote playback, use -1 to reset the stream type evaluation
mAudioManager.forceVolumeControlStream(stream);
// Showing dialog - use collapsed state
if (mShowCombinedVolumes) {
if (mZenModeCapable) {
collapse();
}
mDialog.show();
if (mDialog != null) {
mDialog.show();
}
}
// Do a little vibrate if applicable (only when going into vibrate mode)
@@ -751,6 +803,10 @@ public class VolumePanel extends Handler {
}
}
private boolean isShowing() {
return mDialog != null ? mDialog.isShowing() : mParent.isAttachedToWindow();
}
protected void onPlaySound(int streamType, int flags) {
if (hasMessages(MSG_STOP_SOUNDS)) {
@@ -795,9 +851,9 @@ public class VolumePanel extends Handler {
// streamType is the real stream type being affected, but for the UI sliders, we
// refer to AudioService.STREAM_REMOTE_MUSIC. We still play the beeps on the real
// stream type.
if (LOGD) Log.d(TAG, "onRemoteVolumeChanged(stream:"+streamType+", flags: " + flags + ")");
if (LOGD) Log.d(mTag, "onRemoteVolumeChanged(stream:"+streamType+", flags: " + flags + ")");
if (((flags & AudioManager.FLAG_SHOW_UI) != 0) || mDialog.isShowing()) {
if (((flags & AudioManager.FLAG_SHOW_UI) != 0) || isShowing()) {
synchronized (this) {
if (mActiveStreamType != AudioService.STREAM_REMOTE_MUSIC) {
reorderSliders(AudioService.STREAM_REMOTE_MUSIC);
@@ -805,7 +861,7 @@ public class VolumePanel extends Handler {
onShowVolumeChanged(AudioService.STREAM_REMOTE_MUSIC, flags);
}
} else {
if (LOGD) Log.d(TAG, "not calling onShowVolumeChanged(), no FLAG_SHOW_UI or no UI");
if (LOGD) Log.d(mTag, "not calling onShowVolumeChanged(), no FLAG_SHOW_UI or no UI");
}
if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 && ! mRingIsSilent) {
@@ -825,8 +881,8 @@ public class VolumePanel extends Handler {
}
protected void onRemoteVolumeUpdateIfShown() {
if (LOGD) Log.d(TAG, "onRemoteVolumeUpdateIfShown()");
if (mDialog.isShowing()
if (LOGD) Log.d(mTag, "onRemoteVolumeUpdateIfShown()");
if (isShowing()
&& (mActiveStreamType == AudioService.STREAM_REMOTE_MUSIC)
&& (mStreamControls != null)) {
onShowVolumeChanged(AudioService.STREAM_REMOTE_MUSIC, 0);
@@ -842,7 +898,7 @@ public class VolumePanel extends Handler {
* @param visible
*/
synchronized protected void onSliderVisibilityChanged(int streamType, int visible) {
if (LOGD) Log.d(TAG, "onSliderVisibilityChanged(stream="+streamType+", visi="+visible+")");
if (LOGD) Log.d(mTag, "onSliderVisibilityChanged(stream="+streamType+", visi="+visible+")");
boolean isVisible = (visible == 1);
for (int i = STREAMS.length - 1 ; i >= 0 ; i--) {
StreamResources streamRes = STREAMS[i];
@@ -857,7 +913,7 @@ public class VolumePanel extends Handler {
}
protected void onDisplaySafeVolumeWarning(int flags) {
if ((flags & AudioManager.FLAG_SHOW_UI) != 0 || mDialog.isShowing()) {
if ((flags & AudioManager.FLAG_SHOW_UI) != 0 || isShowing()) {
synchronized (sConfirmSafeVolumeLock) {
if (sConfirmSafeVolumeDialog != null) {
return;
@@ -907,7 +963,7 @@ public class VolumePanel extends Handler {
mToneGenerators[streamType] = new ToneGenerator(streamType, MAX_VOLUME);
} catch (RuntimeException e) {
if (LOGD) {
Log.d(TAG, "ToneGenerator constructor failed with "
Log.d(mTag, "ToneGenerator constructor failed with "
+ "RuntimeException: " + e);
}
}
@@ -976,8 +1032,10 @@ public class VolumePanel extends Handler {
}
case MSG_TIMEOUT: {
if (mDialog.isShowing()) {
mDialog.dismiss();
if (isShowing()) {
if (mDialog != null) {
mDialog.dismiss();
}
mActiveStreamType = -1;
}
synchronized (sConfirmSafeVolumeLock) {
@@ -988,7 +1046,7 @@ public class VolumePanel extends Handler {
break;
}
case MSG_RINGER_MODE_CHANGED: {
if (mDialog.isShowing()) {
if (isShowing()) {
updateStates();
}
break;
@@ -1010,17 +1068,30 @@ public class VolumePanel extends Handler {
case MSG_DISPLAY_SAFE_VOLUME_WARNING:
onDisplaySafeVolumeWarning(msg.arg1);
break;
case MSG_LAYOUT_DIRECTION:
setLayoutDirection(msg.arg1);
break;
case MSG_ZEN_MODE_CHANGED:
updateZenMode(msg.arg1 != 0);
break;
}
}
private void resetTimeout() {
public void resetTimeout() {
if (LOGD) Log.d(mTag, "resetTimeout at " + System.currentTimeMillis());
removeMessages(MSG_TIMEOUT);
sendMessageDelayed(obtainMessage(MSG_TIMEOUT), TIMEOUT_DELAY);
sendEmptyMessageDelayed(MSG_TIMEOUT, TIMEOUT_DELAY);
}
private void forceTimeout() {
removeMessages(MSG_TIMEOUT);
sendMessage(obtainMessage(MSG_TIMEOUT));
sendEmptyMessage(MSG_TIMEOUT);
}
public ZenModeController getZenController() {
return mZenController;
}
private final OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() {
@@ -1061,10 +1132,22 @@ public class VolumePanel extends Handler {
private final View.OnClickListener mClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
if (v == mMoreButton) {
expand();
if (v == mExpandButton && mZenController != null) {
final boolean newZen = !mZenController.isZen();
mZenController.setZen(newZen);
if (newZen) {
expand();
} else {
collapse();
}
}
resetTimeout();
}
};
private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() {
public void onZenChanged(boolean zen) {
updateZenMode(zen);
}
};
}

View File

@@ -1,16 +1,22 @@
package com.android.systemui.volume;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.media.AudioManager;
import android.media.IVolumeController;
import android.net.Uri;
import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Log;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.statusbar.policy.ZenModeControllerImpl;
/*
* Copyright (C) 2014 The Android Open Source Project
@@ -34,21 +40,21 @@ public class VolumeUI extends SystemUI {
private static final Uri SETTING_URI = Settings.Global.getUriFor(SETTING);
private static final int DEFAULT = 1; // enabled by default
private final Handler mHandler = new Handler();
private AudioManager mAudioManager;
private VolumeController mVolumeController;
@Override
public void start() {
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
mVolumeController = new VolumeController(mContext);
putComponent(VolumeComponent.class, mVolumeController);
updateController();
mContext.getContentResolver().registerContentObserver(SETTING_URI, false, mObserver);
}
private void updateController() {
if (Settings.Global.getInt(mContext.getContentResolver(), SETTING, DEFAULT) != 0) {
if (mVolumeController == null) {
mVolumeController = new VolumeController(mContext);
}
Log.d(TAG, "Registering volume controller");
mAudioManager.setVolumeController(mVolumeController);
} else {
@@ -57,7 +63,7 @@ public class VolumeUI extends SystemUI {
}
}
private final ContentObserver mObserver = new ContentObserver(new Handler()) {
private final ContentObserver mObserver = new ContentObserver(mHandler) {
public void onChange(boolean selfChange, Uri uri) {
if (SETTING_URI.equals(uri)) {
updateController();
@@ -66,13 +72,38 @@ public class VolumeUI extends SystemUI {
};
/** For now, simply host an unmodified base volume panel in this process. */
private final class VolumeController extends IVolumeController.Stub {
private final VolumePanel mPanel;
private final class VolumeController extends IVolumeController.Stub implements VolumeComponent {
private final VolumePanel mDialogPanel;
private VolumePanel mPanel;
public VolumeController(Context context) {
mPanel = new VolumePanel(context);
mPanel = new VolumePanel(context, null, new ZenModeControllerImpl(mContext, mHandler));
final int delay = context.getResources().getInteger(R.integer.feedback_start_delay);
mPanel.setZenModePanelCallback(new ZenModePanel.Callback() {
@Override
public void onMoreSettings() {
mHandler.removeCallbacks(mStartZenSettings);
mHandler.postDelayed(mStartZenSettings, delay);
}
@Override
public void onInteraction() {
mDialogPanel.resetTimeout();
}
});
mDialogPanel = mPanel;
}
private final Runnable mStartZenSettings = new Runnable() {
@Override
public void run() {
mDialogPanel.postDismiss();
final Intent intent = ZenModePanel.ZEN_SETTINGS;
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
mContext.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
}
};
@Override
public void hasNewRemotePlaybackInfo() throws RemoteException {
mPanel.postHasNewRemotePlaybackInfo();
@@ -114,12 +145,22 @@ public class VolumeUI extends SystemUI {
@Override
public void setLayoutDirection(int layoutDirection)
throws RemoteException {
mPanel.setLayoutDirection(layoutDirection);
mPanel.postLayoutDirection(layoutDirection);
}
@Override
public void dismiss() throws RemoteException {
mPanel.postDismiss();
}
@Override
public ZenModeController getZenController() {
return mDialogPanel.getZenController();
}
@Override
public void setVolumePanel(VolumePanel panel) {
mPanel = panel == null ? mDialogPanel : panel;
}
}
}

View File

@@ -0,0 +1,248 @@
/*
* Copyright (C) 2014 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.Intent;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.provider.Settings;
import android.service.notification.Condition;
import android.util.AttributeSet;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RadioButton;
import android.widget.TextView;
import com.android.systemui.R;
import com.android.systemui.statusbar.policy.ZenModeController;
import java.util.Arrays;
import java.util.HashSet;
public class ZenModePanel extends LinearLayout {
private static final int[] MINUTES = new int[] { 15, 30, 45, 60, 120, 180, 240, 480 };
public static final Intent ZEN_SETTINGS = new Intent(Settings.ACTION_ZEN_MODE_SETTINGS);
private final LayoutInflater mInflater;
private final HashSet<RadioButton> mRadioButtons = new HashSet<RadioButton>();
private final H mHandler = new H();
private LinearLayout mConditions;
private int mMinutesIndex = Arrays.binarySearch(MINUTES, 60); // default to one hour
private Callback mCallback;
private ZenModeController mController;
private boolean mRequestingConditions;
public ZenModePanel(Context context, AttributeSet attrs) {
super(context, attrs);
mInflater = LayoutInflater.from(new ContextThemeWrapper(context, R.style.QSWhiteTheme));
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mConditions = (LinearLayout) findViewById(android.R.id.content);
findViewById(android.R.id.button2).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
fireMoreSettings();
}
});
}
@Override
public void setVisibility(int visibility) {
super.setVisibility(visibility);
setRequestingConditions(visibility == VISIBLE);
}
/** Start or stop requesting relevant zen mode exit conditions */
private void setRequestingConditions(boolean requesting) {
if (mRequestingConditions == requesting) return;
mRequestingConditions = requesting;
if (mRequestingConditions) {
mController.addCallback(mZenCallback);
} else {
mController.removeCallback(mZenCallback);
}
mController.requestConditions(mRequestingConditions);
}
public void init(ZenModeController controller) {
mController = controller;
mConditions.removeAllViews();
bind(updateTimeCondition(), mConditions.getChildAt(0));
handleUpdateConditions(new Condition[0]);
}
public void setCallback(Callback callback) {
mCallback = callback;
}
private Condition updateTimeCondition() {
final int minutes = MINUTES[mMinutesIndex];
final long millis = System.currentTimeMillis() + minutes * 60 * 1000;
final Uri id = new Uri.Builder().scheme(Condition.SCHEME).authority("android")
.appendPath("countdown").appendPath(Long.toString(millis)).build();
final int num = minutes < 60 ? minutes : minutes / 60;
final int resId = minutes < 60
? R.plurals.zen_mode_duration_minutes
: R.plurals.zen_mode_duration_hours;
final String caption = mContext.getResources().getQuantityString(resId, num, num);
return new Condition(id, caption, "", "", 0, Condition.STATE_TRUE,
Condition.FLAG_RELEVANT_NOW);
}
private void handleUpdateConditions(Condition[] conditions) {
final int newCount = conditions == null ? 0 : conditions.length;
for (int i = mConditions.getChildCount() - 1; i > newCount; i--) {
mConditions.removeViewAt(i);
}
for (int i = 0; i < newCount; i++) {
bind(conditions[i], mConditions.getChildAt(i + 1));
}
bind(null, mConditions.getChildAt(newCount + 1));
}
private void editTimeCondition(int delta) {
final int i = mMinutesIndex + delta;
if (i < 0 || i >= MINUTES.length) return;
mMinutesIndex = i;
final Condition c = updateTimeCondition();
bind(c, mConditions.getChildAt(0));
}
private void bind(final Condition condition, View convertView) {
final boolean enabled = condition == null || condition.state == Condition.STATE_TRUE;
final View row;
if (convertView == null) {
row = mInflater.inflate(R.layout.zen_mode_condition, this, false);
mConditions.addView(row);
} else {
row = convertView;
}
final int position = mConditions.indexOfChild(row);
final RadioButton rb = (RadioButton) row.findViewById(android.R.id.checkbox);
mRadioButtons.add(rb);
rb.setEnabled(enabled);
rb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
for (RadioButton otherButton : mRadioButtons) {
if (otherButton == rb) continue;
otherButton.setChecked(false);
}
mController.select(condition);
fireInteraction();
}
}
});
final TextView title = (TextView) row.findViewById(android.R.id.title);
if (condition == null) {
title.setText(R.string.zen_mode_forever);
} else {
title.setText(condition.summary);
}
title.setEnabled(enabled);
title.setAlpha(enabled ? 1 : .5f);
final ImageView button1 = (ImageView) row.findViewById(android.R.id.button1);
button1.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
rb.setChecked(true);
editTimeCondition(-1);
fireInteraction();
}
});
final ImageView button2 = (ImageView) row.findViewById(android.R.id.button2);
button2.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
rb.setChecked(true);
editTimeCondition(1);
fireInteraction();
}
});
title.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
rb.setChecked(true);
fireInteraction();
}
});
if (position == 0) {
button1.setEnabled(mMinutesIndex > 0);
button2.setEnabled(mMinutesIndex < MINUTES.length - 1);
button1.setImageAlpha(button1.isEnabled() ? 0xff : 0x7f);
button2.setImageAlpha(button2.isEnabled() ? 0xff : 0x7f);
} else {
button1.setVisibility(View.GONE);
button2.setVisibility(View.GONE);
}
if (position == 0 && mConditions.getChildCount() == 1) {
rb.setChecked(true);
}
}
private void fireMoreSettings() {
if (mCallback != null) {
mCallback.onMoreSettings();
}
}
private void fireInteraction() {
if (mCallback != null) {
mCallback.onInteraction();
}
}
private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() {
@Override
public void onConditionsChanged(Condition[] conditions) {
mHandler.obtainMessage(H.UPDATE_CONDITIONS, conditions).sendToTarget();
}
};
private final class H extends Handler {
private static final int UPDATE_CONDITIONS = 1;
private H() {
super(Looper.getMainLooper());
}
@Override
public void handleMessage(Message msg) {
if (msg.what == UPDATE_CONDITIONS) {
handleUpdateConditions((Condition[])msg.obj);
}
}
}
public interface Callback {
void onMoreSettings();
void onInteraction();
}
}