summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJohn Spurlock <jspurlock@google.com>2015-02-11 19:04:11 -0500
committerJohn Spurlock <jspurlock@google.com>2015-02-12 22:48:37 -0500
commitcdb57aeb0e2c83a887c86da0ca2a890df7f02f41 (patch)
treecd58462862527b7eb432d1749e5752745f1b8889
parentad680d46be19cbee16d42cbed4d2ed250648ac0b (diff)
downloadframeworks_base-cdb57aeb0e2c83a887c86da0ca2a890df7f02f41.zip
frameworks_base-cdb57aeb0e2c83a887c86da0ca2a890df7f02f41.tar.gz
frameworks_base-cdb57aeb0e2c83a887c86da0ca2a890df7f02f41.tar.bz2
Allow sysui-managed remote volume controllers.
- Relax restriction on audio service calls that assume the volume ui is systemui, allow calls from a blessed component app. - Blessed component app service saved in secure settings. - SystemUI mediates requests to replace the volume dialog, prompts the user on activation. - Show a low pri ongoing notification when the volume dialog is being replaced, to allow user restoration at any time. - Replace the controller management code in VolumeUI to use a ServiceMonitor, backed by the new blessed app component setting. - Add proper zen-related noman client wrappers, make avail to the registered volume controller. - Everything is still @hidden, no api impact. Bug: 19260237 Change-Id: Ie1383f57659090318a7eda737fdad5b8f88737d4
-rw-r--r--core/java/android/app/INotificationManager.aidl1
-rw-r--r--core/java/android/app/NotificationManager.java51
-rw-r--r--core/java/android/provider/Settings.java4
-rw-r--r--media/java/android/media/AudioManagerInternal.java4
-rw-r--r--packages/SystemUI/res/values/ids.xml1
-rw-r--r--packages/SystemUI/res/values/strings.xml15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ServiceMonitor.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java181
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java61
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java34
11 files changed, 322 insertions, 70 deletions
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 88b9080..5d864df 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -75,6 +75,7 @@ interface INotificationManager
ZenModeConfig getZenModeConfig();
boolean setZenModeConfig(in ZenModeConfig config);
+ oneway void setZenMode(int mode);
oneway void notifyConditions(String pkg, in IConditionProvider provider, in Condition[] conditions);
oneway void requestZenModeConditions(in IConditionListener callback, int relevance);
oneway void setZenModeCondition(in Condition condition);
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index cf54107..479327d 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -27,6 +27,9 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.StrictMode;
import android.os.UserHandle;
+import android.service.notification.Condition;
+import android.service.notification.IConditionListener;
+import android.service.notification.ZenModeConfig;
import android.util.Log;
/**
@@ -276,5 +279,53 @@ public class NotificationManager
}
}
+ /**
+ * @hide
+ */
+ public void setZenMode(int mode) {
+ INotificationManager service = getService();
+ try {
+ service.setZenMode(mode);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void requestZenModeConditions(IConditionListener listener, int relevance) {
+ INotificationManager service = getService();
+ try {
+ service.requestZenModeConditions(listener, relevance);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void setZenModeCondition(Condition exitCondition) {
+ INotificationManager service = getService();
+ try {
+ service.setZenModeCondition(exitCondition);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public Condition getZenModeCondition() {
+ INotificationManager service = getService();
+ try {
+ final ZenModeConfig config = service.getZenModeConfig();
+ if (config != null) {
+ return config.exitCondition;
+ }
+ } catch (RemoteException e) {
+ }
+ return null;
+ }
+
private Context mContext;
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index dddbe78..5380cd6 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4762,6 +4762,10 @@ public final class Settings {
public static final String BAR_SERVICE_COMPONENT = "bar_service_component";
/** @hide */
+ public static final String VOLUME_CONTROLLER_SERVICE_COMPONENT
+ = "volume_controller_service_component";
+
+ /** @hide */
public static final String IMMERSIVE_MODE_CONFIRMATIONS = "immersive_mode_confirmations";
/**
diff --git a/media/java/android/media/AudioManagerInternal.java b/media/java/android/media/AudioManagerInternal.java
index 873c142..ef5710c 100644
--- a/media/java/android/media/AudioManagerInternal.java
+++ b/media/java/android/media/AudioManagerInternal.java
@@ -15,8 +15,6 @@
*/
package android.media;
-import android.os.IBinder;
-
import com.android.server.LocalServices;
/**
@@ -47,6 +45,8 @@ public abstract class AudioManagerInternal {
public abstract void setRingerModeInternal(int ringerMode, String caller);
+ public abstract int getVolumeControllerUid();
+
public interface RingerModeDelegate {
/** Called when external ringer mode is evaluated, returns the new internal ringer mode */
int onSetRingerModeExternal(int ringerModeOld, int ringerModeNew, String caller,
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index aa53a3e..3fc75d2 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -40,6 +40,7 @@
<item type="id" name="notification_power"/>
<item type="id" name="notification_screenshot"/>
<item type="id" name="notification_hidden"/>
+ <item type="id" name="notification_volumeui"/>
<!-- Whether the icon is from a notification for which targetSdk < L -->
<item type="id" name="icon_is_pre_L"/>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 6afca8a..0420d35 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -932,4 +932,19 @@
<!-- Hide quick settings tile confirmation button -->
<string name="quick_settings_reset_confirmation_button">Hide</string>
+
+ <!-- VolumeUI activation dialog: warning message -->
+ <string name="volumeui_prompt_message"><xliff:g id="app_name" example="Volume Prototype 1">%1$s</xliff:g> wants to be the volume dialog.</string>
+
+ <!-- VolumeUI activation dialog: allow button label -->
+ <string name="volumeui_prompt_allow">Allow</string>
+
+ <!-- VolumeUI activation dialog: deny button label -->
+ <string name="volumeui_prompt_deny">Deny</string>
+
+ <!-- VolumeUI restoration notification: title -->
+ <string name="volumeui_notification_title"><xliff:g id="app_name" example="Volume Prototype 1">%1$s</xliff:g> is the volume dialog</string>
+
+ <!-- VolumeUI restoration notification: text -->
+ <string name="volumeui_notification_text">Touch to restore the original.</string>
</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ServiceMonitor.java b/packages/SystemUI/src/com/android/systemui/statusbar/ServiceMonitor.java
index aea9ec6..69a4932 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ServiceMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ServiceMonitor.java
@@ -279,4 +279,14 @@ public class ServiceMonitor {
}
return sb.append('}').toString();
}
+
+ public ComponentName getComponent() {
+ return getComponentNameFromSetting();
+ }
+
+ public void setComponent(ComponentName component) {
+ final String setting = component == null ? null : component.flattenToShortString();
+ Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ mSettingKey, setting, UserHandle.USER_CURRENT);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index dbdb578..bea0c86 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.policy;
import android.app.AlarmManager;
-import android.app.INotificationManager;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -28,8 +27,6 @@ import android.content.IntentFilter;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
-import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.UserHandle;
import android.provider.Settings.Global;
import android.provider.Settings.Secure;
@@ -53,7 +50,7 @@ public class ZenModeControllerImpl implements ZenModeController {
private final Context mContext;
private final GlobalSetting mModeSetting;
private final GlobalSetting mConfigSetting;
- private final INotificationManager mNoMan;
+ private final NotificationManager mNoMan;
private final LinkedHashMap<Uri, Condition> mConditions = new LinkedHashMap<Uri, Condition>();
private final AlarmManager mAlarmManager;
private final SetupObserver mSetupObserver;
@@ -78,8 +75,7 @@ public class ZenModeControllerImpl implements ZenModeController {
};
mModeSetting.setListening(true);
mConfigSetting.setListening(true);
- mNoMan = INotificationManager.Stub.asInterface(
- ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+ mNoMan = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
mSetupObserver = new SetupObserver(handler);
mSetupObserver.register();
@@ -113,11 +109,7 @@ public class ZenModeControllerImpl implements ZenModeController {
@Override
public void requestConditions(boolean request) {
mRequesting = request;
- try {
- mNoMan.requestZenModeConditions(mListener, request ? Condition.FLAG_RELEVANT_NOW : 0);
- } catch (RemoteException e) {
- // noop
- }
+ mNoMan.requestZenModeConditions(mListener, request ? Condition.FLAG_RELEVANT_NOW : 0);
if (!mRequesting) {
mConditions.clear();
}
@@ -125,24 +117,12 @@ public class ZenModeControllerImpl implements ZenModeController {
@Override
public void setExitCondition(Condition exitCondition) {
- try {
- mNoMan.setZenModeCondition(exitCondition);
- } catch (RemoteException e) {
- // noop
- }
+ mNoMan.setZenModeCondition(exitCondition);
}
@Override
public Condition getExitCondition() {
- try {
- final ZenModeConfig config = mNoMan.getZenModeConfig();
- if (config != null) {
- return config.exitCondition;
- }
- } catch (RemoteException e) {
- // noop
- }
- return null;
+ return mNoMan.getZenModeCondition();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
index 7102c2a..8048a48 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
@@ -1,60 +1,73 @@
+/*
+ * 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.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
-import android.database.ContentObserver;
import android.media.AudioManager;
import android.media.IRemoteVolumeController;
import android.media.IVolumeController;
import android.media.session.ISessionController;
import android.media.session.MediaController;
import android.media.session.MediaSessionManager;
-import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
import android.provider.Settings;
+import android.text.TextUtils;
import android.util.Log;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.statusbar.ServiceMonitor;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.statusbar.policy.ZenModeControllerImpl;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-/*
- * 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.
- */
-
public class VolumeUI extends SystemUI {
private static final String TAG = "VolumeUI";
- private static final String SETTING = "systemui_volume_controller"; // for testing
- private static final Uri SETTING_URI = Settings.Global.getUriFor(SETTING);
- private static final int DEFAULT = 1; // enabled by default
+ private static boolean LOGD = Log.isLoggable(TAG, Log.DEBUG);
private final Handler mHandler = new Handler();
+ private final Receiver mReceiver = new Receiver();
+ private final RestorationNotification mRestorationNotification = new RestorationNotification();
private boolean mEnabled;
private AudioManager mAudioManager;
+ private NotificationManager mNotificationManager;
private MediaSessionManager mMediaSessionManager;
private VolumeController mVolumeController;
private RemoteVolumeController mRemoteVolumeController;
+ private ServiceMonitor mVolumeControllerService;
private VolumePanel mPanel;
private int mDismissDelay;
@@ -64,14 +77,19 @@ public class VolumeUI extends SystemUI {
mEnabled = mContext.getResources().getBoolean(R.bool.enable_volume_ui);
if (!mEnabled) return;
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ mNotificationManager =
+ (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
mMediaSessionManager = (MediaSessionManager) mContext
.getSystemService(Context.MEDIA_SESSION_SERVICE);
initPanel();
mVolumeController = new VolumeController();
mRemoteVolumeController = new RemoteVolumeController();
putComponent(VolumeComponent.class, mVolumeController);
- updateController();
- mContext.getContentResolver().registerContentObserver(SETTING_URI, false, mObserver);
+ mReceiver.start();
+ mVolumeControllerService = new ServiceMonitor(TAG, LOGD,
+ mContext, Settings.Secure.VOLUME_CONTROLLER_SERVICE_COMPONENT,
+ new ServiceMonitorCallbacks());
+ mVolumeControllerService.start();
}
@Override
@@ -85,18 +103,19 @@ public class VolumeUI extends SystemUI {
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.print("mEnabled="); pw.println(mEnabled);
+ pw.print("mVolumeControllerService="); pw.println(mVolumeControllerService.getComponent());
if (mPanel != null) {
mPanel.dump(fd, pw, args);
}
}
- private void updateController() {
- if (Settings.Global.getInt(mContext.getContentResolver(), SETTING, DEFAULT) != 0) {
- Log.d(TAG, "Registering volume controller");
+ private void setVolumeController(boolean register) {
+ if (register) {
+ if (LOGD) Log.d(TAG, "Registering volume controller");
mAudioManager.setVolumeController(mVolumeController);
mMediaSessionManager.setRemoteVolumeController(mRemoteVolumeController);
} else {
- Log.d(TAG, "Unregistering volume controller");
+ if (LOGD) Log.d(TAG, "Unregistering volume controller");
mAudioManager.setVolumeController(null);
mMediaSessionManager.setRemoteVolumeController(null);
}
@@ -129,13 +148,32 @@ public class VolumeUI extends SystemUI {
});
}
- private final ContentObserver mObserver = new ContentObserver(mHandler) {
- public void onChange(boolean selfChange, Uri uri) {
- if (SETTING_URI.equals(uri)) {
- updateController();
+ private String getAppLabel(ComponentName component) {
+ final String pkg = component.getPackageName();
+ try {
+ final ApplicationInfo ai = mContext.getPackageManager().getApplicationInfo(pkg, 0);
+ final String rt = mContext.getPackageManager().getApplicationLabel(ai).toString();
+ if (!TextUtils.isEmpty(rt)) {
+ return rt;
}
+ } catch (Exception e) {
+ Log.w(TAG, "Error loading app label", e);
}
- };
+ return pkg;
+ }
+
+ private void showServiceActivationDialog(final ComponentName component) {
+ final SystemUIDialog d = new SystemUIDialog(mContext);
+ d.setMessage(mContext.getString(R.string.volumeui_prompt_message, getAppLabel(component)));
+ d.setPositiveButton(R.string.volumeui_prompt_allow, new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ mVolumeControllerService.setComponent(component);
+ }
+ });
+ d.setNegativeButton(R.string.volumeui_prompt_deny, null);
+ d.show();
+ }
private final Runnable mStartZenSettings = new Runnable() {
@Override
@@ -213,4 +251,83 @@ public class VolumeUI extends SystemUI {
// than by remoteVolumeChanged.
}
}
+
+ private final class ServiceMonitorCallbacks implements ServiceMonitor.Callbacks {
+ @Override
+ public void onNoService() {
+ if (LOGD) Log.d(TAG, "onNoService");
+ setVolumeController(true);
+ mRestorationNotification.hide();
+ }
+
+ @Override
+ public long onServiceStartAttempt() {
+ if (LOGD) Log.d(TAG, "onServiceStartAttempt");
+ setVolumeController(false);
+ mVolumeController.dismissNow();
+ mRestorationNotification.show();
+ return 0;
+ }
+ }
+
+ private final class Receiver extends BroadcastReceiver {
+ private static final String ENABLE = "com.android.systemui.vui.ENABLE";
+ private static final String DISABLE = "com.android.systemui.vui.DISABLE";
+ private static final String EXTRA_COMPONENT = "component";
+
+ public void start() {
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(ENABLE);
+ filter.addAction(DISABLE);
+ mContext.registerReceiver(this, filter, null, mHandler);
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ final ComponentName component = intent.getParcelableExtra(EXTRA_COMPONENT);
+ final boolean current = component.equals(mVolumeControllerService.getComponent());
+ if (ENABLE.equals(action) && component != null) {
+ if (!current) {
+ showServiceActivationDialog(component);
+ }
+ }
+ if (DISABLE.equals(action) && component != null) {
+ if (current) {
+ mVolumeControllerService.setComponent(null);
+ }
+ }
+ }
+ }
+
+ private final class RestorationNotification {
+ public void hide() {
+ mNotificationManager.cancel(R.id.notification_volumeui);
+ }
+
+ public void show() {
+ final ComponentName component = mVolumeControllerService.getComponent();
+ if (component == null) {
+ Log.w(TAG, "Not showing restoration notification, component not active");
+ return;
+ }
+ final Intent intent = new Intent(Receiver.DISABLE)
+ .putExtra(Receiver.EXTRA_COMPONENT, component);
+ mNotificationManager.notify(R.id.notification_volumeui,
+ new Notification.Builder(mContext)
+ .setSmallIcon(R.drawable.ic_ringer_audible)
+ .setWhen(0)
+ .setShowWhen(false)
+ .setOngoing(true)
+ .setContentTitle(mContext.getString(
+ R.string.volumeui_notification_title, getAppLabel(component)))
+ .setContentText(mContext.getString(R.string.volumeui_notification_text))
+ .setContentIntent(PendingIntent.getBroadcast(mContext, 0, intent, 0))
+ .setPriority(Notification.PRIORITY_MIN)
+ .setVisibility(Notification.VISIBILITY_PUBLIC)
+ .setColor(mContext.getResources().getColor(
+ com.android.internal.R.color.system_notification_accent_color))
+ .build());
+ }
+ }
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index eaece09..4301427 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -182,6 +182,7 @@ public class AudioService extends IAudioService.Stub {
/** The controller for the volume UI. */
private final VolumeController mVolumeController = new VolumeController();
+ private final ControllerService mControllerService = new ControllerService();
// sendMsg() flags
/** If the msg is already queued, replace it with this one. */
@@ -708,6 +709,7 @@ public class AudioService extends IAudioService.Stub {
SAFE_VOLUME_CONFIGURE_TIMEOUT_MS);
StreamOverride.init(mContext);
+ mControllerService.init();
}
private void createAudioSystemThread() {
@@ -1833,7 +1835,7 @@ public class AudioService extends IAudioService.Stub {
}
public void setRingerModeInternal(int ringerMode, String caller) {
- enforceSelfOrSystemUI("setRingerModeInternal");
+ enforceVolumeController("setRingerModeInternal");
setRingerMode(ringerMode, caller, false /*external*/);
}
@@ -5013,7 +5015,7 @@ public class AudioService extends IAudioService.Stub {
@Override
public void setRemoteStreamVolume(int index) {
- enforceSelfOrSystemUI("set the remote stream volume");
+ enforceVolumeController("set the remote stream volume");
mMediaFocusControl.setRemoteStreamVolume(index);
}
@@ -5333,7 +5335,7 @@ public class AudioService extends IAudioService.Stub {
@Override
public void disableSafeMediaVolume() {
- enforceSelfOrSystemUI("disable the safe media volume");
+ enforceVolumeController("disable the safe media volume");
synchronized (mSafeMediaVolumeState) {
setSafeMediaVolumeEnabled(false);
if (mPendingVolumeCommand != null) {
@@ -5505,6 +5507,7 @@ public class AudioService extends IAudioService.Stub {
pw.print(" mMusicActiveMs="); pw.println(mMusicActiveMs);
pw.print(" mMcc="); pw.println(mMcc);
pw.print(" mHasVibrator="); pw.println(mHasVibrator);
+ pw.print(" mControllerService="); pw.println(mControllerService);
dumpAudioPolicies(pw);
}
@@ -5528,14 +5531,17 @@ public class AudioService extends IAudioService.Stub {
}
}
- private void enforceSelfOrSystemUI(String action) {
+ private void enforceVolumeController(String action) {
+ if (mControllerService.mUid != 0 && Binder.getCallingUid() == mControllerService.mUid) {
+ return;
+ }
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
"Only SystemUI can " + action);
}
@Override
public void setVolumeController(final IVolumeController controller) {
- enforceSelfOrSystemUI("set the volume controller");
+ enforceVolumeController("set the volume controller");
// return early if things are not actually changing
if (mVolumeController.isSameBinder(controller)) {
@@ -5566,7 +5572,7 @@ public class AudioService extends IAudioService.Stub {
@Override
public void notifyVolumeControllerVisible(final IVolumeController controller, boolean visible) {
- enforceSelfOrSystemUI("notify about volume controller visibility");
+ enforceVolumeController("notify about volume controller visibility");
// return early if the controller is not current
if (!mVolumeController.isSameBinder(controller)) {
@@ -5751,6 +5757,11 @@ public class AudioService extends IAudioService.Stub {
public void setRingerModeInternal(int ringerMode, String caller) {
AudioService.this.setRingerModeInternal(ringerMode, caller);
}
+
+ @Override
+ public int getVolumeControllerUid() {
+ return mControllerService.mUid;
+ }
}
//==========================================================================================
@@ -5915,4 +5926,42 @@ public class AudioService extends IAudioService.Stub {
private HashMap<IBinder, AudioPolicyProxy> mAudioPolicies =
new HashMap<IBinder, AudioPolicyProxy>();
private int mAudioPolicyCounter = 0; // always accessed synchronized on mAudioPolicies
+
+ private class ControllerService extends ContentObserver {
+ private int mUid;
+ private ComponentName mComponent;
+
+ public ControllerService() {
+ super(null);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("{mUid=%d,mComponent=%s}", mUid, mComponent);
+ }
+
+ public void init() {
+ onChange(true);
+ mContentResolver.registerContentObserver(Settings.Secure.getUriFor(
+ Settings.Secure.VOLUME_CONTROLLER_SERVICE_COMPONENT), false, this);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ mUid = 0;
+ mComponent = null;
+ final String setting = Settings.Secure.getString(mContentResolver,
+ Settings.Secure.VOLUME_CONTROLLER_SERVICE_COMPONENT);
+ if (setting == null) return;
+ try {
+ mComponent = ComponentName.unflattenFromString(setting);
+ if (mComponent == null) return;
+ mUid = mContext.getPackageManager()
+ .getApplicationInfo(mComponent.getPackageName(), 0).uid;
+ } catch (Exception e) {
+ Log.w(TAG, "Error loading controller service", e);
+ }
+ if (DEBUG_VOL) Log.d(TAG, "Reloaded controller service: " + this);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 02cacd9..ac3cd1a 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -50,6 +50,7 @@ import android.content.res.Resources;
import android.database.ContentObserver;
import android.media.AudioAttributes;
import android.media.AudioManager;
+import android.media.AudioManagerInternal;
import android.media.AudioSystem;
import android.media.IRingtonePlayer;
import android.net.Uri;
@@ -179,6 +180,7 @@ public class NotificationManagerService extends SystemService {
private IActivityManager mAm;
AudioManager mAudioManager;
+ AudioManagerInternal mAudioManagerInternal;
StatusBarManagerInternal mStatusBar;
Vibrator mVibrator;
@@ -996,6 +998,7 @@ public class NotificationManagerService extends SystemService {
// Grab our optional AudioService
mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
+ mAudioManagerInternal = getLocalService(AudioManagerInternal.class);
mZenModeHelper.onSystemReady();
} else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
// This observer will force an update when observe is called, causing us to
@@ -1468,7 +1471,7 @@ public class NotificationManagerService extends SystemService {
@Override
public ZenModeConfig getZenModeConfig() {
- enforceSystemOrSystemUI("INotificationManager.getZenModeConfig");
+ enforceSystemOrSystemUIOrVolume("INotificationManager.getZenModeConfig");
return mZenModeHelper.getConfig();
}
@@ -1479,6 +1482,17 @@ public class NotificationManagerService extends SystemService {
}
@Override
+ public void setZenMode(int mode) throws RemoteException {
+ enforceSystemOrSystemUIOrVolume("INotificationManager.setZenMode");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mZenModeHelper.setZenMode(mode, "NotificationManager");
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public void notifyConditions(String pkg, IConditionProvider provider,
Condition[] conditions) {
final ManagedServiceInfo info = mConditionProviders.checkServiceToken(provider);
@@ -1493,13 +1507,13 @@ public class NotificationManagerService extends SystemService {
@Override
public void requestZenModeConditions(IConditionListener callback, int relevance) {
- enforceSystemOrSystemUI("INotificationManager.requestZenModeConditions");
+ enforceSystemOrSystemUIOrVolume("INotificationManager.requestZenModeConditions");
mConditionProviders.requestZenModeConditions(callback, relevance);
}
@Override
public void setZenModeCondition(Condition condition) {
- enforceSystemOrSystemUI("INotificationManager.setZenModeCondition");
+ enforceSystemOrSystemUIOrVolume("INotificationManager.setZenModeCondition");
final long identity = Binder.clearCallingIdentity();
try {
mConditionProviders.setZenModeCondition(condition, "binderCall");
@@ -1520,6 +1534,16 @@ public class NotificationManagerService extends SystemService {
return mConditionProviders.getAutomaticZenModeConditions();
}
+ private void enforceSystemOrSystemUIOrVolume(String message) {
+ if (mAudioManagerInternal != null) {
+ final int vcuid = mAudioManagerInternal.getVolumeControllerUid();
+ if (vcuid > 0 && Binder.getCallingUid() == vcuid) {
+ return;
+ }
+ }
+ enforceSystemOrSystemUI(message);
+ }
+
private void enforceSystemOrSystemUI(String message) {
if (isCallerSystem()) return;
getContext().enforceCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
@@ -1541,7 +1565,7 @@ public class NotificationManagerService extends SystemService {
@Override
public ComponentName getEffectsSuppressor() {
- enforceSystemOrSystemUI("INotificationManager.getEffectsSuppressor");
+ enforceSystemOrSystemUIOrVolume("INotificationManager.getEffectsSuppressor");
return mEffectsSuppressor;
}
@@ -1558,7 +1582,7 @@ public class NotificationManagerService extends SystemService {
@Override
public boolean isSystemConditionProviderEnabled(String path) {
- enforceSystemOrSystemUI("INotificationManager.isSystemConditionProviderEnabled");
+ enforceSystemOrSystemUIOrVolume("INotificationManager.isSystemConditionProviderEnabled");
return mConditionProviders.isSystemConditionProviderEnabled(path);
}
};