1/*
2 * Copyright (C) 2008 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.settings;
18
19import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN;
20
21import com.android.internal.telephony.TelephonyIntents;
22
23import android.app.Dialog;
24import android.content.BroadcastReceiver;
25import android.content.ContentResolver;
26import android.content.Context;
27import android.content.Intent;
28import android.content.IntentFilter;
29import android.media.AudioManager;
30import android.media.AudioSystem;
31import android.net.Uri;
32import android.os.Handler;
33import android.os.Looper;
34import android.os.Message;
35import android.os.Parcel;
36import android.os.Parcelable;
37import android.preference.VolumePreference;
38import android.provider.Settings;
39import android.provider.Settings.System;
40import android.util.AttributeSet;
41import android.util.Log;
42import android.view.KeyEvent;
43import android.view.View;
44import android.view.View.OnClickListener;
45import android.widget.CheckBox;
46import android.widget.CompoundButton;
47import android.widget.ImageView;
48import android.widget.SeekBar;
49import android.widget.TextView;
50
51/**
52 * Special preference type that allows configuration of both the ring volume and
53 * notification volume.
54 */
55public class RingerVolumePreference extends VolumePreference {
56    private static final String TAG = "RingerVolumePreference";
57    private static final int MSG_RINGER_MODE_CHANGED = 101;
58
59    private SeekBarVolumizer [] mSeekBarVolumizer;
60
61    // These arrays must all match in length and order
62    private static final int[] SEEKBAR_ID = new int[] {
63        R.id.media_volume_seekbar,
64        R.id.ringer_volume_seekbar,
65        R.id.notification_volume_seekbar,
66        R.id.alarm_volume_seekbar
67    };
68
69    private static final int[] SEEKBAR_TYPE = new int[] {
70        AudioManager.STREAM_MUSIC,
71        AudioManager.STREAM_RING,
72        AudioManager.STREAM_NOTIFICATION,
73        AudioManager.STREAM_ALARM
74    };
75
76    private static final int[] CHECKBOX_VIEW_ID = new int[] {
77        R.id.media_mute_button,
78        R.id.ringer_mute_button,
79        R.id.notification_mute_button,
80        R.id.alarm_mute_button
81    };
82
83    private static final int[] SEEKBAR_SECTION_ID = new int[] {
84        R.id.media_section,
85        R.id.ringer_section,
86        R.id.notification_section,
87        R.id.alarm_section
88    };
89
90    private static final int[] SEEKBAR_MUTED_RES_ID = new int[] {
91        com.android.internal.R.drawable.ic_audio_vol_mute,
92        com.android.internal.R.drawable.ic_audio_ring_notif_mute,
93        com.android.internal.R.drawable.ic_audio_notification_mute,
94        com.android.internal.R.drawable.ic_audio_alarm_mute
95    };
96
97    private static final int[] SEEKBAR_UNMUTED_RES_ID = new int[] {
98        com.android.internal.R.drawable.ic_audio_vol,
99        com.android.internal.R.drawable.ic_audio_ring_notif,
100        com.android.internal.R.drawable.ic_audio_notification,
101        com.android.internal.R.drawable.ic_audio_alarm
102    };
103
104    private ImageView[] mCheckBoxes = new ImageView[SEEKBAR_MUTED_RES_ID.length];
105    private SeekBar[] mSeekBars = new SeekBar[SEEKBAR_ID.length];
106
107    private Handler mHandler = new Handler() {
108        public void handleMessage(Message msg) {
109            updateSlidersAndMutedStates();
110        }
111    };
112
113    @Override
114    public void createActionButtons() {
115        setPositiveButtonText(android.R.string.ok);
116        setNegativeButtonText(null);
117    }
118
119    private void updateSlidersAndMutedStates() {
120        for (int i = 0; i < SEEKBAR_TYPE.length; i++) {
121            int streamType = SEEKBAR_TYPE[i];
122            boolean muted = mAudioManager.isStreamMute(streamType);
123
124            if (mCheckBoxes[i] != null) {
125                if (((streamType == AudioManager.STREAM_RING) ||
126                        (streamType == AudioManager.STREAM_NOTIFICATION)) &&
127                        (mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE)) {
128                    mCheckBoxes[i].setImageResource(
129                            com.android.internal.R.drawable.ic_audio_ring_notif_vibrate);
130                } else {
131                    mCheckBoxes[i].setImageResource(
132                            muted ? SEEKBAR_MUTED_RES_ID[i] : SEEKBAR_UNMUTED_RES_ID[i]);
133                }
134            }
135            if (mSeekBars[i] != null) {
136                final int volume = mAudioManager.getStreamVolume(streamType);
137                mSeekBars[i].setProgress(volume);
138                if (streamType != mAudioManager.getMasterStreamType() && muted) {
139                    mSeekBars[i].setEnabled(false);
140                } else {
141                    mSeekBars[i].setEnabled(true);
142                }
143            }
144        }
145    }
146
147    private BroadcastReceiver mRingModeChangedReceiver;
148    private AudioManager mAudioManager;
149
150    //private SeekBarVolumizer mNotificationSeekBarVolumizer;
151    //private TextView mNotificationVolumeTitle;
152
153    public RingerVolumePreference(Context context, AttributeSet attrs) {
154        super(context, attrs);
155
156        // The always visible seekbar is for ring volume
157        setStreamType(AudioManager.STREAM_RING);
158
159        setDialogLayoutResource(R.layout.preference_dialog_ringervolume);
160        //setDialogIcon(R.drawable.ic_settings_sound);
161
162        mSeekBarVolumizer = new SeekBarVolumizer[SEEKBAR_ID.length];
163
164        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
165    }
166
167    @Override
168    protected void onBindDialogView(View view) {
169        super.onBindDialogView(view);
170
171        for (int i = 0; i < SEEKBAR_ID.length; i++) {
172            SeekBar seekBar = (SeekBar) view.findViewById(SEEKBAR_ID[i]);
173            mSeekBars[i] = seekBar;
174            if (SEEKBAR_TYPE[i] == AudioManager.STREAM_MUSIC) {
175                mSeekBarVolumizer[i] = new SeekBarVolumizer(getContext(), seekBar,
176                        SEEKBAR_TYPE[i], getMediaVolumeUri(getContext()));
177            } else {
178                mSeekBarVolumizer[i] = new SeekBarVolumizer(getContext(), seekBar,
179                        SEEKBAR_TYPE[i]);
180            }
181        }
182
183        // Register callbacks for mute/unmute buttons
184        for (int i = 0; i < mCheckBoxes.length; i++) {
185            ImageView checkbox = (ImageView) view.findViewById(CHECKBOX_VIEW_ID[i]);
186            mCheckBoxes[i] = checkbox;
187        }
188
189        // Load initial states from AudioManager
190        updateSlidersAndMutedStates();
191
192        // Listen for updates from AudioManager
193        if (mRingModeChangedReceiver == null) {
194            final IntentFilter filter = new IntentFilter();
195            filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
196            mRingModeChangedReceiver = new BroadcastReceiver() {
197                public void onReceive(Context context, Intent intent) {
198                    final String action = intent.getAction();
199                    if (AudioManager.RINGER_MODE_CHANGED_ACTION.equals(action)) {
200                        mHandler.sendMessage(mHandler.obtainMessage(MSG_RINGER_MODE_CHANGED, intent
201                                .getIntExtra(AudioManager.EXTRA_RINGER_MODE, -1), 0));
202                    }
203                }
204            };
205            getContext().registerReceiver(mRingModeChangedReceiver, filter);
206        }
207
208        boolean useMasterVolume = getContext().getResources().
209                getBoolean(com.android.internal.R.bool.config_useMasterVolume);
210        if (useMasterVolume) {
211            // If config_useMasterVolume is true, all streams are treated as STREAM_MASTER.
212            // So hide all except a stream.
213            int id;
214            if (Utils.isVoiceCapable(getContext())) {
215                id = R.id.ringer_section;
216            } else {
217                id = R.id.media_section;
218            }
219            for (int i = 0; i < SEEKBAR_SECTION_ID.length; i++) {
220                if (SEEKBAR_SECTION_ID[i] != id) {
221                    view.findViewById(SEEKBAR_SECTION_ID[i]).setVisibility(View.GONE);
222                }
223            }
224        } else {
225            // Disable either ringer+notifications or notifications
226            int id;
227            if (!Utils.isVoiceCapable(getContext())) {
228                id = R.id.ringer_section;
229            } else {
230                id = R.id.notification_section;
231            }
232            View hideSection = view.findViewById(id);
233            hideSection.setVisibility(View.GONE);
234        }
235    }
236
237    private Uri getMediaVolumeUri(Context context) {
238        return Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://"
239                + context.getPackageName()
240                + "/" + R.raw.media_volume);
241    }
242
243    @Override
244    protected void onDialogClosed(boolean positiveResult) {
245        super.onDialogClosed(positiveResult);
246
247        if (!positiveResult) {
248            for (SeekBarVolumizer vol : mSeekBarVolumizer) {
249                if (vol != null) vol.revertVolume();
250            }
251        }
252        cleanup();
253    }
254
255    @Override
256    public void onActivityStop() {
257        super.onActivityStop();
258
259        for (SeekBarVolumizer vol : mSeekBarVolumizer) {
260            if (vol != null) vol.stopSample();
261        }
262    }
263
264    @Override
265    public boolean onKey(View v, int keyCode, KeyEvent event) {
266        boolean isdown = (event.getAction() == KeyEvent.ACTION_DOWN);
267        switch (keyCode) {
268            case KeyEvent.KEYCODE_VOLUME_DOWN:
269            case KeyEvent.KEYCODE_VOLUME_UP:
270            case KeyEvent.KEYCODE_VOLUME_MUTE:
271                return true;
272            default:
273                return false;
274        }
275    }
276
277    @Override
278    protected void onSampleStarting(SeekBarVolumizer volumizer) {
279        super.onSampleStarting(volumizer);
280        for (SeekBarVolumizer vol : mSeekBarVolumizer) {
281            if (vol != null && vol != volumizer) vol.stopSample();
282        }
283    }
284
285    private void cleanup() {
286        for (int i = 0; i < SEEKBAR_ID.length; i++) {
287            if (mSeekBarVolumizer[i] != null) {
288                Dialog dialog = getDialog();
289                if (dialog != null && dialog.isShowing()) {
290                    // Stopped while dialog was showing, revert changes
291                    mSeekBarVolumizer[i].revertVolume();
292                }
293                mSeekBarVolumizer[i].stop();
294                mSeekBarVolumizer[i] = null;
295            }
296        }
297        if (mRingModeChangedReceiver != null) {
298            getContext().unregisterReceiver(mRingModeChangedReceiver);
299            mRingModeChangedReceiver = null;
300        }
301    }
302
303    @Override
304    protected Parcelable onSaveInstanceState() {
305        final Parcelable superState = super.onSaveInstanceState();
306        if (isPersistent()) {
307            // No need to save instance state since it's persistent
308            return superState;
309        }
310
311        final SavedState myState = new SavedState(superState);
312        VolumeStore[] volumeStore = myState.getVolumeStore(SEEKBAR_ID.length);
313        for (int i = 0; i < SEEKBAR_ID.length; i++) {
314            SeekBarVolumizer vol = mSeekBarVolumizer[i];
315            if (vol != null) {
316                vol.onSaveInstanceState(volumeStore[i]);
317            }
318        }
319        return myState;
320    }
321
322    @Override
323    protected void onRestoreInstanceState(Parcelable state) {
324        if (state == null || !state.getClass().equals(SavedState.class)) {
325            // Didn't save state for us in onSaveInstanceState
326            super.onRestoreInstanceState(state);
327            return;
328        }
329
330        SavedState myState = (SavedState) state;
331        super.onRestoreInstanceState(myState.getSuperState());
332        VolumeStore[] volumeStore = myState.getVolumeStore(SEEKBAR_ID.length);
333        for (int i = 0; i < SEEKBAR_ID.length; i++) {
334            SeekBarVolumizer vol = mSeekBarVolumizer[i];
335            if (vol != null) {
336                vol.onRestoreInstanceState(volumeStore[i]);
337            }
338        }
339    }
340
341    private static class SavedState extends BaseSavedState {
342        VolumeStore [] mVolumeStore;
343
344        public SavedState(Parcel source) {
345            super(source);
346            mVolumeStore = new VolumeStore[SEEKBAR_ID.length];
347            for (int i = 0; i < SEEKBAR_ID.length; i++) {
348                mVolumeStore[i] = new VolumeStore();
349                mVolumeStore[i].volume = source.readInt();
350                mVolumeStore[i].originalVolume = source.readInt();
351            }
352        }
353
354        @Override
355        public void writeToParcel(Parcel dest, int flags) {
356            super.writeToParcel(dest, flags);
357            for (int i = 0; i < SEEKBAR_ID.length; i++) {
358                dest.writeInt(mVolumeStore[i].volume);
359                dest.writeInt(mVolumeStore[i].originalVolume);
360            }
361        }
362
363        VolumeStore[] getVolumeStore(int count) {
364            if (mVolumeStore == null || mVolumeStore.length != count) {
365                mVolumeStore = new VolumeStore[count];
366                for (int i = 0; i < count; i++) {
367                    mVolumeStore[i] = new VolumeStore();
368                }
369            }
370            return mVolumeStore;
371        }
372
373        public SavedState(Parcelable superState) {
374            super(superState);
375        }
376
377        public static final Parcelable.Creator<SavedState> CREATOR =
378                new Parcelable.Creator<SavedState>() {
379            public SavedState createFromParcel(Parcel in) {
380                return new SavedState(in);
381            }
382
383            public SavedState[] newArray(int size) {
384                return new SavedState[size];
385            }
386        };
387    }
388}
389