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.Ringtone;
22import android.media.RingtoneManager;
23import android.net.Uri;
24import android.os.Handler;
25import android.os.HandlerThread;
26import android.os.Message;
27import android.provider.Settings;
28
29import com.android.internal.util.Preconditions;
30
31/**
32 * Plays the default ringtone. Uses {@link Ringtone} in a separate thread so that this class can be
33 * used from the main thread.
34 */
35class AsyncRingtonePlayer {
36    // Message codes used with the ringtone thread.
37    private static final int EVENT_PLAY = 1;
38    private static final int EVENT_STOP = 2;
39    private static final int EVENT_REPEAT = 3;
40
41    // The interval in which to restart the ringer.
42    private static final int RESTART_RINGER_MILLIS = 3000;
43
44    /** Handler running on the ringtone thread. */
45    private Handler mHandler;
46
47    /** The current ringtone. Only used by the ringtone thread. */
48    private Ringtone mRingtone;
49
50    /**
51     * The context.
52     */
53    private final Context mContext;
54
55    AsyncRingtonePlayer(Context context) {
56        mContext = context;
57    }
58
59    /** Plays the ringtone. */
60    void play(Uri ringtone) {
61        Log.d(this, "Posting play.");
62        postMessage(EVENT_PLAY, true /* shouldCreateHandler */, ringtone);
63    }
64
65    /** Stops playing the ringtone. */
66    void stop() {
67        Log.d(this, "Posting stop.");
68        postMessage(EVENT_STOP, false /* shouldCreateHandler */, null);
69    }
70
71    /**
72     * Posts a message to the ringtone-thread handler. Creates the handler if specified by the
73     * parameter shouldCreateHandler.
74     *
75     * @param messageCode The message to post.
76     * @param shouldCreateHandler True when a handler should be created to handle this message.
77     */
78    private void postMessage(int messageCode, boolean shouldCreateHandler, Uri ringtone) {
79        synchronized(this) {
80            if (mHandler == null && shouldCreateHandler) {
81                mHandler = getNewHandler();
82            }
83
84            if (mHandler == null) {
85                Log.d(this, "Message %d skipped because there is no handler.", messageCode);
86            } else {
87                mHandler.obtainMessage(messageCode, ringtone).sendToTarget();
88            }
89        }
90    }
91
92    /**
93     * Creates a new ringtone Handler running in its own thread.
94     */
95    private Handler getNewHandler() {
96        Preconditions.checkState(mHandler == null);
97
98        HandlerThread thread = new HandlerThread("ringtone-player");
99        thread.start();
100
101        return new Handler(thread.getLooper()) {
102            @Override
103            public void handleMessage(Message msg) {
104                switch(msg.what) {
105                    case EVENT_PLAY:
106                        handlePlay((Uri) msg.obj);
107                        break;
108                    case EVENT_REPEAT:
109                        handleRepeat();
110                        break;
111                    case EVENT_STOP:
112                        handleStop();
113                        break;
114                }
115            }
116        };
117    }
118
119    /**
120     * Starts the actual playback of the ringtone. Executes on ringtone-thread.
121     */
122    private void handlePlay(Uri ringtoneUri) {
123        // don't bother with any of this if there is an EVENT_STOP waiting.
124        if (mHandler.hasMessages(EVENT_STOP)) {
125            return;
126        }
127
128        ThreadUtil.checkNotOnMainThread();
129        Log.i(this, "Play ringtone.");
130
131        if (mRingtone == null) {
132            mRingtone = getRingtone(ringtoneUri);
133
134            // Cancel everything if there is no ringtone.
135            if (mRingtone == null) {
136                handleStop();
137                return;
138            }
139        }
140
141        handleRepeat();
142    }
143
144    private void handleRepeat() {
145        if (mRingtone == null) {
146            return;
147        }
148
149        if (mRingtone.isPlaying()) {
150            Log.d(this, "Ringtone already playing.");
151        } else {
152            mRingtone.play();
153            Log.i(this, "Repeat ringtone.");
154        }
155
156        // Repost event to restart ringer in {@link RESTART_RINGER_MILLIS}.
157        synchronized(this) {
158            if (!mHandler.hasMessages(EVENT_REPEAT)) {
159                mHandler.sendEmptyMessageDelayed(EVENT_REPEAT, RESTART_RINGER_MILLIS);
160            }
161        }
162    }
163
164    /**
165     * Stops the playback of the ringtone. Executes on the ringtone-thread.
166     */
167    private void handleStop() {
168        ThreadUtil.checkNotOnMainThread();
169        Log.i(this, "Stop ringtone.");
170
171        if (mRingtone != null) {
172            Log.d(this, "Ringtone.stop() invoked.");
173            mRingtone.stop();
174            mRingtone = null;
175        }
176
177        synchronized(this) {
178            // At the time that STOP is handled, there should be no need for repeat messages in the
179            // queue.
180            mHandler.removeMessages(EVENT_REPEAT);
181
182            if (mHandler.hasMessages(EVENT_PLAY)) {
183                Log.v(this, "Keeping alive ringtone thread for subsequent play request.");
184            } else {
185                mHandler.removeMessages(EVENT_STOP);
186                mHandler.getLooper().quitSafely();
187                mHandler = null;
188                Log.v(this, "Handler cleared.");
189            }
190        }
191    }
192
193    private Ringtone getRingtone(Uri ringtoneUri) {
194        if (ringtoneUri == null) {
195            ringtoneUri = Settings.System.DEFAULT_RINGTONE_URI;
196        }
197
198        Ringtone ringtone = RingtoneManager.getRingtone(mContext, ringtoneUri);
199        if (ringtone != null) {
200            ringtone.setStreamType(AudioManager.STREAM_RING);
201        }
202        return ringtone;
203    }
204}
205