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