CallButtonFragment.java revision fa6d1c3cc9cc049a062d9308b4f4042df2ecfbab
1/*
2 * Copyright (C) 2013 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.incallui;
18
19import android.content.Context;
20import android.graphics.drawable.LayerDrawable;
21import android.media.AudioManager;
22import android.os.Bundle;
23import android.view.LayoutInflater;
24import android.view.Menu;
25import android.view.MenuItem;
26import android.view.View;
27import android.view.ViewGroup;
28import android.widget.CompoundButton;
29import android.widget.PopupMenu;
30import android.widget.PopupMenu.OnDismissListener;
31import android.widget.PopupMenu.OnMenuItemClickListener;
32import android.widget.ToggleButton;
33
34import com.android.services.telephony.common.AudioMode;
35
36/**
37 * Fragment for call control buttons
38 */
39public class CallButtonFragment extends BaseFragment<CallButtonPresenter>
40        implements CallButtonPresenter.CallButtonUi, OnMenuItemClickListener,
41                OnDismissListener {
42
43    private ToggleButton mMuteButton;
44    private ToggleButton mAudioButton;
45    private ToggleButton mHoldButton;
46    private ToggleButton mShowDialpadButton;
47
48    private PopupMenu mAudioModePopup;
49    private boolean mAudioModePopupVisible;
50    private View mEndCallButton;
51
52    @Override
53    CallButtonPresenter createPresenter() {
54        // TODO: find a cleaner way to include audio mode provider than
55        // having a singleton instance.
56        return new CallButtonPresenter(AudioModeProvider.getInstance());
57    }
58
59    @Override
60    public void onCreate(Bundle savedInstanceState) {
61        super.onCreate(savedInstanceState);
62    }
63
64    @Override
65    public View onCreateView(LayoutInflater inflater, ViewGroup container,
66            Bundle savedInstanceState) {
67        final View parent = inflater.inflate(R.layout.call_button_fragment, container, false);
68
69        mEndCallButton = parent.findViewById(R.id.endButton);
70        mEndCallButton.setOnClickListener(new View.OnClickListener() {
71            @Override
72            public void onClick(View v) {
73                getPresenter().endCallClicked();
74            }
75        });
76
77        mMuteButton = (ToggleButton) parent.findViewById(R.id.muteButton);
78        mMuteButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
79            @Override
80            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
81                getPresenter().muteClicked(isChecked);
82            }
83        });
84
85        mAudioButton = (ToggleButton) parent.findViewById(R.id.audioButton);
86        mAudioButton.setOnClickListener(new View.OnClickListener() {
87            @Override
88            public void onClick(View view) {
89                onAudioButtonClicked();
90            }
91        });
92
93        mHoldButton = (ToggleButton) parent.findViewById(R.id.holdButton);
94        mHoldButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
95            @Override
96            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
97                getPresenter().holdClicked(isChecked);
98            }
99        });
100
101        mShowDialpadButton = (ToggleButton) parent.findViewById(R.id.dialpadButton);
102        mShowDialpadButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
103            @Override
104            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
105                getPresenter().showDialpadClicked(isChecked);
106            }
107        });
108
109        return parent;
110    }
111
112    @Override
113    public void onViewCreated(View view, Bundle savedInstanceState) {
114        getPresenter().onUiReady(this);
115
116        // set the buttons
117        updateAudioButtons(getPresenter().getSupportedAudio());
118    }
119
120    @Override
121    public void onDestroyView() {
122        super.onDestroyView();
123        getPresenter().onUiUnready(this);
124    }
125
126    @Override
127    public void setVisible(boolean on) {
128        if (on) {
129            getView().setVisibility(View.VISIBLE);
130        } else {
131            getView().setVisibility(View.INVISIBLE);
132        }
133    }
134
135    @Override
136    public void setMute(boolean value) {
137        mMuteButton.setChecked(value);
138    }
139
140    @Override
141    public void setHold(boolean value) {
142        mHoldButton.setChecked(value);
143    }
144
145    @Override
146    public void showHold(boolean show) {
147        mHoldButton.setVisibility(show ? View.VISIBLE : View.GONE);
148    }
149
150    @Override
151    public void setAudio(int mode) {
152    }
153
154    @Override
155    public void setSupportedAudio(int modeMask) {
156        updateAudioButtons(modeMask);
157        refreshAudioModePopup();
158    }
159
160    @Override
161    public boolean onMenuItemClick(MenuItem item) {
162        Logger.d(this, "- onMenuItemClick: " + item);
163        Logger.d(this, "  id: " + item.getItemId());
164        Logger.d(this, "  title: '" + item.getTitle() + "'");
165
166        int mode = AudioMode.WIRED_OR_EARPIECE;
167
168        switch (item.getItemId()) {
169            case R.id.audio_mode_speaker:
170                mode = AudioMode.SPEAKER;
171                break;
172            case R.id.audio_mode_earpiece:
173            case R.id.audio_mode_wired_headset:
174                // InCallAudioMode.EARPIECE means either the handset earpiece,
175                // or the wired headset (if connected.)
176                mode = AudioMode.WIRED_OR_EARPIECE;
177                break;
178            case R.id.audio_mode_bluetooth:
179                mode = AudioMode.BLUETOOTH;
180                break;
181            default:
182                Logger.e(this, "onMenuItemClick:  unexpected View ID " + item.getItemId()
183                        + " (MenuItem = '" + item + "')");
184                break;
185        }
186
187        getPresenter().setAudioMode(mode);
188
189        return true;
190    }
191
192    // PopupMenu.OnDismissListener implementation; see showAudioModePopup().
193    // This gets called when the PopupMenu gets dismissed for *any* reason, like
194    // the user tapping outside its bounds, or pressing Back, or selecting one
195    // of the menu items.
196    @Override
197    public void onDismiss(PopupMenu menu) {
198        Logger.d(this, "- onDismiss: " + menu);
199        mAudioModePopupVisible = false;
200    }
201
202    /**
203     * Checks for supporting modes.  If bluetooth is supported, it uses the audio
204     * pop up menu.  Otherwise, it toggles the speakerphone.
205     */
206    private void onAudioButtonClicked() {
207        Logger.d(this, "onAudioButtonClicked: " +
208                AudioMode.toString(getPresenter().getSupportedAudio()));
209
210        if (isSupported(AudioMode.BLUETOOTH)) {
211            showAudioModePopup();
212        } else {
213            getPresenter().toggleSpeakerphone();
214        }
215    }
216
217    /**
218     * Refreshes the "Audio mode" popup if it's visible.  This is useful
219     * (for example) when a wired headset is plugged or unplugged,
220     * since we need to switch back and forth between the "earpiece"
221     * and "wired headset" items.
222     *
223     * This is safe to call even if the popup is already dismissed, or even if
224     * you never called showAudioModePopup() in the first place.
225     */
226    public void refreshAudioModePopup() {
227        if (mAudioModePopup != null && mAudioModePopupVisible) {
228            // Dismiss the previous one
229            mAudioModePopup.dismiss();  // safe even if already dismissed
230            // And bring up a fresh PopupMenu
231            showAudioModePopup();
232        }
233    }
234
235    /**
236     * Updates the audio button so that the appriopriate visual layers
237     * are visible based on the supported audio formats.
238     */
239    private void updateAudioButtons(int supportedModes) {
240        final boolean bluetoothSupported = isSupported(AudioMode.BLUETOOTH);
241        final boolean speakerSupported = isSupported(AudioMode.SPEAKER);
242
243        boolean audioButtonEnabled = false;
244        boolean audioButtonChecked = false;
245        boolean showMoreIndicator = false;
246
247        boolean showBluetoothIcon = false;
248        boolean showSpeakerphoneOnIcon = false;
249        boolean showSpeakerphoneOffIcon = false;
250        boolean showHandsetIcon = false;
251
252        boolean showToggleIndicator = false;
253
254        if (bluetoothSupported) {
255            Logger.d(this, "updateAudioButtons - popup menu mode");
256
257            audioButtonEnabled = true;
258            showMoreIndicator = true;
259            // The audio button is NOT a toggle in this state.  (And its
260            // setChecked() state is irrelevant since we completely hide the
261            // btn_compound_background layer anyway.)
262
263            // Update desired layers:
264            if (isAudio(AudioMode.BLUETOOTH)) {
265                showBluetoothIcon = true;
266            } else if (isAudio(AudioMode.SPEAKER)) {
267                showSpeakerphoneOnIcon = true;
268            } else {
269                showHandsetIcon = true;
270                // TODO: if a wired headset is plugged in, that takes precedence
271                // over the handset earpiece.  If so, maybe we should show some
272                // sort of "wired headset" icon here instead of the "handset
273                // earpiece" icon.  (Still need an asset for that, though.)
274            }
275        } else if (speakerSupported) {
276            Logger.d(this, "updateAudioButtons - speaker toggle mode");
277
278            audioButtonEnabled = true;
279
280            // The audio button *is* a toggle in this state, and indicated the
281            // current state of the speakerphone.
282            audioButtonChecked = isAudio(AudioMode.SPEAKER);
283
284            // update desired layers:
285            showToggleIndicator = true;
286
287            showSpeakerphoneOnIcon = isAudio(AudioMode.SPEAKER);
288            showSpeakerphoneOffIcon = !showSpeakerphoneOnIcon;
289        } else {
290            Logger.d(this, "updateAudioButtons - disabled...");
291
292            // The audio button is a toggle in this state, but that's mostly
293            // irrelevant since it's always disabled and unchecked.
294            audioButtonEnabled = false;
295            audioButtonChecked = false;
296
297            // update desired layers:
298            showToggleIndicator = true;
299            showSpeakerphoneOffIcon = true;
300        }
301
302        // Finally, update it all!
303
304        Logger.v(this, "audioButtonEnabled: " + audioButtonEnabled);
305        Logger.v(this, "audioButtonChecked: " + audioButtonChecked);
306        Logger.v(this, "showMoreIndicator: " + showMoreIndicator);
307        Logger.v(this, "showBluetoothIcon: " + showBluetoothIcon);
308        Logger.v(this, "showSpeakerphoneOnIcon: " + showSpeakerphoneOnIcon);
309        Logger.v(this, "showSpeakerphoneOffIcon: " + showSpeakerphoneOffIcon);
310        Logger.v(this, "showHandsetIcon: " + showHandsetIcon);
311
312        // Constants for Drawable.setAlpha()
313        final int HIDDEN = 0;
314        final int VISIBLE = 255;
315
316        mAudioButton.setEnabled(audioButtonEnabled);
317        mAudioButton.setChecked(audioButtonChecked);
318
319        final LayerDrawable layers = (LayerDrawable) mAudioButton.getBackground();
320        Logger.d(this, "'layers' drawable: " + layers);
321
322        layers.findDrawableByLayerId(R.id.compoundBackgroundItem)
323                .setAlpha(showToggleIndicator ? VISIBLE : HIDDEN);
324
325        layers.findDrawableByLayerId(R.id.moreIndicatorItem)
326                .setAlpha(showMoreIndicator ? VISIBLE : HIDDEN);
327
328        layers.findDrawableByLayerId(R.id.bluetoothItem)
329                .setAlpha(showBluetoothIcon ? VISIBLE : HIDDEN);
330
331        layers.findDrawableByLayerId(R.id.handsetItem)
332                .setAlpha(showHandsetIcon ? VISIBLE : HIDDEN);
333
334        layers.findDrawableByLayerId(R.id.speakerphoneOnItem)
335                .setAlpha(showSpeakerphoneOnIcon ? VISIBLE : HIDDEN);
336
337        layers.findDrawableByLayerId(R.id.speakerphoneOffItem)
338                .setAlpha(showSpeakerphoneOffIcon ? VISIBLE : HIDDEN);
339    }
340
341    private void showAudioModePopup() {
342        Logger.d(this, "showAudioPopup()...");
343
344        mAudioModePopup = new PopupMenu(getView().getContext(), mAudioButton /* anchorView */);
345        mAudioModePopup.getMenuInflater().inflate(R.menu.incall_audio_mode_menu,
346                mAudioModePopup.getMenu());
347        mAudioModePopup.setOnMenuItemClickListener(this);
348        mAudioModePopup.setOnDismissListener(this);
349
350        final Menu menu = mAudioModePopup.getMenu();
351
352        // TODO: Still need to have the "currently active" audio mode come
353        // up pre-selected (or focused?) with a blue highlight.  Still
354        // need exact visual design, and possibly framework support for this.
355        // See comments below for the exact logic.
356
357        final MenuItem speakerItem = menu.findItem(R.id.audio_mode_speaker);
358        speakerItem.setEnabled(isSupported(AudioMode.SPEAKER));
359        // TODO: Show speakerItem as initially "selected" if
360        // speaker is on.
361
362        // We display *either* "earpiece" or "wired headset", never both,
363        // depending on whether a wired headset is physically plugged in.
364        final MenuItem earpieceItem = menu.findItem(R.id.audio_mode_earpiece);
365        final MenuItem wiredHeadsetItem = menu.findItem(R.id.audio_mode_wired_headset);
366
367        final boolean usingHeadset = isSupported(AudioMode.WIRED_HEADSET);
368        earpieceItem.setVisible(!usingHeadset);
369        earpieceItem.setEnabled(!usingHeadset);
370        wiredHeadsetItem.setVisible(usingHeadset);
371        wiredHeadsetItem.setEnabled(usingHeadset);
372        // TODO: Show the above item (either earpieceItem or wiredHeadsetItem)
373        // as initially "selected" if speakerOn and
374        // bluetoothIndicatorOn are both false.
375
376        final MenuItem bluetoothItem = menu.findItem(R.id.audio_mode_bluetooth);
377        bluetoothItem.setEnabled(isSupported(AudioMode.BLUETOOTH));
378        // TODO: Show bluetoothItem as initially "selected" if
379        // bluetoothIndicatorOn is true.
380
381        mAudioModePopup.show();
382
383        // Unfortunately we need to manually keep track of the popup menu's
384        // visiblity, since PopupMenu doesn't have an isShowing() method like
385        // Dialogs do.
386        mAudioModePopupVisible = true;
387    }
388
389    private boolean isSupported(int mode) {
390        return (mode == (getPresenter().getSupportedAudio() & mode));
391    }
392
393    private boolean isAudio(int mode) {
394        return (mode == getPresenter().getAudioMode());
395    }
396
397    @Override
398    public void displayDialpad(boolean value) {
399        mShowDialpadButton.setChecked(value);
400        if (getActivity() != null && getActivity() instanceof InCallActivity) {
401            ((InCallActivity) getActivity()).displayDialpad(value);
402        }
403    }
404}
405