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 
17 package com.android.server.telecom;
18 
19 import android.telecom.CallState;
20 
21 import com.android.internal.util.Preconditions;
22 
23 /**
24  * Plays ringback tones. Ringback is different from other tones because it operates as the current
25  * audio for a call, whereas most tones play as simple timed events. This means ringback must be
26  * able to turn off and on as the user switches between calls. This is why it is implemented as its
27  * own class.
28  */
29 class RingbackPlayer extends CallsManagerListenerBase {
30 
31     private final CallsManager mCallsManager;
32 
33     private final InCallTonePlayer.Factory mPlayerFactory;
34 
35     /**
36      * The current call for which the ringback tone is being played.
37      */
38     private Call mCall;
39 
40     /**
41      * The currently active player.
42      */
43     private InCallTonePlayer mTonePlayer;
44 
RingbackPlayer(CallsManager callsManager, InCallTonePlayer.Factory playerFactory)45     RingbackPlayer(CallsManager callsManager, InCallTonePlayer.Factory playerFactory) {
46         mCallsManager = callsManager;
47         mPlayerFactory = playerFactory;
48     }
49 
50     /** {@inheritDoc} */
51     @Override
onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall)52     public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) {
53         if (oldForegroundCall != null) {
54             stopRingbackForCall(oldForegroundCall);
55         }
56 
57         if (shouldStartRinging(newForegroundCall)) {
58             startRingbackForCall(newForegroundCall);
59         }
60     }
61 
62     @Override
onConnectionServiceChanged( Call call, ConnectionServiceWrapper oldService, ConnectionServiceWrapper newService)63     public void onConnectionServiceChanged(
64             Call call,
65             ConnectionServiceWrapper oldService,
66             ConnectionServiceWrapper newService) {
67 
68         // Treat as ending or begining dialing based on the state transition.
69         if (shouldStartRinging(call)) {
70             startRingbackForCall(call);
71         } else if (newService == null) {
72             stopRingbackForCall(call);
73         }
74     }
75 
76     @Override
onRingbackRequested(Call call, boolean ignored)77     public void onRingbackRequested(Call call, boolean ignored) {
78         if (shouldStartRinging(call)) {
79             startRingbackForCall(call);
80         } else {
81             stopRingbackForCall(call);
82         }
83     }
84 
85     /**
86      * Starts ringback for the specified dialing call as needed.
87      *
88      * @param call The call for which to ringback.
89      */
startRingbackForCall(Call call)90     private void startRingbackForCall(Call call) {
91         Preconditions.checkState(call.getState() == CallState.DIALING);
92         ThreadUtil.checkOnMainThread();
93 
94         if (mCall == call) {
95             Log.w(this, "Ignoring duplicate requests to ring for %s.", call);
96             return;
97         }
98 
99         if (mCall != null) {
100             // We only get here for the foreground call so, there's no reason why there should
101             // exist a current dialing call.
102             Log.wtf(this, "Ringback player thinks there are two foreground-dialing calls.");
103         }
104 
105         mCall = call;
106         if (mTonePlayer == null) {
107             Log.d(this, "Playing the ringback tone for %s.", call);
108             mTonePlayer = mPlayerFactory.createPlayer(InCallTonePlayer.TONE_RING_BACK);
109             mTonePlayer.startTone();
110         }
111     }
112 
113     /**
114      * Stops the ringback for the specified dialing call as needed.
115      *
116      * @param call The call for which to stop ringback.
117      */
stopRingbackForCall(Call call)118     private void stopRingbackForCall(Call call) {
119         ThreadUtil.checkOnMainThread();
120 
121         if (mCall == call) {
122             // The foreground call is no longer dialing or is no longer the foreground call. In
123             // either case, stop the ringback tone.
124             mCall = null;
125 
126             if (mTonePlayer == null) {
127                 Log.w(this, "No player found to stop.");
128             } else {
129                 Log.i(this, "Stopping the ringback tone for %s.", call);
130                 mTonePlayer.stopTone();
131                 mTonePlayer = null;
132             }
133         }
134     }
135 
shouldStartRinging(Call call)136     private boolean shouldStartRinging(Call call) {
137         return call != null
138                 && mCallsManager.getForegroundCall() == call
139                 && call.getState() == CallState.DIALING
140                 && call.isRingbackRequested();
141     }
142 }
143