diff --git a/core/res/res/drawable/ic_audio_ring_notif.xml b/core/res/res/drawable/ic_audio_ring_notif.xml index 247d1b48f8500..b52db5c332056 100644 --- a/core/res/res/drawable/ic_audio_ring_notif.xml +++ b/core/res/res/drawable/ic_audio_ring_notif.xml @@ -1,23 +1,28 @@ - + + + + - + + diff --git a/core/res/res/drawable/ic_audio_ring_notif_mute.xml b/core/res/res/drawable/ic_audio_ring_notif_mute.xml index 72aaa9dc04ea5..8d7d6cbe0747e 100644 --- a/core/res/res/drawable/ic_audio_ring_notif_mute.xml +++ b/core/res/res/drawable/ic_audio_ring_notif_mute.xml @@ -1,23 +1,28 @@ - + + + + - + + diff --git a/core/res/res/drawable/ic_audio_ring_notif_vibrate.xml b/core/res/res/drawable/ic_audio_ring_notif_vibrate.xml index 9e31aba1305c0..2f1d940eb1a20 100644 --- a/core/res/res/drawable/ic_audio_ring_notif_vibrate.xml +++ b/core/res/res/drawable/ic_audio_ring_notif_vibrate.xml @@ -1,23 +1,28 @@ - + + + + - + + diff --git a/core/res/res/layout/global_actions_silent_mode.xml b/core/res/res/layout/global_actions_silent_mode.xml index 79401af25bb5d..a3586232a1527 100644 --- a/core/res/res/layout/global_actions_silent_mode.xml +++ b/core/res/res/layout/global_actions_silent_mode.xml @@ -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" diff --git a/core/res/res/layout/volume_adjust.xml b/core/res/res/layout/volume_adjust.xml deleted file mode 100644 index 3ad1f23575b82..0000000000000 --- a/core/res/res/layout/volume_adjust.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 52b021f356ed9..b0dc47d491bba 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -206,9 +206,6 @@ 240dip - - 16dp - 8dp 8dp diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index dcff97804785e..69f73e50527fd 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -69,8 +69,6 @@ - - @@ -161,9 +159,7 @@ - - @@ -181,7 +177,6 @@ - @@ -343,7 +338,6 @@ - @@ -1199,8 +1193,6 @@ - - diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java index c736fc79fa331..5b620fd57ee14 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -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 @@ private void onSetStreamVolume(int streamType, int index, int flags, int device) 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 @@ private boolean checkForRingerModeChange(int oldIndex, int direction, int step) } } 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 @@ private boolean checkForRingerModeChange(int oldIndex, int direction, int step) 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) { diff --git a/packages/SystemUI/res/drawable/ic_qs_ringer_audible.xml b/packages/SystemUI/res/drawable/ic_qs_ringer_audible.xml index 3a20c58684f12..787eec5517153 100644 --- a/packages/SystemUI/res/drawable/ic_qs_ringer_audible.xml +++ b/packages/SystemUI/res/drawable/ic_qs_ringer_audible.xml @@ -24,5 +24,5 @@ Copyright (C) 2014 The Android Open Source Project + 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"/> diff --git a/packages/SystemUI/res/drawable/ic_ringer_audible.xml b/packages/SystemUI/res/drawable/ic_ringer_audible.xml new file mode 100644 index 0000000000000..296994825b1fb --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_ringer_audible.xml @@ -0,0 +1,28 @@ + + + + + + + + diff --git a/packages/SystemUI/res/drawable/ic_ringer_silent.xml b/packages/SystemUI/res/drawable/ic_ringer_silent.xml new file mode 100644 index 0000000000000..b5837f65cecf5 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_ringer_silent.xml @@ -0,0 +1,28 @@ + + + + + + + + diff --git a/packages/SystemUI/res/drawable/ic_ringer_vibrate.xml b/packages/SystemUI/res/drawable/ic_ringer_vibrate.xml new file mode 100644 index 0000000000000..d8ded5844dc4b --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_ringer_vibrate.xml @@ -0,0 +1,28 @@ + + + + + + + + diff --git a/packages/SystemUI/res/drawable/ic_vol_zen_off.xml b/packages/SystemUI/res/drawable/ic_vol_zen_off.xml new file mode 100644 index 0000000000000..ea5ab7077b7ff --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_vol_zen_off.xml @@ -0,0 +1,30 @@ + + + + + + + + diff --git a/packages/SystemUI/res/drawable/ic_vol_zen_on.xml b/packages/SystemUI/res/drawable/ic_vol_zen_on.xml new file mode 100644 index 0000000000000..44024f30945a4 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_vol_zen_on.xml @@ -0,0 +1,28 @@ + + + + + + + + diff --git a/packages/SystemUI/res/layout/qs_zen_mode_detail.xml b/packages/SystemUI/res/layout/qs_detail.xml similarity index 52% rename from packages/SystemUI/res/layout/qs_zen_mode_detail.xml rename to packages/SystemUI/res/layout/qs_detail.xml index 85b294dd44a2a..e73b431b75f32 100644 --- a/packages/SystemUI/res/layout/qs_zen_mode_detail.xml +++ b/packages/SystemUI/res/layout/qs_detail.xml @@ -14,64 +14,44 @@ See the License for the specific language governing permissions and limitations under the License. --> - + android:background="@color/system_primary_color" > - - + android:contentDescription="@string/accessibility_quick_settings_close" + android:padding="@dimen/qs_panel_padding" + android:src="@drawable/ic_qs_close" /> + android:textAppearance="@style/TextAppearance.QS.DetailHeader" /> - + android:layout_marginLeft="16dip" + android:layout_marginRight="16dip" + android:scaleType="fitXY" + android:src="?android:attr/dividerHorizontal" /> - - - + android:layout_below="@android:id/custom" /> - \ No newline at end of file + \ No newline at end of file diff --git a/packages/SystemUI/res/layout/user_switcher_host.xml b/packages/SystemUI/res/layout/user_switcher_host.xml index 70c5042d07d80..816af575c364c 100644 --- a/packages/SystemUI/res/layout/user_switcher_host.xml +++ b/packages/SystemUI/res/layout/user_switcher_host.xml @@ -27,7 +27,7 @@ + + + + + + + + + + + + + + diff --git a/core/res/res/layout/volume_adjust_item.xml b/packages/SystemUI/res/layout/volume_panel_item.xml similarity index 95% rename from core/res/res/layout/volume_adjust_item.xml rename to packages/SystemUI/res/layout/volume_panel_item.xml index 57cecf4794892..98cb8f4287ef6 100644 --- a/core/res/res/layout/volume_adjust_item.xml +++ b/packages/SystemUI/res/layout/volume_panel_item.xml @@ -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" /> + android:layout_height="wrap_content" > + android:textAppearance="@style/TextAppearance.QS.DetailItemPrimary" /> + 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" /> + 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" /> - + \ No newline at end of file diff --git a/packages/SystemUI/res/layout/zen_mode_panel.xml b/packages/SystemUI/res/layout/zen_mode_panel.xml new file mode 100644 index 0000000000000..ae04bf503a8b6 --- /dev/null +++ b/packages/SystemUI/res/layout/zen_mode_panel.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 0bd4f1839f95e..6405ae657b84b 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -135,5 +135,8 @@ platform + + + 300 diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index bf0cb682788de..6e35230dd9913 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -199,6 +199,9 @@ 8dp + 8dp + 48dp + 192dp @@ -291,4 +294,9 @@ 22dp 36dp + + 16dp + + + 300dp diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index f5bc353606214..ef3956e992901 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -388,6 +388,12 @@ Location %s. Alarm set for %s. + + Close panel + + More time + + Less time 2G-3G data disabled @@ -512,6 +518,8 @@ Tethering Hotspot + + Notifications RECENTS @@ -563,4 +571,19 @@ Swipe up to unlock %s\n%s (%s) + + + Until you turn this off + + + + For one minute + For %d minutes + + + + + For one hour + For %d hours + diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 19888a8b56a57..6a12232b82bc5 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -133,6 +133,32 @@ horizontal + + + + + + + + + + - diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java index d7ce25583a17c..630ba133183b8 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java @@ -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, }; /** diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java index 835a5c460cb32..c76ee8c9f6559 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java @@ -26,6 +26,7 @@ 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.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 @@ public abstract class QSTile 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 @@ protected QSTile(Host host) { 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 void userSwitch(int newUserId) { 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 interface Host { ZenModeController getZenModeController(); TetheringController getTetheringController(); CastController getCastController(); + VolumeComponent getVolumeComponent(); } public static class State { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java index 5eecc20dbccd0..2edd8d5a06907 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java @@ -84,7 +84,8 @@ private void recreateLabel() { 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)); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BugreportTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BugreportTile.java index fa418370a035d..07ea8259dbcee 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BugreportTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BugreportTile.java @@ -58,13 +58,13 @@ public void setListening(boolean listening) { @Override protected void handleClick() { - mHandler.postDelayed(new Runnable() { + postAfterFeedback(new Runnable() { @Override public void run() { mHost.collapsePanels(); mUiHandler.post(mShowDialog); } - }, FEEDBACK_START_DELAY); + }); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java index 907c77ee96efc..679305114f782 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java @@ -65,12 +65,12 @@ protected void handleUserSwitch(int newUserId) { @Override protected void handleClick() { - mHandler.postDelayed(new Runnable() { + postAfterFeedback(new Runnable() { public void run() { mHost.collapsePanels(); mUiHandler.post(mShowDialog); } - }, FEEDBACK_START_DELAY); + }); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NotificationsTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NotificationsTile.java new file mode 100644 index 0000000000000..130f9ce60523a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NotificationsTile.java @@ -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 { + 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(); + } + } + }; +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RingerModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RingerModeTile.java deleted file mode 100644 index c5e9b52518f05..0000000000000 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RingerModeTile.java +++ /dev/null @@ -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 { - - 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; - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ZenModeDetail.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ZenModeDetail.java deleted file mode 100644 index f30f791f193f8..0000000000000 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ZenModeDetail.java +++ /dev/null @@ -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 { - private final LayoutInflater mInflater; - private final HashSet mRadioButtons = new HashSet(); - - 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; - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ZenModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ZenModeTile.java deleted file mode 100644 index bfa9c1937912d..0000000000000 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ZenModeTile.java +++ /dev/null @@ -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 { - 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); - } - }; -} diff --git a/packages/SystemUI/src/com/android/systemui/settings/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/BrightnessDialog.java index 27881c4f7d064..65e1cc6ef3e25 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/BrightnessDialog.java +++ b/packages/SystemUI/src/com/android/systemui/settings/BrightnessDialog.java @@ -72,8 +72,7 @@ public void onCreate(Bundle savedInstanceState) { 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; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index b36c2efa8cd3e..a3025e5684331 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -121,10 +121,12 @@ 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 boolean onTouch(View v, MotionEvent event) { 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 ValueAnimator createRevealAnimator(View v, int centerX, int centerY, 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); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java index 70298982d1125..1344703287add 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java @@ -21,6 +21,7 @@ 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.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.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> mTiles = new ArrayList>(); + 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 QSTileHost(Context context, PhoneStatusBar statusBar, mZen = zen; mTethering = tethering; mCast = cast; + mVolume = volume; final HandlerThread ht = new HandlerThread(QSTileHost.class.getSimpleName()); ht.start(); @@ -86,8 +90,7 @@ public QSTileHost(Context context, PhoneStatusBar statusBar, 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 void onUserSwitched(int newUserId) { } }; mUserTracker.startTracking(); + mFeedbackStartDelay = mContext.getResources().getInteger(R.integer.feedback_start_delay); } @Override @@ -112,7 +116,7 @@ public List> getTiles() { @Override public void startSettingsActivity(final Intent intent) { - mStatusBar.postStartSettingsActivity(intent, QSTile.FEEDBACK_START_DELAY); + mStatusBar.postStartSettingsActivity(intent, mFeedbackStartDelay); } @Override @@ -169,4 +173,9 @@ public TetheringController getTetheringController() { public CastController getCastController() { return mCast; } + + @Override + public VolumeComponent getVolumeComponent() { + return mVolume; + } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java new file mode 100644 index 0000000000000..5ee892530a6bc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java @@ -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); +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java index 8657e07823cce..06f4c2e17e13c 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java @@ -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.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.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 @@ * @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 @@ private enum StreamResources { 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 @@ private enum StreamResources { 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 @@ private void cleanUp() { } - 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 VolumePanel(Context context) { 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); - - mDialog.setCanceledOnTouchOutside(true); - mDialog.setContentView(R.layout.volume_adjust); - mDialog.setOnDismissListener(new OnDismissListener() { - @Override - public void onDismiss(DialogInterface dialog) { - mActiveStreamType = -1; - mAudioManager.forceVolumeControlStream(mActiveStreamType); - } - }); + }; + + // 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.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 boolean onTouch(View v, MotionEvent event) { listenToRingerMode(); } - public void setLayoutDirection(int layoutDirection) { + private void setLayoutDirection(int layoutDirection) { mPanel.setLayoutDirection(layoutDirection); updateStates(); } @@ -406,21 +432,19 @@ private void createSliders() { 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 @@ private void createSliders() { } 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 @@ private void updateSlider(StreamControl sc) { 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 @@ private void updateSlider(StreamControl sc) { } } + 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 void postDisplaySafeVolumeWarning(int flags) { } 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 void postDismiss() { */ 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 @@ protected void onVolumeChanged(int streamType, int flags) { 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 @@ protected void onShowVolumeChanged(int streamType, int flags) { mRingIsSilent = false; if (LOGD) { - Log.d(TAG, "onShowVolumeChanged(streamType: " + streamType + Log.d(mTag, "onShowVolumeChanged(streamType: " + streamType + ", flags: " + flags + "), index: " + index); } @@ -707,7 +757,7 @@ protected void onShowVolumeChanged(int streamType, int flags) { } 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 @@ protected void onShowVolumeChanged(int streamType, int flags) { } } - 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 @@ protected void onShowVolumeChanged(int streamType, int flags) { } } + 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 @@ protected void onRemoteVolumeChanged(int streamType, int flags) { // 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 @@ protected void onRemoteVolumeChanged(int streamType, int flags) { 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 @@ protected void onRemoteVolumeChanged(int streamType, int flags) { } 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 @@ protected void onRemoteVolumeUpdateIfShown() { * @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 @@ synchronized protected void onSliderVisibilityChanged(int streamType, int visibl } 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 @@ private ToneGenerator getOrCreateToneGenerator(int streamType) { 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 void handleMessage(Message msg) { } case MSG_TIMEOUT: { - if (mDialog.isShowing()) { - mDialog.dismiss(); + if (isShowing()) { + if (mDialog != null) { + mDialog.dismiss(); + } mActiveStreamType = -1; } synchronized (sConfirmSafeVolumeLock) { @@ -988,7 +1046,7 @@ public void handleMessage(Message msg) { break; } case MSG_RINGER_MODE_CHANGED: { - if (mDialog.isShowing()) { + if (isShowing()) { updateStates(); } break; @@ -1010,17 +1068,30 @@ public void handleMessage(Message msg) { 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 void onStopTrackingTouch(SeekBar seekBar) { 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); + } + }; } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java index 9bd75b78e68bb..7da90d8a6f5a1 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java @@ -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 @@ private void updateController() { } } - 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 void onChange(boolean selfChange, Uri uri) { }; /** 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 void masterMuteChanged(int flags) throws RemoteException { @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; + } } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java new file mode 100644 index 0000000000000..77d267e681177 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java @@ -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 mRadioButtons = new HashSet(); + 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(); + } +}