CallButtonFragment.java revision c1d4aef578dafd4ed130e664ea0fe025e25f5631
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 static com.android.incallui.CallButtonFragment.Buttons.*;
20
21import android.app.AlertDialog;
22import android.content.Context;
23import android.content.DialogInterface;
24import android.content.res.ColorStateList;
25import android.content.res.Resources;
26import android.graphics.drawable.Drawable;
27import android.graphics.drawable.LayerDrawable;
28import android.graphics.drawable.GradientDrawable;
29import android.graphics.drawable.RippleDrawable;
30import android.graphics.drawable.StateListDrawable;
31import android.os.Bundle;
32import android.telecom.AudioState;
33import android.telecom.TelecomManager;
34import android.telecom.VideoProfile;
35import android.util.SparseIntArray;
36import android.view.ContextThemeWrapper;
37import android.view.HapticFeedbackConstants;
38import android.view.LayoutInflater;
39import android.view.Menu;
40import android.view.MenuItem;
41import android.view.View;
42import android.view.ViewGroup;
43import android.widget.CompoundButton;
44import android.widget.ImageButton;
45import android.widget.PopupMenu;
46import android.widget.Toast;
47import android.widget.PopupMenu.OnDismissListener;
48import android.widget.PopupMenu.OnMenuItemClickListener;
49
50import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette;
51import java.util.ArrayList;
52
53/**
54 * Fragment for call control buttons
55 */
56public class CallButtonFragment
57        extends BaseFragment<CallButtonPresenter, CallButtonPresenter.CallButtonUi>
58        implements CallButtonPresenter.CallButtonUi, OnMenuItemClickListener, OnDismissListener,
59        View.OnClickListener {
60    private static final int INVALID_INDEX = -1;
61    private static final int BUTTON_MAX_VISIBLE = 5;
62    // The button is currently visible in the UI
63    private static final int BUTTON_VISIBLE = 1;
64    // The button is hidden in the UI
65    private static final int BUTTON_HIDDEN = 2;
66    // The button has been collapsed into the overflow menu
67    private static final int BUTTON_MENU = 3;
68
69    public interface Buttons {
70        public static final int BUTTON_AUDIO = 0;
71        public static final int BUTTON_MUTE = 1;
72        public static final int BUTTON_DIALPAD = 2;
73        public static final int BUTTON_HOLD = 3;
74        public static final int BUTTON_SWAP = 4;
75        public static final int BUTTON_UPGRADE_TO_VIDEO = 5;
76        public static final int BUTTON_SWITCH_CAMERA = 6;
77        public static final int BUTTON_ADD_CALL = 7;
78        public static final int BUTTON_MERGE = 8;
79        public static final int BUTTON_PAUSE_VIDEO = 9;
80        public static final int BUTTON_MANAGE_VIDEO_CONFERENCE = 10;
81        public static final int BUTTON_COUNT = 11;
82    }
83
84    private SparseIntArray mButtonVisibilityMap = new SparseIntArray(BUTTON_COUNT);
85
86    private CompoundButton mAudioButton;
87    private CompoundButton mMuteButton;
88    private CompoundButton mShowDialpadButton;
89    private CompoundButton mHoldButton;
90    private ImageButton mSwapButton;
91    private ImageButton mChangeToVideoButton;
92    private CompoundButton mSwitchCameraButton;
93    private ImageButton mAddCallButton;
94    private ImageButton mMergeButton;
95    private CompoundButton mPauseVideoButton;
96    private ImageButton mOverflowButton;
97    private ImageButton mManageVideoCallConferenceButton;
98
99    private PopupMenu mAudioModePopup;
100    private boolean mAudioModePopupVisible;
101    private PopupMenu mOverflowPopup;
102
103    private int mPrevAudioMode = 0;
104
105    // Constants for Drawable.setAlpha()
106    private static final int HIDDEN = 0;
107    private static final int VISIBLE = 255;
108
109    private boolean mIsEnabled;
110    private MaterialPalette mCurrentThemeColors;
111
112    @Override
113    CallButtonPresenter createPresenter() {
114        // TODO: find a cleaner way to include audio mode provider than having a singleton instance.
115        return new CallButtonPresenter();
116    }
117
118    @Override
119    CallButtonPresenter.CallButtonUi getUi() {
120        return this;
121    }
122
123    @Override
124    public void onCreate(Bundle savedInstanceState) {
125        super.onCreate(savedInstanceState);
126
127        for (int i = 0; i < BUTTON_COUNT; i++) {
128            mButtonVisibilityMap.put(i, BUTTON_HIDDEN);
129        }
130    }
131
132    @Override
133    public View onCreateView(LayoutInflater inflater, ViewGroup container,
134            Bundle savedInstanceState) {
135        final View parent = inflater.inflate(R.layout.call_button_fragment, container, false);
136
137        mAudioButton = (CompoundButton) parent.findViewById(R.id.audioButton);
138        mAudioButton.setOnClickListener(this);
139        mMuteButton = (CompoundButton) parent.findViewById(R.id.muteButton);
140        mMuteButton.setOnClickListener(this);
141        mShowDialpadButton = (CompoundButton) parent.findViewById(R.id.dialpadButton);
142        mShowDialpadButton.setOnClickListener(this);
143        mHoldButton = (CompoundButton) parent.findViewById(R.id.holdButton);
144        mHoldButton.setOnClickListener(this);
145        mSwapButton = (ImageButton) parent.findViewById(R.id.swapButton);
146        mSwapButton.setOnClickListener(this);
147        mChangeToVideoButton = (ImageButton) parent.findViewById(R.id.changeToVideoButton);
148        mChangeToVideoButton.setOnClickListener(this);
149        mSwitchCameraButton = (CompoundButton) parent.findViewById(R.id.switchCameraButton);
150        mSwitchCameraButton.setOnClickListener(this);
151        mAddCallButton = (ImageButton) parent.findViewById(R.id.addButton);
152        mAddCallButton.setOnClickListener(this);
153        mMergeButton = (ImageButton) parent.findViewById(R.id.mergeButton);
154        mMergeButton.setOnClickListener(this);
155        mPauseVideoButton = (CompoundButton) parent.findViewById(R.id.pauseVideoButton);
156        mPauseVideoButton.setOnClickListener(this);
157        mOverflowButton = (ImageButton) parent.findViewById(R.id.overflowButton);
158        mOverflowButton.setOnClickListener(this);
159        mManageVideoCallConferenceButton = (ImageButton) parent.findViewById(
160            R.id.manageVideoCallConferenceButton);
161        mManageVideoCallConferenceButton.setOnClickListener(this);
162        return parent;
163    }
164
165    @Override
166    public void onActivityCreated(Bundle savedInstanceState) {
167        super.onActivityCreated(savedInstanceState);
168
169        // set the buttons
170        updateAudioButtons(getPresenter().getSupportedAudio());
171    }
172
173    @Override
174    public void onResume() {
175        if (getPresenter() != null) {
176            getPresenter().refreshMuteState();
177        }
178        super.onResume();
179
180        updateColors();
181    }
182
183    @Override
184    public void onClick(View view) {
185        int id = view.getId();
186        Log.d(this, "onClick(View " + view + ", id " + id + ")...");
187
188        switch(id) {
189            case R.id.audioButton:
190                onAudioButtonClicked();
191                break;
192            case R.id.addButton:
193                getPresenter().addCallClicked();
194                break;
195            case R.id.muteButton: {
196                getPresenter().muteClicked(!mMuteButton.isSelected());
197                break;
198            }
199            case R.id.mergeButton:
200                getPresenter().mergeClicked();
201                mMergeButton.setEnabled(false);
202                break;
203            case R.id.holdButton: {
204                getPresenter().holdClicked(!mHoldButton.isSelected());
205                break;
206            }
207            case R.id.swapButton:
208                getPresenter().swapClicked();
209                break;
210            case R.id.dialpadButton:
211                getPresenter().showDialpadClicked(!mShowDialpadButton.isSelected());
212                break;
213            case R.id.changeToVideoButton:
214                getPresenter().changeToVideoClicked();
215                break;
216            case R.id.switchCameraButton:
217                getPresenter().switchCameraClicked(
218                        mSwitchCameraButton.isSelected() /* useFrontFacingCamera */);
219                break;
220            case R.id.pauseVideoButton:
221                getPresenter().pauseVideoClicked(
222                        !mPauseVideoButton.isSelected() /* pause */);
223                break;
224            case R.id.overflowButton:
225                if (mOverflowPopup != null) {
226                    mOverflowPopup.show();
227                }
228                break;
229            case R.id.manageVideoCallConferenceButton:
230                onManageVideoCallConferenceClicked();
231                break;
232            default:
233                Log.wtf(this, "onClick: unexpected");
234                return;
235        }
236
237        view.performHapticFeedback(
238                HapticFeedbackConstants.VIRTUAL_KEY,
239                HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
240    }
241
242    public void updateColors() {
243        MaterialPalette themeColors = InCallPresenter.getInstance().getThemeColors();
244
245        if (mCurrentThemeColors != null && mCurrentThemeColors.equals(themeColors)) {
246            return;
247        }
248
249        View[] compoundButtons = {
250                mAudioButton,
251                mMuteButton,
252                mShowDialpadButton,
253                mHoldButton,
254                mSwitchCameraButton,
255                mPauseVideoButton
256        };
257
258        for (View button : compoundButtons) {
259            final LayerDrawable layers = (LayerDrawable) button.getBackground();
260            final RippleDrawable btnCompoundDrawable = compoundBackgroundDrawable(themeColors);
261            layers.setDrawableByLayerId(R.id.compoundBackgroundItem, btnCompoundDrawable);
262        }
263
264        ImageButton[] normalButtons = {
265            mSwapButton,
266            mChangeToVideoButton,
267            mAddCallButton,
268            mMergeButton,
269            mOverflowButton
270        };
271
272        for (ImageButton button : normalButtons) {
273            final LayerDrawable layers = (LayerDrawable) button.getBackground();
274            final RippleDrawable btnDrawable = backgroundDrawable(themeColors);
275            layers.setDrawableByLayerId(R.id.backgroundItem, btnDrawable);
276        }
277
278        mCurrentThemeColors = themeColors;
279    }
280
281    /**
282     * Generate a RippleDrawable which will be the background for a compound button, i.e.
283     * a button with pressed and unpressed states. The unpressed state will be the same color
284     * as the rest of the call card, the pressed state will be the dark version of that color.
285     */
286    private RippleDrawable compoundBackgroundDrawable(MaterialPalette palette) {
287        Resources res = getResources();
288        ColorStateList rippleColor =
289                ColorStateList.valueOf(res.getColor(R.color.incall_accent_color));
290
291        StateListDrawable stateListDrawable = new StateListDrawable();
292        addSelectedAndFocused(res, stateListDrawable);
293        addFocused(res, stateListDrawable);
294        addSelected(res, stateListDrawable, palette);
295        addUnselected(res, stateListDrawable, palette);
296
297        return new RippleDrawable(rippleColor, stateListDrawable, null);
298    }
299
300    /**
301     * Generate a RippleDrawable which will be the background of a button to ensure it
302     * is the same color as the rest of the call card.
303     */
304    private RippleDrawable backgroundDrawable(MaterialPalette palette) {
305        Resources res = getResources();
306        ColorStateList rippleColor =
307                ColorStateList.valueOf(res.getColor(R.color.incall_accent_color));
308
309        StateListDrawable stateListDrawable = new StateListDrawable();
310        addFocused(res, stateListDrawable);
311        addUnselected(res, stateListDrawable, palette);
312
313        return new RippleDrawable(rippleColor, stateListDrawable, null);
314    }
315
316    // state_selected and state_focused
317    private void addSelectedAndFocused(Resources res, StateListDrawable drawable) {
318        int[] selectedAndFocused = {android.R.attr.state_selected, android.R.attr.state_focused};
319        Drawable selectedAndFocusedDrawable = res.getDrawable(R.drawable.btn_selected_focused);
320        drawable.addState(selectedAndFocused, selectedAndFocusedDrawable);
321    }
322
323    // state_focused
324    private void addFocused(Resources res, StateListDrawable drawable) {
325        int[] focused = {android.R.attr.state_focused};
326        Drawable focusedDrawable = res.getDrawable(R.drawable.btn_unselected_focused);
327        drawable.addState(focused, focusedDrawable);
328    }
329
330    // state_selected
331    private void addSelected(Resources res, StateListDrawable drawable, MaterialPalette palette) {
332        int[] selected = {android.R.attr.state_selected};
333        LayerDrawable selectedDrawable = (LayerDrawable) res.getDrawable(R.drawable.btn_selected);
334        ((GradientDrawable) selectedDrawable.getDrawable(0)).setColor(palette.mSecondaryColor);
335        drawable.addState(selected, selectedDrawable);
336    }
337
338    // default
339    private void addUnselected(Resources res, StateListDrawable drawable, MaterialPalette palette) {
340        LayerDrawable unselectedDrawable =
341                (LayerDrawable) res.getDrawable(R.drawable.btn_unselected);
342        ((GradientDrawable) unselectedDrawable.getDrawable(0)).setColor(palette.mPrimaryColor);
343        drawable.addState(new int[0], unselectedDrawable);
344    }
345
346    @Override
347    public void setEnabled(boolean isEnabled) {
348        mIsEnabled = isEnabled;
349        View view = getView();
350        if (view.getVisibility() != View.VISIBLE) {
351            view.setVisibility(View.VISIBLE);
352        }
353
354        mAudioButton.setEnabled(isEnabled);
355        mMuteButton.setEnabled(isEnabled);
356        mShowDialpadButton.setEnabled(isEnabled);
357        mHoldButton.setEnabled(isEnabled);
358        mSwapButton.setEnabled(isEnabled);
359        mChangeToVideoButton.setEnabled(isEnabled);
360        mSwitchCameraButton.setEnabled(isEnabled);
361        mAddCallButton.setEnabled(isEnabled);
362        mMergeButton.setEnabled(isEnabled);
363        mPauseVideoButton.setEnabled(isEnabled);
364        mOverflowButton.setEnabled(isEnabled);
365        mManageVideoCallConferenceButton.setEnabled(isEnabled);
366    }
367
368    @Override
369    public void showButton(int buttonId, boolean show) {
370        mButtonVisibilityMap.put(buttonId, show ? BUTTON_VISIBLE : BUTTON_HIDDEN);
371    }
372
373    @Override
374    public void enableButton(int buttonId, boolean enable) {
375        final View button = getButtonById(buttonId);
376        if (button != null) {
377            button.setEnabled(enable);
378        }
379    }
380
381    private View getButtonById(int id) {
382        switch (id) {
383            case BUTTON_AUDIO:
384                return mAudioButton;
385            case BUTTON_MUTE:
386                return mMuteButton;
387            case BUTTON_DIALPAD:
388                return mShowDialpadButton;
389            case BUTTON_HOLD:
390                return mHoldButton;
391            case BUTTON_SWAP:
392                return mSwapButton;
393            case BUTTON_UPGRADE_TO_VIDEO:
394                return mChangeToVideoButton;
395            case BUTTON_SWITCH_CAMERA:
396                return mSwitchCameraButton;
397            case BUTTON_ADD_CALL:
398                return mAddCallButton;
399            case BUTTON_MERGE:
400                return mMergeButton;
401            case BUTTON_PAUSE_VIDEO:
402                return mPauseVideoButton;
403            case BUTTON_MANAGE_VIDEO_CONFERENCE:
404                return mManageVideoCallConferenceButton;
405            default:
406                Log.w(this, "Invalid button id");
407                return null;
408        }
409    }
410
411    @Override
412    public void setHold(boolean value) {
413        if (mHoldButton.isSelected() != value) {
414            mHoldButton.setSelected(value);
415            mHoldButton.setContentDescription(getContext().getString(
416                    value ? R.string.onscreenHoldText_selected
417                            : R.string.onscreenHoldText_unselected));
418        }
419    }
420
421    @Override
422    public void setCameraSwitched(boolean isBackFacingCamera) {
423        mSwitchCameraButton.setSelected(isBackFacingCamera);
424    }
425
426    @Override
427    public void setVideoPaused(boolean isPaused) {
428        mPauseVideoButton.setSelected(isPaused);
429    }
430
431    @Override
432    public void setMute(boolean value) {
433        if (mMuteButton.isSelected() != value) {
434            mMuteButton.setSelected(value);
435        }
436    }
437
438    private void addToOverflowMenu(int id, View button, PopupMenu menu) {
439        button.setVisibility(View.GONE);
440        menu.getMenu().add(Menu.NONE, id, Menu.NONE, button.getContentDescription());
441        mButtonVisibilityMap.put(id, BUTTON_MENU);
442    }
443
444    private PopupMenu getPopupMenu() {
445        return new PopupMenu(new ContextThemeWrapper(getActivity(), R.style.InCallPopupMenuStyle),
446                mOverflowButton);
447    }
448
449    /**
450     * Iterates through the list of buttons and toggles their visibility depending on the
451     * setting configured by the CallButtonPresenter. If there are more visible buttons than
452     * the allowed maximum, the excess buttons are collapsed into a single overflow menu.
453     */
454    @Override
455    public void updateButtonStates() {
456        View prevVisibleButton = null;
457        int prevVisibleId = -1;
458        PopupMenu menu = null;
459        int visibleCount = 0;
460        for (int i = 0; i < BUTTON_COUNT; i++) {
461            final int visibility = mButtonVisibilityMap.get(i);
462            final View button = getButtonById(i);
463            if (visibility == BUTTON_VISIBLE) {
464                visibleCount++;
465                if (visibleCount <= BUTTON_MAX_VISIBLE) {
466                    button.setVisibility(View.VISIBLE);
467                    prevVisibleButton = button;
468                    prevVisibleId = i;
469                } else {
470                    if (menu == null) {
471                        menu = getPopupMenu();
472                    }
473                    // Collapse the current button into the overflow menu. If is the first visible
474                    // button that exceeds the threshold, also collapse the previous visible button
475                    // so that the total number of visible buttons will never exceed the threshold.
476                    if (prevVisibleButton != null) {
477                        addToOverflowMenu(prevVisibleId, prevVisibleButton, menu);
478                        prevVisibleButton = null;
479                        prevVisibleId = -1;
480                    }
481                    addToOverflowMenu(i, button, menu);
482                }
483            } else if (visibility == BUTTON_HIDDEN){
484                button.setVisibility(View.GONE);
485            }
486        }
487
488        mOverflowButton.setVisibility(menu != null ? View.VISIBLE : View.GONE);
489        if (menu != null) {
490            mOverflowPopup = menu;
491            mOverflowPopup.setOnMenuItemClickListener(new OnMenuItemClickListener() {
492                @Override
493                public boolean onMenuItemClick(MenuItem item) {
494                    final int id = item.getItemId();
495                    getButtonById(id).performClick();
496                    return true;
497                }
498            });
499        }
500    }
501
502    @Override
503    public void setAudio(int mode) {
504        updateAudioButtons(getPresenter().getSupportedAudio());
505        refreshAudioModePopup();
506
507        if (mPrevAudioMode != mode) {
508            updateAudioButtonContentDescription(mode);
509            mPrevAudioMode = mode;
510        }
511    }
512
513    @Override
514    public void setSupportedAudio(int modeMask) {
515        updateAudioButtons(modeMask);
516        refreshAudioModePopup();
517    }
518
519    @Override
520    public boolean onMenuItemClick(MenuItem item) {
521        Log.d(this, "- onMenuItemClick: " + item);
522        Log.d(this, "  id: " + item.getItemId());
523        Log.d(this, "  title: '" + item.getTitle() + "'");
524
525        int mode = AudioState.ROUTE_WIRED_OR_EARPIECE;
526
527        switch (item.getItemId()) {
528            case R.id.audio_mode_speaker:
529                mode = AudioState.ROUTE_SPEAKER;
530                break;
531            case R.id.audio_mode_earpiece:
532            case R.id.audio_mode_wired_headset:
533                // InCallAudioState.ROUTE_EARPIECE means either the handset earpiece,
534                // or the wired headset (if connected.)
535                mode = AudioState.ROUTE_WIRED_OR_EARPIECE;
536                break;
537            case R.id.audio_mode_bluetooth:
538                mode = AudioState.ROUTE_BLUETOOTH;
539                break;
540            default:
541                Log.e(this, "onMenuItemClick:  unexpected View ID " + item.getItemId()
542                        + " (MenuItem = '" + item + "')");
543                break;
544        }
545
546        getPresenter().setAudioMode(mode);
547
548        return true;
549    }
550
551    // PopupMenu.OnDismissListener implementation; see showAudioModePopup().
552    // This gets called when the PopupMenu gets dismissed for *any* reason, like
553    // the user tapping outside its bounds, or pressing Back, or selecting one
554    // of the menu items.
555    @Override
556    public void onDismiss(PopupMenu menu) {
557        Log.d(this, "- onDismiss: " + menu);
558        mAudioModePopupVisible = false;
559        updateAudioButtons(getPresenter().getSupportedAudio());
560    }
561
562    /**
563     * Checks for supporting modes.  If bluetooth is supported, it uses the audio
564     * pop up menu.  Otherwise, it toggles the speakerphone.
565     */
566    private void onAudioButtonClicked() {
567        Log.d(this, "onAudioButtonClicked: " +
568                AudioState.audioRouteToString(getPresenter().getSupportedAudio()));
569
570        if (isSupported(AudioState.ROUTE_BLUETOOTH)) {
571            showAudioModePopup();
572        } else {
573            getPresenter().toggleSpeakerphone();
574        }
575    }
576
577    private void onManageVideoCallConferenceClicked() {
578        Log.d(this, "onManageVideoCallConferenceClicked");
579        InCallPresenter.getInstance().showConferenceCallManager(true);
580    }
581
582    /**
583     * Refreshes the "Audio mode" popup if it's visible.  This is useful
584     * (for example) when a wired headset is plugged or unplugged,
585     * since we need to switch back and forth between the "earpiece"
586     * and "wired headset" items.
587     *
588     * This is safe to call even if the popup is already dismissed, or even if
589     * you never called showAudioModePopup() in the first place.
590     */
591    public void refreshAudioModePopup() {
592        if (mAudioModePopup != null && mAudioModePopupVisible) {
593            // Dismiss the previous one
594            mAudioModePopup.dismiss();  // safe even if already dismissed
595            // And bring up a fresh PopupMenu
596            showAudioModePopup();
597        }
598    }
599
600    /**
601     * Updates the audio button so that the appriopriate visual layers
602     * are visible based on the supported audio formats.
603     */
604    private void updateAudioButtons(int supportedModes) {
605        final boolean bluetoothSupported = isSupported(AudioState.ROUTE_BLUETOOTH);
606        final boolean speakerSupported = isSupported(AudioState.ROUTE_SPEAKER);
607
608        boolean audioButtonEnabled = false;
609        boolean audioButtonChecked = false;
610        boolean showMoreIndicator = false;
611
612        boolean showBluetoothIcon = false;
613        boolean showSpeakerphoneIcon = false;
614        boolean showHandsetIcon = false;
615
616        boolean showToggleIndicator = false;
617
618        if (bluetoothSupported) {
619            Log.d(this, "updateAudioButtons - popup menu mode");
620
621            audioButtonEnabled = true;
622            audioButtonChecked = true;
623            showMoreIndicator = true;
624
625            // Update desired layers:
626            if (isAudio(AudioState.ROUTE_BLUETOOTH)) {
627                showBluetoothIcon = true;
628            } else if (isAudio(AudioState.ROUTE_SPEAKER)) {
629                showSpeakerphoneIcon = true;
630            } else {
631                showHandsetIcon = true;
632                // TODO: if a wired headset is plugged in, that takes precedence
633                // over the handset earpiece.  If so, maybe we should show some
634                // sort of "wired headset" icon here instead of the "handset
635                // earpiece" icon.  (Still need an asset for that, though.)
636            }
637
638            // The audio button is NOT a toggle in this state, so set selected to false.
639            mAudioButton.setSelected(false);
640        } else if (speakerSupported) {
641            Log.d(this, "updateAudioButtons - speaker toggle mode");
642
643            audioButtonEnabled = true;
644
645            // The audio button *is* a toggle in this state, and indicated the
646            // current state of the speakerphone.
647            audioButtonChecked = isAudio(AudioState.ROUTE_SPEAKER);
648            mAudioButton.setSelected(audioButtonChecked);
649
650            // update desired layers:
651            showToggleIndicator = true;
652            showSpeakerphoneIcon = true;
653        } else {
654            Log.d(this, "updateAudioButtons - disabled...");
655
656            // The audio button is a toggle in this state, but that's mostly
657            // irrelevant since it's always disabled and unchecked.
658            audioButtonEnabled = false;
659            audioButtonChecked = false;
660            mAudioButton.setSelected(false);
661
662            // update desired layers:
663            showToggleIndicator = true;
664            showSpeakerphoneIcon = true;
665        }
666
667        // Finally, update it all!
668
669        Log.v(this, "audioButtonEnabled: " + audioButtonEnabled);
670        Log.v(this, "audioButtonChecked: " + audioButtonChecked);
671        Log.v(this, "showMoreIndicator: " + showMoreIndicator);
672        Log.v(this, "showBluetoothIcon: " + showBluetoothIcon);
673        Log.v(this, "showSpeakerphoneIcon: " + showSpeakerphoneIcon);
674        Log.v(this, "showHandsetIcon: " + showHandsetIcon);
675
676        // Only enable the audio button if the fragment is enabled.
677        mAudioButton.setEnabled(audioButtonEnabled && mIsEnabled);
678        mAudioButton.setChecked(audioButtonChecked);
679
680        final LayerDrawable layers = (LayerDrawable) mAudioButton.getBackground();
681        Log.d(this, "'layers' drawable: " + layers);
682
683        layers.findDrawableByLayerId(R.id.compoundBackgroundItem)
684                .setAlpha(showToggleIndicator ? VISIBLE : HIDDEN);
685
686        layers.findDrawableByLayerId(R.id.moreIndicatorItem)
687                .setAlpha(showMoreIndicator ? VISIBLE : HIDDEN);
688
689        layers.findDrawableByLayerId(R.id.bluetoothItem)
690                .setAlpha(showBluetoothIcon ? VISIBLE : HIDDEN);
691
692        layers.findDrawableByLayerId(R.id.handsetItem)
693                .setAlpha(showHandsetIcon ? VISIBLE : HIDDEN);
694
695        layers.findDrawableByLayerId(R.id.speakerphoneItem)
696                .setAlpha(showSpeakerphoneIcon ? VISIBLE : HIDDEN);
697
698    }
699
700    /**
701     * Update the content description of the audio button.
702     */
703    private void updateAudioButtonContentDescription(int mode) {
704        int stringId = 0;
705
706        // If bluetooth is not supported, the audio buttion will toggle, so use the label "speaker".
707        // Otherwise, use the label of the currently selected audio mode.
708        if (!isSupported(AudioState.ROUTE_BLUETOOTH)) {
709            stringId = R.string.audio_mode_speaker;
710        } else {
711            switch (mode) {
712                case AudioState.ROUTE_EARPIECE:
713                    stringId = R.string.audio_mode_earpiece;
714                    break;
715                case AudioState.ROUTE_BLUETOOTH:
716                    stringId = R.string.audio_mode_bluetooth;
717                    break;
718                case AudioState.ROUTE_WIRED_HEADSET:
719                    stringId = R.string.audio_mode_wired_headset;
720                    break;
721                case AudioState.ROUTE_SPEAKER:
722                    stringId = R.string.audio_mode_speaker;
723                    break;
724            }
725        }
726
727        if (stringId != 0) {
728            mAudioButton.setContentDescription(getResources().getString(stringId));
729        }
730    }
731
732    private void showAudioModePopup() {
733        Log.d(this, "showAudioPopup()...");
734
735        final ContextThemeWrapper contextWrapper = new ContextThemeWrapper(getActivity(),
736                R.style.InCallPopupMenuStyle);
737        mAudioModePopup = new PopupMenu(contextWrapper, mAudioButton /* anchorView */);
738        mAudioModePopup.getMenuInflater().inflate(R.menu.incall_audio_mode_menu,
739                mAudioModePopup.getMenu());
740        mAudioModePopup.setOnMenuItemClickListener(this);
741        mAudioModePopup.setOnDismissListener(this);
742
743        final Menu menu = mAudioModePopup.getMenu();
744
745        // TODO: Still need to have the "currently active" audio mode come
746        // up pre-selected (or focused?) with a blue highlight.  Still
747        // need exact visual design, and possibly framework support for this.
748        // See comments below for the exact logic.
749
750        final MenuItem speakerItem = menu.findItem(R.id.audio_mode_speaker);
751        speakerItem.setEnabled(isSupported(AudioState.ROUTE_SPEAKER));
752        // TODO: Show speakerItem as initially "selected" if
753        // speaker is on.
754
755        // We display *either* "earpiece" or "wired headset", never both,
756        // depending on whether a wired headset is physically plugged in.
757        final MenuItem earpieceItem = menu.findItem(R.id.audio_mode_earpiece);
758        final MenuItem wiredHeadsetItem = menu.findItem(R.id.audio_mode_wired_headset);
759
760        final boolean usingHeadset = isSupported(AudioState.ROUTE_WIRED_HEADSET);
761        earpieceItem.setVisible(!usingHeadset);
762        earpieceItem.setEnabled(!usingHeadset);
763        wiredHeadsetItem.setVisible(usingHeadset);
764        wiredHeadsetItem.setEnabled(usingHeadset);
765        // TODO: Show the above item (either earpieceItem or wiredHeadsetItem)
766        // as initially "selected" if speakerOn and
767        // bluetoothIndicatorOn are both false.
768
769        final MenuItem bluetoothItem = menu.findItem(R.id.audio_mode_bluetooth);
770        bluetoothItem.setEnabled(isSupported(AudioState.ROUTE_BLUETOOTH));
771        // TODO: Show bluetoothItem as initially "selected" if
772        // bluetoothIndicatorOn is true.
773
774        mAudioModePopup.show();
775
776        // Unfortunately we need to manually keep track of the popup menu's
777        // visiblity, since PopupMenu doesn't have an isShowing() method like
778        // Dialogs do.
779        mAudioModePopupVisible = true;
780    }
781
782    private boolean isSupported(int mode) {
783        return (mode == (getPresenter().getSupportedAudio() & mode));
784    }
785
786    private boolean isAudio(int mode) {
787        return (mode == getPresenter().getAudioMode());
788    }
789
790    @Override
791    public void displayDialpad(boolean value, boolean animate) {
792        mShowDialpadButton.setSelected(value);
793        if (getActivity() != null && getActivity() instanceof InCallActivity) {
794            ((InCallActivity) getActivity()).displayDialpad(value, animate);
795        }
796    }
797
798    @Override
799    public boolean isDialpadVisible() {
800        if (getActivity() != null && getActivity() instanceof InCallActivity) {
801            return ((InCallActivity) getActivity()).isDialpadVisible();
802        }
803        return false;
804    }
805
806    @Override
807    public Context getContext() {
808        return getActivity();
809    }
810}
811