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.graphics.drawable.LayerDrawable;
     20 import android.os.Bundle;
     21 import android.view.LayoutInflater;
     22 import android.view.Menu;
     23 import android.view.MenuItem;
     24 import android.view.View;
     25 import android.view.View.OnClickListener;
     26 import android.view.ViewGroup;
     27 import android.widget.CompoundButton;
     28 import android.widget.ImageButton;
     29 import android.widget.PopupMenu;
     30 import android.widget.PopupMenu.OnDismissListener;
     31 import android.widget.PopupMenu.OnMenuItemClickListener;
     32 import android.widget.ToggleButton;
     33 
     34 import com.android.services.telephony.common.AudioMode;
     35 
     36 /**
     37  * Fragment for call control buttons
     38  */
     39 public class CallButtonFragment
     40         extends BaseFragment<CallButtonPresenter, CallButtonPresenter.CallButtonUi>
     41         implements CallButtonPresenter.CallButtonUi, OnMenuItemClickListener, OnDismissListener,
     42         View.OnClickListener, CompoundButton.OnCheckedChangeListener {
     43 
     44     private ImageButton mMuteButton;
     45     private ImageButton mAudioButton;
     46     private ImageButton mHoldButton;
     47     private ToggleButton mShowDialpadButton;
     48     private ImageButton mMergeButton;
     49     private ImageButton mAddCallButton;
     50     private ImageButton mSwapButton;
     51 
     52     private PopupMenu mAudioModePopup;
     53     private boolean mAudioModePopupVisible;
     54     private View mEndCallButton;
     55     private View mExtraRowButton;
     56     private View mManageConferenceButton;
     57     private View mGenericMergeButton;
     58 
     59     @Override
     60     CallButtonPresenter createPresenter() {
     61         // TODO: find a cleaner way to include audio mode provider than
     62         // having a singleton instance.
     63         return new CallButtonPresenter();
     64     }
     65 
     66     @Override
     67     CallButtonPresenter.CallButtonUi getUi() {
     68         return this;
     69     }
     70 
     71     @Override
     72     public void onCreate(Bundle savedInstanceState) {
     73         super.onCreate(savedInstanceState);
     74     }
     75 
     76     @Override
     77     public View onCreateView(LayoutInflater inflater, ViewGroup container,
     78             Bundle savedInstanceState) {
     79         final View parent = inflater.inflate(R.layout.call_button_fragment, container, false);
     80 
     81         mExtraRowButton = parent.findViewById(R.id.extraButtonRow);
     82 
     83         mManageConferenceButton = parent.findViewById(R.id.manageConferenceButton);
     84         mManageConferenceButton.setOnClickListener(new View.OnClickListener() {
     85             @Override
     86             public void onClick(View v) {
     87                 getPresenter().manageConferenceButtonClicked();
     88             }
     89         });
     90         mGenericMergeButton = parent.findViewById(R.id.cdmaMergeButton);
     91         mGenericMergeButton.setOnClickListener(new View.OnClickListener() {
     92             @Override
     93             public void onClick(View v) {
     94                 getPresenter().mergeClicked();
     95             }
     96         });
     97 
     98         mEndCallButton = parent.findViewById(R.id.endButton);
     99         mEndCallButton.setOnClickListener(new View.OnClickListener() {
    100             @Override
    101             public void onClick(View v) {
    102                 getPresenter().endCallClicked();
    103             }
    104         });
    105 
    106         // make the hit target smaller for the end button so that is creates a deadzone
    107         // along the inside perimeter of the button.
    108         mEndCallButton.setOnTouchListener(new SmallerHitTargetTouchListener());
    109 
    110         mMuteButton = (ImageButton) parent.findViewById(R.id.muteButton);
    111         mMuteButton.setOnClickListener(new OnClickListener() {
    112             @Override
    113             public void onClick(View v) {
    114                 final ImageButton button = (ImageButton) v;
    115                 getPresenter().muteClicked(!button.isSelected());
    116             }
    117         });
    118 
    119         mAudioButton = (ImageButton) parent.findViewById(R.id.audioButton);
    120         mAudioButton.setOnClickListener(new View.OnClickListener() {
    121             @Override
    122             public void onClick(View view) {
    123                 onAudioButtonClicked();
    124             }
    125         });
    126 
    127         mHoldButton = (ImageButton) parent.findViewById(R.id.holdButton);
    128         mHoldButton.setOnClickListener(new OnClickListener() {
    129             @Override
    130             public void onClick(View v) {
    131                 final ImageButton button = (ImageButton) v;
    132                 getPresenter().holdClicked(!button.isSelected());
    133             }
    134         });
    135 
    136         mShowDialpadButton = (ToggleButton) parent.findViewById(R.id.dialpadButton);
    137         mShowDialpadButton.setOnClickListener(this);
    138         mAddCallButton = (ImageButton) parent.findViewById(R.id.addButton);
    139         mAddCallButton.setOnClickListener(this);
    140         mMergeButton = (ImageButton) parent.findViewById(R.id.mergeButton);
    141         mMergeButton.setOnClickListener(this);
    142         mSwapButton = (ImageButton) parent.findViewById(R.id.swapButton);
    143         mSwapButton.setOnClickListener(this);
    144 
    145         return parent;
    146     }
    147 
    148     @Override
    149     public void onActivityCreated(Bundle savedInstanceState) {
    150         super.onActivityCreated(savedInstanceState);
    151 
    152         // set the buttons
    153         updateAudioButtons(getPresenter().getSupportedAudio());
    154     }
    155 
    156     @Override
    157     public void onResume() {
    158         if (getPresenter() != null) {
    159             getPresenter().refreshMuteState();
    160         }
    161         super.onResume();
    162     }
    163 
    164     @Override
    165     public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
    166     }
    167 
    168     @Override
    169     public void onClick(View view) {
    170         int id = view.getId();
    171         Log.d(this, "onClick(View " + view + ", id " + id + ")...");
    172 
    173         switch(id) {
    174             case R.id.addButton:
    175                 getPresenter().addCallClicked();
    176                 break;
    177             case R.id.mergeButton:
    178                 getPresenter().mergeClicked();
    179                 break;
    180             case R.id.swapButton:
    181                 getPresenter().swapClicked();
    182                 break;
    183             case R.id.dialpadButton:
    184                 getPresenter().showDialpadClicked(mShowDialpadButton.isChecked());
    185                 break;
    186             default:
    187                 Log.wtf(this, "onClick: unexpected");
    188                 break;
    189         }
    190     }
    191 
    192     @Override
    193     public void setEnabled(boolean isEnabled) {
    194         View view = getView();
    195         if (view.getVisibility() != View.VISIBLE) {
    196             view.setVisibility(View.VISIBLE);
    197         }
    198 
    199         // The main end-call button spanning across the screen.
    200         mEndCallButton.setEnabled(isEnabled);
    201 
    202         // The smaller buttons laid out horizontally just below the end-call button.
    203         mMuteButton.setEnabled(isEnabled);
    204         mAudioButton.setEnabled(isEnabled);
    205         mHoldButton.setEnabled(isEnabled);
    206         mShowDialpadButton.setEnabled(isEnabled);
    207         mMergeButton.setEnabled(isEnabled);
    208         mAddCallButton.setEnabled(isEnabled);
    209         mSwapButton.setEnabled(isEnabled);
    210     }
    211 
    212     @Override
    213     public void setMute(boolean value) {
    214         mMuteButton.setSelected(value);
    215     }
    216 
    217     @Override
    218     public void enableMute(boolean enabled) {
    219         mMuteButton.setEnabled(enabled);
    220     }
    221 
    222     @Override
    223     public void setHold(boolean value) {
    224         mHoldButton.setSelected(value);
    225     }
    226 
    227     @Override
    228     public void showHold(boolean show) {
    229         mHoldButton.setVisibility(show ? View.VISIBLE : View.GONE);
    230     }
    231 
    232     @Override
    233     public void enableHold(boolean enabled) {
    234         mHoldButton.setEnabled(enabled);
    235     }
    236 
    237     @Override
    238     public void showMerge(boolean show) {
    239         mMergeButton.setVisibility(show ? View.VISIBLE : View.GONE);
    240     }
    241 
    242     @Override
    243     public void showSwap(boolean show) {
    244         mSwapButton.setVisibility(show ? View.VISIBLE : View.GONE);
    245     }
    246 
    247     @Override
    248     public void showAddCall(boolean show) {
    249         mAddCallButton.setVisibility(show ? View.VISIBLE : View.GONE);
    250     }
    251 
    252     @Override
    253     public void enableAddCall(boolean enabled) {
    254         mAddCallButton.setEnabled(enabled);
    255     }
    256 
    257     @Override
    258     public void setAudio(int mode) {
    259         updateAudioButtons(getPresenter().getSupportedAudio());
    260         refreshAudioModePopup();
    261     }
    262 
    263     @Override
    264     public void setSupportedAudio(int modeMask) {
    265         updateAudioButtons(modeMask);
    266         refreshAudioModePopup();
    267     }
    268 
    269     @Override
    270     public boolean onMenuItemClick(MenuItem item) {
    271         Log.d(this, "- onMenuItemClick: " + item);
    272         Log.d(this, "  id: " + item.getItemId());
    273         Log.d(this, "  title: '" + item.getTitle() + "'");
    274 
    275         int mode = AudioMode.WIRED_OR_EARPIECE;
    276 
    277         switch (item.getItemId()) {
    278             case R.id.audio_mode_speaker:
    279                 mode = AudioMode.SPEAKER;
    280                 break;
    281             case R.id.audio_mode_earpiece:
    282             case R.id.audio_mode_wired_headset:
    283                 // InCallAudioMode.EARPIECE means either the handset earpiece,
    284                 // or the wired headset (if connected.)
    285                 mode = AudioMode.WIRED_OR_EARPIECE;
    286                 break;
    287             case R.id.audio_mode_bluetooth:
    288                 mode = AudioMode.BLUETOOTH;
    289                 break;
    290             default:
    291                 Log.e(this, "onMenuItemClick:  unexpected View ID " + item.getItemId()
    292                         + " (MenuItem = '" + item + "')");
    293                 break;
    294         }
    295 
    296         getPresenter().setAudioMode(mode);
    297 
    298         return true;
    299     }
    300 
    301     // PopupMenu.OnDismissListener implementation; see showAudioModePopup().
    302     // This gets called when the PopupMenu gets dismissed for *any* reason, like
    303     // the user tapping outside its bounds, or pressing Back, or selecting one
    304     // of the menu items.
    305     @Override
    306     public void onDismiss(PopupMenu menu) {
    307         Log.d(this, "- onDismiss: " + menu);
    308         mAudioModePopupVisible = false;
    309     }
    310 
    311     /**
    312      * Checks for supporting modes.  If bluetooth is supported, it uses the audio
    313      * pop up menu.  Otherwise, it toggles the speakerphone.
    314      */
    315     private void onAudioButtonClicked() {
    316         Log.d(this, "onAudioButtonClicked: " +
    317                 AudioMode.toString(getPresenter().getSupportedAudio()));
    318 
    319         if (isSupported(AudioMode.BLUETOOTH)) {
    320             showAudioModePopup();
    321         } else {
    322             getPresenter().toggleSpeakerphone();
    323         }
    324     }
    325 
    326     /**
    327      * Refreshes the "Audio mode" popup if it's visible.  This is useful
    328      * (for example) when a wired headset is plugged or unplugged,
    329      * since we need to switch back and forth between the "earpiece"
    330      * and "wired headset" items.
    331      *
    332      * This is safe to call even if the popup is already dismissed, or even if
    333      * you never called showAudioModePopup() in the first place.
    334      */
    335     public void refreshAudioModePopup() {
    336         if (mAudioModePopup != null && mAudioModePopupVisible) {
    337             // Dismiss the previous one
    338             mAudioModePopup.dismiss();  // safe even if already dismissed
    339             // And bring up a fresh PopupMenu
    340             showAudioModePopup();
    341         }
    342     }
    343 
    344     /**
    345      * Updates the audio button so that the appriopriate visual layers
    346      * are visible based on the supported audio formats.
    347      */
    348     private void updateAudioButtons(int supportedModes) {
    349         final boolean bluetoothSupported = isSupported(AudioMode.BLUETOOTH);
    350         final boolean speakerSupported = isSupported(AudioMode.SPEAKER);
    351 
    352         boolean audioButtonEnabled = false;
    353         boolean audioButtonChecked = false;
    354         boolean showMoreIndicator = false;
    355 
    356         boolean showBluetoothIcon = false;
    357         boolean showSpeakerphoneOnIcon = false;
    358         boolean showSpeakerphoneOffIcon = false;
    359         boolean showHandsetIcon = false;
    360 
    361         boolean showToggleIndicator = false;
    362 
    363         if (bluetoothSupported) {
    364             Log.d(this, "updateAudioButtons - popup menu mode");
    365 
    366             audioButtonEnabled = true;
    367             showMoreIndicator = true;
    368             // The audio button is NOT a toggle in this state.  (And its
    369             // setChecked() state is irrelevant since we completely hide the
    370             // btn_compound_background layer anyway.)
    371 
    372             // Update desired layers:
    373             if (isAudio(AudioMode.BLUETOOTH)) {
    374                 showBluetoothIcon = true;
    375             } else if (isAudio(AudioMode.SPEAKER)) {
    376                 showSpeakerphoneOnIcon = true;
    377             } else {
    378                 showHandsetIcon = true;
    379                 // TODO: if a wired headset is plugged in, that takes precedence
    380                 // over the handset earpiece.  If so, maybe we should show some
    381                 // sort of "wired headset" icon here instead of the "handset
    382                 // earpiece" icon.  (Still need an asset for that, though.)
    383             }
    384         } else if (speakerSupported) {
    385             Log.d(this, "updateAudioButtons - speaker toggle mode");
    386 
    387             audioButtonEnabled = true;
    388 
    389             // The audio button *is* a toggle in this state, and indicated the
    390             // current state of the speakerphone.
    391             audioButtonChecked = isAudio(AudioMode.SPEAKER);
    392 
    393             // update desired layers:
    394             showToggleIndicator = true;
    395 
    396             showSpeakerphoneOnIcon = isAudio(AudioMode.SPEAKER);
    397             showSpeakerphoneOffIcon = !showSpeakerphoneOnIcon;
    398         } else {
    399             Log.d(this, "updateAudioButtons - disabled...");
    400 
    401             // The audio button is a toggle in this state, but that's mostly
    402             // irrelevant since it's always disabled and unchecked.
    403             audioButtonEnabled = false;
    404             audioButtonChecked = false;
    405 
    406             // update desired layers:
    407             showToggleIndicator = true;
    408             showSpeakerphoneOffIcon = true;
    409         }
    410 
    411         // Finally, update it all!
    412 
    413         Log.v(this, "audioButtonEnabled: " + audioButtonEnabled);
    414         Log.v(this, "audioButtonChecked: " + audioButtonChecked);
    415         Log.v(this, "showMoreIndicator: " + showMoreIndicator);
    416         Log.v(this, "showBluetoothIcon: " + showBluetoothIcon);
    417         Log.v(this, "showSpeakerphoneOnIcon: " + showSpeakerphoneOnIcon);
    418         Log.v(this, "showSpeakerphoneOffIcon: " + showSpeakerphoneOffIcon);
    419         Log.v(this, "showHandsetIcon: " + showHandsetIcon);
    420 
    421         // Constants for Drawable.setAlpha()
    422         final int HIDDEN = 0;
    423         final int VISIBLE = 255;
    424 
    425         mAudioButton.setEnabled(audioButtonEnabled);
    426         mAudioButton.setSelected(audioButtonChecked);
    427 
    428         final LayerDrawable layers = (LayerDrawable) mAudioButton.getBackground();
    429         Log.d(this, "'layers' drawable: " + layers);
    430 
    431         layers.findDrawableByLayerId(R.id.compoundBackgroundItem)
    432                 .setAlpha(showToggleIndicator ? VISIBLE : HIDDEN);
    433 
    434         layers.findDrawableByLayerId(R.id.moreIndicatorItem)
    435                 .setAlpha(showMoreIndicator ? VISIBLE : HIDDEN);
    436 
    437         layers.findDrawableByLayerId(R.id.bluetoothItem)
    438                 .setAlpha(showBluetoothIcon ? VISIBLE : HIDDEN);
    439 
    440         layers.findDrawableByLayerId(R.id.handsetItem)
    441                 .setAlpha(showHandsetIcon ? VISIBLE : HIDDEN);
    442 
    443         layers.findDrawableByLayerId(R.id.speakerphoneOnItem)
    444                 .setAlpha(showSpeakerphoneOnIcon ? VISIBLE : HIDDEN);
    445 
    446         layers.findDrawableByLayerId(R.id.speakerphoneOffItem)
    447                 .setAlpha(showSpeakerphoneOffIcon ? VISIBLE : HIDDEN);
    448     }
    449 
    450     private void showAudioModePopup() {
    451         Log.d(this, "showAudioPopup()...");
    452 
    453         mAudioModePopup = new PopupMenu(getView().getContext(), mAudioButton /* anchorView */);
    454         mAudioModePopup.getMenuInflater().inflate(R.menu.incall_audio_mode_menu,
    455                 mAudioModePopup.getMenu());
    456         mAudioModePopup.setOnMenuItemClickListener(this);
    457         mAudioModePopup.setOnDismissListener(this);
    458 
    459         final Menu menu = mAudioModePopup.getMenu();
    460 
    461         // TODO: Still need to have the "currently active" audio mode come
    462         // up pre-selected (or focused?) with a blue highlight.  Still
    463         // need exact visual design, and possibly framework support for this.
    464         // See comments below for the exact logic.
    465 
    466         final MenuItem speakerItem = menu.findItem(R.id.audio_mode_speaker);
    467         speakerItem.setEnabled(isSupported(AudioMode.SPEAKER));
    468         // TODO: Show speakerItem as initially "selected" if
    469         // speaker is on.
    470 
    471         // We display *either* "earpiece" or "wired headset", never both,
    472         // depending on whether a wired headset is physically plugged in.
    473         final MenuItem earpieceItem = menu.findItem(R.id.audio_mode_earpiece);
    474         final MenuItem wiredHeadsetItem = menu.findItem(R.id.audio_mode_wired_headset);
    475 
    476         final boolean usingHeadset = isSupported(AudioMode.WIRED_HEADSET);
    477         earpieceItem.setVisible(!usingHeadset);
    478         earpieceItem.setEnabled(!usingHeadset);
    479         wiredHeadsetItem.setVisible(usingHeadset);
    480         wiredHeadsetItem.setEnabled(usingHeadset);
    481         // TODO: Show the above item (either earpieceItem or wiredHeadsetItem)
    482         // as initially "selected" if speakerOn and
    483         // bluetoothIndicatorOn are both false.
    484 
    485         final MenuItem bluetoothItem = menu.findItem(R.id.audio_mode_bluetooth);
    486         bluetoothItem.setEnabled(isSupported(AudioMode.BLUETOOTH));
    487         // TODO: Show bluetoothItem as initially "selected" if
    488         // bluetoothIndicatorOn is true.
    489 
    490         mAudioModePopup.show();
    491 
    492         // Unfortunately we need to manually keep track of the popup menu's
    493         // visiblity, since PopupMenu doesn't have an isShowing() method like
    494         // Dialogs do.
    495         mAudioModePopupVisible = true;
    496     }
    497 
    498     private boolean isSupported(int mode) {
    499         return (mode == (getPresenter().getSupportedAudio() & mode));
    500     }
    501 
    502     private boolean isAudio(int mode) {
    503         return (mode == getPresenter().getAudioMode());
    504     }
    505 
    506     @Override
    507     public void displayDialpad(boolean value) {
    508         mShowDialpadButton.setChecked(value);
    509         if (getActivity() != null && getActivity() instanceof InCallActivity) {
    510             ((InCallActivity) getActivity()).displayDialpad(value);
    511         }
    512     }
    513 
    514     @Override
    515     public boolean isDialpadVisible() {
    516         if (getActivity() != null && getActivity() instanceof InCallActivity) {
    517             return ((InCallActivity) getActivity()).isDialpadVisible();
    518         }
    519         return false;
    520     }
    521 
    522     @Override
    523     public void displayManageConferencePanel(boolean value) {
    524         if (getActivity() != null && getActivity() instanceof InCallActivity) {
    525             ((InCallActivity) getActivity()).displayManageConferencePanel(value);
    526         }
    527     }
    528 
    529 
    530     @Override
    531     public void showManageConferenceCallButton() {
    532         mExtraRowButton.setVisibility(View.VISIBLE);
    533         mManageConferenceButton.setVisibility(View.VISIBLE);
    534         mGenericMergeButton.setVisibility(View.GONE);
    535     }
    536 
    537     @Override
    538     public void showGenericMergeButton() {
    539         mExtraRowButton.setVisibility(View.VISIBLE);
    540         mManageConferenceButton.setVisibility(View.GONE);
    541         mGenericMergeButton.setVisibility(View.VISIBLE);
    542     }
    543 
    544     @Override
    545     public void hideExtraRow() {
    546        mExtraRowButton.setVisibility(View.GONE);
    547     }
    548 }
    549