VolumeUI.java revision ee5ad729b90deff435f9875337cbc434be4f8fe7
1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.systemui.volume; 18 19import android.app.Notification; 20import android.app.NotificationManager; 21import android.app.PendingIntent; 22import android.content.BroadcastReceiver; 23import android.content.ComponentName; 24import android.content.Context; 25import android.content.DialogInterface; 26import android.content.DialogInterface.OnClickListener; 27import android.content.Intent; 28import android.content.IntentFilter; 29import android.content.pm.ApplicationInfo; 30import android.content.res.Configuration; 31import android.media.AudioManager; 32import android.media.IRemoteVolumeController; 33import android.media.IVolumeController; 34import android.media.session.ISessionController; 35import android.media.session.MediaController; 36import android.media.session.MediaSessionManager; 37import android.os.Bundle; 38import android.os.Handler; 39import android.os.RemoteException; 40import android.provider.Settings; 41import android.text.TextUtils; 42import android.util.Log; 43 44import com.android.systemui.R; 45import com.android.systemui.SystemUI; 46import com.android.systemui.keyguard.KeyguardViewMediator; 47import com.android.systemui.qs.tiles.DndTile; 48import com.android.systemui.statusbar.ServiceMonitor; 49import com.android.systemui.statusbar.phone.PhoneStatusBar; 50import com.android.systemui.statusbar.phone.SystemUIDialog; 51import com.android.systemui.statusbar.policy.ZenModeController; 52import com.android.systemui.statusbar.policy.ZenModeControllerImpl; 53 54import java.io.FileDescriptor; 55import java.io.PrintWriter; 56 57public class VolumeUI extends SystemUI { 58 private static final String TAG = "VolumeUI"; 59 private static boolean LOGD = Log.isLoggable(TAG, Log.DEBUG); 60 61 private final Handler mHandler = new Handler(); 62 private final Receiver mReceiver = new Receiver(); 63 private final RestorationNotification mRestorationNotification = new RestorationNotification(); 64 65 private boolean mEnabled; 66 private AudioManager mAudioManager; 67 private NotificationManager mNotificationManager; 68 private MediaSessionManager mMediaSessionManager; 69 private VolumeController mVolumeController; 70 private RemoteVolumeController mRemoteVolumeController; 71 private ServiceMonitor mVolumeControllerService; 72 73 private VolumePanel mPanel; 74 private int mDismissDelay; 75 76 @Override 77 public void start() { 78 mEnabled = mContext.getResources().getBoolean(R.bool.enable_volume_ui); 79 if (!mEnabled) return; 80 mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); 81 mNotificationManager = 82 (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); 83 mMediaSessionManager = (MediaSessionManager) mContext 84 .getSystemService(Context.MEDIA_SESSION_SERVICE); 85 initPanel(); 86 mVolumeController = new VolumeController(); 87 mRemoteVolumeController = new RemoteVolumeController(); 88 putComponent(VolumeComponent.class, mVolumeController); 89 mReceiver.start(); 90 mVolumeControllerService = new ServiceMonitor(TAG, LOGD, 91 mContext, Settings.Secure.VOLUME_CONTROLLER_SERVICE_COMPONENT, 92 new ServiceMonitorCallbacks()); 93 mVolumeControllerService.start(); 94 } 95 96 @Override 97 protected void onConfigurationChanged(Configuration newConfig) { 98 super.onConfigurationChanged(newConfig); 99 if (mPanel != null) { 100 mPanel.onConfigurationChanged(newConfig); 101 } 102 } 103 104 @Override 105 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 106 pw.print("mEnabled="); pw.println(mEnabled); 107 pw.print("mVolumeControllerService="); pw.println(mVolumeControllerService.getComponent()); 108 if (mPanel != null) { 109 mPanel.dump(fd, pw, args); 110 } 111 } 112 113 private void setVolumeController(boolean register) { 114 if (register) { 115 if (LOGD) Log.d(TAG, "Registering default volume controller"); 116 mAudioManager.setVolumeController(mVolumeController); 117 mMediaSessionManager.setRemoteVolumeController(mRemoteVolumeController); 118 DndTile.setVisible(mContext, false); 119 } else { 120 if (LOGD) Log.d(TAG, "Unregistering default volume controller"); 121 mAudioManager.setVolumeController(null); 122 mMediaSessionManager.setRemoteVolumeController(null); 123 } 124 } 125 126 private void initPanel() { 127 mDismissDelay = mContext.getResources().getInteger(R.integer.volume_panel_dismiss_delay); 128 mPanel = new VolumePanel(mContext, new ZenModeControllerImpl(mContext, mHandler)); 129 mPanel.setCallback(new VolumePanel.Callback() { 130 @Override 131 public void onZenSettings() { 132 mHandler.removeCallbacks(mStartZenSettings); 133 mHandler.post(mStartZenSettings); 134 } 135 136 @Override 137 public void onInteraction() { 138 final KeyguardViewMediator kvm = getComponent(KeyguardViewMediator.class); 139 if (kvm != null) { 140 kvm.userActivity(); 141 } 142 } 143 144 @Override 145 public void onVisible(boolean visible) { 146 if (mAudioManager != null && mVolumeController != null) { 147 mAudioManager.notifyVolumeControllerVisible(mVolumeController, visible); 148 } 149 } 150 }); 151 } 152 153 private String getAppLabel(ComponentName component) { 154 final String pkg = component.getPackageName(); 155 try { 156 final ApplicationInfo ai = mContext.getPackageManager().getApplicationInfo(pkg, 0); 157 final String rt = mContext.getPackageManager().getApplicationLabel(ai).toString(); 158 if (!TextUtils.isEmpty(rt)) { 159 return rt; 160 } 161 } catch (Exception e) { 162 Log.w(TAG, "Error loading app label", e); 163 } 164 return pkg; 165 } 166 167 private void showServiceActivationDialog(final ComponentName component) { 168 final SystemUIDialog d = new SystemUIDialog(mContext); 169 d.setMessage(mContext.getString(R.string.volumeui_prompt_message, getAppLabel(component))); 170 d.setPositiveButton(R.string.volumeui_prompt_allow, new OnClickListener() { 171 @Override 172 public void onClick(DialogInterface dialog, int which) { 173 mVolumeControllerService.setComponent(component); 174 } 175 }); 176 d.setNegativeButton(R.string.volumeui_prompt_deny, null); 177 d.show(); 178 } 179 180 private final Runnable mStartZenSettings = new Runnable() { 181 @Override 182 public void run() { 183 getComponent(PhoneStatusBar.class).startActivityDismissingKeyguard( 184 ZenModePanel.ZEN_SETTINGS, true /* onlyProvisioned */, true /* dismissShade */); 185 mPanel.postDismiss(mDismissDelay); 186 } 187 }; 188 189 /** For now, simply host an unmodified base volume panel in this process. */ 190 private final class VolumeController extends IVolumeController.Stub implements VolumeComponent { 191 192 @Override 193 public void displaySafeVolumeWarning(int flags) throws RemoteException { 194 mPanel.postDisplaySafeVolumeWarning(flags); 195 } 196 197 @Override 198 public void volumeChanged(int streamType, int flags) 199 throws RemoteException { 200 mPanel.postVolumeChanged(streamType, flags); 201 } 202 203 @Override 204 public void masterMuteChanged(int flags) throws RemoteException { 205 // no-op 206 } 207 208 @Override 209 public void setLayoutDirection(int layoutDirection) 210 throws RemoteException { 211 mPanel.postLayoutDirection(layoutDirection); 212 } 213 214 @Override 215 public void dismiss() throws RemoteException { 216 dismissNow(); 217 } 218 219 @Override 220 public ZenModeController getZenController() { 221 return mPanel.getZenController(); 222 } 223 224 @Override 225 public void dispatchDemoCommand(String command, Bundle args) { 226 mPanel.dispatchDemoCommand(command, args); 227 } 228 229 @Override 230 public void dismissNow() { 231 mPanel.postDismiss(0); 232 } 233 } 234 235 private final class RemoteVolumeController extends IRemoteVolumeController.Stub { 236 237 @Override 238 public void remoteVolumeChanged(ISessionController binder, int flags) 239 throws RemoteException { 240 MediaController controller = new MediaController(mContext, binder); 241 mPanel.postRemoteVolumeChanged(controller, flags); 242 } 243 244 @Override 245 public void updateRemoteController(ISessionController session) throws RemoteException { 246 mPanel.postRemoteSliderVisibility(session != null); 247 // TODO stash default session in case the slider can be opened other 248 // than by remoteVolumeChanged. 249 } 250 } 251 252 private final class ServiceMonitorCallbacks implements ServiceMonitor.Callbacks { 253 @Override 254 public void onNoService() { 255 if (LOGD) Log.d(TAG, "onNoService"); 256 setVolumeController(true); 257 mRestorationNotification.hide(); 258 if (!mVolumeControllerService.isPackageAvailable()) { 259 mVolumeControllerService.setComponent(null); 260 } 261 } 262 263 @Override 264 public long onServiceStartAttempt() { 265 if (LOGD) Log.d(TAG, "onServiceStartAttempt"); 266 // poke the setting to update the uid 267 mVolumeControllerService.setComponent(mVolumeControllerService.getComponent()); 268 setVolumeController(false); 269 mVolumeController.dismissNow(); 270 mRestorationNotification.show(); 271 return 0; 272 } 273 } 274 275 private final class Receiver extends BroadcastReceiver { 276 private static final String ENABLE = "com.android.systemui.vui.ENABLE"; 277 private static final String DISABLE = "com.android.systemui.vui.DISABLE"; 278 private static final String EXTRA_COMPONENT = "component"; 279 280 public void start() { 281 final IntentFilter filter = new IntentFilter(); 282 filter.addAction(ENABLE); 283 filter.addAction(DISABLE); 284 mContext.registerReceiver(this, filter, null, mHandler); 285 } 286 287 @Override 288 public void onReceive(Context context, Intent intent) { 289 final String action = intent.getAction(); 290 final ComponentName component = intent.getParcelableExtra(EXTRA_COMPONENT); 291 final boolean current = component.equals(mVolumeControllerService.getComponent()); 292 if (ENABLE.equals(action) && component != null) { 293 if (!current) { 294 showServiceActivationDialog(component); 295 } 296 } 297 if (DISABLE.equals(action) && component != null) { 298 if (current) { 299 mVolumeControllerService.setComponent(null); 300 } 301 } 302 } 303 } 304 305 private final class RestorationNotification { 306 public void hide() { 307 mNotificationManager.cancel(R.id.notification_volumeui); 308 } 309 310 public void show() { 311 final ComponentName component = mVolumeControllerService.getComponent(); 312 if (component == null) { 313 Log.w(TAG, "Not showing restoration notification, component not active"); 314 return; 315 } 316 final Intent intent = new Intent(Receiver.DISABLE) 317 .putExtra(Receiver.EXTRA_COMPONENT, component); 318 mNotificationManager.notify(R.id.notification_volumeui, 319 new Notification.Builder(mContext) 320 .setSmallIcon(R.drawable.ic_ringer_audible) 321 .setWhen(0) 322 .setShowWhen(false) 323 .setOngoing(true) 324 .setContentTitle(mContext.getString( 325 R.string.volumeui_notification_title, getAppLabel(component))) 326 .setContentText(mContext.getString(R.string.volumeui_notification_text)) 327 .setContentIntent(PendingIntent.getBroadcast(mContext, 0, intent, 328 PendingIntent.FLAG_UPDATE_CURRENT)) 329 .setPriority(Notification.PRIORITY_MIN) 330 .setVisibility(Notification.VISIBILITY_PUBLIC) 331 .setColor(mContext.getResources().getColor( 332 com.android.internal.R.color.system_notification_accent_color)) 333 .build()); 334 } 335 } 336} 337