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