11ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon/*
21ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon * Copyright 2014, The Android Open Source Project
31ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon *
41ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon * Licensed under the Apache License, Version 2.0 (the "License");
51ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon * you may not use this file except in compliance with the License.
61ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon * You may obtain a copy of the License at
71ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon *
81ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon *     http://www.apache.org/licenses/LICENSE-2.0
91ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon *
101ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon * Unless required by applicable law or agreed to in writing, software
111ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon * distributed under the License is distributed on an "AS IS" BASIS,
121ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
131ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon * See the License for the specific language governing permissions and
141ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon * limitations under the License.
151ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon */
161ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon
177cc70b4f0ad1064a4a0dce6056ad82b205887160Tyler Gunnpackage com.android.server.telecom;
181ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon
191ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordonimport android.media.Ringtone;
208c89b20bc84c06294bff803b3a6043992b613e76Hall Liuimport android.net.Uri;
211ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordonimport android.os.Handler;
221ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordonimport android.os.HandlerThread;
231ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordonimport android.os.Message;
24a3eccfee788c3ac3c831a443b085b141b39bb63dBrad Ebingerimport android.telecom.Log;
251ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon
26d931a017a0abea32ad4485a91402b5f62b9ddb0eBrad Ebingerimport com.android.internal.annotations.VisibleForTesting;
278c89b20bc84c06294bff803b3a6043992b613e76Hall Liuimport com.android.internal.os.SomeArgs;
2891d43cf9c985cc5a83795f256ef5c46ebb8fbdc1Tyler Gunnimport com.android.internal.util.Preconditions;
291ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon
301ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon/**
311ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon * Plays the default ringtone. Uses {@link Ringtone} in a separate thread so that this class can be
321ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon * used from the main thread.
331ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon */
34d931a017a0abea32ad4485a91402b5f62b9ddb0eBrad Ebinger@VisibleForTesting
35d931a017a0abea32ad4485a91402b5f62b9ddb0eBrad Ebingerpublic class AsyncRingtonePlayer {
361ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon    // Message codes used with the ringtone thread.
37b21c5daa6ce683d7f6a1605a50a7ae05c520309fSantos Cordon    private static final int EVENT_PLAY = 1;
38b21c5daa6ce683d7f6a1605a50a7ae05c520309fSantos Cordon    private static final int EVENT_STOP = 2;
39b21c5daa6ce683d7f6a1605a50a7ae05c520309fSantos Cordon    private static final int EVENT_REPEAT = 3;
40b21c5daa6ce683d7f6a1605a50a7ae05c520309fSantos Cordon
41b21c5daa6ce683d7f6a1605a50a7ae05c520309fSantos Cordon    // The interval in which to restart the ringer.
42b21c5daa6ce683d7f6a1605a50a7ae05c520309fSantos Cordon    private static final int RESTART_RINGER_MILLIS = 3000;
431ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon
441ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon    /** Handler running on the ringtone thread. */
451ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon    private Handler mHandler;
461ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon
471ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon    /** The current ringtone. Only used by the ringtone thread. */
481ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon    private Ringtone mRingtone;
491ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon
501ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon    /** Plays the ringtone. */
51c9286f49d12769d775d8ad7a66f9117c49c71162Brad Ebinger    public void play(RingtoneFactory factory, Call incomingCall) {
521ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon        Log.d(this, "Posting play.");
538c89b20bc84c06294bff803b3a6043992b613e76Hall Liu        SomeArgs args = SomeArgs.obtain();
548c89b20bc84c06294bff803b3a6043992b613e76Hall Liu        args.arg1 = factory;
55c9286f49d12769d775d8ad7a66f9117c49c71162Brad Ebinger        args.arg2 = incomingCall;
568c89b20bc84c06294bff803b3a6043992b613e76Hall Liu        postMessage(EVENT_PLAY, true /* shouldCreateHandler */, args);
571ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon    }
581ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon
591ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon    /** Stops playing the ringtone. */
60d931a017a0abea32ad4485a91402b5f62b9ddb0eBrad Ebinger    public void stop() {
611ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon        Log.d(this, "Posting stop.");
625ba7f27491e287f39a999ddd3d1ed6a7bad78272Santos Cordon        postMessage(EVENT_STOP, false /* shouldCreateHandler */, null);
631ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon    }
641ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon
651ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon    /**
661ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon     * Posts a message to the ringtone-thread handler. Creates the handler if specified by the
671ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon     * parameter shouldCreateHandler.
681ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon     *
691ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon     * @param messageCode The message to post.
701ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon     * @param shouldCreateHandler True when a handler should be created to handle this message.
711ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon     */
728c89b20bc84c06294bff803b3a6043992b613e76Hall Liu    private void postMessage(int messageCode, boolean shouldCreateHandler, SomeArgs args) {
731ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon        synchronized(this) {
741ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon            if (mHandler == null && shouldCreateHandler) {
751ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon                mHandler = getNewHandler();
761ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon            }
771ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon
781ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon            if (mHandler == null) {
791ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon                Log.d(this, "Message %d skipped because there is no handler.", messageCode);
801ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon            } else {
818c89b20bc84c06294bff803b3a6043992b613e76Hall Liu                mHandler.obtainMessage(messageCode, args).sendToTarget();
821ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon            }
831ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon        }
841ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon    }
851ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon
861ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon    /**
871ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon     * Creates a new ringtone Handler running in its own thread.
881ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon     */
891ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon    private Handler getNewHandler() {
901ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon        Preconditions.checkState(mHandler == null);
911ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon
921ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon        HandlerThread thread = new HandlerThread("ringtone-player");
931ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon        thread.start();
941ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon
951ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon        return new Handler(thread.getLooper()) {
961ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon            @Override
971ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon            public void handleMessage(Message msg) {
981ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon                switch(msg.what) {
991ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon                    case EVENT_PLAY:
1008c89b20bc84c06294bff803b3a6043992b613e76Hall Liu                        handlePlay((SomeArgs) msg.obj);
1011ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon                        break;
102b21c5daa6ce683d7f6a1605a50a7ae05c520309fSantos Cordon                    case EVENT_REPEAT:
103b21c5daa6ce683d7f6a1605a50a7ae05c520309fSantos Cordon                        handleRepeat();
104b21c5daa6ce683d7f6a1605a50a7ae05c520309fSantos Cordon                        break;
1051ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon                    case EVENT_STOP:
1061ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon                        handleStop();
1071ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon                        break;
1081ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon                }
1091ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon            }
1101ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon        };
1111ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon    }
1121ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon
1131ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon    /**
1141ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon     * Starts the actual playback of the ringtone. Executes on ringtone-thread.
1151ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon     */
1168c89b20bc84c06294bff803b3a6043992b613e76Hall Liu    private void handlePlay(SomeArgs args) {
1178c89b20bc84c06294bff803b3a6043992b613e76Hall Liu        RingtoneFactory factory = (RingtoneFactory) args.arg1;
118c9286f49d12769d775d8ad7a66f9117c49c71162Brad Ebinger        Call incomingCall = (Call) args.arg2;
1198c89b20bc84c06294bff803b3a6043992b613e76Hall Liu        args.recycle();
120b21c5daa6ce683d7f6a1605a50a7ae05c520309fSantos Cordon        // don't bother with any of this if there is an EVENT_STOP waiting.
121b21c5daa6ce683d7f6a1605a50a7ae05c520309fSantos Cordon        if (mHandler.hasMessages(EVENT_STOP)) {
122b21c5daa6ce683d7f6a1605a50a7ae05c520309fSantos Cordon            return;
123b21c5daa6ce683d7f6a1605a50a7ae05c520309fSantos Cordon        }
124b21c5daa6ce683d7f6a1605a50a7ae05c520309fSantos Cordon
125ad54e7f053f00d6c77fe18c11cc5e7c5a7b8fb60Brad Ebinger        // If the Ringtone Uri is EMPTY, then the "None" Ringtone has been selected. Do not play
126ad54e7f053f00d6c77fe18c11cc5e7c5a7b8fb60Brad Ebinger        // anything.
127ad54e7f053f00d6c77fe18c11cc5e7c5a7b8fb60Brad Ebinger        if(Uri.EMPTY.equals(incomingCall.getRingtone())) {
128ad54e7f053f00d6c77fe18c11cc5e7c5a7b8fb60Brad Ebinger            mRingtone = null;
129ad54e7f053f00d6c77fe18c11cc5e7c5a7b8fb60Brad Ebinger            return;
130ad54e7f053f00d6c77fe18c11cc5e7c5a7b8fb60Brad Ebinger        }
131ad54e7f053f00d6c77fe18c11cc5e7c5a7b8fb60Brad Ebinger
1321ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon        ThreadUtil.checkNotOnMainThread();
1331ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon        Log.i(this, "Play ringtone.");
1341ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon
1351ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon        if (mRingtone == null) {
136c9286f49d12769d775d8ad7a66f9117c49c71162Brad Ebinger            mRingtone = factory.getRingtone(incomingCall);
1378c89b20bc84c06294bff803b3a6043992b613e76Hall Liu            if (mRingtone == null) {
138c9286f49d12769d775d8ad7a66f9117c49c71162Brad Ebinger                Uri ringtoneUri = incomingCall.getRingtone();
139c9286f49d12769d775d8ad7a66f9117c49c71162Brad Ebinger                String ringtoneUriString = (ringtoneUri == null) ? "null" :
140c9286f49d12769d775d8ad7a66f9117c49c71162Brad Ebinger                        ringtoneUri.toSafeString();
141a3eccfee788c3ac3c831a443b085b141b39bb63dBrad Ebinger                Log.addEvent(null, LogUtils.Events.ERROR_LOG, "Failed to get ringtone from " +
142a3eccfee788c3ac3c831a443b085b141b39bb63dBrad Ebinger                        "factory. Skipping ringing. Uri was: " + ringtoneUriString);
1438c89b20bc84c06294bff803b3a6043992b613e76Hall Liu                return;
1448c89b20bc84c06294bff803b3a6043992b613e76Hall Liu            }
1451ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon        }
1461ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon
147b21c5daa6ce683d7f6a1605a50a7ae05c520309fSantos Cordon        handleRepeat();
148b21c5daa6ce683d7f6a1605a50a7ae05c520309fSantos Cordon    }
149b21c5daa6ce683d7f6a1605a50a7ae05c520309fSantos Cordon
150b21c5daa6ce683d7f6a1605a50a7ae05c520309fSantos Cordon    private void handleRepeat() {
151b21c5daa6ce683d7f6a1605a50a7ae05c520309fSantos Cordon        if (mRingtone == null) {
152b21c5daa6ce683d7f6a1605a50a7ae05c520309fSantos Cordon            return;
153b21c5daa6ce683d7f6a1605a50a7ae05c520309fSantos Cordon        }
154b21c5daa6ce683d7f6a1605a50a7ae05c520309fSantos Cordon
1551ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon        if (mRingtone.isPlaying()) {
1561ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon            Log.d(this, "Ringtone already playing.");
1571ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon        } else {
1581ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon            mRingtone.play();
159b21c5daa6ce683d7f6a1605a50a7ae05c520309fSantos Cordon            Log.i(this, "Repeat ringtone.");
160b21c5daa6ce683d7f6a1605a50a7ae05c520309fSantos Cordon        }
161b21c5daa6ce683d7f6a1605a50a7ae05c520309fSantos Cordon
162b21c5daa6ce683d7f6a1605a50a7ae05c520309fSantos Cordon        // Repost event to restart ringer in {@link RESTART_RINGER_MILLIS}.
163b21c5daa6ce683d7f6a1605a50a7ae05c520309fSantos Cordon        synchronized(this) {
164b21c5daa6ce683d7f6a1605a50a7ae05c520309fSantos Cordon            if (!mHandler.hasMessages(EVENT_REPEAT)) {
165b21c5daa6ce683d7f6a1605a50a7ae05c520309fSantos Cordon                mHandler.sendEmptyMessageDelayed(EVENT_REPEAT, RESTART_RINGER_MILLIS);
166b21c5daa6ce683d7f6a1605a50a7ae05c520309fSantos Cordon            }
1671ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon        }
1681ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon    }
1691ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon
1701ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon    /**
1711ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon     * Stops the playback of the ringtone. Executes on the ringtone-thread.
1721ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon     */
1731ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon    private void handleStop() {
1741ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon        ThreadUtil.checkNotOnMainThread();
1751ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon        Log.i(this, "Stop ringtone.");
1761ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon
1771ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon        if (mRingtone != null) {
1781ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon            Log.d(this, "Ringtone.stop() invoked.");
1791ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon            mRingtone.stop();
1801ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon            mRingtone = null;
1811ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon        }
1821ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon
1831ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon        synchronized(this) {
184b21c5daa6ce683d7f6a1605a50a7ae05c520309fSantos Cordon            // At the time that STOP is handled, there should be no need for repeat messages in the
185b21c5daa6ce683d7f6a1605a50a7ae05c520309fSantos Cordon            // queue.
186b21c5daa6ce683d7f6a1605a50a7ae05c520309fSantos Cordon            mHandler.removeMessages(EVENT_REPEAT);
187b21c5daa6ce683d7f6a1605a50a7ae05c520309fSantos Cordon
1881ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon            if (mHandler.hasMessages(EVENT_PLAY)) {
189b21c5daa6ce683d7f6a1605a50a7ae05c520309fSantos Cordon                Log.v(this, "Keeping alive ringtone thread for subsequent play request.");
1901ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon            } else {
1911ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon                mHandler.removeMessages(EVENT_STOP);
1921ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon                mHandler.getLooper().quitSafely();
1931ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon                mHandler = null;
1941ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon                Log.v(this, "Handler cleared.");
1951ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon            }
1961ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon        }
1971ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon    }
1981ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon}
199