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.session.MediaSessionManager; 33import android.os.Handler; 34import android.provider.Settings; 35import android.text.TextUtils; 36import android.util.Log; 37 38import com.android.systemui.Prefs; 39import com.android.systemui.R; 40import com.android.systemui.SystemUI; 41import com.android.systemui.qs.tiles.DndTile; 42import com.android.systemui.statusbar.ServiceMonitor; 43import com.android.systemui.statusbar.phone.SystemUIDialog; 44import com.android.systemui.statusbar.policy.ZenModeController; 45import com.android.systemui.statusbar.policy.ZenModeControllerImpl; 46 47import java.io.FileDescriptor; 48import java.io.PrintWriter; 49 50public class VolumeUI extends SystemUI { 51 private static final String TAG = "VolumeUI"; 52 private static boolean LOGD = Log.isLoggable(TAG, Log.DEBUG); 53 54 private final Handler mHandler = new Handler(); 55 private final Receiver mReceiver = new Receiver(); 56 private final RestorationNotification mRestorationNotification = new RestorationNotification(); 57 58 private boolean mEnabled; 59 private AudioManager mAudioManager; 60 private NotificationManager mNotificationManager; 61 private MediaSessionManager mMediaSessionManager; 62 private ServiceMonitor mVolumeControllerService; 63 64 private VolumeDialogComponent mVolumeComponent; 65 66 @Override 67 public void start() { 68 mEnabled = mContext.getResources().getBoolean(R.bool.enable_volume_ui); 69 if (!mEnabled) return; 70 mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); 71 mNotificationManager = 72 (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); 73 mMediaSessionManager = (MediaSessionManager) mContext 74 .getSystemService(Context.MEDIA_SESSION_SERVICE); 75 final ZenModeController zenController = new ZenModeControllerImpl(mContext, mHandler); 76 mVolumeComponent = new VolumeDialogComponent(this, mContext, null, zenController); 77 putComponent(VolumeComponent.class, getVolumeComponent()); 78 mReceiver.start(); 79 mVolumeControllerService = new ServiceMonitor(TAG, LOGD, 80 mContext, Settings.Secure.VOLUME_CONTROLLER_SERVICE_COMPONENT, 81 new ServiceMonitorCallbacks()); 82 mVolumeControllerService.start(); 83 } 84 85 private VolumeComponent getVolumeComponent() { 86 return mVolumeComponent; 87 } 88 89 @Override 90 protected void onConfigurationChanged(Configuration newConfig) { 91 super.onConfigurationChanged(newConfig); 92 if (!mEnabled) return; 93 getVolumeComponent().onConfigurationChanged(newConfig); 94 } 95 96 @Override 97 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 98 pw.print("mEnabled="); pw.println(mEnabled); 99 if (!mEnabled) return; 100 pw.print("mVolumeControllerService="); pw.println(mVolumeControllerService.getComponent()); 101 getVolumeComponent().dump(fd, pw, args); 102 } 103 104 private void setDefaultVolumeController(boolean register) { 105 if (register) { 106 DndTile.setVisible(mContext, true); 107 if (LOGD) Log.d(TAG, "Registering default volume controller"); 108 getVolumeComponent().register(); 109 } else { 110 if (LOGD) Log.d(TAG, "Unregistering default volume controller"); 111 mAudioManager.setVolumeController(null); 112 mMediaSessionManager.setRemoteVolumeController(null); 113 } 114 } 115 116 private String getAppLabel(ComponentName component) { 117 final String pkg = component.getPackageName(); 118 try { 119 final ApplicationInfo ai = mContext.getPackageManager().getApplicationInfo(pkg, 0); 120 final String rt = mContext.getPackageManager().getApplicationLabel(ai).toString(); 121 if (!TextUtils.isEmpty(rt)) { 122 return rt; 123 } 124 } catch (Exception e) { 125 Log.w(TAG, "Error loading app label", e); 126 } 127 return pkg; 128 } 129 130 private void showServiceActivationDialog(final ComponentName component) { 131 final SystemUIDialog d = new SystemUIDialog(mContext); 132 d.setMessage(mContext.getString(R.string.volumeui_prompt_message, getAppLabel(component))); 133 d.setPositiveButton(R.string.volumeui_prompt_allow, new OnClickListener() { 134 @Override 135 public void onClick(DialogInterface dialog, int which) { 136 mVolumeControllerService.setComponent(component); 137 } 138 }); 139 d.setNegativeButton(R.string.volumeui_prompt_deny, null); 140 d.show(); 141 } 142 143 private final class ServiceMonitorCallbacks implements ServiceMonitor.Callbacks { 144 @Override 145 public void onNoService() { 146 if (LOGD) Log.d(TAG, "onNoService"); 147 setDefaultVolumeController(true); 148 mRestorationNotification.hide(); 149 if (!mVolumeControllerService.isPackageAvailable()) { 150 mVolumeControllerService.setComponent(null); 151 } 152 } 153 154 @Override 155 public long onServiceStartAttempt() { 156 if (LOGD) Log.d(TAG, "onServiceStartAttempt"); 157 // poke the setting to update the uid 158 mVolumeControllerService.setComponent(mVolumeControllerService.getComponent()); 159 setDefaultVolumeController(false); 160 getVolumeComponent().dismissNow(); 161 mRestorationNotification.show(); 162 return 0; 163 } 164 } 165 166 private final class Receiver extends BroadcastReceiver { 167 private static final String ENABLE = "com.android.systemui.vui.ENABLE"; 168 private static final String DISABLE = "com.android.systemui.vui.DISABLE"; 169 private static final String EXTRA_COMPONENT = "component"; 170 171 private static final String PREF = "com.android.systemui.PREF"; 172 private static final String EXTRA_KEY = "key"; 173 private static final String EXTRA_VALUE = "value"; 174 175 public void start() { 176 final IntentFilter filter = new IntentFilter(); 177 filter.addAction(ENABLE); 178 filter.addAction(DISABLE); 179 filter.addAction(PREF); 180 mContext.registerReceiver(this, filter, null, mHandler); 181 } 182 183 @Override 184 public void onReceive(Context context, Intent intent) { 185 final String action = intent.getAction(); 186 if (PREF.equals(action)) { 187 final String key = intent.getStringExtra(EXTRA_KEY); 188 if (key != null && intent.getExtras() != null) { 189 final Object value = intent.getExtras().get(EXTRA_VALUE); 190 if (value == null) { 191 Prefs.remove(mContext, key); 192 } else if (value instanceof Boolean) { 193 Prefs.putBoolean(mContext, key, (Boolean) value); 194 } else if (value instanceof Integer) { 195 Prefs.putInt(mContext, key, (Integer) value); 196 } else if (value instanceof Long) { 197 Prefs.putLong(mContext, key, (Long) value); 198 } 199 } 200 return; 201 } 202 final ComponentName component = intent.getParcelableExtra(EXTRA_COMPONENT); 203 final boolean current = component != null 204 && component.equals(mVolumeControllerService.getComponent()); 205 if (ENABLE.equals(action) && component != null) { 206 if (!current) { 207 showServiceActivationDialog(component); 208 } 209 } 210 if (DISABLE.equals(action) && component != null) { 211 if (current) { 212 mVolumeControllerService.setComponent(null); 213 } 214 } 215 } 216 } 217 218 private final class RestorationNotification { 219 public void hide() { 220 mNotificationManager.cancel(R.id.notification_volumeui); 221 } 222 223 public void show() { 224 final ComponentName component = mVolumeControllerService.getComponent(); 225 if (component == null) { 226 Log.w(TAG, "Not showing restoration notification, component not active"); 227 return; 228 } 229 final Intent intent = new Intent(Receiver.DISABLE) 230 .putExtra(Receiver.EXTRA_COMPONENT, component); 231 Notification.Builder builder = new Notification.Builder(mContext) 232 .setSmallIcon(R.drawable.ic_volume_media) 233 .setWhen(0) 234 .setShowWhen(false) 235 .setOngoing(true) 236 .setContentTitle(mContext.getString( 237 R.string.volumeui_notification_title, getAppLabel(component))) 238 .setContentText(mContext.getString(R.string.volumeui_notification_text)) 239 .setContentIntent(PendingIntent.getBroadcast(mContext, 0, intent, 240 PendingIntent.FLAG_UPDATE_CURRENT)) 241 .setPriority(Notification.PRIORITY_MIN) 242 .setVisibility(Notification.VISIBILITY_PUBLIC) 243 .setColor(mContext.getColor( 244 com.android.internal.R.color.system_notification_accent_color)); 245 overrideNotificationAppName(mContext, builder); 246 mNotificationManager.notify(R.id.notification_volumeui, 247 builder.build()); 248 } 249 } 250} 251