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