summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/Android.mk1
-rw-r--r--packages/SystemUI/res/drawable/car_rounded_bg_bottom.xml11
-rw-r--r--packages/SystemUI/res/layout/car_volume_dialog.xml68
-rw-r--r--packages/SystemUI/res/layout/car_volume_dialog_row.xml47
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java835
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java10
-rw-r--r--packages/SystemUI/tests/Android.mk1
7 files changed, 973 insertions, 0 deletions
diff --git a/packages/SystemUI/Android.mk b/packages/SystemUI/Android.mk
index f65efb893e34..68293d964601 100644
--- a/packages/SystemUI/Android.mk
+++ b/packages/SystemUI/Android.mk
@@ -37,6 +37,7 @@ LOCAL_SRC_FILES := \
LOCAL_STATIC_ANDROID_LIBRARIES := \
SystemUIPluginLib \
SystemUISharedLib \
+ android-support-car \
android-support-v4 \
android-support-v7-recyclerview \
android-support-v7-preference \
diff --git a/packages/SystemUI/res/drawable/car_rounded_bg_bottom.xml b/packages/SystemUI/res/drawable/car_rounded_bg_bottom.xml
new file mode 100644
index 000000000000..25b449ab3a8a
--- /dev/null
+++ b/packages/SystemUI/res/drawable/car_rounded_bg_bottom.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="?android:attr/colorBackgroundFloating" />
+ <corners
+ android:bottomLeftRadius="@dimen/car_radius_3"
+ android:topLeftRadius="0dp"
+ android:bottomRightRadius="@dimen/car_radius_3"
+ android:topRightRadius="0dp"
+ />
+</shape>
diff --git a/packages/SystemUI/res/layout/car_volume_dialog.xml b/packages/SystemUI/res/layout/car_volume_dialog.xml
new file mode 100644
index 000000000000..dca50a5e929c
--- /dev/null
+++ b/packages/SystemUI/res/layout/car_volume_dialog.xml
@@ -0,0 +1,68 @@
+<!--
+ Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/car_margin"
+ android:layout_marginEnd="@dimen/car_margin"
+ android:background="@drawable/car_rounded_bg_bottom"
+ android:theme="@style/qs_theme"
+ android:clipChildren="false" >
+ <LinearLayout
+ android:id="@+id/volume_dialog"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal|top"
+ android:orientation="vertical"
+ android:clipChildren="false" >
+
+ <LinearLayout
+ android:id="@+id/main"
+ android:layout_width="match_parent"
+ android:minWidth="@dimen/volume_dialog_panel_width"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:elevation="@dimen/volume_panel_elevation" >
+ <LinearLayout
+ android:id="@+id/car_volume_dialog_rows"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:orientation="vertical" >
+ <!-- volume rows added and removed here! :-) -->
+ </LinearLayout>
+ </LinearLayout>
+ </LinearLayout>
+ <FrameLayout
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/car_single_line_list_item_height"
+ android:gravity="center"
+ android:layout_marginEnd="@dimen/car_keyline_1">
+ <ImageButton
+ android:id="@+id/expand"
+ android:layout_gravity="center"
+ android:layout_width="@dimen/car_primary_icon_size"
+ android:layout_height="@dimen/car_primary_icon_size"
+ android:layout_marginEnd="@dimen/car_keyline_1"
+ android:src="@drawable/car_ic_arrow_drop_up"
+ android:tint="@color/car_tint"
+ android:scaleType="fitCenter"
+ />
+ </FrameLayout>
+</FrameLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/car_volume_dialog_row.xml b/packages/SystemUI/res/layout/car_volume_dialog_row.xml
new file mode 100644
index 000000000000..14baf49f819b
--- /dev/null
+++ b/packages/SystemUI/res/layout/car_volume_dialog_row.xml
@@ -0,0 +1,47 @@
+<!--
+ Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:tag="row"
+ android:layout_height="@dimen/car_single_line_list_item_height"
+ android:layout_width="match_parent"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:theme="@style/qs_theme">
+
+ <LinearLayout
+ android:layout_height="match_parent"
+ android:layout_width="match_parent"
+ android:gravity="center"
+ android:layout_gravity="center"
+ android:orientation="horizontal" >
+ <com.android.keyguard.AlphaOptimizedImageButton
+ android:id="@+id/volume_row_icon"
+ android:layout_width="@dimen/car_primary_icon_size"
+ android:layout_height="@dimen/car_primary_icon_size"
+ android:layout_marginStart="@dimen/car_keyline_1"
+ android:tint="@color/car_tint"
+ android:scaleType="fitCenter"
+ android:soundEffectsEnabled="false" />
+ <SeekBar
+ android:id="@+id/volume_row_slider"
+ android:clickable="true"
+ android:layout_marginStart="@dimen/car_keyline_3"
+ android:layout_marginEnd="@dimen/car_keyline_3"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/car_single_line_list_item_height"/>
+ </LinearLayout>
+</FrameLayout>
diff --git a/packages/SystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
new file mode 100644
index 000000000000..41b094a32682
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
@@ -0,0 +1,835 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume;
+
+import android.animation.ObjectAnimator;
+import android.annotation.SuppressLint;
+import android.app.Dialog;
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.ColorDrawable;
+import android.media.AudioManager;
+import android.media.AudioSystem;
+import android.os.Debug;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+import android.provider.Settings.Global;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseBooleanArray;
+import android.view.ContextThemeWrapper;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.ImageButton;
+import android.widget.SeekBar;
+import android.widget.SeekBar.OnSeekBarChangeListener;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.android.settingslib.Utils;
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+import com.android.systemui.plugins.VolumeDialog;
+import com.android.systemui.plugins.VolumeDialogController;
+import com.android.systemui.plugins.VolumeDialogController.State;
+import com.android.systemui.plugins.VolumeDialogController.StreamState;
+
+/**
+ * Car version of the volume dialog.
+ *
+ * A client of VolumeDialogControllerImpl and its state model.
+ *
+ * Methods ending in "H" must be called on the (ui) handler.
+ */
+public class CarVolumeDialogImpl implements VolumeDialog {
+ private static final String TAG = Util.logTag(CarVolumeDialogImpl.class);
+
+ private static final long USER_ATTEMPT_GRACE_PERIOD = 1000;
+ private static final int UPDATE_ANIMATION_DURATION = 80;
+
+ private final Context mContext;
+ private final H mHandler = new H();
+ private final VolumeDialogController mController;
+
+ private Window mWindow;
+ private CustomDialog mDialog;
+ private ViewGroup mDialogView;
+ private ViewGroup mDialogRowsView;
+ private final List<VolumeRow> mRows = new ArrayList<>();
+ private ConfigurableTexts mConfigurableTexts;
+ private final SparseBooleanArray mDynamic = new SparseBooleanArray();
+ private final KeyguardManager mKeyguard;
+ private final Object mSafetyWarningLock = new Object();
+ private final ColorStateList mActiveSliderTint;
+ private final ColorStateList mInactiveSliderTint;
+
+ private boolean mShowing;
+
+ private int mActiveStream;
+ private int mPrevActiveStream;
+ private boolean mAutomute = VolumePrefs.DEFAULT_ENABLE_AUTOMUTE;
+ private boolean mSilentMode = VolumePrefs.DEFAULT_ENABLE_SILENT_MODE;
+ private State mState;
+ private SafetyWarningDialog mSafetyWarning;
+ private boolean mHovering = false;
+ private boolean mExpanded = false;
+ private View mExpandBtn;
+
+ public CarVolumeDialogImpl(Context context) {
+ mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme);
+ mController = Dependency.get(VolumeDialogController.class);
+ mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
+ mActiveSliderTint = ColorStateList.valueOf(Utils.getColorAccent(mContext));
+ mInactiveSliderTint = loadColorStateList(R.color.volume_slider_inactive);
+ }
+
+ public void init(int windowType, Callback callback) {
+ initDialog();
+
+ mController.addCallback(mControllerCallbackH, mHandler);
+ mController.getState();
+ }
+
+ @Override
+ public void destroy() {
+ mController.removeCallback(mControllerCallbackH);
+ mHandler.removeCallbacksAndMessages(null);
+ }
+
+ private void initDialog() {
+ mDialog = new CustomDialog(mContext);
+
+ mConfigurableTexts = new ConfigurableTexts(mContext);
+ mHovering = false;
+ mShowing = false;
+ mWindow = mDialog.getWindow();
+ mWindow.requestFeature(Window.FEATURE_NO_TITLE);
+ mWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
+ mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND
+ | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
+ mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+ | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+ | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
+ mWindow.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
+ mWindow.setWindowAnimations(com.android.internal.R.style.Animation_Toast);
+ final WindowManager.LayoutParams lp = mWindow.getAttributes();
+ lp.format = PixelFormat.TRANSLUCENT;
+ lp.setTitle(VolumeDialogImpl.class.getSimpleName());
+ lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
+ lp.windowAnimations = -1;
+ mWindow.setAttributes(lp);
+ mWindow.setLayout(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+
+ mDialog.setCanceledOnTouchOutside(true);
+ mDialog.setContentView(R.layout.car_volume_dialog);
+ mDialog.setOnShowListener(dialog -> {
+ mDialogView.setTranslationY(-mDialogView.getHeight());
+ mDialogView.setAlpha(0);
+ mDialogView.animate()
+ .alpha(1)
+ .translationY(0)
+ .setDuration(300)
+ .setInterpolator(new SystemUIInterpolators.LogDecelerateInterpolator())
+ .start();
+ });
+ mExpandBtn = mDialog.findViewById(R.id.expand);
+ mExpandBtn.setOnClickListener(v -> {
+ mExpanded = !mExpanded;
+ updateRowsH(getActiveRow());
+ });
+ mDialogView = mDialog.findViewById(R.id.volume_dialog);
+ mDialogView.setOnHoverListener((v, event) -> {
+ int action = event.getActionMasked();
+ mHovering = (action == MotionEvent.ACTION_HOVER_ENTER)
+ || (action == MotionEvent.ACTION_HOVER_MOVE);
+ rescheduleTimeoutH();
+ return true;
+ });
+
+ mDialogRowsView = mDialog.findViewById(R.id.car_volume_dialog_rows);
+
+ if (mRows.isEmpty()) {
+ addRow(AudioManager.STREAM_MUSIC,
+ R.drawable.ic_volume_media, R.drawable.ic_volume_media_mute, true, true);
+ addRow(AudioManager.STREAM_RING,
+ R.drawable.ic_volume_ringer, R.drawable.ic_volume_ringer_mute, true, false);
+ addRow(AudioManager.STREAM_ALARM,
+ R.drawable.ic_volume_alarm, R.drawable.ic_volume_alarm_mute, true, false);
+ } else {
+ addExistingRows();
+ }
+
+ updateRowsH(getActiveRow());
+ }
+
+ private ColorStateList loadColorStateList(int colorResId) {
+ return ColorStateList.valueOf(mContext.getColor(colorResId));
+ }
+
+ public void setStreamImportant(int stream, boolean important) {
+ mHandler.obtainMessage(H.SET_STREAM_IMPORTANT, stream, important ? 1 : 0).sendToTarget();
+ }
+
+ public void setAutomute(boolean automute) {
+ if (mAutomute == automute) return;
+ mAutomute = automute;
+ mHandler.sendEmptyMessage(H.RECHECK_ALL);
+ }
+
+ public void setSilentMode(boolean silentMode) {
+ if (mSilentMode == silentMode) return;
+ mSilentMode = silentMode;
+ mHandler.sendEmptyMessage(H.RECHECK_ALL);
+ }
+
+ private void addRow(int stream, int iconRes, int iconMuteRes, boolean important,
+ boolean defaultStream) {
+ addRow(stream, iconRes, iconMuteRes, important, defaultStream, false);
+ }
+
+ private void addRow(int stream, int iconRes, int iconMuteRes, boolean important,
+ boolean defaultStream, boolean dynamic) {
+ if (D.BUG) Slog.d(TAG, "Adding row for stream " + stream);
+ VolumeRow row = new VolumeRow();
+ initRow(row, stream, iconRes, iconMuteRes, important, defaultStream);
+ mDialogRowsView.addView(row.view);
+ mRows.add(row);
+ }
+
+ private void addExistingRows() {
+ int N = mRows.size();
+ for (int i = 0; i < N; i++) {
+ final VolumeRow row = mRows.get(i);
+ initRow(row, row.stream, row.iconRes, row.iconMuteRes, row.important,
+ row.defaultStream);
+ mDialogRowsView.addView(row.view);
+ updateVolumeRowH(row);
+ }
+ }
+
+ private VolumeRow getActiveRow() {
+ for (VolumeRow row : mRows) {
+ if (row.stream == mActiveStream) {
+ return row;
+ }
+ }
+ return mRows.get(0);
+ }
+
+ private VolumeRow findRow(int stream) {
+ for (VolumeRow row : mRows) {
+ if (row.stream == stream) return row;
+ }
+ return null;
+ }
+
+ public void dump(PrintWriter writer) {
+ writer.println(VolumeDialogImpl.class.getSimpleName() + " state:");
+ writer.print(" mShowing: "); writer.println(mShowing);
+ writer.print(" mActiveStream: "); writer.println(mActiveStream);
+ writer.print(" mDynamic: "); writer.println(mDynamic);
+ writer.print(" mAutomute: "); writer.println(mAutomute);
+ writer.print(" mSilentMode: "); writer.println(mSilentMode);
+ }
+
+ private static int getImpliedLevel(SeekBar seekBar, int progress) {
+ final int m = seekBar.getMax();
+ final int n = m / 100 - 1;
+ final int level = progress == 0 ? 0
+ : progress == m ? (m / 100) : (1 + (int)((progress / (float) m) * n));
+ return level;
+ }
+
+ @SuppressLint("InflateParams")
+ private void initRow(final VolumeRow row, final int stream, int iconRes, int iconMuteRes,
+ boolean important, boolean defaultStream) {
+ row.stream = stream;
+ row.iconRes = iconRes;
+ row.iconMuteRes = iconMuteRes;
+ row.important = important;
+ row.defaultStream = defaultStream;
+ row.view = mDialog.getLayoutInflater().inflate(R.layout.car_volume_dialog_row, null);
+ row.view.setId(row.stream);
+ row.view.setTag(row);
+ row.slider = row.view.findViewById(R.id.volume_row_slider);
+ row.slider.setOnSeekBarChangeListener(new VolumeSeekBarChangeListener(row));
+ row.anim = null;
+
+ row.icon = row.view.findViewById(R.id.volume_row_icon);
+ row.icon.setImageResource(iconRes);
+ }
+
+ public void show(int reason) {
+ mHandler.obtainMessage(H.SHOW, reason, 0).sendToTarget();
+ }
+
+ public void dismiss(int reason) {
+ mHandler.obtainMessage(H.DISMISS, reason, 0).sendToTarget();
+ }
+
+ private void showH(int reason) {
+ if (D.BUG) Log.d(TAG, "showH r=" + Events.DISMISS_REASONS[reason]);
+ mHandler.removeMessages(H.SHOW);
+ mHandler.removeMessages(H.DISMISS);
+ rescheduleTimeoutH();
+ if (mShowing) return;
+ mShowing = true;
+
+ mDialog.show();
+ Events.writeEvent(mContext, Events.EVENT_SHOW_DIALOG, reason, mKeyguard.isKeyguardLocked());
+ mController.notifyVisible(true);
+ }
+
+ protected void rescheduleTimeoutH() {
+ mHandler.removeMessages(H.DISMISS);
+ final int timeout = computeTimeoutH();
+ mHandler.sendMessageDelayed(mHandler
+ .obtainMessage(H.DISMISS, Events.DISMISS_REASON_TIMEOUT, 0), timeout);
+ if (D.BUG) Log.d(TAG, "rescheduleTimeout " + timeout + " " + Debug.getCaller());
+ mController.userActivity();
+ }
+
+ private int computeTimeoutH() {
+ if (mHovering) return 16000;
+ if (mSafetyWarning != null) return 5000;
+ return 3000;
+ }
+
+ protected void dismissH(int reason) {
+ mHandler.removeMessages(H.DISMISS);
+ mHandler.removeMessages(H.SHOW);
+ if (!mShowing) return;
+ mDialogView.animate().cancel();
+ mShowing = false;
+
+ mDialogView.setTranslationY(0);
+ mDialogView.setAlpha(1);
+ mDialogView.animate()
+ .alpha(0)
+ .translationY(-mDialogView.getHeight())
+ .setDuration(250)
+ .setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator())
+ .withEndAction(() -> mHandler.postDelayed(() -> {
+ if (D.BUG) Log.d(TAG, "mDialog.dismiss()");
+ mDialog.dismiss();
+ }, 50))
+ .start();
+
+ Events.writeEvent(mContext, Events.EVENT_DISMISS_DIALOG, reason);
+ mController.notifyVisible(false);
+ synchronized (mSafetyWarningLock) {
+ if (mSafetyWarning != null) {
+ if (D.BUG) Log.d(TAG, "SafetyWarning dismissed");
+ mSafetyWarning.dismiss();
+ }
+ }
+ }
+
+ private boolean shouldBeVisibleH(VolumeRow row) {
+ if (mExpanded) {
+ return true;
+ }
+ return row.defaultStream;
+ }
+
+ private void updateRowsH(final VolumeRow activeRow) {
+ if (D.BUG) Log.d(TAG, "updateRowsH");
+ if (!mShowing) {
+ trimObsoleteH();
+ }
+ // apply changes to all rows
+ for (final VolumeRow row : mRows) {
+ final boolean isActive = row == activeRow;
+ final boolean shouldBeVisible = shouldBeVisibleH(row);
+ Util.setVisOrGone(row.view, shouldBeVisible);
+ if (row.view.isShown()) {
+ updateVolumeRowSliderTintH(row, isActive);
+ }
+ }
+ }
+
+ private void trimObsoleteH() {
+ if (D.BUG) Log.d(TAG, "trimObsoleteH");
+ for (int i = mRows.size() - 1; i >= 0; i--) {
+ final VolumeRow row = mRows.get(i);
+ if (row.ss == null || !row.ss.dynamic) continue;
+ if (!mDynamic.get(row.stream)) {
+ mRows.remove(i);
+ mDialogRowsView.removeView(row.view);
+ }
+ }
+ }
+
+ protected void onStateChangedH(State state) {
+ mState = state;
+ mDynamic.clear();
+ // add any new dynamic rows
+ for (int i = 0; i < state.states.size(); i++) {
+ final int stream = state.states.keyAt(i);
+ final StreamState ss = state.states.valueAt(i);
+ if (!ss.dynamic) continue;
+ mDynamic.put(stream, true);
+ if (findRow(stream) == null) {
+ addRow(stream, R.drawable.ic_volume_remote, R.drawable.ic_volume_remote_mute, true,
+ false, true);
+ }
+ }
+
+ if (mActiveStream != state.activeStream) {
+ mPrevActiveStream = mActiveStream;
+ mActiveStream = state.activeStream;
+ updateRowsH(getActiveRow());
+ rescheduleTimeoutH();
+ }
+ for (VolumeRow row : mRows) {
+ updateVolumeRowH(row);
+ }
+
+ }
+
+ private void updateVolumeRowH(VolumeRow row) {
+ if (D.BUG) Log.d(TAG, "updateVolumeRowH s=" + row.stream);
+ if (mState == null) return;
+ final StreamState ss = mState.states.get(row.stream);
+ if (ss == null) return;
+ row.ss = ss;
+ if (ss.level > 0) {
+ row.lastAudibleLevel = ss.level;
+ }
+ if (ss.level == row.requestedLevel) {
+ row.requestedLevel = -1;
+ }
+ final boolean isRingStream = row.stream == AudioManager.STREAM_RING;
+ final boolean isSystemStream = row.stream == AudioManager.STREAM_SYSTEM;
+ final boolean isAlarmStream = row.stream == AudioManager.STREAM_ALARM;
+ final boolean isMusicStream = row.stream == AudioManager.STREAM_MUSIC;
+ final boolean isRingVibrate = isRingStream
+ && mState.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE;
+ final boolean isRingSilent = isRingStream
+ && mState.ringerModeInternal == AudioManager.RINGER_MODE_SILENT;
+ final boolean isZenPriorityOnly = mState.zenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+ final boolean isZenAlarms = mState.zenMode == Global.ZEN_MODE_ALARMS;
+ final boolean isZenNone = mState.zenMode == Global.ZEN_MODE_NO_INTERRUPTIONS;
+ final boolean zenMuted = isZenAlarms ? (isRingStream || isSystemStream)
+ : isZenNone ? (isRingStream || isSystemStream || isAlarmStream || isMusicStream)
+ : isZenPriorityOnly ? ((isAlarmStream && mState.disallowAlarms) ||
+ (isMusicStream && mState.disallowMedia) ||
+ (isRingStream && mState.disallowRinger) ||
+ (isSystemStream && mState.disallowSystem))
+ : false;
+
+ // update slider max
+ final int max = ss.levelMax * 100;
+ if (max != row.slider.getMax()) {
+ row.slider.setMax(max);
+ }
+
+ // update icon
+ final boolean iconEnabled = (mAutomute || ss.muteSupported) && !zenMuted;
+ row.icon.setEnabled(iconEnabled);
+ row.icon.setAlpha(iconEnabled ? 1 : 0.5f);
+ final int iconRes =
+ isRingVibrate ? R.drawable.ic_volume_ringer_vibrate
+ : isRingSilent || zenMuted ? row.iconMuteRes
+ : ss.routedToBluetooth ?
+ (ss.muted ? R.drawable.ic_volume_media_bt_mute
+ : R.drawable.ic_volume_media_bt)
+ : mAutomute && ss.level == 0 ? row.iconMuteRes
+ : (ss.muted ? row.iconMuteRes : row.iconRes);
+ row.icon.setImageResource(iconRes);
+ row.iconState =
+ iconRes == R.drawable.ic_volume_ringer_vibrate ? Events.ICON_STATE_VIBRATE
+ : (iconRes == R.drawable.ic_volume_media_bt_mute || iconRes == row.iconMuteRes)
+ ? Events.ICON_STATE_MUTE
+ : (iconRes == R.drawable.ic_volume_media_bt || iconRes == row.iconRes)
+ ? Events.ICON_STATE_UNMUTE
+ : Events.ICON_STATE_UNKNOWN;
+ if (iconEnabled) {
+ if (isRingStream) {
+ if (isRingVibrate) {
+ row.icon.setContentDescription(mContext.getString(
+ R.string.volume_stream_content_description_unmute,
+ getStreamLabelH(ss)));
+ } else {
+ if (mController.hasVibrator()) {
+ row.icon.setContentDescription(mContext.getString(
+ R.string.volume_stream_content_description_vibrate,
+ getStreamLabelH(ss)));
+ } else {
+ row.icon.setContentDescription(mContext.getString(
+ R.string.volume_stream_content_description_mute,
+ getStreamLabelH(ss)));
+ }
+ }
+ } else {
+ if (ss.muted || mAutomute && ss.level == 0) {
+ row.icon.setContentDescription(mContext.getString(
+ R.string.volume_stream_content_description_unmute,
+ getStreamLabelH(ss)));
+ } else {
+ row.icon.setContentDescription(mContext.getString(
+ R.string.volume_stream_content_description_mute,
+ getStreamLabelH(ss)));
+ }
+ }
+ } else {
+ row.icon.setContentDescription(getStreamLabelH(ss));
+ }
+
+ // ensure tracking is disabled if zenMuted
+ if (zenMuted) {
+ row.tracking = false;
+ }
+
+ // update slider
+ final boolean enableSlider = !zenMuted;
+ final int vlevel = row.ss.muted && (!isRingStream && !zenMuted) ? 0
+ : row.ss.level;
+ updateVolumeRowSliderH(row, enableSlider, vlevel);
+ }
+
+ private String getStreamLabelH(StreamState ss) {
+ if (ss.remoteLabel != null) {
+ return ss.remoteLabel;
+ }
+ try {
+ return mContext.getResources().getString(ss.name);
+ } catch (Resources.NotFoundException e) {
+ Slog.e(TAG, "Can't find translation for stream " + ss);
+ return "";
+ }
+ }
+
+ private void updateVolumeRowSliderTintH(VolumeRow row, boolean isActive) {
+ if (isActive) {
+ row.slider.requestFocus();
+ }
+ final ColorStateList tint = isActive && row.slider.isEnabled() ? mActiveSliderTint
+ : mInactiveSliderTint;
+ if (tint == row.cachedSliderTint) return;
+ row.cachedSliderTint = tint;
+ row.slider.setProgressTintList(tint);
+ row.slider.setThumbTintList(tint);
+ }
+
+ private void updateVolumeRowSliderH(VolumeRow row, boolean enable, int vlevel) {
+ row.slider.setEnabled(enable);
+ updateVolumeRowSliderTintH(row, row.stream == mActiveStream);
+ if (row.tracking) {
+ return; // don't update if user is sliding
+ }
+ final int progress = row.slider.getProgress();
+ final int level = getImpliedLevel(row.slider, progress);
+ final boolean rowVisible = row.view.getVisibility() == View.VISIBLE;
+ final boolean inGracePeriod = (SystemClock.uptimeMillis() - row.userAttempt)
+ < USER_ATTEMPT_GRACE_PERIOD;
+ mHandler.removeMessages(H.RECHECK, row);
+ if (mShowing && rowVisible && inGracePeriod) {
+ if (D.BUG) Log.d(TAG, "inGracePeriod");
+ mHandler.sendMessageAtTime(mHandler.obtainMessage(H.RECHECK, row),
+ row.userAttempt + USER_ATTEMPT_GRACE_PERIOD);
+ return; // don't update if visible and in grace period
+ }
+ if (vlevel == level) {
+ if (mShowing && rowVisible) {
+ return; // don't clamp if visible
+ }
+ }
+ final int newProgress = vlevel * 100;
+ if (progress != newProgress) {
+ if (mShowing && rowVisible) {
+ // animate!
+ if (row.anim != null && row.anim.isRunning()
+ && row.animTargetProgress == newProgress) {
+ return; // already animating to the target progress
+ }
+ // start/update animation
+ if (row.anim == null) {
+ row.anim = ObjectAnimator.ofInt(row.slider, "progress", progress, newProgress);
+ row.anim.setInterpolator(new DecelerateInterpolator());
+ } else {
+ row.anim.cancel();
+ row.anim.setIntValues(progress, newProgress);
+ }
+ row.animTargetProgress = newProgress;
+ row.anim.setDuration(UPDATE_ANIMATION_DURATION);
+ row.anim.start();
+ } else {
+ // update slider directly to clamped value
+ if (row.anim != null) {
+ row.anim.cancel();
+ }
+ row.slider.setProgress(newProgress, true);
+ }
+ }
+ }
+
+ private void recheckH(VolumeRow row) {
+ if (row == null) {
+ if (D.BUG) Log.d(TAG, "recheckH ALL");
+ trimObsoleteH();
+ for (VolumeRow r : mRows) {
+ updateVolumeRowH(r);
+ }
+ } else {
+ if (D.BUG) Log.d(TAG, "recheckH " + row.stream);
+ updateVolumeRowH(row);
+ }
+ }
+
+ private void setStreamImportantH(int stream, boolean important) {
+ for (VolumeRow row : mRows) {
+ if (row.stream == stream) {
+ row.important = important;
+ return;
+ }
+ }
+ }
+
+ private void showSafetyWarningH(int flags) {
+ if ((flags & (AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_SHOW_UI_WARNINGS)) != 0
+ || mShowing) {
+ synchronized (mSafetyWarningLock) {
+ if (mSafetyWarning != null) {
+ return;
+ }
+ mSafetyWarning = new SafetyWarningDialog(mContext, mController.getAudioManager()) {
+ @Override
+ protected void cleanUp() {
+ synchronized (mSafetyWarningLock) {
+ mSafetyWarning = null;
+ }
+ recheckH(null);
+ }
+ };
+ mSafetyWarning.show();
+ }
+ recheckH(null);
+ }
+ rescheduleTimeoutH();
+ }
+
+ private final VolumeDialogController.Callbacks mControllerCallbackH
+ = new VolumeDialogController.Callbacks() {
+ @Override
+ public void onShowRequested(int reason) {
+ showH(reason);
+ }
+
+ @Override
+ public void onDismissRequested(int reason) {
+ dismissH(reason);
+ }
+
+ @Override
+ public void onScreenOff() {
+ dismissH(Events.DISMISS_REASON_SCREEN_OFF);
+ }
+
+ @Override
+ public void onStateChanged(State state) {
+ onStateChangedH(state);
+ }
+
+ @Override
+ public void onLayoutDirectionChanged(int layoutDirection) {
+ mDialogView.setLayoutDirection(layoutDirection);
+ }
+
+ @Override
+ public void onConfigurationChanged() {
+ mDialog.dismiss();
+ initDialog();
+ mConfigurableTexts.update();
+ }
+
+ @Override
+ public void onShowVibrateHint() {
+ if (mSilentMode) {
+ mController.setRingerMode(AudioManager.RINGER_MODE_SILENT, false);
+ }
+ }
+
+ @Override
+ public void onShowSilentHint() {
+ if (mSilentMode) {
+ mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false);
+ }
+ }
+
+ @Override
+ public void onShowSafetyWarning(int flags) {
+ showSafetyWarningH(flags);
+ }
+
+ @Override
+ public void onAccessibilityModeChanged(Boolean showA11yStream) {
+ }
+ };
+
+ private final class H extends Handler {
+ private static final int SHOW = 1;
+ private static final int DISMISS = 2;
+ private static final int RECHECK = 3;
+ private static final int RECHECK_ALL = 4;
+ private static final int SET_STREAM_IMPORTANT = 5;
+ private static final int RESCHEDULE_TIMEOUT = 6;
+ private static final int STATE_CHANGED = 7;
+
+ public H() {
+ super(Looper.getMainLooper());
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case SHOW: showH(msg.arg1); break;
+ case DISMISS: dismissH(msg.arg1); break;
+ case RECHECK: recheckH((VolumeRow) msg.obj); break;
+ case RECHECK_ALL: recheckH(null); break;
+ case SET_STREAM_IMPORTANT: setStreamImportantH(msg.arg1, msg.arg2 != 0); break;
+ case RESCHEDULE_TIMEOUT: rescheduleTimeoutH(); break;
+ case STATE_CHANGED: onStateChangedH(mState); break;
+ }
+ }
+ }
+
+ private final class CustomDialog extends Dialog implements DialogInterface {
+ public CustomDialog(Context context) {
+ super(context, com.android.systemui.R.style.qs_theme);
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ rescheduleTimeoutH();
+ return super.dispatchTouchEvent(ev);
+ }
+
+ @Override
+ protected void onStart() {
+ super.setCanceledOnTouchOutside(true);
+ super.onStart();
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ mHandler.sendEmptyMessage(H.RECHECK_ALL);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (isShowing()) {
+ if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
+ dismissH(Events.DISMISS_REASON_TOUCH_OUTSIDE);
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ private final class VolumeSeekBarChangeListener implements OnSeekBarChangeListener {
+ private final VolumeRow mRow;
+
+ private VolumeSeekBarChangeListener(VolumeRow row) {
+ mRow = row;
+ }
+
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ if (mRow.ss == null) return;
+ if (D.BUG) Log.d(TAG, AudioSystem.streamToString(mRow.stream)
+ + " onProgressChanged " + progress + " fromUser=" + fromUser);
+ if (!fromUser) return;
+ if (mRow.ss.levelMin > 0) {
+ final int minProgress = mRow.ss.levelMin * 100;
+ if (progress < minProgress) {
+ seekBar.setProgress(minProgress);
+ progress = minProgress;
+ }
+ }
+ final int userLevel = getImpliedLevel(seekBar, progress);
+ if (mRow.ss.level != userLevel || mRow.ss.muted && userLevel > 0) {
+ mRow.userAttempt = SystemClock.uptimeMillis();
+ if (mRow.requestedLevel != userLevel) {
+ mController.setStreamVolume(mRow.stream, userLevel);
+ mRow.requestedLevel = userLevel;
+ Events.writeEvent(mContext, Events.EVENT_TOUCH_LEVEL_CHANGED, mRow.stream,
+ userLevel);
+ }
+ }
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ if (D.BUG) Log.d(TAG, "onStartTrackingTouch"+ " " + mRow.stream);
+ mController.setActiveStream(mRow.stream);
+ mRow.tracking = true;
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ if (D.BUG) Log.d(TAG, "onStopTrackingTouch"+ " " + mRow.stream);
+ mRow.tracking = false;
+ mRow.userAttempt = SystemClock.uptimeMillis();
+ final int userLevel = getImpliedLevel(seekBar, seekBar.getProgress());
+ Events.writeEvent(mContext, Events.EVENT_TOUCH_LEVEL_DONE, mRow.stream, userLevel);
+ if (mRow.ss.level != userLevel) {
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(H.RECHECK, mRow),
+ USER_ATTEMPT_GRACE_PERIOD);
+ }
+ }
+ }
+
+ private static class VolumeRow {
+ private View view;
+ private ImageButton icon;
+ private SeekBar slider;
+ private int stream;
+ private StreamState ss;
+ private long userAttempt; // last user-driven slider change
+ private boolean tracking; // tracking slider touch
+ private int requestedLevel = -1; // pending user-requested level via progress changed
+ private int iconRes;
+ private int iconMuteRes;
+ private boolean important;
+ private boolean defaultStream;
+ private ColorStateList cachedSliderTint;
+ private int iconState; // from Events
+ private ObjectAnimator anim; // slider progress animation for non-touch-related updates
+ private int animTargetProgress;
+ private int lastAudibleLevel = 1;
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
index 0203c43d3683..6e5b5484cabe 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
@@ -19,6 +19,7 @@ package com.android.systemui.volume;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.media.AudioManager;
import android.media.VolumePolicy;
@@ -80,6 +81,7 @@ public class VolumeDialogComponent implements VolumeComponent, TunerService.Tuna
Dependency.get(ExtensionController.class).newExtension(VolumeDialog.class)
.withPlugin(VolumeDialog.class)
.withDefault(this::createDefault)
+ .withFeature(PackageManager.FEATURE_AUTOMOTIVE, this::createCarDefault)
.withCallback(dialog -> {
if (mDialog != null) {
mDialog.destroy();
@@ -100,6 +102,14 @@ public class VolumeDialogComponent implements VolumeComponent, TunerService.Tuna
return impl;
}
+ private VolumeDialog createCarDefault() {
+ CarVolumeDialogImpl impl = new CarVolumeDialogImpl(mContext);
+ impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
+ impl.setAutomute(true);
+ impl.setSilentMode(false);
+ return impl;
+ }
+
@Override
public void onTuningChanged(String key, String newValue) {
if (VOLUME_DOWN_SILENT.equals(key)) {
diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk
index ebb088be8171..107ce1eecf2f 100644
--- a/packages/SystemUI/tests/Android.mk
+++ b/packages/SystemUI/tests/Android.mk
@@ -40,6 +40,7 @@ LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res \
LOCAL_STATIC_ANDROID_LIBRARIES := \
SystemUIPluginLib \
SystemUISharedLib \
+ android-support-car \
android-support-v4 \
android-support-v7-recyclerview \
android-support-v7-preference \