Add CarVolumeDialogController in SystemUI for Android Auto.
Cars usually have an external audio module. When Android is serving as
the car's headunit, users should be able to adjust the car's volume
through SystemUI. The following changes are made to make it work:
+ Load VolumeDialogController from SystemUIFactory
+ Added CarSystemUIFactory
+ Added CarVolumeDialogController which extends VolumeDialogController
and it uses CarAudioManager as source of truth for volume controls.
+ Some refactor in VolumeDialogController to make it easier for
subclasses to override volume controls.
Note that CarAudioManager does not completely replace AudioManager.
Majority of code in VolumeDialogController still applies in the car use
case, so I made CarVolumeDialogController a subclass of
VolumeDialogController instead of making them peers.
Bug: 27595951
Change-Id: Id4adec7281e41aa71f3de034e5b88a32a89be305
diff --git a/packages/SystemUI/Android.mk b/packages/SystemUI/Android.mk
index ad3c26b..6d8b9f6 100644
--- a/packages/SystemUI/Android.mk
+++ b/packages/SystemUI/Android.mk
@@ -31,6 +31,7 @@
SystemUI-proto-tags
LOCAL_JAVA_LIBRARIES := telephony-common
+LOCAL_JAVA_LIBRARIES += android.car
LOCAL_PACKAGE_NAME := SystemUI
LOCAL_CERTIFICATE := platform
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index c248adf..8f7515d 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -162,6 +162,9 @@
<!-- It's like, reality, but, you know, virtual -->
<uses-permission android:name="android.permission.ACCESS_VR_MANAGER" />
+ <!-- To control car audio module volume -->
+ <uses-permission android:name="android.car.permission.CAR_CONTROL_AUDIO_VOLUME" />
+
<application
android:name=".SystemUIApplication"
android:persistent="true"
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index d0c2d29..9182f7e 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -13,6 +13,7 @@
-keep class com.android.systemui.statusbar.car.CarStatusBar
-keep class com.android.systemui.statusbar.phone.PhoneStatusBar
-keep class com.android.systemui.statusbar.tv.TvStatusBar
+-keep class com.android.systemui.car.CarSystemUIFactory
-keep class com.android.systemui.SystemUIFactory
-keepclassmembers class ** {
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 53c2233..7dca192 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -16,6 +16,7 @@
package com.android.systemui;
+import android.content.ComponentName;
import android.content.Context;
import android.util.Log;
import android.view.View;
@@ -46,6 +47,7 @@
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.volume.VolumeDialogController;
/**
* Class factory to provide customizable SystemUI components.
@@ -93,6 +95,11 @@
return new ScrimController(scrimBehind, scrimInFront, headsUpScrim);
}
+ public VolumeDialogController createVolumeDialogController(Context context,
+ ComponentName name) {
+ return new VolumeDialogController(context, name);
+ }
+
public NotificationIconAreaController createNotificationIconAreaController(Context context,
PhoneStatusBar phoneStatusBar) {
return new NotificationIconAreaController(context, phoneStatusBar);
diff --git a/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java
new file mode 100644
index 0000000..67aa4dc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2016 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.car;
+
+import android.content.ComponentName;
+import android.content.Context;
+
+import com.android.systemui.SystemUIFactory;
+import com.android.systemui.volume.VolumeDialogController;
+import com.android.systemui.volume.car.CarVolumeDialogController;
+
+/**
+ * Class factory to provide car specific SystemUI components.
+ */
+public class CarSystemUIFactory extends SystemUIFactory {
+ @Override
+ public VolumeDialogController createVolumeDialogController(Context context,
+ ComponentName name) {
+ return new CarVolumeDialogController(context, name);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
index 3d33809..51c2ad8 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
@@ -16,30 +16,38 @@
package com.android.systemui.volume;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.media.AudioManager;
import android.media.VolumePolicy;
import android.os.Bundle;
import android.os.Handler;
import android.provider.Settings;
+import android.util.Log;
import android.view.WindowManager;
+import com.android.systemui.R;
import com.android.systemui.SystemUI;
+import com.android.systemui.SystemUIFactory;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.qs.tiles.DndTile;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.tuner.TunerService;
+import com.android.systemui.volume.car.CarVolumeDialogController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.lang.reflect.Constructor;
/**
* Implementation of VolumeComponent backed by the new volume dialog.
*/
-public class VolumeDialogComponent implements VolumeComponent, TunerService.Tunable {
+public class VolumeDialogComponent implements VolumeComponent, TunerService.Tunable,
+ VolumeDialogController.UserActivityListener{
public static final String VOLUME_DOWN_SILENT = "sysui_volume_down_silent";
public static final String VOLUME_UP_SILENT = "sysui_volume_up_silent";
@@ -65,12 +73,8 @@
ZenModeController zen) {
mSysui = sysui;
mContext = context;
- mController = new VolumeDialogController(context, null) {
- @Override
- protected void onUserActivityW() {
- sendUserActivity();
- }
- };
+ mController = SystemUIFactory.getInstance().createVolumeDialogController(context, null);
+ mController.setUserActivityListener(this);
mZenModeController = zen;
mDialog = new VolumeDialog(context, WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY,
mController, zen, mVolumeDialogCallback);
@@ -112,7 +116,8 @@
mController.setVolumePolicy(mVolumePolicy);
}
- private void sendUserActivity() {
+ @Override
+ public void onUserActivity() {
final KeyguardViewMediator kvm = mSysui.getComponent(KeyguardViewMediator.class);
if (kvm != null) {
kvm.userActivity();
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
index 99c0f59..c20cc84 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
@@ -44,6 +44,7 @@
import android.util.Log;
import android.util.SparseArray;
+import com.android.internal.annotations.GuardedBy;
import com.android.systemui.R;
import com.android.systemui.qs.tiles.DndTile;
@@ -82,13 +83,12 @@
private final HandlerThread mWorkerThread;
private final W mWorker;
private final Context mContext;
- private final AudioManager mAudio;
+ private AudioManager mAudio;
private final NotificationManager mNoMan;
private final ComponentName mComponent;
private final SettingObserver mObserver;
private final Receiver mReceiver = new Receiver();
private final MediaSessions mMediaSessions;
- private final VC mVolumeController = new VC();
private final C mCallbacks = new C();
private final State mState = new State();
private final String[] mStreamTitles;
@@ -100,6 +100,10 @@
private boolean mDestroyed;
private VolumePolicy mVolumePolicy;
private boolean mShowDndTile = true;
+ @GuardedBy("this")
+ private UserActivityListener mUserActivityListener;
+
+ protected final VC mVolumeController = new VC();
public VolumeDialogController(Context context, ComponentName component) {
mContext = context.getApplicationContext();
@@ -128,13 +132,33 @@
mCallbacks.onDismissRequested(Events.DISMISS_REASON_VOLUME_CONTROLLER);
}
- public void register() {
+ protected void setVolumeController() {
try {
mAudio.setVolumeController(mVolumeController);
} catch (SecurityException e) {
Log.w(TAG, "Unable to set the volume controller", e);
return;
}
+ }
+
+ protected void setAudioManagerStreamVolume(int stream, int level, int flag) {
+ mAudio.setStreamVolume(stream, level, flag);
+ }
+
+ protected int getAudioManagerStreamVolume(int stream) {
+ return mAudio.getLastAudibleStreamVolume(stream);
+ }
+
+ protected int getAudioManagerStreamMaxVolume(int stream) {
+ return mAudio.getStreamMaxVolume(stream);
+ }
+
+ protected int getAudioManagerStreamMinVolume(int stream) {
+ return mAudio.getStreamMinVolume(stream);
+ }
+
+ public void register() {
+ setVolumeController();
setVolumePolicy(mVolumePolicy);
showDndTile(mShowDndTile);
try {
@@ -188,6 +212,13 @@
mCallbacks.add(callback, handler);
}
+ public void setUserActivityListener(UserActivityListener listener) {
+ if (mDestroyed) return;
+ synchronized (this) {
+ mUserActivityListener = listener;
+ }
+ }
+
public void removeCallback(Callbacks callback) {
mCallbacks.remove(callback);
}
@@ -258,8 +289,12 @@
}
}
- protected void onUserActivityW() {
- // hook for subclasses
+ private void onUserActivityW() {
+ synchronized (this) {
+ if (mUserActivityListener != null) {
+ mUserActivityListener.onUserActivity();
+ }
+ }
}
private void onShowSafetyWarningW(int flags) {
@@ -288,7 +323,7 @@
if (showUI) {
changed |= updateActiveStreamW(stream);
}
- int lastAudibleStreamVolume = mAudio.getLastAudibleStreamVolume(stream);
+ int lastAudibleStreamVolume = getAudioManagerStreamVolume(stream);
changed |= updateStreamLevelW(stream, lastAudibleStreamVolume);
changed |= checkRoutedToBluetoothW(showUI ? AudioManager.STREAM_MUSIC : stream);
if (changed) {
@@ -331,9 +366,9 @@
private void onGetStateW() {
for (int stream : STREAMS) {
- updateStreamLevelW(stream, mAudio.getLastAudibleStreamVolume(stream));
- streamStateW(stream).levelMin = mAudio.getStreamMinVolume(stream);
- streamStateW(stream).levelMax = mAudio.getStreamMaxVolume(stream);
+ updateStreamLevelW(stream, getAudioManagerStreamVolume(stream));
+ streamStateW(stream).levelMin = getAudioManagerStreamMinVolume(stream);
+ streamStateW(stream).levelMax = getAudioManagerStreamMaxVolume(stream);
updateStreamMuteW(stream, mAudio.isStreamMute(stream));
final StreamState ss = streamStateW(stream);
ss.muteSupported = mAudio.isStreamAffectedByMute(stream);
@@ -460,7 +495,7 @@
mMediaSessionsCallbacksW.setStreamVolume(stream, level);
return;
}
- mAudio.setStreamVolume(stream, level, 0);
+ setAudioManagerStreamVolume(stream, level, 0);
}
private void onSetActiveStreamW(int stream) {
@@ -999,4 +1034,8 @@
void onScreenOff();
void onShowSafetyWarning(int flags);
}
+
+ public interface UserActivityListener {
+ void onUserActivity();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/car/CarVolumeDialogController.java b/packages/SystemUI/src/com/android/systemui/volume/car/CarVolumeDialogController.java
new file mode 100644
index 0000000..a2c32b7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/car/CarVolumeDialogController.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2016 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.car;
+
+import android.car.Car;
+import android.car.CarNotConnectedException;
+import android.car.media.CarAudioManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.util.Log;
+
+import com.android.systemui.volume.VolumeDialogController;
+
+/**
+ * A volume dialog controller for the automotive use case.
+ *
+ * {@link android.car.media.CarAudioManager} is the source of truth to get the stream volumes.
+ * And volume changes should be sent to the car's audio module instead of the android's audio mixer.
+ */
+public class CarVolumeDialogController extends VolumeDialogController {
+ private static final String TAG = "CarVolumeDialogController";
+
+ private final Car mCar;
+ private CarAudioManager mCarAudioManager;
+
+ private final ServiceConnection mConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
+ try {
+ mCarAudioManager = (CarAudioManager) mCar.getCarManager(Car.AUDIO_SERVICE);
+ setVolumeController();
+ CarVolumeDialogController.this.getState();
+ } catch (CarNotConnectedException e) {
+ Log.e(TAG, "Car is not connected!", e);
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName componentName) {
+ Log.e(TAG, "Car service is disconnected");
+ }
+ };
+
+ public CarVolumeDialogController(Context context, ComponentName component) {
+ super(context, component);
+ mCar = Car.createCar(context, mConnection);
+ mCar.connect();
+ }
+
+ @Override
+ protected void setAudioManagerStreamVolume(int stream, int level, int flag) {
+ if (mCarAudioManager == null) {
+ Log.d(TAG, "Car audio manager is not initialized yet");
+ return;
+ }
+ try {
+ mCarAudioManager.setStreamVolume(stream, level, flag);
+ } catch (CarNotConnectedException e) {
+ Log.e(TAG, "Car is not connected", e);
+ }
+ }
+
+ @Override
+ protected int getAudioManagerStreamVolume(int stream) {
+ if(mCarAudioManager == null) {
+ Log.d(TAG, "Car audio manager is not initialized yet");
+ return 0;
+ }
+
+ try {
+ return mCarAudioManager.getStreamVolume(stream);
+ } catch (CarNotConnectedException e) {
+ Log.e(TAG, "Car is not connected", e);
+ return 0;
+ }
+ }
+
+ @Override
+ protected int getAudioManagerStreamMaxVolume(int stream) {
+ if(mCarAudioManager == null) {
+ Log.d(TAG, "Car audio manager is not initialized yet");
+ return 0;
+ }
+
+ try {
+ return mCarAudioManager.getStreamMaxVolume(stream);
+ } catch (CarNotConnectedException e) {
+ Log.e(TAG, "Car is not connected", e);
+ return 0;
+ }
+ }
+
+ @Override
+ protected int getAudioManagerStreamMinVolume(int stream) {
+ if(mCarAudioManager == null) {
+ Log.d(TAG, "Car audio manager is not initialized yet");
+ return 0;
+ }
+
+ try {
+ return mCarAudioManager.getStreamMinVolume(stream);
+ } catch (CarNotConnectedException e) {
+ Log.e(TAG, "Car is not connected", e);
+ return 0;
+ }
+ }
+
+ @Override
+ public void setVolumeController() {
+ if (mCarAudioManager == null) {
+ Log.d(TAG, "Car audio manager is not initialized yet");
+ return;
+ }
+ try {
+ mCarAudioManager.setVolumeController(mVolumeController);
+ } catch (CarNotConnectedException e) {
+ Log.e(TAG, "Car is not connected", e);
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk
index c9c5805..6c39e35 100644
--- a/packages/SystemUI/tests/Android.mk
+++ b/packages/SystemUI/tests/Android.mk
@@ -40,7 +40,7 @@
frameworks/base/packages/SystemUI/res \
frameworks/base/packages/Keyguard/res
-LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common
+LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common android.car
LOCAL_PACKAGE_NAME := SystemUITests