1/*
2 * Copyright 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.server.telecom;
18
19import android.content.Context;
20import android.media.AudioManager;
21import android.media.ToneGenerator;
22import android.provider.Settings;
23
24// TODO: Needed for move to system service: import com.android.internal.R;
25import com.google.common.collect.ImmutableMap;
26
27import java.util.Map;
28
29/**
30 * Plays DTMF tones locally for the caller to hear. In order to reduce (1) the amount of times we
31 * check the "play local tones" setting and (2) the length of time we keep the tone generator, this
32 * class employs a concept of a call "session" that starts and stops when the foreground call
33 * changes.
34 */
35class DtmfLocalTonePlayer extends CallsManagerListenerBase {
36    private static final Map<Character, Integer> TONE_MAP =
37            ImmutableMap.<Character, Integer>builder()
38                    .put('1', ToneGenerator.TONE_DTMF_1)
39                    .put('2', ToneGenerator.TONE_DTMF_2)
40                    .put('3', ToneGenerator.TONE_DTMF_3)
41                    .put('4', ToneGenerator.TONE_DTMF_4)
42                    .put('5', ToneGenerator.TONE_DTMF_5)
43                    .put('6', ToneGenerator.TONE_DTMF_6)
44                    .put('7', ToneGenerator.TONE_DTMF_7)
45                    .put('8', ToneGenerator.TONE_DTMF_8)
46                    .put('9', ToneGenerator.TONE_DTMF_9)
47                    .put('0', ToneGenerator.TONE_DTMF_0)
48                    .put('#', ToneGenerator.TONE_DTMF_P)
49                    .put('*', ToneGenerator.TONE_DTMF_S)
50                    .build();
51
52    /** Generator used to actually play the tone. */
53    private ToneGenerator mToneGenerator;
54
55    /** The current call associated with an existing dtmf session. */
56    private Call mCall;
57
58    /** The context. */
59    private final Context mContext;
60
61    public DtmfLocalTonePlayer(Context context) {
62        mContext = context;
63    }
64
65    /** {@inheritDoc} */
66    @Override
67    public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) {
68        endDtmfSession(oldForegroundCall);
69        startDtmfSession(newForegroundCall);
70    }
71
72    /**
73     * Starts playing the dtmf tone specified by c.
74     *
75     * @param call The associated call.
76     * @param c The digit to play.
77     */
78    void playTone(Call call, char c) {
79        // Do nothing if it is not the right call.
80        if (mCall != call) {
81            return;
82        }
83
84        if (mToneGenerator == null) {
85            Log.d(this, "playTone: mToneGenerator == null, %c.", c);
86        } else {
87            Log.d(this, "starting local tone: %c.", c);
88            if (TONE_MAP.containsKey(c)) {
89                mToneGenerator.startTone(TONE_MAP.get(c), -1 /* toneDuration */);
90            }
91        }
92    }
93
94    /**
95     * Stops any currently playing dtmf tone.
96     *
97     * @param call The associated call.
98     */
99    void stopTone(Call call) {
100        // Do nothing if it's not the right call.
101        if (mCall != call) {
102            return;
103        }
104
105        if (mToneGenerator == null) {
106            Log.d(this, "stopTone: mToneGenerator == null.");
107        } else {
108            Log.d(this, "stopping local tone.");
109            mToneGenerator.stopTone();
110        }
111    }
112
113    /**
114     * Runs initialization requires to play local tones during a call.
115     *
116     * @param call The call associated with this dtmf session.
117     */
118    private void startDtmfSession(Call call) {
119        if (call == null) {
120            return;
121        }
122        final Context context = call.getContext();
123        final boolean areLocalTonesEnabled;
124        if (context.getResources().getBoolean(R.bool.allow_local_dtmf_tones)) {
125            areLocalTonesEnabled = Settings.System.getInt(
126                    context.getContentResolver(), Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1;
127        } else {
128            areLocalTonesEnabled = false;
129        }
130
131        mCall = call;
132
133        if (areLocalTonesEnabled) {
134            if (mToneGenerator == null) {
135                try {
136                    mToneGenerator = new ToneGenerator(AudioManager.STREAM_DTMF, 80);
137                } catch (RuntimeException e) {
138                    Log.e(this, e, "Error creating local tone generator.");
139                    mToneGenerator = null;
140                }
141            }
142        }
143    }
144
145    /**
146     * Releases resources needed for playing local dtmf tones.
147     *
148     * @param call The call associated with the session to end.
149     */
150    private void endDtmfSession(Call call) {
151        if (call != null && mCall == call) {
152            // Do a stopTone() in case the sessions ends before we are told to stop the tone.
153            stopTone(call);
154
155            mCall = null;
156
157            if (mToneGenerator != null) {
158                mToneGenerator.release();
159                mToneGenerator = null;
160            }
161        }
162    }
163}
164