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