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