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