1b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson/*
2b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson * Copyright (C) 2011 The Android Open Source Project
3b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson *
4b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson * Licensed under the Apache License, Version 2.0 (the "License");
5b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson * you may not use this file except in compliance with the License.
6b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson * You may obtain a copy of the License at
7b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson *
8b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson *      http://www.apache.org/licenses/LICENSE-2.0
9b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson *
10b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson * Unless required by applicable law or agreed to in writing, software
11b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson * distributed under the License is distributed on an "AS IS" BASIS,
12b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson * See the License for the specific language governing permissions and
14b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson * limitations under the License.
15b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson */
16b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson
17b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudsonpackage com.android.contacts.voicemail;
18b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson
19a9ad6945588d3f81968f2462c78fcf1d36874b99Hugo Hudsonimport static com.android.contacts.CallDetailActivity.EXTRA_VOICEMAIL_START_PLAYBACK;
20a9ad6945588d3f81968f2462c78fcf1d36874b99Hugo Hudsonimport static com.android.contacts.CallDetailActivity.EXTRA_VOICEMAIL_URI;
21a9ad6945588d3f81968f2462c78fcf1d36874b99Hugo Hudson
226faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudsonimport android.app.Activity;
23b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudsonimport android.app.Fragment;
246faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudsonimport android.content.ContentResolver;
25b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudsonimport android.content.Context;
266faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudsonimport android.content.Intent;
276faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudsonimport android.database.ContentObserver;
286faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudsonimport android.database.Cursor;
29b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudsonimport android.media.AudioManager;
30b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudsonimport android.net.Uri;
31b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudsonimport android.os.Bundle;
32fd1b98ba83b1ef9b94329f32f0eacf7ddb31cf8bFlavio Lerdaimport android.os.PowerManager;
336faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudsonimport android.provider.VoicemailContract;
34757aa55194ba962c71feabe99d47b3021c3f294bHugo Hudsonimport android.util.Log;
35b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudsonimport android.view.LayoutInflater;
36b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudsonimport android.view.View;
37b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudsonimport android.view.ViewGroup;
38b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudsonimport android.widget.ImageButton;
39b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudsonimport android.widget.SeekBar;
40b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudsonimport android.widget.TextView;
41b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson
42e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Chengimport com.android.common.io.MoreCloseables;
43e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Chengimport com.android.contacts.ProximitySensorAware;
44e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Chengimport com.android.contacts.R;
45e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Chengimport com.android.contacts.util.AsyncTaskExecutors;
46e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Chengimport com.android.ex.variablespeed.MediaPlayerProxy;
47e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Chengimport com.android.ex.variablespeed.VariableSpeed;
48e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Chengimport com.google.common.base.Preconditions;
49e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Cheng
50b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudsonimport java.util.concurrent.ExecutorService;
51b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudsonimport java.util.concurrent.Executors;
52b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudsonimport java.util.concurrent.ScheduledExecutorService;
53d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudsonimport java.util.concurrent.TimeUnit;
54b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson
55d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudsonimport javax.annotation.concurrent.GuardedBy;
56b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudsonimport javax.annotation.concurrent.NotThreadSafe;
57b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson
58b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson/**
59b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson * Displays and plays back a single voicemail.
60b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson * <p>
61b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson * When the Activity containing this Fragment is created, voicemail playback
62b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson * will begin immediately. The Activity is expected to be started via an intent
63b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson * containing a suitable voicemail uri to playback.
64b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson * <p>
65b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson * This class is not thread-safe, it is thread-confined. All calls to all public
66b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson * methods on this class are expected to come from the main ui thread.
67b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson */
68b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson@NotThreadSafe
69b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudsonpublic class VoicemailPlaybackFragment extends Fragment {
70757aa55194ba962c71feabe99d47b3021c3f294bHugo Hudson    private static final String TAG = "VoicemailPlayback";
71b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson    private static final int NUMBER_OF_THREADS_IN_POOL = 2;
726faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson    private static final String[] HAS_CONTENT_PROJECTION = new String[] {
736faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        VoicemailContract.Voicemails.HAS_CONTENT,
746faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson    };
75b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson
76b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson    private VoicemailPlaybackPresenter mPresenter;
77b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson    private ScheduledExecutorService mScheduledExecutorService;
786faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson    private View mPlaybackLayout;
79b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson
80b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson    @Override
81b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson    public View onCreateView(LayoutInflater inflater, ViewGroup container,
82b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson            Bundle savedInstanceState) {
836faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        mPlaybackLayout = inflater.inflate(R.layout.playback_layout, null);
846faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        return mPlaybackLayout;
85b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson    }
86b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson
87b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson    @Override
88b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson    public void onActivityCreated(Bundle savedInstanceState) {
89b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        super.onActivityCreated(savedInstanceState);
90b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        mScheduledExecutorService = createScheduledExecutorService();
91a9ad6945588d3f81968f2462c78fcf1d36874b99Hugo Hudson        Bundle arguments = getArguments();
92a9ad6945588d3f81968f2462c78fcf1d36874b99Hugo Hudson        Preconditions.checkNotNull(arguments, "fragment must be started with arguments");
93a9ad6945588d3f81968f2462c78fcf1d36874b99Hugo Hudson        Uri voicemailUri = arguments.getParcelable(EXTRA_VOICEMAIL_URI);
94a9ad6945588d3f81968f2462c78fcf1d36874b99Hugo Hudson        Preconditions.checkNotNull(voicemailUri, "fragment must contain EXTRA_VOICEMAIL_URI");
95a9ad6945588d3f81968f2462c78fcf1d36874b99Hugo Hudson        boolean startPlayback = arguments.getBoolean(EXTRA_VOICEMAIL_START_PLAYBACK, false);
96fd1b98ba83b1ef9b94329f32f0eacf7ddb31cf8bFlavio Lerda        PowerManager powerManager =
97fd1b98ba83b1ef9b94329f32f0eacf7ddb31cf8bFlavio Lerda                (PowerManager) getActivity().getSystemService(Context.POWER_SERVICE);
98fd1b98ba83b1ef9b94329f32f0eacf7ddb31cf8bFlavio Lerda        PowerManager.WakeLock wakeLock =
99fd1b98ba83b1ef9b94329f32f0eacf7ddb31cf8bFlavio Lerda                powerManager.newWakeLock(
100fd1b98ba83b1ef9b94329f32f0eacf7ddb31cf8bFlavio Lerda                        PowerManager.SCREEN_DIM_WAKE_LOCK, getClass().getSimpleName());
1016faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        mPresenter = new VoicemailPlaybackPresenter(createPlaybackViewImpl(),
102a9ad6945588d3f81968f2462c78fcf1d36874b99Hugo Hudson                createMediaPlayer(mScheduledExecutorService), voicemailUri,
1039c747ac625d212a10176082fba6b087035f5d144Hugo Hudson                mScheduledExecutorService, startPlayback,
104fd1b98ba83b1ef9b94329f32f0eacf7ddb31cf8bFlavio Lerda                AsyncTaskExecutors.createAsyncTaskExecutor(), wakeLock);
105b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        mPresenter.onCreate(savedInstanceState);
106b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson    }
107b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson
108b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson    @Override
109b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson    public void onSaveInstanceState(Bundle outState) {
110b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        mPresenter.onSaveInstanceState(outState);
111b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        super.onSaveInstanceState(outState);
112b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson    }
113b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson
114b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson    @Override
115b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson    public void onDestroy() {
116b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        mPresenter.onDestroy();
117b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        mScheduledExecutorService.shutdown();
118b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        super.onDestroy();
119b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson    }
120b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson
121c06f21bd72611b335f4d34586f656225fb02f0fbHugo Hudson    @Override
122c06f21bd72611b335f4d34586f656225fb02f0fbHugo Hudson    public void onPause() {
123c06f21bd72611b335f4d34586f656225fb02f0fbHugo Hudson        mPresenter.onPause();
124c06f21bd72611b335f4d34586f656225fb02f0fbHugo Hudson        super.onPause();
125c06f21bd72611b335f4d34586f656225fb02f0fbHugo Hudson    }
126c06f21bd72611b335f4d34586f656225fb02f0fbHugo Hudson
1276faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson    private PlaybackViewImpl createPlaybackViewImpl() {
1286faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        return new PlaybackViewImpl(new ActivityReference(), getActivity().getApplicationContext(),
1296faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson                mPlaybackLayout);
1306faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson    }
1316faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson
132b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson    private MediaPlayerProxy createMediaPlayer(ExecutorService executorService) {
133b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        return VariableSpeed.createVariableSpeed(executorService);
134b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson    }
135b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson
136b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson    private ScheduledExecutorService createScheduledExecutorService() {
137b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        return Executors.newScheduledThreadPool(NUMBER_OF_THREADS_IN_POOL);
138b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson    }
139b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson
140b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson    /**
141b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson     * Formats a number of milliseconds as something that looks like {@code 00:05}.
142b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson     * <p>
143b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson     * We always use four digits, two for minutes two for seconds.  In the very unlikely event
144b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson     * that the voicemail duration exceeds 99 minutes, the display is capped at 99 minutes.
145b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson     */
1466faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson    private static String formatAsMinutesAndSeconds(int millis) {
147b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        int seconds = millis / 1000;
148b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        int minutes = seconds / 60;
149b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        seconds -= minutes * 60;
150b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        if (minutes > 99) {
151b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson            minutes = 99;
152b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        }
153b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        return String.format("%02d:%02d", minutes, seconds);
154b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson    }
155b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson
1566faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson    /**
1576faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson     * An object that can provide us with an Activity.
1586faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson     * <p>
1596faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson     * Fragments suffer the drawback that the Activity they belong to may sometimes be null. This
1606faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson     * can happen if the Fragment is detached, for example. In that situation a call to
1616faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson     * {@link Fragment#getString(int)} will throw and {@link IllegalStateException}. Also, calling
1626faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson     * {@link Fragment#getActivity()} is dangerous - it may sometimes return null. And thus blindly
1636faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson     * calling a method on the result of getActivity() is dangerous too.
1646faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson     * <p>
1656faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson     * To work around this, I have made the {@link PlaybackViewImpl} class static, so that it does
1666faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson     * not have access to any Fragment methods directly. Instead it uses an application Context for
1676faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson     * things like accessing strings, accessing system services. It only uses the Activity when it
1686faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson     * absolutely needs it - and does so through this class. This makes it easy to see where we have
1696faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson     * to check for null properly.
1706faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson     */
1716faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson    private final class ActivityReference {
1726faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        /** Gets this Fragment's Activity: <b>may be null</b>. */
1736faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        public final Activity get() {
1746faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson            return getActivity();
1756faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        }
176b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson    }
177b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson
178b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson    /**  Methods required by the PlaybackView for the VoicemailPlaybackPresenter. */
1796faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson    private static final class PlaybackViewImpl implements VoicemailPlaybackPresenter.PlaybackView {
1806faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        private final ActivityReference mActivityReference;
1816faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        private final Context mApplicationContext;
1826faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        private final SeekBar mPlaybackSeek;
1836faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        private final ImageButton mStartStopButton;
1846faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        private final ImageButton mPlaybackSpeakerphone;
1856faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        private final ImageButton mRateDecreaseButton;
1866faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        private final ImageButton mRateIncreaseButton;
1876faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        private final TextViewWithMessagesController mTextController;
1886faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson
1896faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        public PlaybackViewImpl(ActivityReference activityReference, Context applicationContext,
1906faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson                View playbackLayout) {
1916faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson            Preconditions.checkNotNull(activityReference);
1926faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson            Preconditions.checkNotNull(applicationContext);
1936faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson            Preconditions.checkNotNull(playbackLayout);
1946faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson            mActivityReference = activityReference;
1956faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson            mApplicationContext = applicationContext;
1966faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson            mPlaybackSeek = (SeekBar) playbackLayout.findViewById(R.id.playback_seek);
1976faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson            mStartStopButton = (ImageButton) playbackLayout.findViewById(
1986faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson                    R.id.playback_start_stop);
1996faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson            mPlaybackSpeakerphone = (ImageButton) playbackLayout.findViewById(
2006faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson                    R.id.playback_speakerphone);
2016faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson            mRateDecreaseButton = (ImageButton) playbackLayout.findViewById(
2026faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson                    R.id.rate_decrease_button);
2036faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson            mRateIncreaseButton = (ImageButton) playbackLayout.findViewById(
2046faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson                    R.id.rate_increase_button);
2056faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson            mTextController = new TextViewWithMessagesController(
2066faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson                    (TextView) playbackLayout.findViewById(R.id.playback_position_text),
2076faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson                    (TextView) playbackLayout.findViewById(R.id.playback_speed_text));
2086faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        }
2096faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson
210b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        @Override
211b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        public void finish() {
2126faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson            Activity activity = mActivityReference.get();
2136faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson            if (activity != null) {
2146faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson                activity.finish();
2156faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson            }
216b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        }
217b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson
218b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        @Override
219b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        public void runOnUiThread(Runnable runnable) {
2206faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson            Activity activity = mActivityReference.get();
2216faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson            if (activity != null) {
2226faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson                activity.runOnUiThread(runnable);
2236faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson            }
224b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        }
225b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson
226b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        @Override
227b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        public Context getDataSourceContext() {
2286faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson            return mApplicationContext;
229b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        }
230b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson
231b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        @Override
232b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        public void setRateDecreaseButtonListener(View.OnClickListener listener) {
233b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson            mRateDecreaseButton.setOnClickListener(listener);
234b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        }
235b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson
236b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        @Override
237b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        public void setRateIncreaseButtonListener(View.OnClickListener listener) {
238b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson            mRateIncreaseButton.setOnClickListener(listener);
239b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        }
240b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson
241b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        @Override
242b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        public void setStartStopListener(View.OnClickListener listener) {
243b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson            mStartStopButton.setOnClickListener(listener);
244b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        }
245b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson
246b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        @Override
247b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        public void setSpeakerphoneListener(View.OnClickListener listener) {
248b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson            mPlaybackSpeakerphone.setOnClickListener(listener);
249b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        }
250b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson
251b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        @Override
252d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson        public void setRateDisplay(float rate, int stringResourceId) {
253d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson            mTextController.setTemporaryText(
2546faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson                    mApplicationContext.getString(stringResourceId), 1, TimeUnit.SECONDS);
255b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        }
256b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson
257b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        @Override
258b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        public void setPositionSeekListener(SeekBar.OnSeekBarChangeListener listener) {
259b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson            mPlaybackSeek.setOnSeekBarChangeListener(listener);
260b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        }
261b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson
262b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        @Override
263b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        public void playbackStarted() {
26439fac71fa6420e4da324c13f141e6956f2837356Hugo Hudson            mStartStopButton.setImageResource(R.drawable.ic_hold_pause_holo_dark);
265b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        }
266b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson
267b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        @Override
268b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        public void playbackStopped() {
2695b32cd5d397675fff2189f6b1662695cc33a1348Flavio Lerda            mStartStopButton.setImageResource(R.drawable.ic_play);
270b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        }
271b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson
272b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        @Override
273fd1b98ba83b1ef9b94329f32f0eacf7ddb31cf8bFlavio Lerda        public void enableProximitySensor() {
274fd1b98ba83b1ef9b94329f32f0eacf7ddb31cf8bFlavio Lerda            // Only change the state if the activity is still around.
275fd1b98ba83b1ef9b94329f32f0eacf7ddb31cf8bFlavio Lerda            Activity activity = mActivityReference.get();
276fd1b98ba83b1ef9b94329f32f0eacf7ddb31cf8bFlavio Lerda            if (activity != null && activity instanceof ProximitySensorAware) {
277fd1b98ba83b1ef9b94329f32f0eacf7ddb31cf8bFlavio Lerda                ((ProximitySensorAware) activity).enableProximitySensor();
278fd1b98ba83b1ef9b94329f32f0eacf7ddb31cf8bFlavio Lerda            }
279fd1b98ba83b1ef9b94329f32f0eacf7ddb31cf8bFlavio Lerda        }
280fd1b98ba83b1ef9b94329f32f0eacf7ddb31cf8bFlavio Lerda
281fd1b98ba83b1ef9b94329f32f0eacf7ddb31cf8bFlavio Lerda        @Override
282fd1b98ba83b1ef9b94329f32f0eacf7ddb31cf8bFlavio Lerda        public void disableProximitySensor() {
283fd1b98ba83b1ef9b94329f32f0eacf7ddb31cf8bFlavio Lerda            // Only change the state if the activity is still around.
284fd1b98ba83b1ef9b94329f32f0eacf7ddb31cf8bFlavio Lerda            Activity activity = mActivityReference.get();
285fd1b98ba83b1ef9b94329f32f0eacf7ddb31cf8bFlavio Lerda            if (activity != null && activity instanceof ProximitySensorAware) {
286fd1b98ba83b1ef9b94329f32f0eacf7ddb31cf8bFlavio Lerda                ((ProximitySensorAware) activity).disableProximitySensor(true);
287fd1b98ba83b1ef9b94329f32f0eacf7ddb31cf8bFlavio Lerda            }
288fd1b98ba83b1ef9b94329f32f0eacf7ddb31cf8bFlavio Lerda        }
289fd1b98ba83b1ef9b94329f32f0eacf7ddb31cf8bFlavio Lerda
290fd1b98ba83b1ef9b94329f32f0eacf7ddb31cf8bFlavio Lerda        @Override
2916faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        public void registerContentObserver(Uri uri, ContentObserver observer) {
2926faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson            mApplicationContext.getContentResolver().registerContentObserver(uri, false, observer);
2936faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        }
2946faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson
2956faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        @Override
2966faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        public void unregisterContentObserver(ContentObserver observer) {
2976faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson            mApplicationContext.getContentResolver().unregisterContentObserver(observer);
2986faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        }
2996faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson
3006faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        @Override
301d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson        public void setClipPosition(int clipPositionInMillis, int clipLengthInMillis) {
302d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson            int seekBarPosition = Math.max(0, clipPositionInMillis);
303d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson            int seekBarMax = Math.max(seekBarPosition, clipLengthInMillis);
304d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson            if (mPlaybackSeek.getMax() != seekBarMax) {
305d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson                mPlaybackSeek.setMax(seekBarMax);
306d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson            }
307d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson            mPlaybackSeek.setProgress(seekBarPosition);
308d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson            mTextController.setPermanentText(
309d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson                    formatAsMinutesAndSeconds(seekBarMax - seekBarPosition));
310b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        }
311b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson
3126faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        private String getString(int resId) {
3136faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson            return mApplicationContext.getString(resId);
3146faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        }
3156faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson
316b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        @Override
31751ada3680de8700bc273a7e652886823f4c1981fHugo Hudson        public void setIsBuffering() {
3186faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson            disableUiElements();
3196faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson            mTextController.setPermanentText(getString(R.string.voicemail_buffering));
3206faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        }
3216faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson
3226faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        @Override
3236faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        public void setIsFetchingContent() {
3246faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson            disableUiElements();
3256faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson            mTextController.setPermanentText(getString(R.string.voicemail_fetching_content));
3266faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        }
3276faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson
3286faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        @Override
3296faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        public void setFetchContentTimeout() {
3306faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson            disableUiElements();
3316faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson            mTextController.setPermanentText(getString(R.string.voicemail_fetching_timout));
33251ada3680de8700bc273a7e652886823f4c1981fHugo Hudson        }
33351ada3680de8700bc273a7e652886823f4c1981fHugo Hudson
33451ada3680de8700bc273a7e652886823f4c1981fHugo Hudson        @Override
335b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        public int getDesiredClipPosition() {
336b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson            return mPlaybackSeek.getProgress();
337b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        }
338b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson
339b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        @Override
3406faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        public void disableUiElements() {
341757aa55194ba962c71feabe99d47b3021c3f294bHugo Hudson            mRateIncreaseButton.setEnabled(false);
342757aa55194ba962c71feabe99d47b3021c3f294bHugo Hudson            mRateDecreaseButton.setEnabled(false);
343b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson            mStartStopButton.setEnabled(false);
3446faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson            mPlaybackSpeakerphone.setEnabled(false);
345b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson            mPlaybackSeek.setProgress(0);
346b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson            mPlaybackSeek.setEnabled(false);
3476faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        }
3486faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson
3496faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        @Override
3506faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        public void playbackError(Exception e) {
3516faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson            disableUiElements();
35251ada3680de8700bc273a7e652886823f4c1981fHugo Hudson            mTextController.setPermanentText(getString(R.string.voicemail_playback_error));
353757aa55194ba962c71feabe99d47b3021c3f294bHugo Hudson            Log.e(TAG, "Could not play voicemail", e);
354b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        }
355b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson
356b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        @Override
3576faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        public void enableUiElements() {
3586faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson            mRateIncreaseButton.setEnabled(true);
3596faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson            mRateDecreaseButton.setEnabled(true);
3606faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson            mStartStopButton.setEnabled(true);
3616faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson            mPlaybackSpeakerphone.setEnabled(true);
3626faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson            mPlaybackSeek.setEnabled(true);
3636faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        }
3646faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson
3656faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        @Override
3666faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        public void sendFetchVoicemailRequest(Uri voicemailUri) {
3676faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson            Intent intent = new Intent(VoicemailContract.ACTION_FETCH_VOICEMAIL, voicemailUri);
3686faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson            mApplicationContext.sendBroadcast(intent);
3696faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        }
3706faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson
3716faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        @Override
3726faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        public boolean queryHasContent(Uri voicemailUri) {
3736faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson            ContentResolver contentResolver = mApplicationContext.getContentResolver();
3746faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson            Cursor cursor = contentResolver.query(
3756faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson                    voicemailUri, HAS_CONTENT_PROJECTION, null, null, null);
3766faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson            try {
3776faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson                if (cursor != null && cursor.moveToNext()) {
3786faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson                    return cursor.getInt(cursor.getColumnIndexOrThrow(
3796faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson                            VoicemailContract.Voicemails.HAS_CONTENT)) == 1;
3806faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson                }
3816faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson            } finally {
3826faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson                MoreCloseables.closeQuietly(cursor);
3836faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson            }
3846faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson            return false;
3856faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        }
3866faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson
3876faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        private AudioManager getAudioManager() {
3886faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson            return (AudioManager) mApplicationContext.getSystemService(Context.AUDIO_SERVICE);
3896faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        }
3906faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson
3916faccb8a006eb0c55338eebb34d9dc59af154b14Hugo Hudson        @Override
392b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        public boolean isSpeakerPhoneOn() {
393b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson            return getAudioManager().isSpeakerphoneOn();
394b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        }
395b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson
396b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        @Override
397b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        public void setSpeakerPhoneOn(boolean on) {
398b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson            getAudioManager().setSpeakerphoneOn(on);
399b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson            if (on) {
4002ca7a3830d3284d2736cf708a383efaa73c6d683Flavio Lerda                mPlaybackSpeakerphone.setImageResource(R.drawable.ic_speakerphone_on);
401b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson            } else {
4022ca7a3830d3284d2736cf708a383efaa73c6d683Flavio Lerda                mPlaybackSpeakerphone.setImageResource(R.drawable.ic_speakerphone_off);
403b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson            }
404b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson        }
405a62438c9a582dd729e00d4577e599674ba2d4672Flavio Lerda
406a62438c9a582dd729e00d4577e599674ba2d4672Flavio Lerda        @Override
407a62438c9a582dd729e00d4577e599674ba2d4672Flavio Lerda        public void setVolumeControlStream(int streamType) {
408a62438c9a582dd729e00d4577e599674ba2d4672Flavio Lerda            Activity activity = mActivityReference.get();
409a62438c9a582dd729e00d4577e599674ba2d4672Flavio Lerda            if (activity != null) {
410a62438c9a582dd729e00d4577e599674ba2d4672Flavio Lerda                activity.setVolumeControlStream(streamType);
411a62438c9a582dd729e00d4577e599674ba2d4672Flavio Lerda            }
412a62438c9a582dd729e00d4577e599674ba2d4672Flavio Lerda        }
413b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson    }
414d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson
415d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson    /**
416d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson     * Controls a TextView with dynamically changing text.
417d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson     * <p>
418d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson     * There are two methods here of interest,
419d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson     * {@link TextViewWithMessagesController#setPermanentText(String)} and
420d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson     * {@link TextViewWithMessagesController#setTemporaryText(String, long, TimeUnit)}.  The
421d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson     * former is used to set the text on the text view immediately, and is used in our case for
422d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson     * the countdown of duration remaining during voicemail playback.  The second is used to
423d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson     * temporarily replace this countdown with a message, in our case faster voicemail speed or
424d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson     * slower voicemail speed, before returning to the countdown display.
425d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson     * <p>
426d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson     * All the methods on this class must be called from the ui thread.
427d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson     */
428d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson    private static final class TextViewWithMessagesController {
42967519b221d8a644c40836c15a8ad0a32782fca1fHugo Hudson        private static final float VISIBLE = 1;
43067519b221d8a644c40836c15a8ad0a32782fca1fHugo Hudson        private static final float INVISIBLE = 0;
43167519b221d8a644c40836c15a8ad0a32782fca1fHugo Hudson        private static final long SHORT_ANIMATION_MS = 200;
43267519b221d8a644c40836c15a8ad0a32782fca1fHugo Hudson        private static final long LONG_ANIMATION_MS = 400;
433d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson        private final Object mLock = new Object();
43467519b221d8a644c40836c15a8ad0a32782fca1fHugo Hudson        private final TextView mPermanentTextView;
43567519b221d8a644c40836c15a8ad0a32782fca1fHugo Hudson        private final TextView mTemporaryTextView;
43667519b221d8a644c40836c15a8ad0a32782fca1fHugo Hudson        @GuardedBy("mLock") private Runnable mRunnable;
43767519b221d8a644c40836c15a8ad0a32782fca1fHugo Hudson
43867519b221d8a644c40836c15a8ad0a32782fca1fHugo Hudson        public TextViewWithMessagesController(TextView permanentTextView,
43967519b221d8a644c40836c15a8ad0a32782fca1fHugo Hudson                TextView temporaryTextView) {
44067519b221d8a644c40836c15a8ad0a32782fca1fHugo Hudson            mPermanentTextView = permanentTextView;
44167519b221d8a644c40836c15a8ad0a32782fca1fHugo Hudson            mTemporaryTextView = temporaryTextView;
442d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson        }
443d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson
444d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson        public void setPermanentText(String text) {
44567519b221d8a644c40836c15a8ad0a32782fca1fHugo Hudson            mPermanentTextView.setText(text);
446d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson        }
447d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson
448d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson        public void setTemporaryText(String text, long duration, TimeUnit units) {
449d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson            synchronized (mLock) {
45067519b221d8a644c40836c15a8ad0a32782fca1fHugo Hudson                mTemporaryTextView.setText(text);
45167519b221d8a644c40836c15a8ad0a32782fca1fHugo Hudson                mTemporaryTextView.animate().alpha(VISIBLE).setDuration(SHORT_ANIMATION_MS);
45267519b221d8a644c40836c15a8ad0a32782fca1fHugo Hudson                mPermanentTextView.animate().alpha(INVISIBLE).setDuration(SHORT_ANIMATION_MS);
453d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson                mRunnable = new Runnable() {
454d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson                    @Override
455d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson                    public void run() {
456d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson                        synchronized (mLock) {
457d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson                            // We check for (mRunnable == this) becuase if not true, then another
458d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson                            // setTemporaryText call has taken place in the meantime, and this
459d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson                            // one is now defunct and needs to take no action.
460d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson                            if (mRunnable == this) {
461d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson                                mRunnable = null;
46267519b221d8a644c40836c15a8ad0a32782fca1fHugo Hudson                                mTemporaryTextView.animate()
46367519b221d8a644c40836c15a8ad0a32782fca1fHugo Hudson                                        .alpha(INVISIBLE).setDuration(LONG_ANIMATION_MS);
46467519b221d8a644c40836c15a8ad0a32782fca1fHugo Hudson                                mPermanentTextView.animate()
46567519b221d8a644c40836c15a8ad0a32782fca1fHugo Hudson                                        .alpha(VISIBLE).setDuration(LONG_ANIMATION_MS);
466d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson                            }
467d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson                        }
468d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson                    }
469d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson                };
47067519b221d8a644c40836c15a8ad0a32782fca1fHugo Hudson                mTemporaryTextView.postDelayed(mRunnable, units.toMillis(duration));
471d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson            }
472d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson        }
473d1cac908706366a50bd3ffd71f90334bf812792fHugo Hudson    }
474b002f512acfd12b0f25c62579af0b7ecb02d728eHugo Hudson}
475