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